2026-06-12 · J1939 · CAN · automotive · embedded

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:

Analyzer-style breakdown of the J1939 29-bit identifier into priority, PDU format, PDU specific and source address fields, with a worked decode of 0x0CF00400 showing priority 3, PGN 61444 EEC1, source address 0 engine. 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 Numberwhat 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 Addresswho 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.

A CAN trace in analyzer style with an EEC1 frame highlighted; bytes 4 and 5 are boxed and decoded in a panel: raw 0x1368 equals 4968, times 0.125 rpm per bit gives 621 rpm, with a note that 0xFF fields mean not available. 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:

Message sequence chart of a J1939 BAM transfer: the engine broadcasts a TP.CM BAM announcement declaring 23 bytes in 4 packets carrying PGN 65226, then sends four TP.DT data packets of 7 bytes each, spaced 50 to 200 milliseconds apart. 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.