3、软件看门狗基础:IWDG与WWDG的区别与实战
看门狗这东西,说白了就是系统最后的「救命稻草」。我在医疗设备项目里见过太多因为程序跑飞导致设备死机的案例——病人监护仪突然黑屏、输液泵停止工作,这种场景想想都后怕。所以今天咱们把STM32里两个看门狗讲透:独立看门狗(IWDG)和窗口看门狗(WWDG)。
3.1 独立看门狗(IWDG)——最可靠的「保安」
IWDG这个名字很直白:独立。它独立在哪儿?独立在时钟源上。它用的是内部低速振荡器(LSI),大约40kHz。这意味着即使主时钟挂了,IWDG照样能跑。
我个人习惯把IWDG比作一个「只会倒计时的保安」。你给它一个初始值,它就开始往下减。减到0了,系统复位。你要做的就是在它减到0之前,定期「喂狗」——也就是把重装载值重新写进去。
核心要点:
- 时钟源:LSI(约40kHz),独立于主时钟
- 工作模式:递减计数器,减到0触发复位
- 喂狗窗口:只要没到0,随时可以喂
- 适用场景:系统死机后的绝对保护
嗯,这里要注意:IWDG一旦启动,除非复位,否则关不掉。所以你在调试阶段要小心,别让看门狗把你「咬」了。
3.2 窗口看门狗(WWDG)——更「挑剔」的监工
WWDG就不一样了。它用的是主时钟(APB1时钟),所以如果主时钟挂了,WWDG也挂了。但它的设计理念更先进——它要求你在一个特定的「时间窗口」内喂狗。
为什么会这样?你想想看,如果程序跑飞了,但恰好还在循环喂狗,IWDG是检测不出来的。WWDG就能解决这个问题:它要求喂狗不能太早,也不能太晚。太早说明程序执行太快,可能跳过了关键代码;太晚说明程序卡住了。
我在项目中遇到过一件事:一个呼吸机控制程序,因为中断优先级配置错误,导致主循环跑得飞快,每次都在窗口打开之前就喂了狗。WWDG果断复位,帮我们发现了这个隐蔽的bug。要是用IWDG,这问题根本查不出来。
个人经验:
医疗设备里,我建议关键任务用WWDG,因为它能检测「程序跑得太快」这种异常。而IWDG更适合做最后的「兜底」保护。
3.3 预分频器与重装载值——计算口诀
很多初学者看到预分频器和重装载值就头大。其实没那么复杂,我总结了一个口诀:
「预分频定节奏,重装载定时长」
什么意思?
- 预分频器:决定计数器每跳一次的时间间隔。比如IWDG的预分频器有4、8、16、32、64、128、256这几种。预分频值越大,每个计数周期越长。
- 重装载值:决定计数器从多少开始往下减。这个值越大,总的超时时间越长。
计算公式其实很简单:
// IWDG超时时间计算
// T = (预分频值 × 重装载值) / LSI频率
// 举例:预分频=64,重装载=500,LSI=40kHz
// T = (64 × 500) / 40000 = 0.8秒
// WWDG超时时间计算
// T = (重装载值 - 窗口值) × (预分频值 + 1) / PCLK1频率
我一般这么记:
- 想要时间长?加大预分频或者加大重装载值
- 想要时间短?减小预分频或者减小重装载值
- 但注意:重装载值最大是4095(12位计数器)
避坑指南:
我曾经在项目里把IWDG的重装载值设得太大,导致看门狗超时时间长达十几秒。结果系统死机后要等十几秒才能复位,这在医疗设备里是不可接受的。记住:看门狗超时时间一般设在100ms~2秒之间比较合理。
3.4 STM32CubeMX配置实战
咱们直接上手配置。打开STM32CubeMX,选好芯片型号,按以下步骤操作:
- 找到IWDG:在Pinout & Configuration页面,左侧Categories里找到「IWDG」
- 使能IWDG:勾选「Activated」
- 配置参数:
- 预分频器:我一般选64(对应约1.6ms一个计数周期)
- 重装载值:根据需要的超时时间计算
- 比如要1秒超时:重装载值 = 1 / (64/40000) ≈ 625
- 配置WWDG:同样在左侧找到「WWDG」
- 使能WWDG:勾选「Activated」
- 配置参数:
- 预分频器:可选1、2、4、8
- 窗口值:设置喂狗的最早时间
- 重装载值:设置超时时间
配置完成后,生成代码。在main函数里,你会看到类似这样的初始化代码:
// IWDG初始化
void MX_IWDG_Init(void)
{
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_64;
hiwdg.Init.Reload = 625;
if (HAL_IWDG_Init(&hiwdg) != HAL_OK)
{
Error_Handler();
}
}
// 喂狗函数
HAL_IWDG_Refresh(&hiwdg);
// WWDG初始化
void MX_WWDG_Init(void)
{
hwwdg.Instance = WWDG;
hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
hwwdg.Init.Window = 80;
hwwdg.Init.Counter = 127;
hwwdg.Init.EWIMode = WWDG_EWI_ENABLE;
if (HAL_WWDG_Init(&hwwdg) != HAL_OK)
{
Error_Handler();
}
}
// 喂狗函数
HAL_WWDG_Refresh(&hwwdg);
我的习惯:
在医疗设备里,我通常同时使用IWDG和WWDG。IWDG做最后的「保底」,超时时间设长一点(比如2秒)。WWDG做精细监控,超时时间设短一点(比如200ms),窗口值设在中间位置。这样既能检测程序跑飞,又能检测程序跑得太快。
3.5 实际项目中的喂狗策略
喂狗不是随便找个地方喂一下就行。我见过有人把喂狗放在定时器中断里——那等于没装看门狗。因为中断还能跑,主循环可能早就卡死了。
正确的做法是:
- 喂狗点放在主循环的关键路径上:确保所有关键任务都执行完后才喂狗
- 不要放在中断里:除非你明确知道自己在做什么
- 多个任务时,用标志位控制:每个任务完成后置位,所有任务都完成后才喂狗
举个例子:
// 多任务喂狗示例
uint8_t task1_done = 0;
uint8_t task2_done = 0;
uint8_t task3_done = 0;
void main_loop(void)
{
while(1)
{
// 任务1:传感器采集
sensor_read();
task1_done = 1;
// 任务2:数据处理
data_process();
task2_done = 1;
// 任务3:通信
comm_send();
task3_done = 1;
// 所有任务完成,喂狗
if(task1_done && task2_done && task3_done)
{
HAL_IWDG_Refresh(&hiwdg);
// 重置标志位
task1_done = 0;
task2_done = 0;
task3_done = 0;
}
}
}
这样设计的好处是:任何一个任务卡死,看门狗都会复位系统。而且你能从复位前的标志位状态,快速定位是哪个任务出了问题。
曾经踩过的坑:
我曾经在项目里把喂狗放在ADC转换完成中断里。结果ADC一直正常工作,但主循环里的通信任务早就卡死了。设备看起来在运行,实际上已经「假死」了。从那以后,我再也不在中断里喂狗了。
好了,这一章的内容就到这里。记住:看门狗是医疗设备的「安全带」,一定要系好。下一章咱们聊聊硬件看门狗和软件看门狗的配合使用,那才是真正的「双保险」。