4. CAN总线错误处理:错误类型、错误计数器、错误状态机、总线关闭恢复
说实话,CAN总线最让我佩服的地方,就是它的错误处理机制。你想想看,在工业现场那种电磁环境恶劣的地方,信号干扰、电压波动都是家常便饭。要是没有这套机制,数据分分钟就乱套了。
我刚开始做CAN驱动时,总觉得错误处理是「锦上添花」的东西。直到有一次,一个电机控制器在产线上频繁死机,查了三天才发现是总线错误导致节点进入了Bus Off状态。嗯,从那以后,我再也不敢轻视错误处理了。
4.1 错误类型:CAN总线会出哪些问题?
CAN协议定义了5种错误类型。我个人习惯把它们分成两类:格式错误和内容错误。
| 错误类型 | 触发条件 | 检测位置 |
|---|---|---|
| 位错误(Bit Error) | 发送节点监控总线,发现发送的电平与总线实际电平不一致 | 发送节点 |
| 填充错误(Stuff Error) | 连续6个相同电平出现,违反位填充规则 | 所有节点 |
| CRC错误(CRC Error) | 接收节点计算的CRC与发送的CRC不一致 | 接收节点 |
| 格式错误(Form Error) | 固定格式位(如CRC分隔符、ACK分隔符)出现非法电平 | 所有节点 |
| 应答错误(ACK Error) | 发送节点在ACK槽位没有检测到显性电平 | 发送节点 |
这里有个细节要注意:位错误只有发送节点能检测到。为什么?因为只有发送节点才知道自己发了什么。接收节点只负责看总线上的电平,它不知道发送方原本想发什么。
核心要点:位错误是「我说了A,但总线显示B」;填充错误是「总线连续6个相同电平,这不对劲」;CRC错误是「数据算出来的校验码对不上」;格式错误是「该是显性的地方变成了隐性」;应答错误是「我发了消息,没人回应我」。
4.2 错误计数器:犯错要记分
CAN协议给每个节点配了两个计数器:发送错误计数器(TEC)和接收错误计数器(REC)。说白了,就是给每个节点记分,犯错就加分,表现好就减分。
计分规则是这样的:
- 发送节点检测到位错误或ACK错误:TEC加8分
- 接收节点检测到位错误、填充错误、CRC错误或格式错误:REC加1分
- 发送节点成功发送一帧:TEC减1分(前提是TEC > 0)
- 接收节点成功接收一帧:REC减1分(前提是REC > 0)
- 节点从Bus Off恢复后:TEC和REC都清零
我在项目中遇到过一个问题:某个节点频繁发送错误帧,TEC很快就飙到了128以上。但奇怪的是,它并没有进入Bus Off。后来一查,原来是软件里把错误中断给关了。嗯,这个坑我踩过,你们别踩。
注意:错误计数器的加减规则不是对称的。犯错时加分很快(一次加8分),但表现好时减分很慢(一次只减1分)。这种设计是有意为之——让「坏节点」快速被隔离,避免它污染总线。
4.3 错误状态机:三种状态,三种命运
根据TEC和REC的值,CAN节点会处于三种状态之一:
| 状态 | 条件 | 行为 |
|---|---|---|
| 错误主动(Error Active) | TEC < 128 且 REC < 128 | 正常收发,检测到错误时发送主动错误帧(6个显性位) |
| 错误被动(Error Passive) | TEC ≥ 128 或 REC ≥ 128 | 正常收发,检测到错误时发送被动错误帧(6个隐性位),发送前等待8个位时间 |
| 总线关闭(Bus Off) | TEC ≥ 256 | 完全脱离总线,不发送也不接收任何帧 |
你想想看,这个状态机设计得很有意思。错误主动是「我犯错我承认,我发个显性错误帧告诉所有人」;错误被动是「我可能有问题,我发个隐性错误帧,你们爱听不听」;总线关闭是「我彻底闭嘴,不给你们添乱」。
我个人习惯在驱动中把这三个状态都映射到中断回调里。这样上层应用可以实时知道节点的健康状态。比如,当节点进入Error Passive时,可以提前做一些降级处理,而不是等到Bus Off了才手忙脚乱。
经验之谈:我曾经在一个项目中,把Error Passive作为触发条件,让主控节点主动发送一个「复位请求」帧给问题节点。这样在节点进入Bus Off之前,就把它软复位了。效果很好,产线故障率降低了80%。
4.4 总线关闭恢复:怎么让节点重新上线?
节点进入Bus Off后,就彻底沉默了。它不会发送任何帧,也不会响应任何帧。要让它重新上线,有两种方式:
4.4.1 自动恢复
CAN协议规定,节点在Bus Off状态下,会持续监控总线。当检测到128次连续的11个隐性位(即128个空闲帧)后,自动从Bus Off恢复到Error Active。这个过程不需要软件干预,完全由硬件完成。
但这里有个问题:如果导致Bus Off的故障原因没有消除,节点恢复后很快又会进入Bus Off。这样反复进出,反而会干扰总线。所以,我一般不建议用自动恢复。
4.4.2 手动恢复
大多数CAN控制器都提供了软件复位功能。你可以通过写寄存器,手动将节点从Bus Off状态拉回来。具体做法是:
/* 以STM32的bxCAN为例 */
/* 1. 进入初始化模式 */
CAN->MCR |= CAN_MCR_INRQ;
while((CAN->MSR & CAN_MSR_INAK) == 0);
/* 2. 清除Bus Off标志 */
CAN->MSR &= ~CAN_MSR_ERRI;
/* 3. 复位错误计数器 */
CAN->ECR = 0;
/* 4. 退出初始化模式 */
CAN->MCR &= ~CAN_MCR_INRQ;
while((CAN->MSR & CAN_MSR_INAK) != 0);
重要提醒:手动恢复前,一定要确认故障原因已经排除。否则,节点一恢复就继续犯错,TEC瞬间又飙到256以上,再次进入Bus Off。我曾经见过一个项目,节点在1秒内进出Bus Off十几次,整个总线都被它搞瘫痪了。
4.5 实战建议:错误处理的最佳实践
说了这么多理论,最后给几条实战建议:
- 不要依赖自动恢复。自动恢复是硬件行为,你无法控制恢复时机。建议在驱动中实现手动恢复,并加入恢复次数限制和延时逻辑。
- 记录错误日志。每次发生错误时,把TEC、REC、错误类型、时间戳都记录下来。这些数据对排查问题非常有用。
- 区分瞬时错误和永久故障。如果节点频繁进出Error Passive,可能是硬件问题(如终端电阻匹配不当、线缆过长)。如果只是偶尔出现,可能是电磁干扰。
- 利用错误中断。大多数CAN控制器都支持错误中断。把Error Passive和Bus Off中断打开,让上层应用能及时感知。
我记得有一次,客户反馈说他们的设备在工厂里经常掉线。我远程看了日志,发现REC一直在90到110之间波动,但TEC始终是0。这说明节点一直在接收错误帧,但自己发送没问题。后来去现场一测,发现是CAN_H和CAN_L线接反了。嗯,这种低级错误,排查起来反而最费时间。
总结一句话:CAN的错误处理机制很完善,但前提是你得用好它。别把错误处理当成「可有可无」的功能,它其实是CAN总线稳定运行的基石。