Skip to content

CAN 总线协议体系知识

CAN 帧格式类型(物理/链路层)

格式ID 长度数据长度波特率
CAN 2.0A11-bit最多 8 字节125k~1M bps,可自定义
CAN 2.0B29-bit最多 8 字节125k~1M bps,可自定义
CAN FD11 或 29-bit最多 64 字节仲裁段最高 1M,数据段最高 8M bps
CAN XL11 或 29-bit最多 2048 字节推广早期,量产极少

CAN 2.0A/B 只是 ID 位数不同,其余完全相同。两者都属于"基础 CAN",波特率和字节序均可自定义。


协议层次关系(重要)

CAN 2.0A/B 是物理/链路层规范,只管帧格式。
J1939 是建立在 CAN 2.0B 之上的应用层协议,规定 ID 含义、波特率、数据编码。
两者是不同层次,不是并列关系。

类比:CAN 2.0B ≈ 以太网物理层,J1939 ≈ TCP/IP 协议栈


J1939 定义

J1939 是建立在 CAN 2.0B 之上的完整应用层协议族,固定规定了:

  • 物理层:CAN 2.0B,29-bit ID,固定 250 kbps
  • ID 结构3bit Priority | 1bit R | 1bit DP | 8bit PF | 8bit PS | 8bit SA
  • 寻址:PGN(Parameter Group Number)
  • 数据编码:SPN(Suspect Parameter Number),每个信号有固定 Factor/Offset
  • 字节序:Intel 小端(低字节在前)
  • 网络管理:地址申领、命令地址等
  • 多包传输:TP 协议,最多 1785 字节

注意:J1939 中分标准 PGN(SAE 定义,全行业通用)和专有 PGN(厂商私有,需厂商文档才能解析)。


J1939 vs J1708

维度J1708J1939
物理层RS-485CAN 2.0B
速率9.6 kbps250 kbps
ID/地址8-bit MID,固定分配29-bit,动态地址申领
数据长度最多 21 字节8 字节/帧,TP 可达 1785 字节
网络管理
年代19861994+

最核心区别:物理层不同(RS-485 vs CAN),决定了速率、帧格式的根本差异。J1939 在重卡和工程机械上已基本取代 J1708。


目前主流协议

协议物理层主要用途
J1939CAN 2.0B 250kbps重卡/工程机械骨干网,最主流
CANopenCAN工程机械组件,欧系多用
CAN FD / J1939-22CAN FD新平台 ECU,逐步取代传统 CAN
LIN单线串行低速车身附件(灯、雨刷等)
Ethernet / SOME-IP100M/1G自动驾驶、域控制器

数据格式差异要点

不同协议在解析时需注意:

  1. ID 解析方式:普通 CAN 的 ID 厂商自定义;J1939 必须按 PGN + SA 拆解
  2. 字节序:J1939 规定小端;私有协议可能大端,需看 DBC 定义
  3. 数值缩放:J1939/SPN 有固定 Factor/Offset(如转速 Factor = 0.125 rpm/bit);私有协议厂商自定义
  4. 数据长度:CAN 2.0 最多 8 字节;CAN FD 最多 64 字节,需特别处理
  5. 多包组帧:J1939 TP 报文需要多帧组包才能完整解析

我们工具的准确定位

公司 CAN 参数工具(配置文件如 CanCfg_TESTBC):

  • 支持可变 baudrate
  • 支持 11-bit 或 29-bit ID 可选
  • 主要用途:数据采集解析

准确叫法:通用 CAN 参数工具 / CAN 数据采集解析工具
不准确叫法:J1939 工具(因为 J1939 锁定了 29-bit + 250kbps + PGN 寻址,而我们的工具没有强制这些约束)


J1939 ID 的 bit 级拆解(容易搞错的细节)

29-bit ID 完整结构(按 32-bit 表示,最高3bit是填充,因为29<32):

0x18FEF131 = 0001 1000 1111 1110 1111 0001 0011 0001

未使用填充: 3 bit   →  000
Priority:   3 bit   →  110  (=6)
Reserved:   1 bit   →  0
DP:         1 bit   →  0
PF:         8 bit   →  1111 1110  (FE)
PS:         8 bit   →  1111 0001  (F1)
SA:         8 bit   →  0011 0001  (31)

关键纠正点:Priority 不是整齐卡在某个 hex 字符边界上的——它横跨第一个字节的中间3bit,必须按 bit 级拆,不能简单按 hex 字符切(比如不能简单认为"前两位hex就是优先级")。

PGN = PF + PS(中间4位hex),这才是决定"这是什么参数"的核心字段。Source Address(最后2位hex)只决定"是哪个ECU发的"。

Data Page (DP) 的作用

DP 是1bit"翻页开关":DP=0 查 Page 0 的PGN定义表(绝大多数标准车辆参数都在这页),DP=1 查 Page 1(多见于船用NMEA2000、农机厂商扩展等,搜索未能找到权威的DP=1具体PGN编号例子,需查SAE J1939-71官方文档或CSS Electronics在线PGN转换器验证,不应凭记忆给出具体编号)。

业务判断:卡车/货车市场场景下,DP=1 出现概率极低(属于商用车标准范围之外的场景)。工程上若解析时直接忽略DP(恒当作0处理),多数情况下不会出问题,但更稳妥的做法是"解析这一位、但只处理DP=0,遇到DP=1直接丢弃"——避免把DP=1的报文误用DP=0的表解析,得到一个看似正常但实际错误的数值(这是隐性风险,不会报警,只会悄悄给错数据)。


CAN ID 匹配模式:Full ID vs PGN-only(工具功能设计)

背景:现有解析引擎是对全部 CAN ID 做完整匹配。考虑给 J1939(扩展帧)增加一个匹配模式下拉选项——"完整CAN ID" vs "仅匹配PGN",以兼容"只关心参数类型、不关心发送方ECU"的场景。

实现方式:位掩码(Bit Mask)+ 按位 AND

掩码是与原始ID等长的二进制串:掩码某位=1→该位参与比较;掩码某位=0→该位强制清零、不参与比较。

c
uint32_t received_id = can_frame.id;
uint32_t config_id    = parse_hex(json_get("CanID"));
uint32_t mask = (MatchMode == "PGN") ? 0x00FFFF00 : 0xFFFFFFFF;  // 具体数值需按是否含DP精确核定,不可直接照搬

if ((received_id & mask) == (config_id & mask)) {
    // 匹配成功,进入 DataSignal 解析
}

MatchModemask 的转换在配置加载阶段做一次即可,运行时每帧只需一次&==,性能开销可忽略。

配置文件设计建议:新增 MatchMode 字段("FULL"或"PGN"),缺省按"FULL"处理以保证旧配置向后兼容。

风险提示(需在UI层面提示用户):选择"仅匹配PGN"模式后,若总线上有多个ECU发送同一PGN(如双发动机/主备ECU切换场景),设备将无法区分数据来源,所有匹配的报文会被当作同一信号源处理。


CAN 物理层工作机制(底层原理,应用层开发通常不会碰到,但有助于排查底层通信问题)

总线空闲电平 ≠ 逻辑0

容易搞错的点:CAN 总线空闲时不是"全是0",而是 Recessive(隐性,逻辑1) 状态。逻辑0是 Dominant(显性),需要主动拉低(差分电压产生明显压差)。SOF(帧起始)就是从 Recessive→Dominant 的一次跳变。

设备如何"抓到"一帧的开始:硬同步(Hard Sync)

不是"持续按节拍采样、读到0就丢弃"的模型,而是:

  1. 总线空闲时,所有节点的位时序逻辑处于待命状态,不会按帧内位计数持续采样
  2. SOF跳变(Recessive→Dominant)是一个硬件层面立即可检测的电气事件,所有节点的CAN Controller监听到这个跳变后,立刻把这一刻定义为"第0位",重新校准内部位时序计数器(硬同步)
  3. 校准后,按配置好的baudrate固定节拍依次采样后续每一位(无论是0是1都如实记录,不是"是1才要")
  4. 帧结束后回到空闲待命,等待下一次SOF

位填充(Bit Stuffing)与再同步(Resynchronization)

因为各节点时钟晶振存在微小误差,长帧传输中可能逐渐漂移。若数据中连续出现多个相同电平,总线会长时间没有跳变可供节点校准时序。

规则:连续5个相同电平后,发送方的CAN Controller自动强制插入1个相反电平位(不属于数据内容)。接收方的CAN Controller按同样规则自动识别并剔除这个填充位还原原始数据——这个过程在硬件协议引擎层完全自动完成,应用层代码不会感知到,拿到的始终是剔除填充位后的、长度固定的纯净帧内容,不会出现"正常64bit变65bit导致解析错位"的问题。

效果:保证总线上至少每隔5位必然出现一次跳变,节点可借此持续做小幅时序微调(再同步),防止长时间无跳变导致的时钟漂移累积。

两种同步的关系:硬同步只发生一次(SOF时刻,建立整帧起始基准);再同步贯穿整帧传输全程(靠Bit Stuffing制造的跳变持续做微调)。两者配合才能保证长帧最后一位时序依然准确。

ACK 机制(wired-AND特性的应用)

  1. 发送方发完所有字段后,到达ACK Slot这一位,发送方自己主动发出Recessive(1),相当于"留白等待确认"
  2. 总线上所有正确接收到该帧(CRC校验通过)的节点,必须在这一位主动拉Dominant(0)——这是协议强制要求,与业务上是否关心这帧数据无关
  3. 因为CAN总线电气特性是"任意节点拉低,总线就是低"(wired-AND),只要有一个节点听到了,这一位就会被拉成0
  4. 发送方同时监听这一位:读到0→至少有节点确认收到;读到1→总线上无人正确收到,判定错误并重发

CAN Controller vs CAN Transceiver 的分工(容易混淆)

MDR(或任何CAN节点)的硬件链路:

MCU/SoC(内置或外接 CAN Controller)
        ↓ TX/RX 逻辑信号
CAN Transceiver(如 TJA1050、SN65HVD230)
        ↓ 差分信号 CAN_H / CAN_L
CAN 总线物理线缆
层级职责
CAN Controller协议引擎——位填充插入/剔除、CRC计算校验、仲裁、错误检测、ACK处理、Listen-Only静默模式开关
CAN Transceiver纯电平转换,逻辑电平↔差分信号,不理解协议内容,没有"模式"概念

"完全静默/不发ACK"是怎么实现的:通过软件配置CAN Controller的工作模式寄存器(如STM32的CAN_BTR寄存器的SILM位),告诉协议引擎"正常接收、正常CRC校验、正常把数据交给上层软件,但禁止任何需要主动驱动总线的输出(ACK/错误帧/仲裁相关信号),不向Transceiver的TX引脚输出任何信号"。这个配置发生在CAN Controller层,与Transceiver或物理接线无关。

车辆场景为什么需要这个:CAN的ACK机制不区分"目标接收方"——所有正确收到帧的节点都会一起参与拉低ACK位。第三方监听设备(如MDR)若意外参与了这个过程,理论上不会破坏功能,但工程上不希望旁路监听设备对原车总线有任何主动信号输出的可能性,因此直接在硬件层禁用所有主动发送能力,是最彻底的隔离方式。