3. 看门狗与自诊断技术:硬件看门狗与软件看门狗的区别、多级看门狗策略、上电自检(POST)与运行时自检

看门狗,说白了就是给程序请了个监工。这个监工不干活,就盯着你——你按时汇报,它就安心;你一旦卡死,它就一巴掌把系统拍重启。

我在工业现场见过太多因为看门狗设计不当导致的惨案。有一次,一台PLC在高温环境下频繁死机,现场工程师把看门狗时间设成了10秒,结果系统刚重启又被看门狗打断,陷入了无限重启循环。嗯,这就是典型的「看门狗把自己看死了」。

3.1 硬件看门狗 vs 软件看门狗

这两者的区别,我习惯用一个比喻:硬件看门狗是独立的保安,软件看门狗是公司内部的纪律委员。

对比项 硬件看门狗 软件看门狗
独立性 完全独立于CPU,外部芯片或内部独立定时器 依赖CPU和操作系统,与主程序共享资源
可靠性 极高,CPU死机仍能触发复位 较低,CPU死机时看门狗也可能失效
响应速度 微秒级,硬件直接触发 毫秒级,依赖中断或任务调度
灵活性 固定时间窗口,难以动态调整 可灵活配置,支持多级超时
典型应用 安全关键系统(如核电、高铁) 普通工业控制器、嵌入式Linux

硬件看门狗,我建议每个工业级产品都必须配。它不依赖CPU时钟,不依赖软件堆栈,哪怕主芯片的晶振都停了,它照样能工作。我曾经在一个项目中,客户反馈设备偶尔死机,查了三个月没找到原因。最后发现是电源纹波导致CPU偶尔进入未知状态,但硬件看门狗每次都能把它拉回来——只是客户没注意到重启记录。

核心原则:硬件看门狗是最后一道防线,软件看门狗是日常巡逻。两者缺一不可。

软件看门狗,说白了就是利用操作系统或定时器实现的多级监控。它最大的好处是能区分「轻度卡顿」和「彻底死机」。比如,一个任务超过100ms没响应,软件看门狗可以先发警告;超过500ms,才触发复位。

// 软件看门狗示例(基于FreeRTOS)
void WatchdogTask(void *pvParameters) {
    uint32_t lastFeedTime = 0;
    const uint32_t WATCHDOG_TIMEOUT_MS = 500;
    
    while(1) {
        // 检查主任务是否按时喂狗
        if((xTaskGetTickCount() - lastFeedTime) > pdMS_TO_TICKS(WATCHDOG_TIMEOUT_MS)) {
            // 记录错误日志
            logError("Watchdog timeout! Task stuck.");
            // 尝试恢复:先发警告,再复位
            if(++recoveryAttempts > 3) {
                NVIC_SystemReset();  // 软件复位
            }
        }
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

我的经验:软件看门狗的喂狗点不要放在中断里。我曾经见过有人把喂狗放在定时器中断里,结果主程序死循环了,中断还在跑,看门狗永远不超时。这等于把看门狗废了。

3.2 多级看门狗策略

为什么需要多级?你想想看,一个系统里可能有多个任务,每个任务的实时性要求不同。一级看门狗只能检测「系统是否还活着」,但检测不出「哪个任务死了」。

我常用的三级看门狗策略是这样的:

  1. 一级:硬件看门狗(系统级)——时间设为1-2秒,只负责检测CPU是否彻底死机。喂狗点放在最高优先级的空闲任务中。
  2. 二级:任务级看门狗(软件)——每个关键任务独立计时,超时则记录错误并尝试重启该任务。时间设为任务周期的2-3倍。
  3. 三级:通信看门狗(应用级)——检测与上位机或从站之间的通信是否正常。超时则进入安全状态。

注意:多级看门狗的时间设置要避免「级联超时」。比如一级是1秒,二级是800毫秒,那二级超时后还没来得及处理,一级就复位了。我建议各级之间留出至少30%的时间余量。

我曾经在一个多轴运动控制项目中,用了三级看门狗。第一级硬件看门狗保底,第二级检测每个轴的控制任务是否按时完成,第三级检测与伺服驱动器的通信。有一次,一个伺服驱动器因为编码器故障导致通信中断,第三级看门狗立即触发,系统进入安全停机状态——避免了机械碰撞事故。

3.3 上电自检(POST)与运行时自检

上电自检,就是系统启动时先把自己检查一遍。我习惯把它分成三个阶段:

  • 阶段一:硬件完整性检查——检查RAM、Flash、EEPROM、ADC/DAC等关键外设是否正常。这个阶段要快,一般控制在100ms以内。
  • 阶段二:固件完整性检查——计算固件的CRC或哈希值,与存储的校验值对比。防止固件被篡改或损坏。
  • 阶段三:外设状态检查——检查传感器、执行器、通信接口是否在线。比如,检查编码器信号是否正常,继电器线圈是否断路。
// 上电自检示例
typedef enum {
    POST_PASS = 0,
    POST_RAM_FAIL,
    POST_FLASH_FAIL,
    POST_ADC_FAIL,
    POST_SENSOR_FAIL
} POST_Result;

POST_Result POST_Run(void) {
    // 1. RAM测试:写入0x55和0xAA,再读回验证
    if(!RAM_Test()) return POST_RAM_FAIL;
    
    // 2. Flash CRC校验
    if(!Flash_CRC_Check()) return POST_FLASH_FAIL;
    
    // 3. ADC自检:读取内部参考电压
    if(!ADC_SelfTest()) return POST_ADC_FAIL;
    
    // 4. 传感器状态检查
    if(!Sensor_Check()) return POST_SENSOR_FAIL;
    
    return POST_PASS;
}

关键点:上电自检失败后,系统必须进入安全状态,不能尝试继续运行。我见过一个案例,某设备RAM自检失败后仍然启动,结果运行过程中数据错乱,导致输出误动作,差点造成人员伤害。

运行时自检,说白了就是系统在正常工作时,也要时不时给自己做个体检。我常用的方法有:

  • 周期性RAM测试:利用空闲时间,对未使用的RAM区域进行读写测试。注意不要影响正在使用的数据。
  • ADC通道交叉验证:如果系统有多个ADC通道,可以用一个通道测量已知的参考电压,验证ADC是否漂移。
  • 看门狗喂狗时间监控:记录每次喂狗的时间间隔,如果发现喂狗时间异常波动,说明系统负载可能有问题。

我的习惯:运行时自检不要占用主任务的时间。我会在空闲任务或低优先级任务中执行自检,并且每次只做一小部分,分散到多个周期完成。这样既不影响实时性,又能持续监控系统健康状态。

最后说一句,自检结果一定要记录到非易失性存储器中。这样即使系统崩溃重启,也能从日志中看到上次自检失败的原因。我曾经靠这个功能,帮客户定位了一个只在凌晨3点出现的间歇性故障——原来是电源模块在低温下输出不稳定,导致ADC自检偶尔失败。

看门狗与自诊断技术架构图 硬件看门狗 软件看门狗 自诊断技术 独立外部芯片/内部定时器 CPU死机仍能触发复位 微秒级响应,最后防线 多级超时:警告→恢复→复位 任务级监控,定位死循环 通信看门狗,检测链路 上电自检(POST) 运行时自检(周期性) 日志记录与故障定位 多级看门狗策略:一级(硬件) → 二级(任务) → 三级(通信) 各级时间留30%余量,避免级联超时

避坑指南:我曾经在一个项目中,把硬件看门狗的喂狗放在了定时器中断里。结果主程序死循环了,中断还在跑,看门狗永远不超时。这等于把看门狗废了。记住:喂狗点一定要放在主循环或任务中,不能放在中断里。

好了,这一章的内容就到这里。看门狗和自诊断技术,说白了就是给系统上了双保险——一个防死机,一个防故障。两者配合好了,工业级控制程序的可靠性才能真正有保障。

专注资料整理