J1939 商用车协议
卡车、客车、工程机械的行业标准:基于 29 位扩展帧,用 PGN 组织参数组、SPN 定位信号,优先级/PGN/源地址三段 ID 结构,PDU1 点对点与 PDU2 广播,以及 TP 传输协议的 BAM 与 RTS/CTS 多帧机制。
卡车、客车、工程机械的行业标准:基于 29 位扩展帧,用 PGN 组织参数组、SPN 定位信号,优先级/PGN/源地址三段 ID 结构,PDU1 点对点与 PDU2 广播,以及 TP 传输协议的 BAM 与 RTS/CTS 多帧机制。
0xF004(EEC1,电子发动机控制器 1)里含发动机转速、扭矩等。PGN 由 ID 中的 DP、PF、PS 字段组合而成,是 J1939 数据库的主键——查 PGN 就知这帧里有哪些信号、如何解析。J1939 把 29 位扩展 ID 切成六个字段。理解这个结构是解析一切 J1939 报文的前提:
3 位优先级字段位于 ID 最高位,取值 0–7,0 最高、6 是普通数据默认值、7 最低。因为它在 ID 最高位,CAN 仲裁时优先级小的 ID 数值小,天然赢得总线。安全关键报文(如制动)用小优先级,普通遥测用大优先级,无需额外机制即可实现分级传输。
| PGN | 报文名 | 典型 SPN | 信号 | 解析 |
|---|---|---|---|---|
0xF004 | EEC1 | SPN 190 | 发动机转速 | 字节4-5 ×0.125 rpm |
0xFEF1 | CCVS1 | SPN 84 | 车速 | 字节2-3 ×1/256 km/h |
0xFEEE | ET1 | SPN 110 | 冷却液温度 | 字节1 −40 ℃ |
0xFEF2 | LFE1 | SPN 183 | 燃油流量 | 字节1-2 ×0.05 L/h |
0xFEEF | EFL/P1 | SPN 100 | 机油压力 | 字节4 ×4 kPa |
0xFECA | DM1 | SPN+FMI | 活动故障码 | 见下文解析 |
0xEE00 | 地址声明 | — | NAME 64 位 | Address Claim |
0xEA00 | 请求 PGN | — | 请求某报文 | 数据为目标 PGN |
下面用 Python 把 29 位 ID 拆成各字段、算出 PGN,并解析发动机转速信号。
# j1939_parse.py —— 拆解 29 位 ID 并解析 EEC1 转速 import can bus = can.Bus(channel="can0", interface="socketcan") def decode_id(can_id: int): priority = (can_id >> 26) & 0x7 # 高 3 位 dp = (can_id >> 24) & 0x1 pf = (can_id >> 16) & 0xFF # PDU Format ps = (can_id >> 8) & 0xFF sa = can_id & 0xFF # 源地址 if pf < 0xF0: # PDU1 点对点 pgn = (dp << 16) | (pf << 8) # PS 是目标地址,不进 PGN da = ps else: # PDU2 广播 pgn = (dp << 16) | (pf << 8) | ps da = None return priority, pgn, sa, da for msg in bus: if not msg.is_extended_id: continue prio, pgn, sa, da = decode_id(msg.arbitration_id) if pgn == 0xF004: # EEC1 发动机控制 # SPN 190 转速:字节 4-5(小端), 分辨率 0.125 rpm raw = msg.data[3] | (msg.data[4] << 8) rpm = raw * 0.125 print(f"[SA=0x{sa:02X}] 发动机转速: {rpm:.0f} rpm") elif pgn == 0xFEF1: # CCVS1 车速等 # SPN 84 车速:字节 2-3(小端), 分辨率 1/256 km/h raw = msg.data[1] | (msg.data[2] << 8) print(f"[SA=0x{sa:02X}] 车速: {raw/256:.1f} km/h")
当 PGN 数据超过 8 字节,广播场景用 BAM:发送方先发一个 TP.CM_BAM 声明「即将发 N 字节、共 M 包」,随后连续发数据包,接收方按序号拼接,无需应答。
BAM用于广播(一对多),发送方自定节奏、无确认,简单但不可靠。RTS/CTS用于点对点(一对一):发送方发 RTS(控制字 0x10)请求,接收方回 CTS(0x11)告知「现在可以发几包、从第几包开始」,全部收完后回 EndOfMsgAck(0x13)。RTS/CTS 有流控和确认,适合可靠传输大数据(如刷写、参数下载),但只能一对一。
DM1(PGN 0xFECA)广播当前活动故障,灯状态在前 2 字节,随后每 4 字节一个故障(SPN + FMI + 发生次数)。
# dm1_decode.py —— 解析 J1939 DM1 活动故障码 def decode_dm1(data: bytes): # 字节0-1 为灯状态(MIL/红灯/黄灯/保护灯) faults = [] # 从字节2起,每 4 字节一个 DTC for i in range(2, len(data) - 3, 4): b = data[i:i+4] # SPN 19 位分散在 3 字节: 低8 + 中8 + 高3 spn = b[0] | (b[1] << 8) | ((b[2] & 0xE0) << 11) fmi = b[2] & 0x1F # 故障模式 5 位 oc = b[3] & 0x7F # 发生次数 7 位 if spn != 0 and spn != 0x7FFFF: faults.append((spn, fmi, oc)) return faults # 示例: SPN=190(转速) FMI=0(数值过高) OC=5 for spn, fmi, oc in decode_dm1(bytes([0xFF,0xFF,0xBE,0x00,0x00,0x05])): print(f"故障 SPN={spn} FMI={fmi} 次数={oc}")
J1939-73 早期版本对 SPN 的 19 位在 4 字节里的排布有两种约定(version 1–3 与 version 4),高 3 位的位置不同。解析 DM1 时若发现 SPN 数值离谱,很可能是遇到了另一种编码版本。生产工具应参考具体设备的 J1939-73 版本或用车厂提供的解析库,避免误判故障。
J1939 是卡车、客车、工程机械的标准化 CAN 高层协议,强制用 CAN 2.0B 29 位扩展帧,物理层多为 250 kbps。29 位 ID 拆为:优先级(3)、保留(1)、DP(1)、PF(8)、PS(8)、SA(8)。PGN(参数组号)标识报文类型,由 DP·PF·PS 组合:PF<0xF0 为 PDU1(点对点,PS 是目标地址,不进 PGN),PF≥0xF0 为 PDU2(广播,PS 是组扩展,进 PGN)。SPN(可疑参数号)定位 PGN 内的具体信号,含起始位/长度/分辨率/偏移,如 SPN190 转速(EEC1 0xF004,×0.125 rpm)、SPN84 车速(CCVS1 0xFEF1,×1/256 km/h)。源地址 SA 标识发送者,地址声明(PGN 0xEE00)用 64 位 NAME 动态分配地址实现即插即用。超 8 字节数据靠 TP 传输协议:BAM 广播(无应答自控节奏)、RTS/CTS 点对点(有流控确认),控制用 0xEC00、数据用 0xEB00。故障诊断用 DM1(0xFECA)广播活动故障,每条含 SPN+FMI+发生次数。