CAN错误处理:错误类型、错误计数器、错误状态机、总线关闭恢复

各位同学,咱们今天聊点硬核的——CAN总线错误处理。说实话,很多开发者写CAN驱动时,只关注怎么收发报文,对错误处理这块往往一笔带过。但我在实际项目中吃过亏,有一次现场总线莫名其妙就挂了,查了两天才发现是错误状态机没处理好。嗯,这节课咱们就把这块彻底讲透。

4.1 错误类型:CAN总线到底会出哪些错?

CAN协议定义了5种错误类型。你想想看,这5种错误覆盖了从位电平到报文格式的所有异常情况。我习惯把它们分成两类:物理层错误和协议层错误。

错误类型 触发条件 检测节点 个人经验
位错误 发送节点监控总线电平,发现与自己发送的不一致 发送节点 最常见,线缆松动时必出
填充错误 连续6个相同位出现,违反位填充规则 所有节点 时钟漂移严重的节点容易触发
CRC错误 接收节点计算的CRC与发送的CRC不匹配 接收节点 电磁干扰强时频发
格式错误 帧格式中固定位域出现非法电平 所有节点 波特率配置不一致时必现
应答错误 发送节点在ACK槽未检测到显性位 发送节点 只有一个节点在线时出现
小提示:我个人调试时,会先看错误计数器是发送侧涨还是接收侧涨。发送侧涨多半是物理层问题,接收侧涨往往是波特率或干扰问题。这个经验帮我省了不少时间。

4.2 错误计数器:两个计数器管全局

每个CAN节点内部都有两个硬件计数器:TEC(发送错误计数器)和REC(接收错误计数器)。它们怎么涨怎么跌,协议规定得很细。说白了,就是一套奖惩机制——成功就减,失败就加。

具体规则是这样的:

  • 发送成功:TEC减1(前提是TEC > 0)
  • 接收成功:REC减1(前提是REC > 0)
  • 发送错误:TEC加8
  • 接收错误:REC加1
  • 主动错误标志发出后:TEC再加8

为什么发送错误加8,接收错误只加1?因为发送节点对总线负有主要责任。你发错了,影响的是整个网络。我在项目中遇到过一种情况:某个节点TEC涨到128以上,直接进入被动错误状态,但它还在不停重发,结果REC也跟着涨。这就是典型的"错误风暴"。

关键点:错误计数器的值决定了节点在总线上的"话语权"。计数器越高,节点越"沉默"。

4.3 错误状态机:三个状态决定节点命运

CAN节点根据TEC和REC的值,会处于三种状态之一。我画了一张状态机图,大家一看就明白。

CAN节点错误状态机 主动错误 TEC < 128 且 REC < 128 被动错误 TEC ≥ 128 或 REC ≥ 128 总线关闭 TEC > 255 TEC/REC超限 TEC/REC降回 TEC > 255 128个连续11个隐性位(总线空闲) 状态行为差异 • 主动错误:可正常收发,检测到错误时发送主动错误标志(6个显性位) • 被动错误:可收发,但发送被动错误标志(6个隐性位),且发送前需等待8个位时间 • 总线关闭:完全脱离总线,不参与任何通信 • 恢复条件:总线关闭后,检测到128次连续11个隐性位(总线空闲)

这三个状态的行为差异很大。我重点说一下被动错误状态——这是最容易出坑的地方。被动错误节点发送错误标志时,用的是6个隐性位。你想想看,隐性位会被显性位覆盖,所以其他主动错误节点根本感知不到这个错误标志。这就导致一个问题:被动错误节点发错了,别人不知道,它自己还得继续重发。

避坑指南:我曾经在一个项目中,从站节点频繁进入被动错误状态,导致整个网络的实时性严重下降。后来发现是终端电阻匹配问题。记住:被动错误状态不是"小问题",它是总线崩溃的前兆。

4.4 总线关闭恢复:两种策略怎么选?

当TEC超过255,节点就进入总线关闭状态。这时候节点完全脱离总线,不发送也不接收。恢复方式有两种:

  1. 自动恢复:检测到128次连续11个隐性位(即128个总线空闲序列),自动回到主动错误状态。这是硬件自动完成的,不需要软件干预。
  2. 手动恢复:通过软件复位CAN控制器,强制退出总线关闭状态。但要注意,复位后错误计数器清零,之前的历史错误信息也丢了。

我个人建议:能用自动恢复就别用手动恢复。为什么?手动恢复相当于"掩耳盗铃"——你把错误计数器清零了,但物理层的问题还在,一恢复马上又关闭。我在一个车载项目中就犯过这个错,手动恢复后节点反复关闭,最后查出来是CAN收发器的共模电压出了问题。

最佳实践:总线关闭后,先记录错误信息,再等待自动恢复。如果恢复后短时间内再次关闭,说明有硬件故障,需要报警而不是盲目复位。

4.5 实战:从站节点的错误处理代码

下面给出一段从站节点的错误处理代码框架。这段代码我在多个项目中用过,核心逻辑就是:监控错误状态,分级处理,避免死循环。

/* CAN错误状态监控任务 */
void CAN_ErrorMonitorTask(void)
{
    uint8_t error_state;
    uint8_t tec, rec;
    
    while(1)
    {
        /* 读取当前错误状态 */
        error_state = CAN_GetErrorState();
        tec = CAN_GetTEC();
        rec = CAN_GetREC();
        
        switch(error_state)
        {
            case CAN_ERROR_ACTIVE:
                /* 正常状态,计数器小于128 */
                if(tec > 96 || rec > 96)
                {
                    /* 接近被动错误,提前预警 */
                    CAN_SendWarningMessage(tec, rec);
                }
                break;
                
            case CAN_ERROR_PASSIVE:
                /* 被动错误,需要降级处理 */
                /* 停止发送非关键报文 */
                CAN_StopNonCriticalMessages();
                /* 记录错误日志 */
                CAN_LogError("Enter Passive Error", tec, rec);
                break;
                
            case CAN_ERROR_BUSOFF:
                /* 总线关闭,等待自动恢复 */
                CAN_StopAllTransmission();
                CAN_LogError("Bus Off Detected", tec, rec);
                
                /* 等待自动恢复,超时则报警 */
                for(int i = 0; i < 1000; i++)
                {
                    if(CAN_GetErrorState() == CAN_ERROR_ACTIVE)
                    {
                        CAN_LogInfo("Bus Off Recovered");
                        break;
                    }
                    delay_ms(10);
                }
                
                /* 超时未恢复,触发硬件报警 */
                if(CAN_GetErrorState() != CAN_ERROR_ACTIVE)
                {
                    CAN_TriggerHardwareAlarm();
                }
                break;
        }
        
        delay_ms(100);  /* 100ms轮询一次 */
    }
}
经验之谈:代码里我特意加了"接近被动错误"的预警逻辑。计数器到96就预警,而不是等到128。为什么?因为错误计数器涨得很快,等你读到128时,可能已经触发被动错误了。提前预警,给你留出处理时间。

4.6 调试技巧:用错误计数器定位问题

最后分享几个调试技巧。这些是我这些年踩坑踩出来的:

  • 看趋势不看瞬时值:错误计数器偶尔跳一下很正常,但如果持续上涨,说明有问题。
  • 区分发送侧和接收侧:TEC涨得快,查发送电路和终端电阻;REC涨得快,查接收电路和干扰源。
  • 记录错误发生时的报文ID:如果某个特定ID的报文总是触发错误,可能是那个节点的时钟有问题。
  • 用CAN示波器看位时序:错误计数器异常时,用示波器抓一下CAN_H和CAN_L的差分信号,看看位宽度是否一致。

嗯,CAN错误处理这块内容就讲到这里。记住一句话:错误处理不是可有可无的附加功能,它是CAN节点稳定运行的基石。下节课咱们接着聊CANopen协议栈的移植和配置,到时候会用到今天讲的状态机知识。