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,选好芯片型号,按以下步骤操作:

  1. 找到IWDG:在Pinout & Configuration页面,左侧Categories里找到「IWDG」
  2. 使能IWDG:勾选「Activated」
  3. 配置参数
    • 预分频器:我一般选64(对应约1.6ms一个计数周期)
    • 重装载值:根据需要的超时时间计算
    • 比如要1秒超时:重装载值 = 1 / (64/40000) ≈ 625
  4. 配置WWDG:同样在左侧找到「WWDG」
  5. 使能WWDG:勾选「Activated」
  6. 配置参数
    • 预分频器:可选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一直正常工作,但主循环里的通信任务早就卡死了。设备看起来在运行,实际上已经「假死」了。从那以后,我再也不在中断里喂狗了。

好了,这一章的内容就到这里。记住:看门狗是医疗设备的「安全带」,一定要系好。下一章咱们聊聊硬件看门狗和软件看门狗的配合使用,那才是真正的「双保险」。