Chapter 13 · CAN 总线 / 汽车电子

J1939 商用车协议

卡车、客车、工程机械的行业标准:基于 29 位扩展帧,用 PGN 组织参数组、SPN 定位信号,优先级/PGN/源地址三段 ID 结构,PDU1 点对点与 PDU2 广播,以及 TP 传输协议的 BAM 与 RTS/CTS 多帧机制。

课程进度81%

关键概念

SAE J1939
面向商用车(卡车、客车、拖拉机、工程机械、船舶)的 CAN 高层协议,由 SAE 制定。相比乘用车各厂私有报文,J1939 高度标准化——发动机转速、车速、油温等信号在所有符合 J1939 的车辆上定义一致,因此第三方仪表、车队管理系统能通用。物理层通常 250 kbps(也有 500 kbps 的 J1939-14)。
扩展帧 29 位 ID
J1939 强制使用 CAN 2.0B 扩展帧(29 位仲裁 ID),而非乘用车常见的 11 位标准帧。29 位 ID 被结构化拆分为优先级、PGN、源地址等字段,携带了「谁发的、发的什么、优先级多高」的完整寻址信息,这是 J1939 即插即用与广播机制的基础。
PGN (Parameter Group Number)
参数组编号,标识一组相关参数(一条报文的「类型」)。如 0xF004(EEC1,电子发动机控制器 1)里含发动机转速、扭矩等。PGN 由 ID 中的 DP、PF、PS 字段组合而成,是 J1939 数据库的主键——查 PGN 就知这帧里有哪些信号、如何解析。
SPN (Suspect Parameter Number)
可疑参数号,标识一个具体信号(PGN 里的一个字段)。如 SPN 190 = 发动机转速、SPN 84 = 车速、SPN 110 = 冷却液温度。每个 SPN 定义了在报文中的起始字节位、长度、分辨率、偏移。故障码 DTC 也用 SPN + FMI(故障模式标识)来精确表达「哪个信号、什么故障」。
PDU1(点对点)vs PDU2(广播)
由 PF(PDU Format)字段区分:PF < 240(0xF0)为 PDU1,此时 PS 字段是目标地址(DA),报文点对点发给特定 ECU;PF ≥ 240为 PDU2,此时 PS 是组扩展(GE),报文对全网广播。大多数周期数据(转速、车速)是 PDU2 广播;请求特定 ECU 或响应用 PDU1。
源地址 (SA) 与地址声明
ID 最低 8 位是源地址(Source Address),标识发送节点,如 0x00 = 发动机、0x0B = 制动、0xF9 = 离线工具。J1939-81 定义了地址声明(Address Claim,PGN 0xEE00):节点上电用自己的 64 位 NAME 声明想占用的地址,若冲突则按 NAME 优先级仲裁,实现即插即用的动态地址分配。
传输协议 TP(多帧)
CAN 单帧仅 8 字节,超过 8 字节(最多 1785 字节)的 PGN 需用 J1939-21 传输协议分帧。两种模式:BAM(Broadcast Announce Message,广播用,无需接收方应答,发送方自己控制节奏);RTS/CTS(点对点用,接收方用 CTS 控制发多少包,有流控与确认)。控制用 PGN 0xEC00(TP.CM),数据用 PGN 0xEB00(TP.DT)。
FMI 与 DM1 故障报文
FMI(Failure Mode Identifier,故障模式标识,5 位)说明故障类型:如 0=数值过高、1=数值过低、3=电压过高、5=电流过低。DM1(Diagnostic Message 1,PGN 0xFECA)广播当前活动故障,每条含 SPN+FMI+OC(发生次数)。DM2 是历史故障。这是商用车故障诊断的核心报文。

29 位 ID 字段分解

J1939 把 29 位扩展 ID 切成六个字段。理解这个结构是解析一切 J1939 报文的前提:

J1939 扩展帧 29 位 ID 结构(共 29 bit) ═══════════════════════════════════════════════════════════ bit 28 bit 0 │ │ ┌─────┬───┬───┬──────────┬──────────┬──────────┐ │PrioRDP PF PS SA │ │ 3位 │1位│1位│ 8位 │ 8位 │ 8位 │ └─────┴───┴───┴──────────┴──────────┴──────────┘ │ │ │ │ │ │ │ │ │ │ │ └─ 源地址 SA 谁发的 │ │ │ │ └──────────── PS 目标地址(PDU1)/组扩展(PDU2) │ │ │ └──────────────────────── PF PDU格式(决定PDU1/PDU2) │ │ └─────────────────────────────── DP 数据页 │ └─────────────────────────────────── R 保留(=0) └──────────────────────────────────────── Prio 优先级(0最高,6默认) PGN 组成规则: 若 PF < 0xF0 (PDU1):PGN = DP·PF·00 (PS 是目标地址,不进PGN) 若 PF >= 0xF0 (PDU2):PGN = DP·PF·PS (PS 是组扩展,进PGN) 示例:ID = 0x0CF00400 (发动机 EEC1) Prio=3 DP=0 PF=0xF0 PS=0x04 SA=0x00 PF=0xF0 >= 0xF0 → PDU2 广播 PGN = 0x00·F0·04 = 0xF004 (EEC1)
优先级越小越优先

3 位优先级字段位于 ID 最高位,取值 0–7,0 最高、6 是普通数据默认值、7 最低。因为它在 ID 最高位,CAN 仲裁时优先级小的 ID 数值小,天然赢得总线。安全关键报文(如制动)用小优先级,普通遥测用大优先级,无需额外机制即可实现分级传输。

常见 PGN 与 SPN

PGN报文名典型 SPN信号解析
0xF004EEC1SPN 190发动机转速字节4-5 ×0.125 rpm
0xFEF1CCVS1SPN 84车速字节2-3 ×1/256 km/h
0xFEEEET1SPN 110冷却液温度字节1 −40 ℃
0xFEF2LFE1SPN 183燃油流量字节1-2 ×0.05 L/h
0xFEEFEFL/P1SPN 100机油压力字节4 ×4 kPa
0xFECADM1SPN+FMI活动故障码见下文解析
0xEE00地址声明NAME 64 位Address Claim
0xEA00请求 PGN请求某报文数据为目标 PGN

解析 J1939 ID 与信号

下面用 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")

TP 传输协议:BAM 广播多帧

当 PGN 数据超过 8 字节,广播场景用 BAM:发送方先发一个 TP.CM_BAM 声明「即将发 N 字节、共 M 包」,随后连续发数据包,接收方按序号拼接,无需应答。

BAM 广播多帧传输(发送 20 字节, 3 个数据包) ═══════════════════════════════════════════════════════════ 发送方 (SA=0x00) 全网广播 │ ① │ ── TP.CM_BAM (PGN 0xEC00) ─────────────► │ ┌────┬────┬────┬────┬────┬────┬────┬────┐ │ │2014 00│03 │ FF │00 FE 00│ │ └────┴────┴────┴────┴────┴────┴────┴────┘ │ │ │ │ └─ 要传的目标 PGN(小端) │ │ │ └─ 总包数 3 │ │ └─ 总字节 0x0014 = 20 │ └─ 控制字 32(0x20)=BAM │ ② │ ── TP.DT (PGN 0xEB00) 包1 ─────────────► │ │01│d0│d1│d2│d3│d4│d5│d6│ 序号1 + 7字节 ③ │ ── TP.DT 包2 ─────────────────────────► │ │02│d7│d8│d9│dA│dB│dC│dD│ 序号2 + 7字节 ④ │ ── TP.DT 包3 ─────────────────────────► │ │03│dE│dF│dG│dH│dI│dJ│FF│ 序号3 + 剩6字节+填充 │ │ 接收方按序号 1,2,3 拼接 → 20 字节完整数据 │ (BAM 无需接收方 CTS/ACK, 帧间隔 50-200ms)
RTS/CTS 与 BAM 的区别

BAM用于广播(一对多),发送方自定节奏、无确认,简单但不可靠。RTS/CTS用于点对点(一对一):发送方发 RTS(控制字 0x10)请求,接收方回 CTS(0x11)告知「现在可以发几包、从第几包开始」,全部收完后回 EndOfMsgAck(0x13)。RTS/CTS 有流控和确认,适合可靠传输大数据(如刷写、参数下载),但只能一对一。

DM1 故障码解析

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}")
SPN 编码曾有两种字节序

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+发生次数。

⚒️ 配套开发者工具箱 · JSON / 正则 / 时间戳 / Cron 等 17 款免费在线工具