看门狗为什么必要
嵌入式设备长期无人值守,一颗静电、一次电压跌落、一段被踩坏的栈,都可能让程序跑飞或者卡死在某个死循环里。看门狗(Watchdog)就是最后一道防线:它是一颗独立运行的计数器,主程序必须定期"喂狗"清零,一旦超时没有喂狗,它就强制复位整个系统。
没有看门狗的设备,故障往往是永久性的:一个卡死的中断、一个等待永不放下的信号量,会让产品停在坏状态,直到人工断电重启。有了看门狗,异常最多让设备重启一次,配合上电自检与状态恢复,就能做到自愈。
- IWDG 独立不受主时钟影响的自由运行计数器。
- WWDG 窗口必须在时间窗口内喂狗。
- 喂狗策略主循环喂 + 任务心跳校验。
- 恢复治理复位原因记录与故障降级。
看门狗本质上是"信任但验证"的工程手段:它假设软件一定会出 bug,所以用硬件兜底。设计良好的看门狗方案,不是简单在主循环里喂一下狗,而是把系统健康度、任务活性和关键路径的执行频率都纳入监护范围。
独立看门狗 IWDG
独立看门狗由独立的低速时钟驱动,即使主时钟因为外部晶振故障停摆,它依然在走。它的寄存器只有写保护能挡住误操作,一旦启动几乎关不掉,非常适合做最后的兜底。
- 时钟源:通常来自 LSI 低速内部时钟,约 32kHz。
- 分频:可配置 4 到 256 分频,控制计数节拍。
- 重装载值:12 位,决定超时窗口长度。
- 超时计算:超时时间约等于分频系数乘重装载值再除以时钟频率。
把超时设成主循环周期的几倍是常见做法:既不会因为偶发抖动误复位,又能容忍主循环短暂卡顿。注意 IWDG 一旦使能,软件就无法关闭,只有复位才能重新初始化,所以调试阶段要谨慎。
窗口看门狗 WWDG
窗口看门狗更进一步:它规定只能在某个时间窗口内喂狗,喂得太早(计数器还没走到下窗口)或太晚(超过上窗口)都会复位。这能抓住"程序还在跑但已经失去正确节奏"的情况——比如死循环仍然在执行,主循环照常喂狗,但某个周期性任务已经停了。
// STM32 HAL 初始化窗口看门狗 WWDG
WWDG_HandleTypeDef hwwdg;
hwwdg.Instance = WWDG;
hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
hwwdg.Init.Window = 0x50; // 上窗口值,必须在此前刷新
hwwdg.Init.Counter = 0x7F; // 计数器初值
hwwdg.Init.EWIMode = WWDG_EWI_ENABLE; // 提前唤醒中断
HAL_WWDG_Init(&hwwdg);
// 喂狗必须在窗口内完成
HAL_WWDG_Refresh(&hwwdg);
WWDG 还带一个提前唤醒中断(EWI):在真正复位之前先通知软件,让软件有机会记录现场、保存关键参数,再优雅地重启。提前唤醒中断里的处理要短而快,不能在里面做耗时的任务,否则窗口一到照样复位。
喂狗策略与任务监护
最简单的喂狗是在主循环里定时刷新,但这只能证明主循环活着,证明不了业务任务活着。更可靠的策略是任务心跳监护:每个关键任务周期性更新自己的心跳计数,喂狗前检查所有心跳是否都在预期范围内,缺一个就不喂,让看门狗把系统拉回来。
// 任务心跳 + 统一喂狗
volatile uint32_t task_beats[4] = {0};
void task_a(void) { task_beats[0]++; /* 业务逻辑 */ }
void task_b(void) { task_beats[1]++; /* 通信处理 */ }
void task_c(void) { task_beats[2]++; /* 传感器采集 */ }
void task_d(void) { task_beats[3]++; /* 显示刷新 */ }
void wdg_monitor(void) {
for (int i = 0; i < 4; i++) {
if (task_beats[i] == 0) return; // 有任务从未跑过,不喂狗
}
HAL_IWDG_Refresh(&hiwdg); // 全部健康才刷新
}
把喂狗点放在任务调度器或者协议栈主循环里,比放在中断里可靠得多。中断里喂狗是最常见的反面教材:中断正常而主流程卡死时,看门狗形同虚设。另外喂狗代码要尽量放在单一线程,避免多线程竞态导致喂狗时序漂移。
死机恢复与重启治理
看门狗复位只是手段,恢复能力才是目的。设备重启后要能回答三个问题:为什么复位、上次在做什么、接下来怎么继续。
- 复位原因:利用 RCC 复位标志区分上电复位、软件复位、看门狗复位、掉电复位,记录到非易失存储。
- 故障现场:在复位前把出错任务、错误码、关键变量快照写入 Flash 日志区。
- 分级恢复:首次复位走快速恢复,连续多次复位说明问题顽固,进入降级模式或保持安全状态。
- 启动自检:上电先校验关键外设和配置区,不健康就不进入主流程。
连续复位保护特别重要:如果故障导致系统反复复位,设备会变成"重启循环"。常见的做法是记录复位计数,短时间内超过阈值就进入安全模式:关掉高风险外设、停在安全位置、通过指示灯或串口输出错误码,等待人工介入。
多级看门狗与外部看门狗
对可靠性要求极高的场景,会采用多级看门狗架构:一级是应用级看门狗(检查主流程和任务),二级是独立看门狗(IWDG 兜底),三级是外部硬件看门狗(芯片彻底死掉也能拉起来)。
// 外部看门狗喂狗示例:喂狗脚输出脉冲
void ext_wdg_feed(void) {
GPIO_WriteBit(EXT_WDG_PIN, 0);
delay_us(100); // 低电平脉冲宽度满足器件要求
GPIO_WriteBit(EXT_WDG_PIN, 1);
delay_us(100);
GPIO_WriteBit(EXT_WDG_PIN, 0); // 完整喂狗脉冲序列
}
外部看门狗芯片(比如 MAX6369 系列)有自己的超时电路,不依赖 MCU 内部时钟,MCU 死到连 IWDG 都喂不动时它会直接切断或复位电源。多级结构里每一级超时时间逐级放大,避免低级看门狗抢在上级前面复位,掩盖真正的问题。
常见问题排查
// 1. 设备反复复位:超时设太短或主循环卡点,加大超时并抓复位原因
// 2. 中断里喂狗掩盖故障:把喂狗移到主循环并加入任务心跳
// 3. 低功耗唤醒即复位:休眠前关闭或延长看门狗,唤醒后再恢复
// 4. WWDG 窗口误触发:检查窗口值配置与刷新时机
// 5. 外部看门狗不工作:核对喂狗脉冲宽度与器件使能脚
看门狗不是万能药,但它能保证:程序出了错,系统至少还能重启,而不是永远躺平。
把看门狗、复位记录和分级恢复组合起来,设备就具备了最基本的自愈能力,产品的现场可靠性会上一个台阶。