4、CAN错误处理机制:错误类型、错误状态机、错误计数器
各位同学,咱们今天聊聊CAN总线里一个特别实在的话题——错误处理。
说实话,我刚入行那会儿,总觉得错误处理是“锦上添花”的东西。直到有一次,我在一个ADAS项目里,亲眼看着一辆测试车因为总线错误没处理好,直接丢了一帧关键的刹车指令……嗯,从那以后,我再也不敢小看这个模块了。
CAN总线为什么能在汽车这种高噪声环境里活下来?说白了,就是靠这套错误检测 + 错误通知 + 错误恢复的铁三角机制。今天咱们就把这玩意儿拆开揉碎了讲清楚。
4.1 五种错误类型:你总得知道敌人长什么样
CAN协议定义了五种错误。我个人习惯把它们分成两类:“数据对不对”和“格式对不对”。
4.1.1 位错误(Bit Error)
这个最简单。发送节点往总线上发了一个显性位(0),结果自己回读的时候读到了隐性位(1)。或者反过来。这就叫位错误。
为什么会出现? 总线短路、信号反射、或者另一个节点同时在发数据,都会导致。
关键点: 只有发送节点能检测到位错误。接收节点不会报这个错。
4.1.2 填充错误(Stuff Error)
CAN协议规定:连续发送5个相同电平的位之后,必须插入一个相反电平的位。这叫位填充。
如果接收节点发现连续6个位都是相同的电平,那就说明填充规则被破坏了。这就是填充错误。
我的一点经验: 填充错误往往意味着总线上的时钟不同步,或者有节点在乱发数据。我曾经在一个项目里排查了三天,最后发现是一个MCU的CAN控制器初始化时序没配好,导致它在总线空闲时莫名其妙地多发了几个隐性位。
4.1.3 CRC错误(CRC Error)
每个CAN帧末尾都带有一个15位的CRC校验码(CAN FD里是17位或21位)。接收节点会用自己的算法重新算一遍,如果跟收到的CRC不一致,就报CRC错误。
这个错误最常见的原因是总线干扰。你想想看,汽车发动机点火的时候,那电磁干扰可不是闹着玩的。
4.1.4 格式错误(Form Error)
CAN帧的某些字段是固定格式的。比如CRC界定符必须是隐性位,ACK界定符也必须是隐性位。如果接收节点在这些固定位置上读到了显性位,那就报格式错误。
注意: 格式错误跟位错误不一样。位错误是“发送者自己发现发错了”,格式错误是“接收者发现格式不对”。
4.1.5 应答错误(ACK Error)
发送节点在ACK槽(ACK Slot)里发送一个隐性位,然后等着接收节点把它拉成显性位。如果发送节点发现ACK槽还是隐性,那就说明没有一个节点成功接收了这帧数据。这就是应答错误。
为什么会这样? 要么总线上没有其他节点,要么所有接收节点都检测到了错误,主动不发送ACK。
| 错误类型 | 谁检测 | 检测时机 | 常见原因 |
|---|---|---|---|
| 位错误 | 发送节点 | 发送时回读 | 总线短路、多节点冲突 |
| 填充错误 | 接收节点 | 接收数据时 | 时钟不同步、节点异常 |
| CRC错误 | 接收节点 | 接收完数据后 | 电磁干扰、传输错误 |
| 格式错误 | 接收节点 | 接收帧时 | 协议违规、硬件故障 |
| 应答错误 | 发送节点 | ACK槽期间 | 无接收节点、总线断开 |
4.2 错误状态机:节点是怎么“变坏”的
CAN节点不是一检测到错误就立刻“罢工”的。它有三个状态:错误主动(Error Active)、错误被动(Error Passive)、总线关闭(Bus Off)。
这三个状态,说白了就是:
- 错误主动:我很好,我还能主动纠错。
- 错误被动:我有点累了,我只能被动配合。
- 总线关闭:我不行了,我彻底闭嘴。
4.2.1 错误主动(Error Active)
这是正常状态。节点可以正常收发数据。如果检测到错误,它会发送一个主动错误标志——连续6个显性位。这个标志会强制打断当前帧,让所有节点都知道出错了。
4.2.2 错误被动(Error Passive)
当节点的错误计数器超过某个阈值(发送错误计数器≥255,或接收错误计数器≥128),它就进入错误被动状态。
在这个状态下,节点仍然可以收发数据,但检测到错误时,它只能发送被动错误标志——连续6个隐性位。这个标志不会干扰总线,其他节点可能根本注意不到。
这里有个坑: 错误被动的节点发送数据时,必须等待8个隐性位(正常是3个)才能开始下一帧。这是为了给其他节点更多机会占用总线。
4.2.3 总线关闭(Bus Off)
如果发送错误计数器累积到256,节点就进入总线关闭状态。在这个状态下,节点完全不能参与总线通信——既不能发,也不能收。
要退出这个状态,节点必须检测到128次连续的11个隐性位(总线空闲序列)。
我个人的建议: 在应用层设计时,一定要监控节点的错误状态。我曾经见过一个项目,某个节点反复进入Bus Off又恢复,导致总线上出现大量的错误帧,最后整个网络都瘫痪了。后来我们在应用层加了一个“三次Bus Off就永久离线”的逻辑,问题才解决。
4.3 错误计数器:谁在给节点“打分”
每个CAN节点内部都有两个计数器:发送错误计数器(TEC)和接收错误计数器(REC)。它们决定了节点当前处于哪个错误状态。
计数器的增减规则,我直接给你列出来:
| 事件 | TEC变化 | REC变化 |
|---|---|---|
| 发送节点检测到位错误 | +8 | 不变 |
| 发送节点检测到ACK错误 | +8 | 不变 |
| 发送节点检测到格式错误 | +8 | 不变 |
| 接收节点检测到CRC错误 | 不变 | +1 |
| 接收节点检测到填充错误 | 不变 | +1 |
| 节点成功发送一帧 | -1(如果TEC>0) | 不变 |
| 节点成功接收一帧 | 不变 | -1(如果REC>0) |
| 节点检测到主动错误标志 | 不变 | +1 |
你发现没有?发送错误的“惩罚”比接收错误重得多。发送节点出错一次加8分,接收节点出错一次才加1分。这是故意的——因为发送节点对总线的影响更大。
状态切换阈值:
- 错误主动 → 错误被动:TEC ≥ 128 或 REC ≥ 128
- 错误被动 → 总线关闭:TEC ≥ 256
- 错误被动 → 错误主动:TEC ≤ 127 且 REC ≤ 127
4.4 实战中的避坑指南
最后,我分享几个我在项目中踩过的坑:
- 别忽视“间歇性错误”。有些节点在高温或振动环境下会频繁出错,但每次都能恢复。这种节点最危险——它会让总线质量持续下降,但你又很难复现问题。我建议在测试阶段就做长时间的压力测试。
- 错误计数器不是万能的。它只能反映“错误发生的频率”,不能告诉你“错误发生在哪里”。所以,配合CANscope或CANalyzer抓波形,才是排查问题的王道。
- Bus Off后的恢复策略要谨慎。有些芯片默认是自动恢复的,有些需要软件手动恢复。如果你用的是自动恢复,一定要确保恢复条件足够严格,否则节点会在“出错-恢复-再出错”的死循环里出不来。
一个小技巧: 在开发阶段,我习惯把错误计数器的值通过诊断报文发出来。这样在测试时,我可以实时看到每个节点的“健康状态”。一旦发现某个节点的REC或TEC异常增长,就能提前定位问题。
好了,关于CAN的错误处理机制,今天就聊到这儿。下一节咱们会讲CAN FD和CAN XL的帧格式差异,到时候你会发现,错误处理机制在高速总线上又有了新的挑战。