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-2秒,只负责检测CPU是否彻底死机。喂狗点放在最高优先级的空闲任务中。
- 二级:任务级看门狗(软件)——每个关键任务独立计时,超时则记录错误并尝试重启该任务。时间设为任务周期的2-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自检偶尔失败。
避坑指南:我曾经在一个项目中,把硬件看门狗的喂狗放在了定时器中断里。结果主程序死循环了,中断还在跑,看门狗永远不超时。这等于把看门狗废了。记住:喂狗点一定要放在主循环或任务中,不能放在中断里。
好了,这一章的内容就到这里。看门狗和自诊断技术,说白了就是给系统上了双保险——一个防死机,一个防故障。两者配合好了,工业级控制程序的可靠性才能真正有保障。