为什么需要双看门狗?

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 为例)

  1. 初始化 LSI 时钟(IWDG 使用)。
  2. 配置 IWDG:设置预分频和重装载值,启动。
  3. 配置 WWDG:使能 WWDG 时钟,设置预分频、窗口值、计数器初始值,使能中断(可选)。
  4. 编写喂狗函数:IWDG_Feed() 和 WWDG_Feed()。
  5. 在主循环和关键任务中调用喂狗。

完整代码示例

#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 的任务调度,将喂狗操作放在高优先级任务中,确保系统实时性。