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个相同位后面,硬塞了一个反相位。
为什么要这么干?两个原因:
- 时钟同步:接收器需要边沿来同步时钟。如果长时间没有跳变,收发器的PLL会漂移,导致采样错误。
- 避免误判:CAN协议里有些特殊位序列(比如错误帧的6个显性位),如果数据里也出现6个连续显性位,接收器会误以为是错误帧。
避坑指南:我曾经调试过一个CAN节点,发现它偶尔会莫名其妙地发送错误帧。抓波形一看,数据场里有一段连续的0x00 00 00 00,位填充后变成了20多个显性位,直接把总线拉死了。后来我在应用层加了个随机化处理,问题就解决了。
你想想看,如果数据里全是0x00或者0xFF,位填充后的波形会是什么样?嗯,那画面太美我不敢看。所以很多CAN驱动库都会建议:发送数据前做一下位填充友好处理,比如在数据里插入一些随机位。
3.3 仲裁机制:谁先说话谁有理
CAN总线的仲裁机制,是我觉得最巧妙的设计之一。它基于一个简单的原则:显性位(0)优先于隐性位(1)。
具体过程是这样的:
- 所有节点同时发送仲裁场(ID + RTR位)
- 每个节点在发送每一位后,回读总线电平
- 如果发送的是隐性位(1),但读到的是显性位(0),说明有更高优先级的节点在发送,自己立即退出
- 发送显性位的节点继续,直到仲裁结束
说白了,就是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,抓一段实际总线上的波形,对照着帧结构图一个一个位去解析。我当年就是这么干的,虽然费时间,但效果奇好。