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配置流程:
- 使能LSI时钟(如果不确定,可以读RCC_CSR寄存器确认)
- 写入0x5555到IWDG_KR寄存器,解锁预分频器和重装载寄存器
- 设置预分频器(IWDG_PR)和重装载值(IWDG_RLR)
- 写入0xAAAA到IWDG_KR,刷新计数器(喂狗)
- 写入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的步骤:
- 使能WWDG时钟(RCC->APB1ENR)
- 设置预分频器(WWDG_CFR的WDGTB位)
- 设置窗口值(WWDG_CFR的W位)
- 设置计数器初始值(WWDG_CR的T位)
- 使能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的区别与配置,就聊这么多。下一节咱们聊聊如何设计一个健壮的喂狗策略——这可是很多工程师容易忽略的地方。