嵌入式系统看门狗详解——以 STM32 为例
一、什么是看门狗1.1 概念定义看门狗Watchdog Timer简称 WDT是嵌入式系统中的一种硬件定时器模块它的核心思想非常简单在一个固定时间内软件必须喂狗即重新初始化定时器否则看门狗超时后会触发系统复位从而让程序从死机或跑飞状态中恢复过来。可以把它想象成家里养的一只看门狗你每隔一段时间就要去喂它一次喂狗/喂狗操作。如果你忘了喂它它就会叫起来触发复位提醒你出问题了。1.2 看门狗的硬件实现方式实现方式说明特点独立看门狗定时器IWDG由内部低速振荡器LSI驱动不受主时钟影响低功耗下仍可用窗口看门狗定时器WWDG有喂狗时间窗口限制防止过早喂狗能检测到执行时间异常外部看门狗芯片独立 IC如 MAX809、IMP706功能丰富可配置复位电压等1.3 核心概念表概念说明预分频器Prescaler对时钟源进行分频决定计数器的计数速度重装载寄存器Reload设定计数器的初始值喂狗时写入此值当前计数值Counter当前计数器的实时值递减计数溢出/超时计数器减到 0 时触发复位或中断喂狗Feed/Refresh向重装载寄存器写入值重新初始化计数器二、看门狗的作用2.1 自动恢复系统故障嵌入式系统运行环境复杂可能因电磁干扰、电源波动、宇宙射线等原因导致程序跑飞或进入死循环。看门狗能在程序异常时自动复位系统无需人工干预。2.2 检测软件死锁当程序因死锁、优先级反转等原因停止前进一步时看门狗超时复位可以恢复系统运行。2.3 保证系统可靠性在工业控制、汽车电子、医疗设备等对可靠性要求极高的场景中看门狗几乎是必选项。2.4 窗口看门狗的特殊作用窗口看门狗WWDG不仅检测不喂狗还能检测过早喂狗——如果软件在窗口打开之前就喂狗说明程序执行时间可能异常比如跳过了某些关键代码段WWDG 同样会触发复位。三、STM32 看门狗类型概览STM32 系列微控制器通常提供两种看门狗类型全称时钟源特点IWDGIndependent Watchdog内部 40kHz 低速 RC 振荡器LSI独立运行不受主时钟故障影响配置简单WWDGWindow Watchdog系统时钟PCLK分频有喂狗窗口限制能检测执行时间异常关键区别IWDG 只要在规定时间内喂狗即可WWDG 要求在特定时间窗口内喂狗——太早或太晚都会触发复位。四、IWDG独立看门狗使用流程4.1 使用步骤总览1. 开启 IWDG 时钟 2. 设置预分频器和重装载值设定超时时间 3. 重载计数器第一次喂狗 4. 启用 IWDG 5. 主循环中定期喂狗4.2 超时时间计算公式超时时间 (预分频系数 x 重装载值) / LSI频率 其中 - LSI 频率典型值 40 kHz - 预分频系数可选4 / 8 / 16 / 32 / 64 / 128 / 256 - 重装载值范围0 ~ 0x0FFF12位 - 最大超时时间 (256 x 4095) / 40000 262 ms4.3 完整代码示例HAL 库/** * IWDG 初始化函数 * 配置独立看门狗设置预分频和重装载值 */ void MX_IWDG_Init(void) { hiwdg.Instance IWDG; // 预分频器 256重装载值 0x0500 (1280) hiwdg.Init.Prescaler IWDG_PRESCALER_256; hiwdg.Init.Reload 0x0500; if (HAL_IWDG_Init(hiwdg) ! HAL_OK) { // 初始化错误处理 Error_Handler(); } } /** * 主函数示例 */ int main(void) { HAL_Init(); SystemClock_Config(); MX_IWDG_Init(); // 第一次喂狗必须在使用前重载计数器 HAL_IWDG_Refresh(hiwdg); // 启动看门狗 HAL_IWDG_Start(hiwdg); while (1) { // 正常业务流程 Process_Sensor_Data(); Process_Comm_Packet(); Update_Display(); // 喂狗在超时前定期刷新 HAL_IWDG_Refresh(hiwdg); // 延时函数可选用于控制喂狗频率 HAL_Delay(100); } }4.4 使用注意事项喂狗位置喂狗代码应放在主循环的安全区域确保每次循环都能执行到。不要放在可能长时间阻塞的代码段中。禁止在调试时关闭调试阶段可以暂时关闭看门狗以便单步调试但发布版本必须开启。LSI 精度内部 RC 振荡器精度约为 /-5%~10%对超时精度要求高的场景需用外部晶振驱动。不可在中断中喂狗建议在主循环中喂狗避免中断优先级问题导致喂狗不及时。五、WWDG窗口看门狗使用流程5.1 与 IWDG 的核心区别对比项IWDGWWDG喂狗时机超时前任意时刻必须在窗口范围内过早喂狗不允许会触发复位不允许会触发复位检测能力仅检测不喂狗检测不喂狗执行时间异常适用场景通用故障恢复需要精确时序控制的场景5.2 窗口概念图解计数器值变化过程 127 (最大值) |------------------- 窗口上限 (T7) | | 合法喂狗窗口 | (只能在窗口内喂狗) | 64 (中间值) |--------[ 窗口 ]-------- | | 过早喂狗 - 复位 | 0 (下溢) |------------------- 窗口下限 (T6) | v 提前喂狗 - 复位窗口规则计数器从窗口上限递减到窗口下限的过程中只能在这段区间内喂狗。太早计数器 窗口上限或太晚计数器 窗口下限即下溢都会触发复位。5.3 完整代码示例HAL 库/** * WWDG 初始化函数 * 窗口上限 127, 窗口下限 64 * 预分频器 8, 超时约 1.024 ms */ void MX_WWDG_Init(void) { hwwdg.Instance WWDG; // 预分频器 8 hwwdg.Init.Prescaler WWDG_PRESCALER_8; // 窗口值 0x7F (127)计数器初始值也设为 127 hwwdg.Init.Window 0x7F; hwwdg.Init.Counter 0x7F; // 使能窗口比较中断可选可在窗口到达前软中断喂狗 hwwdg.Init.EWDModes WWDG_EWDI_MODE_ENABLE; if (HAL_WWDG_Init(hwwdg) ! HAL_OK) { Error_Handler(); } // 配置窗口比较中断在计数器到达窗口值时产生中断 HAL_WWDG_Start_IT(hwwdg); } /** * 窗口比较回调函数在计数器到达窗口值时调用 * 这是喂狗的最佳时机——在窗口打开的瞬间喂狗 */ void HAL_WWDG_WindowCallback(WWDG_HandleTypeDef *hwwdg) { // 在此处执行关键任务后喂狗 HAL_WWDG_Refresh(hwwdg); } /** * 主函数示例 */ int main(void) { HAL_Init(); SystemClock_Config(); MX_WWDG_Init(); // 启动 WWDG含中断模式 HAL_WWDG_Start(hwwdg); while (1) { // 注意WWDG 的喂狗时机由硬件窗口控制 // 不要在 while(1) 中随意喂狗 // 应在窗口比较中断回调中喂狗 // 正常业务流程 Process_Task_1(); Process_Task_2(); // 禁止在此处调用 HAL_WWDG_Refresh() // 这会因过早喂狗触发复位 } }六、IWDG 与 WWDG 实战对比6.1 场景选择指南场景推荐类型原因通用系统保护IWDG配置简单只需定时喂狗实时任务调度保护WWDG能检测任务执行时间是否超标低功耗场景IWDG可由 LSI 驱动休眠时仍工作安全关键系统IWDG WWDG 双看门狗双重保障可靠性最高6.2 双看门狗架构示例/** * 双看门狗架构IWDG 做底层保护WWDG 做应用层监控 * * 架构说明 * - IWDG超时时间较长如 200ms作为最后防线 * - WWDG超时时间较短如 10ms监控应用层任务调度 * - 应用层每完成一轮任务调度后喂 IWDG * - WWDG 在中断中按窗口喂狗 */ void Dual_Watchdog_Init(void) { // 1. 初始化 WWDG短周期监控任务调度 MX_WWDG_Init(); HAL_WWDG_Start_IT(hwwdg); // 2. 初始化 IWDG长周期最终防线 MX_IWDG_Init(); HAL_IWDG_Refresh(hiwdg); HAL_IWDG_Start(hiwdg); } void Application_Task_Routine(void) { // 执行应用任务 Task_A(); Task_B(); Task_C(); // 一轮任务完成后喂 IWDG最终防线 HAL_IWDG_Refresh(hiwdg); } // WWDG 窗口中断回调——确保在窗口内喂狗 void HAL_WWDG_WindowCallback(WWDG_HandleTypeDef *hwwdg) { HAL_WWDG_Refresh(hwwdg); // 窗口内喂狗 }七、常见陷阱与最佳实践7.1 常见陷阱陷阱描述解决方案喂狗位置错误喂狗代码放在可能阻塞的区域放在主循环末尾或独立任务中关闭调试信息后忘开看门狗调试时关闭 WDG发布时忘记重新开启用宏定义统一控制#ifdef DEBUG_DISABLE_WDG中断中喂狗中断优先级问题导致喂狗不及时主循环中喂狗中断中只做标志位置位预分频计算错误超时时间算错导致频繁复位用表格或计算器预先算好参数WWDG 过早喂狗在 while(1) 中直接调用刷新函数使用中断模式在窗口回调中喂狗看门狗初始化后未第一次喂狗初始化完成后必须立即重载计数器初始化后立即调用一次 Refresh7.2 最佳实践清单[ ] 1. 用宏定义统一控制看门狗的开关方便调试 [ ] 2. 喂狗代码放在主循环最底部确保前面代码异常时不会喂狗 [ ] 3. 关键任务完成后设置标志位主循环检查标志位后再喂狗 [ ] 4. 系统启动完成后延迟一段时间再开启看门狗避免启动过程被复位 [ ] 5. 低功耗模式下确保看门狗仍在工作IWDG 由 LSI 驱动休眠可用 [ ] 6. 在串口调试时打印看门狗触发次数可通过复位源寄存器读取 [ ] 7. 生产环境建议启用双看门狗架构 [ ] 8. 代码审查时将喂狗逻辑作为必查项7.3 调试技巧如何判断是否由看门狗复位/** * 读取复位源寄存器判断是否由看门狗触发复位 */ void Check_Reset_Source(void) { uint32_t reset_flag LL_RCC_GetResetFlags(); if (reset_flag LL_RCC_GET_ICSR_WWDG1F_FLAG) { // 窗口看门狗复位 printf(Reset source: WWDG1\n); LL_RCC_ClearRESETFlags(LL_RCC_GET_ICSR_WWDG1F_FLAG); } if (reset_flag LL_RCC_GET_ICSR_IWDG1F_FLAG) { // 独立看门狗复位 printf(Reset source: IWDG1\n); LL_RCC_ClearRESETFlags(LL_RCC_GET_ICSR_IWDG1F_FLAG); } if (reset_flag LL_RCC_GET_ICSR_SFTRSTF_FLAG) { // 软件复位 printf(Reset source: Software\n); } if (reset_flag LL_RCC_GET_ICSR_PORSFTRSTF_FLAG) { // 电源上电/掉电复位 printf(Reset source: Power\n); } }八、总结要点总结看门狗是什么一个硬件定时器软件必须定期喂狗否则触发系统复位为什么需要自动恢复程序跑飞/死锁提高系统可靠性和自恢复能力STM32 提供什么IWDG独立看门狗 WWDG窗口看门狗两种IWDG 怎么用初始化 - 设置分频和重载值 - 启动 - 主循环中定期喂狗WWDG 怎么用初始化 - 设置窗口值 - 以中断模式启动 - 在窗口回调中喂狗核心注意事项喂狗位置、调试开关控制、LSI精度、双看门狗架构、复位源检测一句话总结看门狗是嵌入式系统的最后一道防线——它不能阻止程序出错但能在程序出错后让系统自动恢复让产品在现场少出死机后需要断电重启的尴尬状况。