4. CAN错误处理:错误类型与状态机
CAN协议有个很厉害的设计——错误处理机制。说实话,这是CAN能在工业、汽车领域活这么久的关键原因之一。它不需要一个中央监控器,每个节点自己就能发现错误、报告错误、甚至把自己关掉。
我刚开始接触CAN时,总觉得这玩意儿太复杂。后来在项目里被总线关闭坑过一次,才老老实实把错误状态机啃完。嗯,今天咱们就把这块掰开揉碎了讲。
4.1 五种错误类型
CAN协议定义了五种错误。任何节点只要检测到其中一种,就会立刻发送错误帧,通知总线上所有其他节点。
| 错误类型 | 检测时机 | 触发条件 |
|---|---|---|
| 位错误 | 发送节点发送位时 | 发送位与总线电平不匹配(仲裁场和应答场除外) |
| 填充错误 | 接收节点接收时 | 连续检测到6个相同电平的位 |
| CRC错误 | 接收节点校验时 | 计算出的CRC与发送的CRC不一致 |
| 形式错误 | 接收节点检测固定位场时 | 固定位场(如CRC界定符、ACK界定符)出现非法电平 |
| 应答错误 | 发送节点在应答场时 | 发送节点未在ACK槽检测到显性位 |
4.1.1 位错误
说白了就是:我说了啥,但总线上不是啥。
发送节点每发一个位,都会回读总线电平。如果读到的和自己发的不一样,就报位错误。但有两个例外:
- 仲裁场:发送显性位但读到隐性位,说明仲裁输了,不是错误。
- 应答场:发送隐性位但读到显性位,说明有节点应答了,也不是错误。
4.1.2 填充错误
CAN协议规定:连续发送5个相同位后,必须插入一个相反位。这叫位填充规则。
如果接收节点发现总线上出现了6个连续相同位,就判定为填充错误。为什么是6个?因为发送节点最多只能发5个相同位,第6个一定是填充违规。
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(接收错误计数器)
这两个计数器决定了节点处于哪种错误状态。状态一共三种:
4.2.1 主动错误状态
这是节点的正常工作状态。TEC和REC都小于等于127。在这个状态下:
- 节点可以正常发送和接收
- 检测到错误时,发送主动错误帧(6个显性位)
- 主动错误帧会覆盖总线上正在传输的数据,强制所有节点发现错误
4.2.2 被动错误状态
当TEC或REC超过127时,节点进入被动错误状态。这个状态有点尴尬:
- 节点仍然可以通信,但发送错误帧时只能发被动错误帧(6个隐性位)
- 被动错误帧不会覆盖总线,其他节点可能根本察觉不到
- 发送节点在发送前必须等待8个连续的隐性位(这叫错误暂停)
4.2.3 总线关闭状态
这是最严重的情况。当TEC超过255时,节点会自动断开与总线的连接。说白了就是把自己关掉了。
总线关闭后的恢复方式:
- 硬件复位:重新上电或复位引脚触发
- 软件恢复:检测到128次连续的11个隐性位(即128个总线空闲序列)
我记得在做一个整车网络测试时,有个节点频繁进入总线关闭。查到最后发现是CAN收发器的共模电感选型不对,导致信号质量差。换了个电感就再也没出过问题。
4.3 错误计数器的增减规则
错误计数器不是简单加一减一,它有权重规则:
| 事件 | TEC变化 | REC变化 |
|---|---|---|
| 发送节点检测到位错误 | +8 | — |
| 发送节点检测到填充错误 | +8 | — |
| 发送节点检测到形式错误 | +8 | — |
| 发送节点检测到应答错误 | +8 | — |
| 接收节点检测到错误 | — | +1 |
| 发送节点成功发送 | -1 | — |
| 接收节点成功接收 | — | -1(若REC > 0) |
| 主动错误节点检测到错误 | — | +1 |
4.4 错误帧结构
当节点检测到错误时,会立即发送错误帧。错误帧分两种:
- 主动错误帧:6个显性位 + 8个隐性界定符
- 被动错误帧:6个隐性位 + 8个隐性界定符
主动错误帧的6个显性位会破坏当前帧的填充规则,导致其他节点也检测到填充错误,从而发送自己的错误帧。最终总线上会出现一堆显性位叠加,所有节点都能感知到。
被动错误帧就不一样了。6个隐性位不会覆盖总线,其他节点可能根本不知道发生了错误。这也是为什么被动错误节点在总线上存在感很低。
4.5 实战建议
另外,我建议你在ECU的Bootloader或应用层中开放错误计数器读取接口。这样在实车测试时,可以通过诊断命令查看TEC和REC的值,提前发现潜在问题。别等到总线关闭了才去排查,那时候已经晚了。