2. CAN协议帧结构详解:数据帧、远程帧、错误帧、过载帧

CAN总线上跑的数据,说白了就四种帧。我刚开始接触CAN的时候,总觉得帧结构很复杂,各种段、各种位,记不住。后来在项目里调了几个月,发现其实核心就那几个东西。今天我把这四种帧掰开揉碎了讲,尤其是仲裁段和控制段,这是丢包和信号问题的重灾区。

核心观点: 理解帧结构,是定位CAN总线丢包的第一步。你连报文长什么样都不知道,怎么分析它为什么丢了?

2.1 四种帧类型速览

CAN协议定义了四种帧,每种干不同的活。我列个表,你一眼就能看明白。

帧类型 用途 谁发的 常见问题
数据帧 发送节点把数据传给其他节点 发送节点 数据内容错误、CRC校验失败
远程帧 请求某个ID的数据 接收节点 请求不到响应、ID不匹配
错误帧 检测到总线错误时主动报告 任何节点 总线关闭、错误计数器飙升
过载帧 接收节点忙不过来,要求延迟 接收节点 帧间隔异常、总线拥堵

实际调试中,数据帧和错误帧是我打交道最多的。远程帧偶尔遇到,过载帧说实话,我在项目里只见过两次——一次是总线负载太高,一次是某个节点的MCU处理不过来。

2.2 数据帧:最常用的帧

数据帧是CAN总线上的主角。它负责把数据从一个节点送到另一个节点。标准帧有11位ID,扩展帧有29位ID。我个人习惯用标准帧,简单、可靠,除非项目要求必须用扩展帧。

数据帧的结构长这样:

帧起始(SOF) + 仲裁段 + 控制段 + 数据段 + CRC段 + ACK段 + 帧结束(EOF)

嗯,这里要注意,数据段最多8个字节。为什么是8个?因为CAN总线设计的时候,考虑到实时性和总线负载,8个字节够用了。你想想看,一个发动机转速信号,也就2个字节。8个字节能塞下四个传感器的数据。

我的经验: 我曾经遇到一个项目,工程师非要把32个字节的数据塞进一帧里,结果拆成了4帧,还搞乱了顺序。其实完全可以用CANopen的SDO或者J1939的多包传输,别硬塞。

2.3 远程帧:请求数据的正确姿势

远程帧跟数据帧长得几乎一样,区别在于远程帧没有数据段。它的作用是:一个节点告诉另一个节点“把你那个ID的数据发给我”。

举个例子:ECU想请求传感器的数据,就发一个远程帧,传感器收到后,发一个数据帧回来。

这里有个坑——我曾经在调试的时候发现,远程帧的DLC(数据长度码)必须跟数据帧的DLC一致,否则接收节点可能不响应。说白了,你请求8个字节的数据,但你的DLC写的是4,那对方可能直接忽略你。

避坑指南: 我曾经在一个项目中,远程帧的DLC写错了,导致传感器一直不响应。查了两天才发现是DLC不匹配。所以,远程帧的DLC一定要跟目标数据帧的DLC保持一致。

2.4 错误帧:总线的“报警器”

错误帧是CAN总线自我保护的机制。当任何一个节点检测到错误,就会主动发送错误帧,通知其他节点“刚才那帧有问题,别用了”。

错误帧分两种:

  • 主动错误帧: 节点状态是“主动错误”时发的,6个连续的显性位,够狠。
  • 被动错误帧: 节点状态是“被动错误”时发的,6个连续的隐性位,比较温和。

我在项目里见过最典型的情况:某个节点的CAN收发器坏了,一直发错误帧,导致整个总线瘫痪。那时候我拿着示波器一看,总线上全是错误帧的波形,数据帧根本插不进去。

2.5 过载帧:我忙,别催我

过载帧用得很少。它的作用是:接收节点告诉发送节点“我还没准备好,你再等等”。

过载帧的结构跟错误帧有点像,但触发条件不同。过载帧只在两种情况下出现:

  1. 接收节点内部缓冲区满了,处理不过来。
  2. 帧间隔时间太短,节点需要更多时间。

说实话,如果你在总线上频繁看到过载帧,说明你的总线负载太高了,或者某个节点的处理能力不够。这时候该考虑优化软件或者升级硬件了。

2.6 仲裁段:谁先说话?

仲裁段是CAN总线最巧妙的设计之一。它解决了“多个节点同时发送时,谁先发”的问题。

仲裁段的核心是ID。ID越小,优先级越高。标准帧的ID是11位,扩展帧是29位。仲裁的时候,每个节点逐位比较ID,谁先出现显性位(0),谁就赢。

举个例子:

  • 节点A的ID是0x100(二进制:001 0000 0000)
  • 节点B的ID是0x200(二进制:010 0000 0000)

在第一位,A是0(显性),B是0(显性),平手。第二位,A是0(显性),B是1(隐性),A赢了。B自动退出发送,转为接收。

关键点: 仲裁段不丢数据。输了的节点会在总线空闲后自动重发。这就是为什么CAN总线不会因为冲突而丢包——至少理论上不会。

我在项目中遇到过一个问题:两个节点的ID设成了相同的值,结果它们同时发送时,仲裁段无法区分,导致数据混乱。后来我强制要求所有节点的ID必须唯一,才解决了这个问题。

2.7 控制段:数据长度的“说明书”

控制段紧跟在仲裁段后面,包含两个重要部分:

  • IDE位: 标识是标准帧还是扩展帧。1表示扩展帧,0表示标准帧。
  • DLC(数据长度码): 4个位,表示数据段有多少个字节。范围是0到8。

DLC的编码很简单:

DLC值(二进制) 数据字节数
0000 0
0001 1
0010 2
... ...
1000 8

这里有个细节:DLC可以设置成大于8的值(比如1001),但数据段仍然只有8个字节。为什么?因为CAN协议规定数据段最多8个字节,DLC超过8的部分被忽略。我曾经见过一个工程师把DLC设成了15,结果接收节点解析数据的时候越界了,程序跑飞了。

避坑指南: DLC不要设超过8。虽然硬件不会报错,但软件解析的时候容易出问题。我建议在代码里加一个DLC校验,超过8的直接丢弃。

2.8 帧结构总览图

下面这张图把数据帧的完整结构画出来了。你对照着看,每个段的位置和作用一目了然。

CAN数据帧结构图 CAN数据帧结构(标准帧) SOF 仲裁段 11位ID + RTR 控制段 IDE + DLC 数据段 0~8字节 CRC段 15位CRC + 定界符 ACK段 2位 EOF 7位 说明: SOF = 帧起始(1位显性位) RTR = 远程帧标识(0=数据帧,1=远程帧) IDE = 扩展帧标识(0=标准帧,1=扩展帧) 总长度:标准帧约108位(含填充位),扩展帧约128位 仲裁段详解 标准帧:11位ID + RTR位 扩展帧:29位ID + SRR位 + IDE位 + RTR位 仲裁规则:ID越小优先级越高 显性位(0)赢隐性位(1) 控制段详解 标准帧:IDE位 + r0 + DLC(4位) 扩展帧:IDE位 + r1 + r0 + DLC(4位) DLC范围:0~8(超过8按8处理) r0、r1为保留位,必须为显性

2.9 总结:帧结构是调试的“地图”

帧结构就像一张地图。你拿着它,才能在总线上找到方向。数据帧是主干道,远程帧是问路,错误帧是路障,过载帧是堵车。仲裁段和控制段是路标,告诉你谁先走、走多远。

我在调试丢包问题时,第一步就是看帧结构对不对。比如:

  • 如果总线上全是错误帧,说明硬件有问题。
  • 如果数据帧的DLC不对,说明发送端配置错了。
  • 如果仲裁段ID冲突,说明节点设计有缺陷。

这些内容,后面几章会一个一个展开讲。今天先把帧结构刻在脑子里,后面调试的时候,你会感谢自己现在的认真。