STM32 看门狗防死机:IWDG+WWDG 双保险设计实战指南
👁 2 阅读 · 2026-08-19 · STM32 看门狗
在嵌入式系统中,程序跑飞或死锁是稳定性的大敌。STM32 提供了独立看门狗(IWDG)和窗口看门狗(WWDG)两种硬件保护机制,但单一使用往往存在盲区。本文深入剖析 IWDG 与 WWDG 的工作原理与差异,并给出双看门狗协同设计的完整方案,包括配置步骤、喂狗策略、完整代码示例及工程注意事项,帮助开发者构建更可靠的防死机防线。
## 为什么需要双看门狗?
STM32 内置的 IWDG(独立看门狗)和 WWDG(窗口看门狗)都能在程序异常时复位系统,但它们的触发机制截然不同:
- **IWDG**:基于独立 RC 振荡器(LSI),超时范围宽(几 ms 到几 s),只要在超时前任意时刻喂狗即可,适合检测长时间卡死。
- **WWDG**:基于系统时钟(APB1),喂狗必须在一个精确的窗口期内(不能太早也不能太晚),能检测到更细微的时序异常,但超时范围窄(通常 < 几十 ms)。
单一使用 IWDG 可能漏掉“程序仍在运行但逻辑错误”的情况;单一使用 WWDG 则可能因时钟故障或中断阻塞而误复位。**双看门狗互补**:IWDG 负责兜底长超时,WWDG 负责监控实时性,形成双重保险。
## 原理详解
### IWDG 工作原理
IWDG 使用 LSI(约 40kHz,具体见芯片手册),通过预分频器和重装载寄存器设置超时时间。一旦启动,无法停止,除非复位。喂狗操作是向关键字寄存器写入 0xAAAA。
超时计算公式:
```
Tout = (Prescaler * Reload) / LSI_Freq
```
例如:LSI=40kHz,Prescaler=64,Reload=625,则 Tout = 64*625/40000 = 1s。
### WWDG 工作原理
WWDG 使用 APB1 时钟(通常 36MHz 或 42MHz),有一个 7 位递减计数器(T[6:0])。喂狗必须在计数器值小于窗口值且大于 0x3F 时进行(即窗口期)。如果计数器减到 0x3F(即 0x40 以下)还没喂狗,或者喂狗时计数器值大于窗口值,都会触发复位。
超时计算公式:
```
Tout = (1/APB1_Freq) * 4096 * Prescaler * (Reload - Window)
```
例如:APB1=36MHz,Prescaler=8,Reload=127,Window=80,则窗口期约为 (1/36M)*4096*8*(127-80) ≈ 4.2ms,总超时约 11.6ms。
## 双看门狗协同设计策略
- **IWDG 超时设置**:设为系统最大容忍死机时间,如 2s。用于捕获严重死循环或硬件故障。
- **WWDG 窗口设置**:设为任务周期的一半左右,如主循环周期 10ms,则窗口设为 5ms。用于强制任务按时执行。
- **喂狗位置**:IWDG 在主循环末尾喂;WWDG 在关键任务(如通信处理、传感器读取)完成后喂,且必须在窗口期内。
## 配置步骤(以 STM32F103 为例)
1. 初始化 LSI 时钟(IWDG 使用)。
2. 配置 IWDG:设置预分频和重装载值,启动。
3. 配置 WWDG:使能 WWDG 时钟,设置预分频、窗口值、计数器初始值,使能中断(可选)。
4. 编写喂狗函数:IWDG_Feed() 和 WWDG_Feed()。
5. 在主循环和关键任务中调用喂狗。
## 完整代码示例
```c
#include "stm32f10x.h"
// 宏定义
#define IWDG_PRESCALER IWDG_Prescaler_64 // 64分频
#define IWDG_RELOAD 625 // 1s超时(假设LSI=40kHz)
#define WWDG_PRESCALER WWDG_Prescaler_8 // 8分频
#define WWDG_WINDOW 80 // 窗口值
#define WWDG_RELOAD 127 // 计数器初值
// 初始化IWDG
void IWDG_Init(void) {
// 使能写访问
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
// 设置预分频
IWDG_SetPrescaler(IWDG_PRESCALER);
// 设置重装载值
IWDG_SetReload(IWDG_RELOAD);
// 重装载计数器
IWDG_ReloadCounter();
// 启动IWDG
IWDG_Enable();
}
// 初始化WWDG
void WWDG_Init(void) {
// 使能WWDG时钟(APB1)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
// 设置窗口值
WWDG_SetWindowValue(WWDG_WINDOW);
// 设置预分频
WWDG_SetPrescaler(WWDG_PRESCALER);
// 设置计数器初值
WWDG_Enable(WWDG_RELOAD);
// 清除中断标志(如果有)
WWDG_ClearFlag();
// 使能WWDG中断(可选,用于提前喂狗)
WWDG_EnableIT();
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
// 喂IWDG
void IWDG_Feed(void) {
IWDG_ReloadCounter();
}
// 喂WWDG(必须在窗口期内调用)
void WWDG_Feed(void) {
// 检查是否在窗口期(计数器值小于窗口值且大于0x3F)
if ((WWDG_GetCounter() < WWDG_WINDOW) && (WWDG_GetCounter() > 0x3F)) {
WWDG_SetCounter(WWDG_RELOAD);
}
}
// WWDG中断服务函数(可选)
void WWDG_IRQHandler(void) {
if (WWDG_GetFlagStatus() != RESET) {
// 提前喂狗或记录错误
WWDG_Feed();
WWDG_ClearFlag();
}
}
// 主函数
int main(void) {
// 初始化系统时钟等
SystemInit();
// 初始化看门狗
IWDG_Init();
WWDG_Init();
// 主循环
while (1) {
// 执行关键任务(如通信处理)
ProcessCommunication();
// 任务完成后喂WWDG(必须在窗口期内)
WWDG_Feed();
// 其他任务...
// 主循环末尾喂IWDG
IWDG_Feed();
}
}
// 示例任务函数
void ProcessCommunication(void) {
// 模拟耗时操作,确保在WWDG窗口期内完成
// 如果超时,WWDG会复位
}
```
## 注意事项
- **IWDG 一旦启动无法关闭**,除非复位。调试时建议先用宏控制,避免调试中断导致复位。
- **WWDG 的窗口值必须小于计数器初值**,否则无法喂狗。
- **喂狗时机**:IWDG 喂狗不要太频繁,否则失去意义;WWDG 喂狗必须精确在窗口期,建议在中断中提前喂狗(如 WWDG 早期中断),但注意中断优先级。
- **时钟源**:IWDG 使用 LSI,精度不高,超时时间有误差;WWDG 使用 APB1,精度高,但系统时钟故障时 WWDG 可能失效。
- **低功耗模式**:进入 STOP 模式前,IWDG 会继续运行,可能导致复位;WWDG 在 STOP 模式下停止。需根据需求处理。
- **调试建议**:使用硬件调试时,可暂时禁用看门狗,或使用断点调试时手动喂狗。
## 总结
IWDG + WWDG 双看门狗设计能有效覆盖长时间卡死和短时序异常,提升系统鲁棒性。关键在于合理设置超时和窗口值,并严格规划喂狗点。实际项目中,建议结合 RTOS 的任务调度,将喂狗操作放在高优先级任务中,确保系统实时性。