4、CAN错误处理机制:位错误、填充错误、CRC错误、形式错误、应答错误、错误状态转换
各位工程师,咱们今天聊聊CAN总线里一个特别实在的话题——错误处理。说实话,很多刚入行的朋友觉得CAN协议稳定,就不太关心错误机制。但我在项目里吃过亏,有一次台架测试时总线莫名其妙丢帧,排查了整整两天,最后发现是某个节点的错误计数器爆了,进入了Bus Off状态。从那以后,我养成了一个习惯:先搞清楚错误是怎么来的,怎么处理的。
CAN的错误处理机制,说白了就是一套「自我诊断+自动恢复」的体系。它不需要你写额外的代码,硬件自己就干了。但你要是不懂,出了问题就抓瞎。咱们一个一个来看。
4.1 五种错误类型
CAN协议定义了五种错误。嗯,这里要注意,这五种错误不是并列的,它们分别对应不同的检测场景。
| 错误类型 | 检测时机 | 谁检测 |
|---|---|---|
| 位错误 | 发送节点发送位时 | 发送节点自己 |
| 填充错误 | 接收节点接收时 | 所有接收节点 |
| CRC错误 | 接收节点计算CRC后 | 所有接收节点 |
| 形式错误 | 接收节点检查固定格式时 | 所有接收节点 |
| 应答错误 | 发送节点等待ACK时 | 发送节点自己 |
4.2 位错误(Bit Error)
位错误是最直观的一种。发送节点在总线上发送一个位,同时它也在监听总线。如果它发送的是显性位(0),但听到总线上是隐性位(1),那就出问题了。
我个人习惯把位错误理解为「自己打自己脸」。你想想看,你明明说了0,结果总线反馈是1,那肯定是有别的节点在跟你抢总线,或者线路出了故障。
关键点:位错误只在发送节点检测。接收节点不会报位错误,因为接收节点本来就不需要「发送后对比」这个动作。
我在项目中遇到过一种情况:某个节点因为晶振偏差太大,导致位时序偏移,发送的位被采样点误判了。这种问题很难查,因为不是每次都错,而是偶发性的。后来我用示波器抓了波形,才发现位宽不对。
4.3 填充错误(Stuff Error)
CAN协议规定:连续发送5个相同电平的位之后,必须插入一个相反电平的位。这叫位填充规则。如果接收节点发现连续出现了6个相同电平的位,那就报填充错误。
为什么要这么做?说白了就是为了同步。没有位填充,总线长时间没有跳变沿,各个节点的时钟就会漂移。你想想看,大家靠什么对时?就是靠这些跳变沿。
小技巧:调试时如果看到填充错误,先别急着怀疑硬件。有时候是软件把CAN控制器配置成了「不自动插入填充位」的模式。我见过有人手写CAN帧,结果忘了填充规则,总线直接罢工。
4.4 CRC错误
CRC(循环冗余校验)是保证数据完整性的最后一道防线。发送节点会计算整个帧的CRC校验码,放在CRC段。接收节点收到后,重新算一遍,如果对不上,就报CRC错误。
CRC错误通常意味着传输过程中有严重的干扰。比如电机启动时的电磁干扰,或者线束过长导致的信号衰减。
我曾经在商用车项目上遇到过:某个节点在发动机高转速时频繁报CRC错误。排查后发现是CAN_H和CAN_L的绞距被破坏了,共模抑制能力下降。重新做了线束后问题消失。
注意:CRC错误不一定是物理层问题。如果两个节点的波特率有微小差异,也可能导致采样点偏移,从而误判CRC。我建议先检查波特率精度。
4.5 形式错误(Form Error)
形式错误检测的是CAN帧中那些固定格式的位段。比如CRC界定符必须是隐性位,ACK界定符必须是隐性位,EOF段必须是7个隐性位。如果这些固定位出现了显性电平,就报形式错误。
说白了,形式错误就是「格式不对」。就像你写代码,语法错了编译器会报错一样。CAN节点对格式要求很严格,一点都不能含糊。
我记得有一次,某个节点在发送数据帧时,EOF段只发了6个隐性位就停了。其他节点立刻报形式错误,然后发送错误帧。那个节点就被动进入了错误被动状态。
4.6 应答错误(ACK Error)
应答错误是发送节点独有的。发送节点在ACK槽位发送一个隐性位,然后等待总线上出现显性位。如果有至少一个接收节点正确收到了帧,它就会在ACK槽位发送显性位来应答。如果发送节点没看到显性位,就报应答错误。
这个机制很有意思。它意味着:如果总线上只有一个节点在发,没有其他节点在收,那发送节点就会一直报应答错误。所以CAN总线至少需要两个节点才能正常工作。
实战经验:调试时如果看到应答错误,先确认总线上有没有其他节点在监听。我曾经用单节点测试,一直报应答错误,还以为是硬件坏了。后来才想起来,CAN协议就是这么设计的。
4.7 错误状态转换
CAN节点有三个错误状态:主动错误、被动错误、总线关闭。这三个状态是根据两个错误计数器来转换的:发送错误计数器(TEC)和接收错误计数器(REC)。
| 状态 | 条件 | 行为 |
|---|---|---|
| 主动错误 | TEC < 128 且 REC < 128 | 检测到错误时,发送主动错误帧(6个显性位) |
| 被动错误 | TEC > 127 或 REC > 127 | 检测到错误时,发送被动错误帧(6个隐性位),且发送前等待8个隐性位 |
| 总线关闭 | TEC > 255 | 完全断开与总线的连接,不发送任何数据 |
这里有个细节:接收错误计数器超过127时,节点进入被动错误状态,但发送错误计数器超过127时也一样。而总线关闭只由发送错误计数器触发。为什么?因为接收错误再多,也只是你一个人收不到,不影响别人。但发送错误多了,说明你可能在污染总线,必须把你隔离。
我建议大家在设计时,给每个节点加一个状态监控。如果发现某个节点频繁进入被动错误,就要警惕了。我曾经在产线上遇到过一批ECU,上电后几分钟就总线关闭。后来发现是CAN收发器的共模电压范围不达标,导致位错误频发。
避坑指南:我曾经在项目里把错误中断的优先级设得太低,导致错误计数器爆了才处理。结果节点直接总线关闭,整车网络瘫痪。后来我把错误中断提到最高优先级,一有错误立刻处理,问题就解决了。
4.8 错误帧与过载帧
当节点检测到错误时,它会发送错误帧。错误帧由两部分组成:错误标志和错误界定符。主动错误节点发送6个显性位作为错误标志,被动错误节点发送6个隐性位。
过载帧则不同。它不是用来报告错误的,而是用来告诉其他节点:「我忙不过来,慢点发」。过载帧的格式和错误帧很像,但触发条件不一样。过载帧通常由接收节点的内部缓冲区满触发。
说实话,过载帧在实际项目中用得不多。现在的CAN控制器缓冲区都比较大,很少出现缓冲区溢出的情况。但如果你在做高负载的CAN网络,比如动力CAN,还是要注意一下。
4.9 总结
CAN的错误处理机制,本质上是一种分布式容错设计。每个节点都在独立检测错误,独立维护自己的错误计数器,独立决定自己的状态。这种设计的好处是:一个节点坏了,不会拖垮整个网络。
但坏处也很明显:调试困难。你很难知道某个节点为什么进入了被动错误,除非你实时监控它的错误计数器。所以我个人习惯在开发阶段,把每个节点的错误计数器通过诊断报文发出来,方便排查。
嗯,今天就聊到这里。下一章咱们聊聊CAN的位时序和采样点配置,这可是调通CAN总线的关键。