STM32 看门狗防死机实战:程序跑飞的恢复与复位策略
👁 10 阅读 · 2026-08-15 · STM32 看门狗
在嵌入式系统中,程序跑飞、死循环或外部干扰常导致系统“假死”。STM32 内置独立看门狗(IWDG)和窗口看门狗(WWDG),是防止死机的最后一道防线。本文从原理出发,深入讲解两种看门狗的工作机制与配置方法,并给出完整的初始化、喂狗和故障恢复代码示例,帮助开发者构建可靠的防死机复位策略。
## 一、为什么需要看门狗?
嵌入式系统运行环境复杂,电磁干扰、电源波动、指针越界、堆栈溢出都可能使 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
}
}
```