2. 硬件看门狗原理:独立看门狗(IWDG)与窗口看门狗(WWDG)的区别、内部结构框图、时钟源选择

好,咱们直接进入正题。看门狗这东西,说白了就是给单片机请了个「监工」。你程序跑飞了,它就把系统复位,让你重新做人。我最早接触看门狗是在做一款运动手环的时候,那会儿电池续航要求高,经常要进休眠模式,结果一睡不醒……嗯,从那以后我就对看门狗特别上心了。

STM32 里给了你两种看门狗:独立看门狗(IWDG)窗口看门狗(WWDG)。名字听着像,但脾气完全不同。咱们一个一个拆开讲。

2.1 独立看门狗(IWDG)—— 最可靠的「保底」方案

独立看门狗,我习惯叫它「死脑筋看门狗」。为什么?因为它只认一个事:你必须在规定时间内喂它,否则它就咬人。它不关心你程序跑得快还是慢,也不管你当前在干啥,超时就复位。

内部结构框图

IWDG 的内部结构其实不复杂,我画个文字版的框图给你看:

LSI (40kHz) → 预分频器 (PR) → 递减计数器 (RLR) → 复位信号
                    ↑
              键寄存器 (KR) 控制喂狗

核心部件就三个:

  • LSI 时钟源:内部低速振荡器,约 40kHz。注意是「约」,实际会有偏差。
  • 预分频器(PR):可配置为 4、8、16、32、64、128、256 分频。
  • 递减计数器(RLR):12 位计数器,最大值 4095。

喂狗时间计算公式:
Tout = (RLR + 1) × 分频系数 / LSI频率

举个例子,LSI 取 40kHz,分频系数 64,RLR 设 1000:
Tout = (1000 + 1) × 64 / 40000 ≈ 1.6 秒

关键点:IWDG 的时钟源是独立的 LSI,即使主时钟挂了,它照样工作。这就是它「独立」二字的含义。

时钟源选择

IWDG 的时钟源只有一个选择:LSI(内部低速振荡器)。没得选,就是这么倔。但这也带来了一个问题——LSI 的精度不高,温度变化时频率会漂。我在做一款户外手表时,夏天高温下 LSI 频率能漂到 45kHz,导致喂狗时间缩短了 10% 以上。所以,喂狗时间一定要留余量,别卡着极限算。

2.2 窗口看门狗(WWDG)—— 更「聪明」的监工

窗口看门狗就有点意思了。它不光要求你按时喂狗,还要求你在一个「窗口期」内喂。喂早了不行,喂晚了也不行。你想想看,这像不像你老板?「报告交早了说你敷衍,交晚了说你拖延,必须卡在 deadline 前 5 分钟交」——嗯,WWDG 就是这种老板脾气。

内部结构框图

PCLK1 (APB1时钟) → 预分频器 → 7位递减计数器 (CNT)
                    ↓
              窗口寄存器 (CFR) 设定上限
                    ↓
              比较器 → 复位信号

WWDG 的核心参数:

  • 7 位递减计数器:从 0x7F 往下减,减到 0x3F 时触发复位。
  • 窗口寄存器(CFR):设定喂狗的上限值。比如设 0x5F,那计数器值必须在 0x5F ~ 0x3F 之间时喂狗才有效。
  • 时钟源:来自 APB1 总线时钟 PCLK1,通常为 36MHz 或 42MHz。

注意:WWDG 的时钟源是 PCLK1,如果主时钟挂了,WWDG 也会失效。所以它没有 IWDG 那么「独立」。但好处是精度高,适合对时间要求严格的场景。

2.3 IWDG vs WWDG:到底该用哪个?

我经常被问到这个问题。我的回答是:看场景。直接上对比表:

对比项 IWDG(独立看门狗) WWDG(窗口看门狗)
时钟源 LSI(约 40kHz,独立) PCLK1(APB1 时钟,依赖主时钟)
计数器位数 12 位(最大 4095) 7 位(最大 127)
喂狗方式 任意时刻喂,超时即复位 必须在窗口期内喂,早喂/晚喂都复位
精度 较低(LSI 漂移大) 较高(PCLK1 精度高)
典型应用 系统死机保护、休眠唤醒 任务超时检测、实时性要求高的场景
功耗 低(LSI 功耗小) 较高(依赖 APB1 时钟)

2.4 实战中的选择建议

我个人习惯是这样用的:

  • 智能手表主控芯片:用 IWDG 做系统级保护。因为手表经常进休眠,主时钟可能被关闭,只有 IWDG 能保证在休眠期间也能监控。
  • 传感器数据采集任务:用 WWDG 做任务超时检测。比如心率传感器必须在 100ms 内完成一次采样,超时就报警。
  • 关键安全功能:两个一起用。IWDG 兜底,WWDG 做精细控制。我曾经在一个医疗手环项目里就是这么干的,双重保护,心里踏实。

避坑指南:我曾经在 IWDG 的喂狗时间上吃过亏。当时算出来是 2 秒,结果 LSI 漂移加上温度变化,实际只有 1.6 秒。程序稍微卡顿一下就被复位了。后来我养成了一个习惯:喂狗时间至少留 50% 的余量。比如你需要 1 秒的监控周期,就把 IWDG 设成 1.5 秒。

2.5 代码示例:IWDG 初始化

最后给个实际代码,你直接拿去用:

void IWDG_Init(uint16_t reload_value, uint8_t prescaler)
{
    // 1. 使能写访问
    IWDG->KR = 0x5555;
    
    // 2. 设置预分频器
    // prescaler: 0=4分频, 1=8分频, ..., 6=256分频
    IWDG->PR = prescaler;
    
    // 3. 设置重装载值
    IWDG->RLR = reload_value;
    
    // 4. 启动看门狗
    IWDG->KR = 0xCCCC;
}

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

注意那个 0x55550xAAAA0xCCCC,这是 STM32 的「键寄存器」机制,防止误操作。说白了就是:你想改配置?先输密码。你想喂狗?再输密码。防止程序跑飞时意外喂狗。

好了,这一章就到这儿。下一章咱们讲怎么在智能手表的实际代码里用好这两个看门狗,包括低功耗模式下的处理技巧。到时候我会分享一个我踩过的坑——手表在睡眠模式下 IWDG 居然不工作了……嗯,到时候细说。