PGN meaning

PGN stands for Parameter Group Number.

In J1939, the PGN tells you what type of message a CAN frame carries. It is derived from the 29-bit CAN ID.

The easiest mental model is:

Example
CAN ID -> PGN -> SPNs -> engineering values

For example, a PGN can represent an engine controller message. SPNs inside it can represent engine speed, torque, and operating state.

The J1939 CAN ID

A J1939 identifier contains these fields:

Example
Priority | Reserved | Data Page | PDU Format | PDU Specific | Source Address
3 bits  |   1 bit  |   1 bit   |   8 bits   |    8 bits    |     8 bits

The source address is the last byte. The PGN comes from the middle fields.

The PDU1 and PDU2 rule

This is the one PGN rule that causes most mistakes.

  • If PDU Format is below 240, the frame is PDU1. PDU Specific is a destination address and the low PGN byte becomes 00.
  • If PDU Format is 240 or above, the frame is PDU2. PDU Specific is part of the PGN.

That means you cannot always extract a PGN by simply shifting the CAN ID right by eight bits. For PDU1 messages, you must clear the destination byte.

Python function to extract a PGN

Python
def j1939_fields(can_id: int) -> dict[str, int | None]:
    priority = (can_id >> 26) & 0x7
    reserved = (can_id >> 25) & 0x1
    data_page = (can_id >> 24) & 0x1
    pdu_format = (can_id >> 16) & 0xFF
    pdu_specific = (can_id >> 8) & 0xFF
    source_address = can_id & 0xFF

    if pdu_format < 240:          # PDU1: destination specific
        pgn = (reserved << 17) | (data_page << 16) | (pdu_format << 8)
        destination = pdu_specific
    else:                         # PDU2: broadcast
        pgn = (
            (reserved << 17)
            | (data_page << 16)
            | (pdu_format << 8)
            | pdu_specific
        )
        destination = None

    return {
        "priority": priority,
        "pgn": pgn,
        "source_address": source_address,
        "destination_address": destination,
    }


fields = j1939_fields(0x0CF00401)
print(fields)
print(f"PGN: {fields['pgn']} / 0x{fields['pgn']:04X}")

Expected PGN:

Example
PGN: 61444 / 0xF004

PGN example: 61444

PGN 61444 is commonly known as EEC1. A raw log line may look like this:

Example
can0  0CF00401   [8]  FF FF FF 40 1F FF FF FF

From the ID:

  • 0xF004 gives PGN 61444
  • 0x01 is the source address
  • the first part of the ID also carries priority

From the data, the correct J1939 definition tells you which bytes belong to each SPN.

What is an SPN?

SPN stands for Suspect Parameter Number. An SPN identifies one parameter inside a PGN.

A PGN is similar to a DBC message. An SPN is similar to a DBC signal.

J1939 termDBC-style comparison
PGNMessage type
SPNSignal inside the message
Source addressECU that sent the message

An SPN definition includes position, length, scale, offset, unit, range, and special values.

Simple SPN decoding example

Suppose the fourth and fifth payload bytes are 40 1F, little-endian, and the scale is 0.125 rpm per bit.

Python
data = bytes.fromhex("FF FF FF 40 1F FF FF FF")

raw = int.from_bytes(data[3:5], byteorder="little")
engine_speed_rpm = raw * 0.125

print(raw)               # 8000
print(engine_speed_rpm)  # 1000.0

Do not copy byte positions from a random example into production code. Use the correct J1939 specification, DBC, or OEM definition for your network.

Common PGN mistakes

Using all 29 bits as the message number

The source address changes between senders. If you match only the complete CAN ID, you may treat the same PGN from two ECUs as two unrelated messages.

Forgetting the PDU1 rule

For PDU1 messages, PDU Specific is the destination address. It is not part of the PGN.

Ignoring unavailable values

J1939 often reserves raw values to mean not available or error. Do not turn those values into normal engineering numbers.

Assuming one frame always holds the full message

Some J1939 parameter groups use the transport protocol to carry more than eight bytes. A raw-frame decoder must reassemble those packets first.

A practical PGN workflow

  1. Record extended CAN frames.
  2. Extract PGN, source address, and destination address.
  3. Count the PGNs to understand the traffic.
  4. Match each PGN to the correct database definition.
  5. Decode SPNs with the right byte order and scaling.
  6. Check unavailable values and message timing.
  7. Validate results against a known display or test value.

The simple summary

A PGN is the J1939 message number. An SPN is a parameter inside that message. Extract the PGN correctly from the 29-bit CAN ID, pay attention to PDU1 versus PDU2, and then use the right definition to decode each SPN.

References