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 本章知识体系

下面这张图把本章的核心逻辑串起来了,你可以对照着理解:

CAN总线通信原理 CSMA/CD+AMP 载波监听 + 仲裁 位填充机制 Bit Stuffing 错误帧与错误处理 Error Frame & Handling 先听后说 → 边说边听 → 仲裁决胜 显性位(0) 覆盖 隐性位(1) ID越小 → 优先级越高 连续5个相同位 → 插入相反位 保证时钟同步(无单独时钟线) 接收端自动去除填充位 5种错误类型检测 错误帧:6显性位 + 8隐性位 TEC/REC计数器 → 状态机 三者协同工作,保证CAN总线的高可靠性与实时性

这张图把三个核心机制串在了一起。CSMA/CD+AMP解决的是“谁先说话”的问题,位填充解决的是“时钟同步”的问题,错误处理解决的是“出错了怎么办”的问题。三者缺一不可。

好了,这一章的内容就到这里。CAN总线的通信原理确实需要多花点时间消化,但一旦理解了这些底层机制,后面学应用层协议就会轻松很多。


公众号:蓝海资料掘金营,微信deep3321