## 为什么需要双看门狗? 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 的任务调度,将喂狗操作放在高优先级任务中,确保系统实时性。