4. CAN错误处理:错误类型与状态机

CAN协议有个很厉害的设计——错误处理机制。说实话,这是CAN能在工业、汽车领域活这么久的关键原因之一。它不需要一个中央监控器,每个节点自己就能发现错误、报告错误、甚至把自己关掉。

我刚开始接触CAN时,总觉得这玩意儿太复杂。后来在项目里被总线关闭坑过一次,才老老实实把错误状态机啃完。嗯,今天咱们就把这块掰开揉碎了讲。

4.1 五种错误类型

CAN协议定义了五种错误。任何节点只要检测到其中一种,就会立刻发送错误帧,通知总线上所有其他节点。

错误类型 检测时机 触发条件
位错误 发送节点发送位时 发送位与总线电平不匹配(仲裁场和应答场除外)
填充错误 接收节点接收时 连续检测到6个相同电平的位
CRC错误 接收节点校验时 计算出的CRC与发送的CRC不一致
形式错误 接收节点检测固定位场时 固定位场(如CRC界定符、ACK界定符)出现非法电平
应答错误 发送节点在应答场时 发送节点未在ACK槽检测到显性位

4.1.1 位错误

说白了就是:我说了啥,但总线上不是啥。

发送节点每发一个位,都会回读总线电平。如果读到的和自己发的不一样,就报位错误。但有两个例外:

  • 仲裁场:发送显性位但读到隐性位,说明仲裁输了,不是错误。
  • 应答场:发送隐性位但读到显性位,说明有节点应答了,也不是错误。
实战经验: 我在调试一个电机控制器时,发现它频繁报位错误。查了半天,原来是收发器的CANH和CANL线接反了。嗯,这种低级错误其实挺常见的。

4.1.2 填充错误

CAN协议规定:连续发送5个相同位后,必须插入一个相反位。这叫位填充规则。

如果接收节点发现总线上出现了6个连续相同位,就判定为填充错误。为什么是6个?因为发送节点最多只能发5个相同位,第6个一定是填充违规。

关键点: 位填充只发生在SOF到CRC场之间。ACK场、EOF、IFS不参与填充。

4.1.3 CRC错误

每个CAN帧都带有一个15位的CRC校验码(标准帧)。接收节点收到数据后,会用同样的多项式重新计算CRC。如果算出来的和收到的对不上,就报CRC错误。

我记得有一次,一个传感器节点在高温下频繁报CRC错误。后来发现是晶振温漂导致位时序偏移,数据采样点跑偏了。换了个温补晶振就解决了。

4.1.4 形式错误

CAN帧里有些位场是固定电平的,比如:

  • CRC界定符:必须是隐性位
  • ACK界定符:必须是隐性位
  • EOF:连续7个隐性位

如果这些固定位场出现了不该出现的电平,就报形式错误。说白了就是格式不对。

4.1.5 应答错误

发送节点在ACK槽会发送一个隐性位。如果总线上没有任何节点用显性位应答,发送节点就知道——没人收到我的帧。

这时候就会报应答错误。常见原因:

  • 总线上只有发送节点一个设备
  • 接收节点处于总线关闭状态
  • 接收节点根本没收到帧(比如滤波没通过)

4.2 错误状态机

每个CAN节点内部都有两个计数器:

  • TEC(发送错误计数器)
  • REC(接收错误计数器)

这两个计数器决定了节点处于哪种错误状态。状态一共三种:

CAN节点错误状态机 主动错误 TEC ≤ 127 REC ≤ 127 被动错误 128 ≤ TEC ≤ 255 或 128 ≤ REC ≤ 255 总线关闭 TEC > 255 TEC/REC 超限 TEC/REC 恢复 TEC > 255 128个隐性位恢复 状态说明: • 主动错误:节点正常参与通信,检测到错误时发送主动错误帧(6个显性位) • 被动错误:节点可通信,但发送被动错误帧(6个隐性位),且发送前需等待8个隐性位 • 总线关闭:节点完全断开总线,不发送任何帧,直到硬件复位或128个隐性位恢复

4.2.1 主动错误状态

这是节点的正常工作状态。TEC和REC都小于等于127。在这个状态下:

  • 节点可以正常发送和接收
  • 检测到错误时,发送主动错误帧(6个显性位)
  • 主动错误帧会覆盖总线上正在传输的数据,强制所有节点发现错误

4.2.2 被动错误状态

当TEC或REC超过127时,节点进入被动错误状态。这个状态有点尴尬:

  • 节点仍然可以通信,但发送错误帧时只能发被动错误帧(6个隐性位)
  • 被动错误帧不会覆盖总线,其他节点可能根本察觉不到
  • 发送节点在发送前必须等待8个连续的隐性位(这叫错误暂停)
避坑指南: 我曾经遇到一个ECU,在被动错误状态下反复发送错误帧,导致总线利用率骤降。排查后发现是收发器电源纹波太大,导致位采样出错。加了滤波电容后恢复正常。

4.2.3 总线关闭状态

这是最严重的情况。当TEC超过255时,节点会自动断开与总线的连接。说白了就是把自己关掉了。

总线关闭后的恢复方式:

  • 硬件复位:重新上电或复位引脚触发
  • 软件恢复:检测到128次连续的11个隐性位(即128个总线空闲序列)

我记得在做一个整车网络测试时,有个节点频繁进入总线关闭。查到最后发现是CAN收发器的共模电感选型不对,导致信号质量差。换了个电感就再也没出过问题。

4.3 错误计数器的增减规则

错误计数器不是简单加一减一,它有权重规则:

事件 TEC变化 REC变化
发送节点检测到位错误 +8 —
发送节点检测到填充错误 +8 —
发送节点检测到形式错误 +8 —
发送节点检测到应答错误 +8 —
接收节点检测到错误 — +1
发送节点成功发送 -1 —
接收节点成功接收 — -1(若REC > 0)
主动错误节点检测到错误 — +1
设计意图: 发送节点出错一次加8,成功一次只减1。这意味着连续出错的节点会快速进入被动甚至总线关闭状态,而偶尔出错的节点可以慢慢恢复。这种设计保证了总线不会因为一个故障节点而瘫痪。

4.4 错误帧结构

当节点检测到错误时,会立即发送错误帧。错误帧分两种:

  • 主动错误帧:6个显性位 + 8个隐性界定符
  • 被动错误帧:6个隐性位 + 8个隐性界定符

主动错误帧的6个显性位会破坏当前帧的填充规则,导致其他节点也检测到填充错误,从而发送自己的错误帧。最终总线上会出现一堆显性位叠加,所有节点都能感知到。

被动错误帧就不一样了。6个隐性位不会覆盖总线,其他节点可能根本不知道发生了错误。这也是为什么被动错误节点在总线上存在感很低。

4.5 实战建议

我的习惯: 在开发阶段,我会在CANoe或PCAN中打开错误帧统计功能。如果发现某个节点频繁发送错误帧,先看是哪种错误类型。位错误和填充错误通常指向硬件问题(线束、收发器、晶振),CRC错误和形式错误多半是软件问题(波特率配置、位时序)。

另外,我建议你在ECU的Bootloader或应用层中开放错误计数器读取接口。这样在实车测试时,可以通过诊断命令查看TEC和REC的值,提前发现潜在问题。别等到总线关闭了才去排查,那时候已经晚了。