3. CAN总线基础:CAN协议帧结构(标准帧/扩展帧)、位填充、仲裁机制、错误帧

各位同学,今天我们聊点硬核的——CAN协议的帧结构。说实话,搞懂这一块,你才算真正入了CAN总线的门。我当年刚接触CAN时,对着数据手册看了三天,脑子里还是一团浆糊。后来在项目里亲手抓了几次波形,才恍然大悟:哦,原来就这么回事。

3.1 标准帧 vs 扩展帧:到底差在哪?

CAN协议里,帧结构分两种:标准帧和扩展帧。说白了,区别就在ID的长度上。

  • 标准帧:11位ID,仲裁场总共12位(11位ID + 1位RTR)
  • 扩展帧:29位ID,仲裁场总共32位(11位基础ID + SRR + IDE + 18位扩展ID + RTR)

你可能会问:为什么要有两种?嗯,历史原因。早期CAN 2.0A只定义了11位ID,后来发现节点多了不够用,CAN 2.0B才加了扩展帧。我个人习惯,新项目一律用扩展帧,省得以后扩容时头疼。

关键区别点:IDE位(Identifier Extension)

  • 标准帧:IDE = 0(显性)
  • 扩展帧:IDE = 1(隐性)

接收器就是靠这个位来判断后面还有没有18位扩展ID的。

我在项目中遇到过一件事:有个同事把标准帧和扩展帧混在同一个网络里,结果某些节点死活收不到报文。查了半天,原来是接收滤波器配置错了——标准帧的ID和扩展帧的ID虽然数值一样,但在硬件看来完全是两码事。

3.2 位填充:为什么要有这玩意儿?

位填充,英文叫Bit Stuffing。听起来高大上,其实原理很简单:连续发送5个相同电平后,自动插入一个相反电平

举个例子,你要发送的数据是:11111 00000 11111。经过位填充后变成:111110 000001 111110。看到没?每5个相同位后面,硬塞了一个反相位。

为什么要这么干?两个原因:

  1. 时钟同步:接收器需要边沿来同步时钟。如果长时间没有跳变,收发器的PLL会漂移,导致采样错误。
  2. 避免误判:CAN协议里有些特殊位序列(比如错误帧的6个显性位),如果数据里也出现6个连续显性位,接收器会误以为是错误帧。

避坑指南:我曾经调试过一个CAN节点,发现它偶尔会莫名其妙地发送错误帧。抓波形一看,数据场里有一段连续的0x00 00 00 00,位填充后变成了20多个显性位,直接把总线拉死了。后来我在应用层加了个随机化处理,问题就解决了。

你想想看,如果数据里全是0x00或者0xFF,位填充后的波形会是什么样?嗯,那画面太美我不敢看。所以很多CAN驱动库都会建议:发送数据前做一下位填充友好处理,比如在数据里插入一些随机位。

3.3 仲裁机制:谁先说话谁有理

CAN总线的仲裁机制,是我觉得最巧妙的设计之一。它基于一个简单的原则:显性位(0)优先于隐性位(1)

具体过程是这样的:

  1. 所有节点同时发送仲裁场(ID + RTR位)
  2. 每个节点在发送每一位后,回读总线电平
  3. 如果发送的是隐性位(1),但读到的是显性位(0),说明有更高优先级的节点在发送,自己立即退出
  4. 发送显性位的节点继续,直到仲裁结束

说白了,就是ID值越小,优先级越高。因为ID的高位先发送,高位是0的节点会一直占着总线不放。

举个例子

  • 节点A发送ID = 0x123(二进制:0001 0010 0011)
  • 节点B发送ID = 0x456(二进制:0100 0101 0110)

从高位开始比:第1位都是0,继续;第2位A是0,B是1 → A胜出,B退出。

最终节点A获得总线控制权。

我记得刚入行时,有个老工程师跟我说:“CAN总线没有主从,只有谁更‘狠’——谁的ID更小,谁就狠。” 这句话我一直记到现在。

注意:仲裁机制只在数据帧和远程帧之间有效。错误帧和过载帧的优先级更高,它们可以在任何时刻打断正常通信。所以如果你的网络里频繁出现错误帧,那正常的数据帧基本发不出去。

3.4 错误帧:总线上的“警察”

错误帧是CAN协议里最霸道的帧。它没有仲裁过程,一旦检测到错误,任何节点都可以立即发送错误帧,把当前传输强行终止。

错误帧的结构很简单:

  • 错误标志:6个连续显性位(主动错误节点)或6个连续隐性位(被动错误节点)
  • 错误分隔符:8个连续隐性位

CAN协议定义了5种错误类型:

错误类型 检测条件 检测节点
位错误 发送位与回读位不一致(仲裁场和应答场除外) 发送节点
填充错误 检测到6个连续相同位 所有节点
CRC错误 接收到的CRC与本地计算不一致 接收节点
形式错误 固定格式位(如CRC分隔符、ACK分隔符)出现错误电平 所有节点
应答错误 发送节点在ACK槽位没有收到显性位 发送节点

这里有个细节:主动错误节点发送6个显性位,被动错误节点发送6个隐性位。为什么?因为被动错误节点如果也发显性位,会把总线拉死。我见过一个案例,某个节点因为硬件故障频繁进入被动错误状态,然后它发的隐性错误标志被其他主动错误节点覆盖成显性位,结果整个网络都在反复重传,吞吐量直接降到零。

实战经验:调试CAN总线时,我最常用的工具就是CANoe的错误统计功能。如果看到CRC错误计数在持续增长,多半是总线终端电阻匹配有问题或者线缆过长。如果填充错误频繁出现,那就要检查一下是不是有节点在发送非法位序列。

3.5 帧结构总结:一张表看懂

最后,我把标准数据帧的完整结构列出来,方便你对照记忆:

位数 说明
帧起始 1 显性位,同步所有节点
仲裁场 12(标准)/ 32(扩展) ID + RTR,用于仲裁
控制场 6 IDE + r0 + DLC(4位数据长度码)
数据场 0~64 实际数据,最多8字节
CRC场 16 15位CRC + 1位CRC分隔符(隐性)
应答场 2 ACK槽(发送节点发隐性,接收节点拉显性)+ ACK分隔符(隐性)
帧结束 7 7个隐性位

嗯,到这里CAN协议帧结构的基础内容就讲完了。你可能会觉得这些细节有点枯燥,但相信我——搞懂帧结构,是理解CAN总线一切高级特性的基石。下一章我们会聊CAN FD,那玩意儿在数据场长度和速率上做了大改动,但帧结构的基本骨架还是从这里来的。

课后建议:打开CANoe,抓一段实际总线上的波形,对照着帧结构图一个一个位去解析。我当年就是这么干的,虽然费时间,但效果奇好。