Part of a series on the open-source J1939 emulator. This post covers how J1939 puts Parameter Groups on the bus and how ECUs join the network in the first place.
If you've stared at a raw J1939 CAN ID and wondered why the same byte means completely different things on different frames, this post is for you. We'll walk through the 29-bit identifier, the PDU1/PDU2 split that trips up almost everyone at first, and how an ECU claims its address on the network.
The 29-Bit J1939 Identifier
Every extended CAN frame on a J1939 bus uses this layout:
| Field |
Size |
Meaning |
| Priority |
3 bits |
Arbitration only — lower number wins the bus |
| R |
1 bit |
Reserved, always transmitted as 0 |
| DP |
1 bit |
Data Page — 0 for the standard SAE PGN catalog, 1 for the extended page |
| PF |
8 bits |
PDU Format — determines how PS is interpreted |
| PS |
8 bits |
Destination Address or Group Extension, depending on PF |
| SA |
8 bits |
Source Address of the sending node |
On the emulator, priority is 3 for EEC1 (engine speed/torque) and 6 for most other messages — this only affects bus arbitration, not meaning. Receivers ignore it entirely when interpreting the message content.
PDU1 vs PDU2 — The #1 Source of Confusion
This is where most people get tripped up, because the same field (PS) means two different things depending on the value of PF.
PDU1 (PF = 0–239): peer-to-peer / directed
- PS = Destination Address — literally who this message is for
- PGN =
(DP << 16) | (PF << 8) — note that PS is not part of the PGN here
- Examples: Address Claimed (PF=0xEE, PS=0xFF for global), Request (PF=0xEA, PS=0x00 to address the engine directly), TP.CM/TP.DT sessions
PDU2 (PF = 240–255): broadcast / group
- PS = Group Extension — it's part of the PGN number itself, not a destination
- PGN =
(DP << 16) | (PF << 8) | PS
- No single destination; any listener on the bus may consume it
On this emulator specifically:
| Message |
PF |
PS |
PGN |
| EEC1 (Engine Controller #1) |
0xF0 |
0x04 |
61444 |
| ET1 (Engine Temperature) |
0xFE |
0xEE |
65262 |
| DM1 (Active DTCs) |
0xFE |
0xCA |
65226 |
| VIN |
0xFE |
0xEC |
65260 |
The important thing to internalize: Group Extension is not a multicast group in the networking sense. It's simply the low byte of the PGN, baked into the CAN ID.
Scenario 1 — Cyclic Broadcast
On START, the emulator puts five J1939-71 parameter groups on the bus, plus address claim, VIN, and DM1 as configured. All five broadcast PGNs are PDU2 — PS is the PGN's low byte, not a destination address. Dash, ABS, telematics, and a logger all see the identical frames; each one simply filters by PGN for what it cares about.
This mirrors how a real engine works: on key-on, it publishes speed and temperature continuously, so the instrument cluster never has to poll for it.
EEC1 deep dive
PGN 61444, CAN ID 0x0CF00400. Example payload at 1250 RPM: F0 AF AD 10 27 F3 FF FF. Change the RPM slider in the GUI and watch bytes 3–4 update live — that's the actual RPM encoding in action, not a mocked value.
ET1 deep dive
PGN 65262, CAN ID 0x18FEEE00. Engine coolant temperature (SPN 110) feeds both the dash gauge and the DTC logic — the same signal that later triggers a fault (SPN 110 / FMI 3, voltage-high) is broadcast here. Broadcast every 1 second, no request required for normal driving — though a service tool can still request it directly for an immediate sample, which brings us to the next scenario.
Broadcast timing, no handshake involved:
| Message |
CAN ID |
Interval |
| EEC1 |
0x0CF00400 |
every 20 ms |
| CCVS |
0x18FEF100 |
every 100 ms |
| ET1 |
0x18FEEE00 |
every 1 s |
Listeners filter by PGN/CAN ID — there's no CTS, no application-layer ACK. (Classical CAN's data-link-layer ACK still happens, from any healthy node on the bus, but that's a different layer entirely.)
Scenario 2 — Tester Requests a PGN
Sometimes a tool doesn't want to wait for the next broadcast cycle — it asks directly. This uses PGN 59904 (0xEA00), and it's a PDU1 message, which means PS finally does mean "destination."
Remember: SA = who sent it, PS on a PDU1 frame = who it's for.
A tool at SA 0xF9 asking the engine (SA 0x00) for ET1 looks like:
- Request frame: CAN ID
0x18EA00F9, 3 data bytes containing the little-endian PGN being requested
- Here, PS=
0x00 means "destination = engine" — it is not the PGN low byte, because this is a PDU1 frame
- The engine typically replies as a normal PDU2 broadcast — on that reply frame, PS=
0xEE is the Group Extension of PGN 65262
That contrast — same byte position, two different meanings depending on message type — is exactly why PDU1/PDU2 trips people up.
One honesty note: v1.0 of the emulator primarily broadcasts; it doesn't yet implement full request/response handling. It's still worth understanding this scenario, though, since it's exactly what a real scanner tool expects.
And there's a dependency worth calling out: if the engine hasn't claimed its address yet, a request just gets silence. Which brings us to address claim.
Scenario 3 — Address Claimed
PGN 60928 (0xEE00), governed by J1939-81, is how a node joins the network. On the emulator, SA 0x00 is claimed on start.
CAN ID: 0x18EEFF00. Worth noting: PF is 0xEE because that's simply the SAE-assigned PGN for Address Claimed (60928 = 0xEE00) — it's not a special "claim bit" hidden somewhere in the ID.
The NAME field
The 8 data bytes carry a 64-bit NAME, stored in the ECU's flash and copied into the Address Claimed payload every time it's sent. Peers decode this little-endian 64-bit value and update their internal address table: "SA 0x00 belongs to this NAME."
Conflict resolution: if two nodes claim the same SA, the one with the smaller 64-bit NAME wins. The loser either changes its SA (if it's configured as address-capable, AAC=1) or goes silent.
Timing
On start, the engine sends Address Claimed. Other nodes store the SA→NAME mapping and wait roughly a 250 ms class window for any contenders. A node may also explicitly request Address Claimed from others, prompting a re-announce. If a rival claims the same SA with a lower NAME, the current node loses that address. Once claiming succeeds, cyclic broadcast (EEC1, CCVS, etc.) begins.
Summary
- PDU2 broadcast: PS = PGN low byte; anyone may listen.
- PDU1 Request: PS = destination SA; SA = the requester.
- Address Claimed: the data payload is the NAME; the CAN ID carries PGN 60928 plus the node's preferred SA.
- Lower NAME wins a contested source address.
Watch It Live
Reading about PGN structure only gets you so far — seeing the actual frames change on a logger while you interact with the emulator makes it click. The full walkthrough, including a live address-claim capture, is in the YouTube series. Source and setup instructions: github.com/EmbeddedVerse/j1939-emulator.
Next in this series: how DTCs get packed into DM1 and split across multiple frames once you have more than one active fault.