SAE J1939: the language of trucks
CAN only defines how frames win the wire — it says nothing about what the bytes mean. Passenger-car makers each invented their own private messaging on top. The heavy-vehicle world — trucks, buses, tractors, excavators, gensets, boats — went the other way and standardized the whole application layer: SAE J1939. The result is remarkable: an engine from one supplier, a transmission from another and a dashboard from a third can be bolted together and talk, out of the box.
Technically it sits on ordinary CAN with 29-bit extended IDs at 250 kbit/s (500 k since J1939-14), 8-byte frames, and a rulebook on top.
The 29-bit ID does all the work
J1939 packs three things into the identifier itself:
Figure 1 — Priority, message type and sender, all inside the arbitration field.
- Priority (3 bits) — plain CAN arbitration, 0 wins. Torque/speed control runs at 3; diagnostics idle around 6.
- PGN — Parameter Group Number — what this frame carries. If the PDU Format byte is ≥ 0xF0 the message is a broadcast (nobody addressed, everyone reads); below 0xF0 the PS byte becomes a destination address for peer-to-peer commands.
- SA — Source Address — who sent it. 0x00 is the engine, 0x03 the transmission, 0xFF means broadcast destination.
So a single ID like 0x0CF00400 already tells you: priority 3, EEC1 engine
controller broadcast, sent by engine #1. No payload inspection needed — which is
also why a CAN filter can cherry-pick J1939 traffic so cheaply.
PGN + SPN: the shared dictionary
Inside each PGN, the standard (J1939-71) defines SPNs — Suspect Parameter Numbers: which bytes, what scaling, what offset, what range. Engine speed is SPN 190: PGN 61444, bytes 4–5, little-endian, 0.125 rpm per bit.
Figure 2 — PGN finds the frame, SPN finds the bytes. Unused signals are filled with 0xFF — “not available” is part of the protocol.
This dictionary is the entire magic of J1939. A dashboard doesn’t care whose engine is on the bus: it listens for PGN 61444 and reads SPN 190 the same way, every time, on every vehicle. The convention extends to error values too — 0xFE means “error”, 0xFF means “not available”, so a dead sensor is distinguishable from a missing one.
More than 8 bytes: the transport protocol
Plenty of parameter groups don’t fit in a CAN frame — the active fault list, the vehicle identification, configuration data. J1939-21 defines a fragmentation layer with two flavors:
Figure 3 — BAM: announce, then stream. The receiver reassembles by sequence number; there is no acknowledgment.
- BAM (Broadcast Announce Message) — fire-and-forget to everyone: a TP.CM frame announces total size and packet count, then TP.DT frames carry 7 bytes each (first byte is the sequence number). No flow control, no retry.
- RTS/CTS — the peer-to-peer version: the receiver grants windows, paces the sender, and can request retransmission. Used for configuration and big diagnostic reads.
A J1939 stack therefore needs reassembly buffers and timers — this is the part where “it’s just CAN” stops being true in firmware.
Address claiming and the NAME
Addresses aren’t hardwired. At power-up every controller broadcasts an Address Claimed message (PGN 60928) containing its 64-bit NAME — manufacturer, function, instance, capability bits. If two nodes want the same address, the one with the numerically lower NAME keeps it; the loser claims another address or sends “cannot claim” and stays quiet. This is how a second identical ECU — a dual-engine boat, a tandem axle — sorts itself out with zero configuration.
Diagnostics: DM1 and friends
Fault handling is standardized as DM (Diagnostic Message) PGNs — the reason a generic scan tool works on any truck:
- DM1 (PGN 65226): the active fault list, broadcast every second — each fault is an SPN + FMI (failure mode: short to ground, out of range, …) + occurrence count, plus the status of the warning lamps. Multiple faults → the list goes out via the transport protocol above.
- DM2: previously active faults. DM3/DM11: clear them.
If you’ve ever watched a truck cluster light the amber lamp the moment a sensor connector is pulled — that’s DM1 doing its one-second rounds.
Field notes
- Everything is little-endian except where it isn’t — multi-byte SPNs are LSB first, but bit-packed signals count from bit 1. Get the J1939-71 PDF, not a forum post.
- Bus load is chatty by design. Dozens of broadcast PGNs at fixed 10–1000 ms rates: a healthy truck bus idles at 30–40% load. Budget accordingly before adding your telemetry node.
- Claim before you talk. A node that transmits before winning address claim will get pathological behavior from compliant neighbors. Implement the claim state machine first, not last.
- 0xFF is your friend. Stuff unimplemented signals with “not available” instead of zeros — a zero is a value, and somebody’s dashboard will display it.
- Mind the 1708 ghosts. Old fleets still carry J1587/J1708 alongside; FMS gateways translate. If a 9-pin Deutsch connector has pins F/G wired, that’s the old bus, not a spare.