4. CAN总线通信原理:CSMA/CD+AMP、位填充、错误帧与错误处理
各位同学,今天我们来啃一块硬骨头——CAN总线的通信原理。说实话,这部分内容我第一次学的时候也觉得挺绕的。但后来在项目里调过几次总线冲突,才真正体会到这些机制有多精妙。
CAN总线为什么能在工业现场这么稳?核心就在于它这套独特的“交通规则”。咱们一个一个来看。
4.1 载波监听多路访问/冲突检测 + 仲裁(CSMA/CD+AMP)
先说说这个名字,挺长的。CSMA/CD+AMP,说白了就是“先听后说,边说边听,撞了还能分胜负”。
载波监听(CSMA):每个节点在发数据前,先听听总线上有没有人在说话。没人说话,我才开口。这个好理解吧?就像开会,别人在讲你就先等着。
多路访问(MA):所有节点都挂在同一根总线上,谁都可以抢着发。但问题来了——万一两个节点同时开口呢?
冲突检测(CD):节点一边发数据,一边监听总线。如果发现自己发的和总线上的不一样,就知道撞车了。
但CAN的高明之处在于AMP(仲裁)。它不靠“撞了就退避重发”这种低效方式,而是用标识符(ID)的优先级来决定谁继续发。
核心机制:隐性位 vs 显性位
CAN总线有两种电平状态:
- 显性位(Dominant):逻辑0,电平强制拉低
- 隐性位(Recessive):逻辑1,电平释放
规则很简单:显性位“覆盖”隐性位。只要有一个节点发0,总线就是0。
仲裁过程是这样的:多个节点同时发送,从ID的最高位开始逐位比较。谁先发出隐性位(1)而别人发出显性位(0),谁就自动退出。剩下的那个节点继续发完整个帧。
我在项目中遇到过一个问题:两个节点ID分别是0x123和0x124,按道理0x123优先级更高。但调试时发现0x124总能抢到总线。查了半天,原来是ID配置反了——CAN的ID越小优先级越高,0x123确实比0x124高。嗯,这个坑我踩过,你们记住。
避坑指南:我曾经在一条总线上挂了30多个节点,ID分配没规划好,导致低优先级节点几乎发不出数据。后来我养成了一个习惯:把实时性要求高的节点分配小ID,比如电机控制给0x001,温度采集给0x100。你想想看,这样紧急的数据永远优先通过。
4.2 位填充机制(Bit Stuffing)
为什么要位填充?这个问题我问过不少新人。其实原因很直接:保证时钟同步。
CAN总线没有单独的时钟线,所有节点靠数据信号的跳变沿来同步时钟。如果连续发5个相同的位(比如00000),总线上就没有跳变沿了,节点之间的时钟就会慢慢漂移,最终导致采样错误。
位填充规则:发送节点在连续发送5个相同位后,自动插入一个相反极性的位。
- 连续5个0 → 插入1个1
- 连续5个1 → 插入1个0
接收节点收到数据后,会自动把这个填充位去掉,恢复原始数据。
举个例子:
原始数据:11111 00000 11111
填充后: 111110 000001 111110
(加粗的是填充位)
我个人习惯在调试时用示波器抓CAN波形,数一数位填充是否正确。有一次发现波形上多了一个不该有的跳变,查出来是发送节点的晶振频率偏差太大,导致位填充时机不对。换了个高精度晶振就解决了。
注意:位填充覆盖的范围是从SOF(帧起始)到CRC结束。ACK场、EOF(帧结束)和帧间空间不进行位填充。这个细节在协议栈实现时容易忽略,我见过有人把EOF也做了位填充,结果接收端死活不认帧。
4.3 错误帧与错误处理
CAN总线的可靠性,很大程度上归功于它这套错误检测与处理机制。说白了,它不仅能发现错误,还能自动重发,甚至能把“捣乱”的节点踢出总线。
4.3.1 五种错误类型
| 错误类型 | 检测机制 | 说明 |
|---|---|---|
| 位错误 | 发送节点比较发送位与总线位 | 不一致则报错(仲裁期间除外) |
| 填充错误 | 接收节点检测到连续6个相同位 | 违反位填充规则 |
| CRC错误 | 接收节点计算CRC与发送方不匹配 | 数据完整性校验失败 |
| 格式错误 | 检测到固定格式位(如EOF)为错误电平 | 帧格式不符合规范 |
| 应答错误 | 发送节点在ACK槽未收到显性位 | 没有节点确认接收 |
4.3.2 错误帧结构
当任何节点检测到错误,它会立即发送错误帧。错误帧由两部分组成:
- 错误标志(Error Flag):6个连续的显性位(主动错误节点)或6个连续的隐性位(被动错误节点)
- 错误界定符(Error Delimiter):8个连续的隐性位
错误帧会破坏当前正在传输的数据帧,迫使所有节点重新开始仲裁和发送。
4.3.3 错误状态机
每个CAN控制器内部维护着发送错误计数器(TEC)和接收错误计数器(REC)。根据计数值,节点处于三种状态之一:
三种错误状态
- 主动错误(Error Active):TEC < 127 且 REC < 127。可以正常发送和接收,检测到错误时发送主动错误帧(6个显性位)
- 被动错误(Error Passive):TEC > 127 或 REC > 127。可以参与通信,但检测到错误时只能发送被动错误帧(6个隐性位),且发送后需等待8个额外位时间
- 总线关闭(Bus Off):TEC > 255。节点完全断开与总线的连接,不再发送任何数据
我曾经在一个项目里遇到过总线间歇性瘫痪的情况。查了三天,最后发现是一个节点的CAN收发器坏了,不停地发错误帧。那个节点的TEC早就超过255了,但软件没做总线恢复处理。后来我加了一段逻辑:检测到总线关闭状态后,等待128个11个隐性位(总线空闲),然后自动恢复。嗯,这个经验后来成了我们团队的代码规范。
调试技巧:我建议你在开发阶段把TEC和REC的值通过CAN报文发出来。这样一旦总线出问题,你能立刻知道是哪个节点在“捣乱”。我曾经用这个办法,五分钟就定位到了一个接线松动的节点。
4.4 本章知识体系
下面这张图把本章的核心逻辑串起来了,你可以对照着理解:
这张图把三个核心机制串在了一起。CSMA/CD+AMP解决的是“谁先说话”的问题,位填充解决的是“时钟同步”的问题,错误处理解决的是“出错了怎么办”的问题。三者缺一不可。
好了,这一章的内容就到这里。CAN总线的通信原理确实需要多花点时间消化,但一旦理解了这些底层机制,后面学应用层协议就会轻松很多。
公众号:蓝海资料掘金营,微信deep3321