## 一、为什么需要看门狗? 嵌入式系统运行环境复杂,电磁干扰、电源波动、指针越界、堆栈溢出都可能使 CPU 偏离正常执行流,陷入死循环或跑飞。此时程序无法响应外部事件,系统“假死”。看门狗的本质是一个独立计数器,若程序未在规定时间内“喂狗”(刷新计数器),看门狗将强制复位 MCU,让系统重新运行。 STM32 提供两种看门狗: - **IWDG(独立看门狗)**:由内部低速时钟(LSI,约 40kHz)驱动,独立于主时钟,即使主时钟失效也能工作。适合需要绝对可靠性的场景。 - **WWDG(窗口看门狗)**:由主时钟(APB1)驱动,喂狗时间必须在窗口期内,过早或过晚都会复位,能检测到程序时序异常。 ## 二、看门狗工作原理 ### 1. IWDG 原理 IWDG 是一个 12 位递减计数器,时钟源为 LSI。启动后计数器从预装载值开始递减,减到 0 时产生复位。程序必须在计数器减到 0 之前重新装载(喂狗)。 关键寄存器: - **KR**(键寄存器):写入 `0x5555` 解除写保护,写入 `0xAAAA` 喂狗,写入 `0xCCCC` 启动看门狗。 - **PR**(预分频器):设置分频系数,决定计数时钟。 - **RLR**(重装载寄存器):设置计数初值。 超时时间计算: \[ T_{out} = \frac{RLR \times 4 \times 2^{PR}}{LSI_{freq}} \] 例如 LSI=40kHz,PR=4(分频 64),RLR=500,则 \( T_{out} = 500 \times 64 / 40000 = 0.8s \)。 ### 2. WWDG 原理 WWDG 是一个 7 位递减计数器,时钟来自 PCLK1。它有两个边界: - **窗口上限**:只能在计数器的值小于某个窗口值时喂狗,太早喂狗(计数值大于窗口值)会复位。 - **下限(0x40)**:计数器减到 0x3F 时复位,所以必须在减到下限之前喂狗。 窗口配置的意义在于:如果程序因为跑飞导致循环时间异常变短,提前喂狗也会触发复位,从而检测逻辑错误。 ## 三、IWDG 配置步骤与代码 ### 1. 配置步骤 1. 写 `0x5555` 到 KR 解锁 PR 和 RLR。 2. 设置 PR 和 RLR 值。 3. 写 `0xAAAA` 到 KR 重新装载(喂狗)。 4. 写 `0xCCCC` 到 KR 启动 IWDG(启动后无法关闭)。 ### 2. 代码示例 ```c // 使用 STM32 HAL 库,初始化 IWDG,超时 1 秒 void IWDG_Init(void) { IWDG_HandleTypeDef hiwdg; hiwdg.Instance = IWDG; hiwdg.Init.Prescaler = IWDG_PRESCALER_64; // 分频 64 hiwdg.Init.Reload = 625; // 625 * 64 / 40000 ≈ 1s HAL_IWDG_Init(&hiwdg); } // 喂狗函数 void IWDG_Feed(void) { HAL_IWDG_Refresh(&hiwdg); // 写 0xAAAA 到 KR } ``` 注意:`hiwdg` 需定义为全局变量或静态变量,以便在喂狗时使用。如果使用寄存器操作,喂狗语句为 `IWDG->KR = 0xAAAA;`。 ## 四、WWDG 配置步骤与代码 ### 1. 配置步骤 1. 计算窗口值和分频。例如 PCLK1=36MHz,分频 4096,计数器时钟 ≈ 8.8kHz。 2. 设置 CFR 窗口值(例如 80)。 3. 设置 CR 计数初值(例如 127)和启动位 WDGA。 4. 使能中断(可选)提前通知喂狗。 ### 2. 代码示例 ```c void WWDG_Init(void) { __HAL_RCC_WWDG_CLK_ENABLE(); WWDG_HandleTypeDef hwwdg; hwwdg.Instance = WWDG; hwwdg.Init.Prescaler = WWDG_PRESCALER_DIV8; // 8 分频 hwwdg.Init.Window = 80; // 窗口值 hwwdg.Init.Counter = 127; // 计数初值 hwwdg.Init.EWIMode = WWDG_EWI_ENABLE; // 开启提前唤醒中断 HAL_WWDG_Init(&hwwdg); } // 窗口看门狗中断回调(可选,用于喂狗警告) void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg) { // 此处可以喂狗,但必须保证计数值小于 Window 且大于 0x3F HAL_WWDG_Refresh(hwwdg); } ``` 注意:WWDG 喂狗窗口很窄,必须在计数器的值从 127 递减到 80 之间喂狗,否则复位。实际中建议在中断回调中喂狗,保证时序精确。 ## 五、程序跑飞恢复策略 看门狗只能复位系统,但复位后如何快速恢复工作状态?建议采取以下策略: 1. **存储关键状态**:在外部 Flash 或备份寄存器保存运行模式、参数、日志等,复位后恢复。 2. **区分复位原因**:通过 `RCC_GetFlagStatus` 检查复位标志(IWDGRSTF / WWDGRSTF),判断是否因看门狗复位,并记录错误计数。 3. **分层喂狗**:不要在 main 循环顶部一次性喂狗,建议在主循环和关键任务中分别喂狗,防止部分任务卡死而主循环仍能喂狗。 4. **延迟复位后的重试**:如果连续多次看门狗复位,说明系统存在严重故障,应进入安全模式,停止高风险外设,并报警。 示例:复位原因判断 ```c void Check_Reset_Source(void) { if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)) { // 独立看门狗复位 __HAL_RCC_CLEAR_RESET_FLAGS(); System_ErrorLog('W'); } else if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)) { // 窗口看门狗复位 __HAL_RCC_CLEAR_RESET_FLAGS(); System_ErrorLog('V'); } } ``` ## 六、注意事项 - **IWDG 一旦启动不可停止**,除非复位 MCU。因此调试时建议使用条件编译,避免频繁复位。 - LSI 时钟精度不高,超时时间有 ±十几% 误差,喂狗周期要留足够裕量(建议小于超时值的 70%)。 - 喂狗不能放在中断里(除非是专门用于喂狗的中断),否则主循环卡死时中断仍可喂狗,失去保护作用。 - WWDG 的窗口值必须大于 0x3F,且计数器初值必须大于窗口值,否则配置无效。 - 不要在所有任务都执行完后才喂狗,应分散喂狗,防止关键任务卡死但非关键任务正常。 - 若使用低功耗模式,需在进入 STANDBY 前关闭看门狗,或确保唤醒后能及时喂狗。 ## 七、总结 STM32 看门狗是防止程序跑飞的有效工具。IWDG 简单粗暴,适合独立性强、对时间精度要求不高的场景;WWDG 提供窗口检测,适合对任务时序有严格要求的系统。实际开发中,应结合复位原因记录、状态保存和分层喂狗策略,才能构建真正健壮的嵌入式系统。记住:看门狗的最终目的是让系统“快速恢复”,而不是“永不重启”。 ```c // 最终推荐:在主循环中每隔一段时间喂狗,并配合任务看门狗 int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); Check_Reset_Source(); IWDG_Init(); while (1) { Task_LED(); // 任务1 IWDG_Feed(); // 喂狗1 Task_KeyScan(); // 任务2 IWDG_Feed(); // 喂狗2 Task_Display(); // 任务3 IWDG_Feed(); // 喂狗3 } } ```