为什么需要双看门狗?
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)40968*(127-80) ≈ 4.2ms,总超时约 11.6ms。
双看门狗协同设计策略
- IWDG 超时设置:设为系统最大容忍死机时间,如 2s。用于捕获严重死循环或硬件故障。
- WWDG 窗口设置:设为任务周期的一半左右,如主循环周期 10ms,则窗口设为 5ms。用于强制任务按时执行。
- 喂狗位置:IWDG 在主循环末尾喂;WWDG 在关键任务(如通信处理、传感器读取)完成后喂,且必须在窗口期内。
配置步骤(以 STM32F103 为例)
- 初始化 LSI 时钟(IWDG 使用)。
- 配置 IWDG:设置预分频和重装载值,启动。
- 配置 WWDG:使能 WWDG 时钟,设置预分频、窗口值、计数器初始值,使能中断(可选)。
- 编写喂狗函数:IWDG_Feed() 和 WWDG_Feed()。
- 在主循环和关键任务中调用喂狗。
完整代码示例
#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 的任务调度,将喂狗操作放在高优先级任务中,确保系统实时性。