4. IWDG实战:CubeMX配置与HAL库喂狗函数

好,咱们直接进入正题。看门狗这东西,说白了就是给单片机请了个「监工」。你程序跑得好好的,它不吭声;一旦你死机了、卡死了,它二话不说直接给你来个硬件复位。

在医疗制氧机里,这个「监工」是必须的。你想啊,病人正吸着氧呢,主控芯片突然死机了——电磁阀不动作了,氧浓度失控了,这可不是闹着玩的。所以独立看门狗(IWDG)是底线中的底线。

4.1 为什么选IWDG而不是WWDG?

STM32里其实有两个看门狗:IWDG(独立看门狗)和WWDG(窗口看门狗)。我个人习惯,在医疗设备里首选IWDG。原因很简单:它用的是独立的40kHz内部RC振荡器,就算主时钟挂了,它照样能工作。

WWDG虽然也能防死锁,但它依赖系统时钟。万一主时钟出问题,WWDG也跟着罢工了。这在医疗设备里是不可接受的。

核心原则:IWDG是最后一道防线,它的时钟源必须独立于主系统。

4.2 CubeMX配置步骤

咱们一步步来。打开CubeMX,选好你的芯片型号(我以STM32F103为例)。

  1. 找到IWDG外设:在Pinout & Configuration标签页里,左侧列表找到「IWDG」。
  2. 激活它:勾选「Activated」。就这么简单,硬件模块就使能了。
  3. 配置预分频器和重装载值:这是关键。

看下面这个表格,是我常用的配置组合:

预分频器(PR) 分频系数 LSI时钟(40kHz)下的计数频率 最大超时时间(12位计数器)
0 4 10kHz 409.6ms
1 8 5kHz 819.2ms
2 16 2.5kHz 1.638s
3 32 1.25kHz 3.276s
4 64 625Hz 6.553s
5 128 312.5Hz 13.107s
6 256 156.25Hz 26.214s

我在制氧机项目里,一般选预分频器为4(PR=2),重装载值设为4095。这样超时时间大约是1.638秒。为什么选这个值?

嗯,这里有个经验:超时时间要大于主循环最长执行时间的2倍。制氧机的主循环里要处理传感器采集、PID运算、电磁阀控制、通信等任务,最坏情况下大概600ms。1.638秒的窗口,留了足够的余量,又不会太长导致死机后复位太慢。

小技巧:重装载值最大是4095(12位计数器)。如果你需要更长的超时时间,就加大预分频器。但注意,预分频器越大,计数精度越低。我一般不超过PR=4。

4.3 HAL库喂狗函数:HAL_IWDG_Refresh

配置好之后,代码里怎么用?HAL库已经封装好了,就一个函数:

/* 喂狗操作 */
HAL_IWDG_Refresh(&hiwdg);

就这么一行。每次调用,就把计数器重置为初始值,防止看门狗超时复位。

但问题来了:在哪里喂狗?

很多新手喜欢在main函数的while(1)循环开头就喂狗。这其实是个坏习惯。你想想看,如果程序在循环中间某个地方卡死了,但每次循环开头都喂了狗,那看门狗永远不会复位——因为它每次都被重置了。

我曾经在一个项目里就吃过这个亏。当时调试一个传感器驱动,I2C总线偶尔会锁死。但我在循环开头喂狗,导致锁死后看门狗不动作,设备一直卡在I2C等待里。后来花了整整两天才定位到问题。

正确的做法是:在关键任务完成后喂狗。比如:

void main_loop(void)
{
    while(1)
    {
        /* 任务1:读取传感器数据 */
        if(read_sensors() != HAL_OK)
        {
            /* 错误处理,但不喂狗 */
            error_handler();
        }
        
        /* 任务2:PID运算 */
        pid_calculate();
        
        /* 任务3:控制电磁阀 */
        control_valves();
        
        /* 所有关键任务完成,喂狗 */
        HAL_IWDG_Refresh(&hiwdg);
        
        /* 任务4:通信(非关键,即使卡住也不影响核心功能) */
        communication_task();
    }
}

看到了吗?我把喂狗放在三个核心任务之后。这样,只要任何一个核心任务卡死,喂狗就不会执行,看门狗就会在1.638秒后复位系统。

重要警告:不要在中断服务函数里喂狗!中断里喂狗会掩盖主循环的死锁问题。我曾经见过一个工程师在定时器中断里喂狗,结果主循环死锁了,中断还在跑,看门狗永远不复位——这等于把看门狗废了。

4.4 实战中的避坑指南

说几个我踩过的坑:

  • 坑一:初始化顺序。IWDG必须在系统时钟初始化之后、外设初始化之前配置。因为IWDG一旦使能,就不能再修改预分频器和重装载值了。我习惯在MX_IWDG_Init()里配置好,后面不动它。
  • 坑二:调试时关掉IWDG。调试模式下,如果你单步执行,看门狗会超时复位。可以在CubeMX里勾选「IWDG in Debug mode」选项,或者调试时暂时注释掉喂狗代码。我一般会在调试版本里加个宏控制:
#ifndef DEBUG_MODE
    HAL_IWDG_Refresh(&hiwdg);
#endif
  • 坑三:低功耗模式。如果制氧机需要进入待机模式省电,IWDG在待机模式下仍然工作。进入待机前要确保最后一次喂狗的时间点,避免待机期间被复位。我一般会在进入待机前先喂一次狗,然后立即进入待机。

4.5 验证看门狗是否生效

配置完了,怎么验证?很简单:写个死循环,看它会不会复位。

void test_watchdog(void)
{
    uint32_t count = 0;
    
    while(1)
    {
        count++;
        
        if(count < 1000000)
        {
            /* 正常喂狗 */
            HAL_IWDG_Refresh(&hiwdg);
        }
        else
        {
            /* 模拟死锁:停止喂狗 */
            /* 什么也不做,等待看门狗复位 */
            while(1);
        }
    }
}

用示波器测量复位引脚,你会看到一个低脉冲——那就是看门狗在干活。如果没看到,检查你的配置和喂狗位置。

好了,IWDG的实战配置就这些。下一节咱们聊聊软件防死锁的设计思路,那才是真正考验架构能力的地方。