2. 故障检测机制:故障检测方法、去抖策略与DTC管理

各位同学,今天我们来聊聊故障检测这块。说实话,这是ECU软件里最考验功力的部分之一。我见过太多项目,功能逻辑写得漂亮,结果故障检测一塌糊涂,最后台架上跑两天就崩了。

故障检测机制,说白了就是ECU怎么知道自己出问题了。它包含三个层面:用什么方法去发现问题、发现问题后怎么确认不是误报、确认后怎么记录和上报。我们一个一个说。

2.1 故障检测方法

常用的检测方法有四种:自检、互检、回读和看门狗。每种方法都有自己的适用场景,我习惯把它们搭配着用。

2.1.1 自检(Self-Test)

自检是ECU自己检查自己。上电时做一次全面体检,运行中定期做快速检查。我在项目中遇到过一个问题:某款MCU的RAM自检只在启动时做一次,结果运行了几个小时后,某位因为辐射翻转了,直到系统崩溃才发现。后来我强制要求:关键RAM区域每100ms做一次自检。

自检的典型场景:

  • 上电自检(POST):检查CPU、RAM、ROM、EEPROM是否正常
  • 运行中自检:周期性检查关键寄存器、堆栈、时钟频率
  • 触发式自检:特定条件下(如进入诊断模式)执行深度检查

2.1.2 互检(Cross-Check)

互检是两个或多个模块互相验证。比如两个传感器测同一个物理量,或者两个CPU核跑同样的算法。嗯,这里要注意:互检不是简单的比较,你得考虑时间差和精度差。

举个例子:轮速传感器左前和右后,理论上车速应该一致。但转弯时内外侧轮速差很大,直接比较就会误报。我踩过这个坑,后来加了一个车辆状态判断:只有在直线行驶时才做互检。

2.1.3 回读(Read-Back)

回读是最简单也最容易被忽视的方法。你写了一个值到寄存器或内存,再读回来看看对不对。特别是对安全相关的输出通道,比如驱动电机PWM的寄存器,写完之后必须回读确认。

我曾经遇到一个案例:某ECU控制刹车灯,软件写了"点亮"指令,但硬件因为管脚虚焊根本没输出。因为没有回读,驾驶员踩刹车时后面完全没反应。后来我们在所有安全相关输出上都加了回读机制。

2.1.4 看门狗(Watchdog)

看门狗是最后一道防线。它分两种:内部看门狗(集成在MCU里)和外部看门狗(独立芯片)。我个人习惯两者都用——内部看门狗负责检测任务卡死,外部看门狗负责检测MCU彻底死机。

我的经验:看门狗的超时时间不要设得太短。我曾经设了50ms,结果正常运行时偶尔因为中断优先级反转导致喂狗延迟,系统频繁复位。后来改成200ms,配合一个"心跳计数器",既保证及时响应,又避免误触发。

2.2 故障确认与去抖(Debounce)策略

检测到故障后,不能立刻上报。为什么?因为很多故障是瞬态的——比如电磁干扰导致信号跳变、电源波动导致电压短暂超标。如果每次瞬态都上报,系统会疯掉。

去抖策略就是解决这个问题的。说白了,就是"让子弹飞一会儿"。

2.2.1 时间去抖

最常见的方法:故障持续超过一定时间才确认。比如检测到电压低于9V,不立刻报"欠压故障",而是等它持续低于9V超过100ms再确认。

时间去抖有两种实现方式:

  • 计数器法:每次检测到故障,计数器加1;每次检测到正常,计数器减1(但不低于0)。计数器超过阈值则确认故障。
  • 定时器法:检测到故障时启动定时器,定时器溢出则确认故障。期间如果恢复正常,定时器清零。

代码示例:计数器法去抖

/* 去抖参数 */
#define DEBOUNCE_THRESHOLD  10   /* 连续10次检测到故障才确认 */

/* 去抖状态机 */
uint8_t debounce_counter = 0;
bool fault_confirmed = false;

void FaultDetectionTask(void)
{
    bool fault_detected = ReadSensorValue() > LIMIT_HIGH;
    
    if (fault_detected)
    {
        if (debounce_counter < DEBOUNCE_THRESHOLD)
            debounce_counter++;
    }
    else
    {
        if (debounce_counter > 0)
            debounce_counter--;
    }
    
    /* 确认故障 */
    if (debounce_counter >= DEBOUNCE_THRESHOLD)
        fault_confirmed = true;
    else
        fault_confirmed = false;
}

2.2.2 幅度去抖

有时候信号在阈值附近来回跳,时间去抖效果不好。这时候可以用幅度去抖:设置一个滞回区间。比如欠压阈值是9V,但恢复阈值设为9.5V。这样信号在9V到9.5V之间波动时,不会频繁切换状态。

我管这个叫"给阈值加个保险带"。你想想看,如果没有滞回,信号刚好在9V附近抖动,系统会反复进入和退出故障状态,那画面太美不敢看。

2.2.3 组合去抖

实际项目中,我经常把时间和幅度去抖结合起来。比如:

  • 电压低于9V且持续100ms → 报欠压故障
  • 电压恢复到9.5V以上且持续50ms → 清除欠压故障

这样既避免了瞬态干扰,又保证了故障恢复的及时性。

2.3 故障码(DTC)生成与管理

故障确认之后,就要生成DTC(Diagnostic Trouble Code)。DTC不是随便编个号,它有一套标准格式。

2.3.1 DTC的结构

按照ISO 14229(UDS)和SAE J2012标准,DTC由3个字节组成:

字节 含义 示例
高字节 系统类型(P=动力、C=底盘、B=车身、U=网络) 0x01 → 动力系统
中字节 子系统/部件编号 0x23 → 节气门位置传感器
低字节 故障类型 0x01 → 信号超上限

比如DTC P0123,P代表动力系统,012代表节气门/踏板位置传感器,3代表信号过高。

2.3.2 DTC的状态管理

DTC不是简单的"有"或"无"。它有一个状态位掩码,记录故障的完整生命周期:

  • Bit 0 (Test Failed):当前检测到故障
  • Bit 1 (Test Failed This Operation Cycle):本次上电周期内检测到故障
  • Bit 2 (Pending):故障已检测但未确认(去抖中)
  • Bit 3 (Confirmed):故障已确认
  • Bit 4 (Test Not Completed Since Last Clear):上次清除后未完成检测
  • Bit 5 (Test Failed Since Last Clear):上次清除后检测到故障
  • Bit 6 (Warning Indicator Requested):请求点亮故障灯
  • Bit 7 (Test Not Completed This Operation Cycle):本次周期内未完成检测

注意:DTC状态管理是功能安全审核的重点。我曾经在审核中被问到:"故障清除后,Bit 5什么时候清零?" 答案是:只有执行了"清除诊断信息"服务(0x14)后,Bit 5才会清零。如果只是故障消失,Bit 5会保持置位。

2.3.3 快照数据(Snapshot Data)

DTC生成时,通常要记录当时的"快照数据"——也就是故障发生时的环境信息。比如发动机转速、车速、冷却液温度、电源电压等。这些数据对后续的故障分析至关重要。

我建议快照数据至少包含:

  • 故障发生时的系统状态(上电次数、运行时间)
  • 相关传感器的原始值和滤波值
  • 系统工作模式(正常、诊断、降级等)
  • 其他相关DTC的状态

2.4 小结

故障检测机制这块,我总结几个要点:

  1. 检测方法要多样:自检、互检、回读、看门狗,根据安全等级搭配使用
  2. 去抖要合理:时间和幅度去抖结合,避免误报和漏报
  3. DTC管理要规范:严格按照标准格式,状态位管理要严谨
  4. 快照数据要完整:为后续分析和问题定位提供依据

下一章我们会讲故障响应策略——检测到故障之后,系统该怎么降级运行。到时候我会分享一个我踩过的坑:降级策略设计不当,导致车辆在高速上突然限速...嗯,那故事挺刺激的。