2. 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个字节能塞下四个传感器的数据。
2.3 远程帧:请求数据的正确姿势
远程帧跟数据帧长得几乎一样,区别在于远程帧没有数据段。它的作用是:一个节点告诉另一个节点“把你那个ID的数据发给我”。
举个例子:ECU想请求传感器的数据,就发一个远程帧,传感器收到后,发一个数据帧回来。
这里有个坑——我曾经在调试的时候发现,远程帧的DLC(数据长度码)必须跟数据帧的DLC一致,否则接收节点可能不响应。说白了,你请求8个字节的数据,但你的DLC写的是4,那对方可能直接忽略你。
2.4 错误帧:总线的“报警器”
错误帧是CAN总线自我保护的机制。当任何一个节点检测到错误,就会主动发送错误帧,通知其他节点“刚才那帧有问题,别用了”。
错误帧分两种:
- 主动错误帧: 节点状态是“主动错误”时发的,6个连续的显性位,够狠。
- 被动错误帧: 节点状态是“被动错误”时发的,6个连续的隐性位,比较温和。
我在项目里见过最典型的情况:某个节点的CAN收发器坏了,一直发错误帧,导致整个总线瘫痪。那时候我拿着示波器一看,总线上全是错误帧的波形,数据帧根本插不进去。
2.5 过载帧:我忙,别催我
过载帧用得很少。它的作用是:接收节点告诉发送节点“我还没准备好,你再等等”。
过载帧的结构跟错误帧有点像,但触发条件不同。过载帧只在两种情况下出现:
- 接收节点内部缓冲区满了,处理不过来。
- 帧间隔时间太短,节点需要更多时间。
说实话,如果你在总线上频繁看到过载帧,说明你的总线负载太高了,或者某个节点的处理能力不够。这时候该考虑优化软件或者升级硬件了。
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自动退出发送,转为接收。
我在项目中遇到过一个问题:两个节点的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,结果接收节点解析数据的时候越界了,程序跑飞了。
2.8 帧结构总览图
下面这张图把数据帧的完整结构画出来了。你对照着看,每个段的位置和作用一目了然。
2.9 总结:帧结构是调试的“地图”
帧结构就像一张地图。你拿着它,才能在总线上找到方向。数据帧是主干道,远程帧是问路,错误帧是路障,过载帧是堵车。仲裁段和控制段是路标,告诉你谁先走、走多远。
我在调试丢包问题时,第一步就是看帧结构对不对。比如:
- 如果总线上全是错误帧,说明硬件有问题。
- 如果数据帧的DLC不对,说明发送端配置错了。
- 如果仲裁段ID冲突,说明节点设计有缺陷。
这些内容,后面几章会一个一个展开讲。今天先把帧结构刻在脑子里,后面调试的时候,你会感谢自己现在的认真。