2. 硬件看门狗设计:独立看门狗(IWDG)与窗口看门狗(WWDG)的区别、STM32中的IWDG配置、STM32中的WWDG配置

看门狗这东西,说白了就是系统的「保命符」。我做了这么多年嵌入式,见过太多因为程序跑飞、死循环导致设备变砖的案例。嗯,今天咱们就聊聊STM32里两种硬件看门狗——IWDG和WWDG。它们虽然都叫看门狗,但脾气秉性完全不同。

2.1 独立看门狗(IWDG)与窗口看门狗(WWDG)的区别

先说说最核心的区别。IWDG是独立看门狗,它有自己的独立时钟——LSI(低速内部振荡器,约40kHz)。这意味着即使主时钟挂了,IWDG照样能跑。WWDG则不同,它挂在APB1总线上,用的是系统时钟。主时钟一停,WWDG也就歇菜了。

我个人的理解是这样的:

  • IWDG:适合「死等」场景。比如系统进入低功耗模式,或者主时钟不稳定时,IWDG依然能监控系统是否活着。
  • WWDG:适合「时间窗口」场景。它要求你在一个特定的时间窗口内喂狗——喂早了不行,喂晚了也不行。说白了,它比IWDG更「苛刻」。

为什么会这样?因为WWDG的设计初衷是检测程序是否「按时」执行了任务。如果程序跑飞了但还在乱喂狗,IWDG可能发现不了,但WWDG能抓住你。

核心区别总结:

特性 IWDG WWDG
时钟源 独立LSI(约40kHz) APB1总线时钟
喂狗方式 任意时间喂狗 必须在窗口内喂狗
复位条件 计数器减到0 计数器减到0x3F或提前喂狗
适用场景 低功耗、主时钟不稳定 任务时序要求严格

2.2 STM32中的IWDG配置

配置IWDG其实很简单。我记得第一次用IWDG时,踩了个坑——忘了开启LSI时钟。嗯,这里要注意,IWDG的时钟是独立的,你得先确保LSI在工作。

下面是一个典型的IWDG配置流程:

  1. 使能LSI时钟(如果不确定,可以读RCC_CSR寄存器确认)
  2. 写入0x5555到IWDG_KR寄存器,解锁预分频器和重装载寄存器
  3. 设置预分频器(IWDG_PR)和重装载值(IWDG_RLR)
  4. 写入0xAAAA到IWDG_KR,刷新计数器(喂狗)
  5. 写入0xCCCC到IWDG_KR,启动看门狗

代码示例:

// 配置IWDG,超时时间约1秒(LSI=40kHz,预分频=64,重装载=625)
void IWDG_Config(void)
{
    // 1. 使能LSI(如果还没使能)
    RCC->CSR |= RCC_CSR_LSION;
    while(!(RCC->CSR & RCC_CSR_LSIRDY)); // 等待LSI稳定
    
    // 2. 解锁预分频器和重装载寄存器
    IWDG->KR = 0x5555;
    
    // 3. 设置预分频器为64分频(IWDG_PR[2:0] = 011)
    IWDG->PR = 3;
    
    // 4. 设置重装载值为625(超时时间 = 625 * 64 / 40000 ≈ 1秒)
    IWDG->RLR = 625;
    
    // 5. 启动看门狗
    IWDG->KR = 0xCCCC;
    
    // 6. 喂狗(第一次喂狗)
    IWDG->KR = 0xAAAA;
}

// 喂狗函数
void IWDG_Feed(void)
{
    IWDG->KR = 0xAAAA;
}

避坑指南:我曾经在调试时发现IWDG复位时间不准,后来查了半天,原来是LSI频率有偏差。STM32的LSI典型值是40kHz,但实际可能在30~60kHz之间。所以,如果你对时间精度要求高,建议用示波器实测一下LSI频率,再调整重装载值。

2.3 STM32中的WWDG配置

WWDG的配置比IWDG稍微复杂一点。因为它有个「窗口」的概念。你想想看,WWDG的计数器是一个7位递减计数器(T[6:0]),当它减到0x3F时就会复位。但如果你在计数器值大于窗口值(W[6:0])时喂狗,也会触发复位。

配置WWDG的步骤:

  1. 使能WWDG时钟(RCC->APB1ENR)
  2. 设置预分频器(WWDG_CFR的WDGTB位)
  3. 设置窗口值(WWDG_CFR的W位)
  4. 设置计数器初始值(WWDG_CR的T位)
  5. 使能WWDG(WWDG_CR的WDGA位)

代码示例:

// 配置WWDG,APB1时钟=36MHz,预分频=8,窗口值=0x50,计数器初始值=0x7F
void WWDG_Config(void)
{
    // 1. 使能WWDG时钟
    RCC->APB1ENR |= RCC_APB1ENR_WWDGEN;
    
    // 2. 设置预分频器为8分频(WDGTB[1:0] = 00)
    WWDG->CFR = 0;
    
    // 3. 设置窗口值为0x50(喂狗必须在计数器值 <= 0x50时进行)
    WWDG->CFR |= (0x50 << 0);
    
    // 4. 设置计数器初始值为0x7F(最大)
    WWDG->CR = 0x7F;
    
    // 5. 使能WWDG
    WWDG->CR |= WWDG_CR_WDGA;
}

// 喂狗函数(必须在窗口内调用)
void WWDG_Feed(void)
{
    // 先检查当前计数器值是否在窗口内
    uint8_t counter = WWDG->CR & 0x7F;
    if(counter <= 0x50)  // 窗口值
    {
        WWDG->CR = 0x7F;  // 重新加载计数器
    }
    else
    {
        // 喂狗太早,会触发复位!这里可以加个调试断点
        // 我曾经在这里吃过亏,后来加了调试输出
        printf("WWDG喂狗太早!当前计数器值:0x%02X\n", counter);
    }
}

重要提醒:WWDG的窗口值设置要合理。如果你把窗口设得太小(比如0x10),那喂狗的时间窗口就很窄,容易喂早或喂晚。我建议窗口值设在0x40~0x60之间,这样有足够的余量。另外,WWDG的计数器一旦启动,就不能停止(除非复位)。所以配置前一定要想清楚。

2.4 实际项目中的选择建议

我个人习惯是这样选的:

  • 低功耗设备(比如电池供电的体温计):用IWDG。因为它不依赖主时钟,休眠时也能工作。
  • 任务时序严格(比如需要定期采集数据):用WWDG。它能检测到程序是否「按时」执行了任务。
  • 关键系统(比如医疗设备):两个都用。IWDG保底,WWDG监控任务时序。但要注意,两个看门狗同时用,喂狗逻辑要设计好,别互相干扰。

我记得有一次做工业控制器,客户要求系统在死机后5秒内自动复位。我用了IWDG,预分频64,重装载值设成3125(5秒)。结果测试时发现,有时候复位时间偏长。后来发现是LSI频率偏低,只有35kHz。调整重装载值到2734后,问题解决。嗯,这就是经验。

好了,关于IWDG和WWDG的区别与配置,就聊这么多。下一节咱们聊聊如何设计一个健壮的喂狗策略——这可是很多工程师容易忽略的地方。