嵌入式系统看门狗详解——以 STM32 为例

一、什么是看门狗

1.1 概念定义

看门狗(Watchdog Timer,简称 WDT)是嵌入式系统中的一种硬件定时器模块,它的核心思想非常简单:在一个固定时间内,软件必须"喂狗"(即重新初始化定时器),否则看门狗超时后会触发系统复位,从而让程序从死机或跑飞状态中恢复过来。

可以把它想象成家里养的一只"看门狗":

你每隔一段时间就要去喂它一次(喂狗/喂狗操作)。如果你忘了喂它,它就会"叫起来"(触发复位),提醒你出问题了。

1.2 看门狗的硬件实现方式

实现方式 说明 特点
独立看门狗定时器(IWDG) 由内部低速振荡器(LSI)驱动 不受主时钟影响,低功耗下仍可用
窗口看门狗定时器(WWDG) 有喂狗时间窗口限制 防止过早喂狗,能检测到执行时间异常
外部看门狗芯片 独立 IC(如 MAX809、IMP706) 功能丰富,可配置复位电压等

1.3 核心概念表

概念 说明
预分频器(Prescaler) 对时钟源进行分频,决定计数器的计数速度
重装载寄存器(Reload) 设定计数器的初始值,喂狗时写入此值
当前计数值(Counter) 当前计数器的实时值,递减计数
溢出/超时 计数器减到 0 时触发复位或中断
喂狗(Feed/Refresh) 向重装载寄存器写入值,重新初始化计数器

二、看门狗的作用

2.1 自动恢复系统故障

嵌入式系统运行环境复杂,可能因电磁干扰、电源波动、宇宙射线等原因导致程序跑飞或进入死循环。看门狗能在程序异常时自动复位系统,无需人工干预。

2.2 检测软件死锁

当程序因死锁、优先级反转等原因停止前进一步时,看门狗超时复位可以恢复系统运行。

2.3 保证系统可靠性

在工业控制、汽车电子、医疗设备等对可靠性要求极高的场景中,看门狗几乎是必选项。

2.4 窗口看门狗的特殊作用

窗口看门狗(WWDG)不仅检测"不喂狗",还能检测"过早喂狗"------如果软件在窗口打开之前就喂狗,说明程序执行时间可能异常(比如跳过了某些关键代码段),WWDG 同样会触发复位。


三、STM32 看门狗类型概览

STM32 系列微控制器通常提供两种看门狗:

类型 全称 时钟源 特点
IWDG Independent Watchdog 内部 40kHz 低速 RC 振荡器(LSI) 独立运行,不受主时钟故障影响,配置简单
WWDG Window Watchdog 系统时钟(PCLK)分频 有喂狗窗口限制,能检测执行时间异常

关键区别 :IWDG 只要在规定时间内喂狗即可;WWDG 要求在特定时间窗口内喂狗------太早或太晚都会触发复位。


四、IWDG(独立看门狗)使用流程

4.1 使用步骤总览

复制代码
1. 开启 IWDG 时钟
2. 设置预分频器和重装载值(设定超时时间)
3. 重载计数器(第一次喂狗)
4. 启用 IWDG
5. 主循环中定期喂狗

4.2 超时时间计算公式

复制代码
超时时间 = (预分频系数 x 重装载值) / LSI频率

其中:
- LSI 频率典型值 = 40 kHz
- 预分频系数可选:4 / 8 / 16 / 32 / 64 / 128 / 256
- 重装载值范围:0 ~ 0x0FFF(12位)
- 最大超时时间 = (256 x 4095) / 40000 = 262 ms

4.3 完整代码示例(HAL 库)

复制代码
/**
 * IWDG 初始化函数
 * 配置独立看门狗,设置预分频和重装载值
 */
void MX_IWDG_Init(void)
{
    hiwdg.Instance = IWDG;
    // 预分频器 = 256,重装载值 = 0x0500 (1280)
    hiwdg.Init.Prescaler = IWDG_PRESCALER_256;
    hiwdg.Init.Reload = 0x0500;

    if (HAL_IWDG_Init(&hiwdg) != HAL_OK)
    {
        // 初始化错误处理
        Error_Handler();
    }
}

/**
 * 主函数示例
 */
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_IWDG_Init();

    // 第一次喂狗(必须在使用前重载计数器)
    HAL_IWDG_Refresh(&hiwdg);
    // 启动看门狗
    HAL_IWDG_Start(&hiwdg);

    while (1)
    {
        // === 正常业务流程 ===
        Process_Sensor_Data();
        Process_Comm_Packet();
        Update_Display();

        // === 喂狗:在超时前定期刷新 ===
        HAL_IWDG_Refresh(&hiwdg);

        // 延时函数(可选,用于控制喂狗频率)
        HAL_Delay(100);
    }
}

4.4 使用注意事项

  • 喂狗位置:喂狗代码应放在主循环的"安全区域",确保每次循环都能执行到。不要放在可能长时间阻塞的代码段中。
  • 禁止在调试时关闭:调试阶段可以暂时关闭看门狗以便单步调试,但发布版本必须开启。
  • LSI 精度:内部 RC 振荡器精度约为 +/-5%~10%,对超时精度要求高的场景需用外部晶振驱动。
  • 不可在中断中喂狗:建议在主循环中喂狗,避免中断优先级问题导致喂狗不及时。

五、WWDG(窗口看门狗)使用流程

5.1 与 IWDG 的核心区别

对比项 IWDG WWDG
喂狗时机 超时前任意时刻 必须在窗口范围内
过早喂狗 不允许(会触发复位) 不允许(会触发复位)
检测能力 仅检测"不喂狗" 检测"不喂狗"+"执行时间异常"
适用场景 通用故障恢复 需要精确时序控制的场景

5.2 窗口概念图解

复制代码
计数器值变化过程:

  127 (最大值)  |------------------- 窗口上限 (T7)
                |
                |  合法喂狗窗口
                |  (只能在窗口内喂狗)
                |
  64  (中间值)  |--------[ 窗口 ]--------
                |
                |  过早喂狗 -> 复位!
                |
   0  (下溢)    |------------------- 窗口下限 (T6)
                |
                v
            提前喂狗 -> 复位!

窗口规则:计数器从窗口上限递减到窗口下限的过程中,只能在这段区间内喂狗。太早(计数器 > 窗口上限)或太晚(计数器 < 窗口下限即下溢)都会触发复位。

5.3 完整代码示例(HAL 库)

复制代码
/**
 * WWDG 初始化函数
 * 窗口上限 = 127, 窗口下限 = 64
 * 预分频器 = 8, 超时约 1.024 ms
 */
void MX_WWDG_Init(void)
{
    hwwdg.Instance = WWDG;
    // 预分频器 = 8
    hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
    // 窗口值 = 0x7F (127),计数器初始值也设为 127
    hwwdg.Init.Window = 0x7F;
    hwwdg.Init.Counter = 0x7F;
    // 使能窗口比较中断(可选,可在窗口到达前软中断喂狗)
    hwwdg.Init.EWDModes = WWDG_EWDI_MODE_ENABLE;

    if (HAL_WWDG_Init(&hwwdg) != HAL_OK)
    {
        Error_Handler();
    }

    // 配置窗口比较中断(在计数器到达窗口值时产生中断)
    HAL_WWDG_Start_IT(&hwwdg);
}

/**
 * 窗口比较回调函数(在计数器到达窗口值时调用)
 * 这是喂狗的最佳时机------在窗口打开的瞬间喂狗
 */
void HAL_WWDG_WindowCallback(WWDG_HandleTypeDef *hwwdg)
{
    // 在此处执行关键任务后喂狗
    HAL_WWDG_Refresh(&hwwdg);
}

/**
 * 主函数示例
 */
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_WWDG_Init();

    // 启动 WWDG(含中断模式)
    HAL_WWDG_Start(&hwwdg);

    while (1)
    {
        // 注意:WWDG 的喂狗时机由硬件窗口控制
        // 不要在 while(1) 中随意喂狗!
        // 应在窗口比较中断回调中喂狗

        // 正常业务流程
        Process_Task_1();
        Process_Task_2();

        // 禁止在此处调用 HAL_WWDG_Refresh()
        // 这会因"过早喂狗"触发复位!
    }
}

六、IWDG 与 WWDG 实战对比

6.1 场景选择指南

场景 推荐类型 原因
通用系统保护 IWDG 配置简单,只需定时喂狗
实时任务调度保护 WWDG 能检测任务执行时间是否超标
低功耗场景 IWDG 可由 LSI 驱动,休眠时仍工作
安全关键系统 IWDG + WWDG 双看门狗 双重保障,可靠性最高

6.2 双看门狗架构示例

复制代码
/**
 * 双看门狗架构:IWDG 做底层保护,WWDG 做应用层监控
 *
 * 架构说明:
 * - IWDG:超时时间较长(如 200ms),作为最后防线
 * - WWDG:超时时间较短(如 10ms),监控应用层任务调度
 * - 应用层每完成一轮任务调度后喂 IWDG
 * - WWDG 在中断中按窗口喂狗
 */

void Dual_Watchdog_Init(void)
{
    // 1. 初始化 WWDG(短周期,监控任务调度)
    MX_WWDG_Init();
    HAL_WWDG_Start_IT(&hwwdg);

    // 2. 初始化 IWDG(长周期,最终防线)
    MX_IWDG_Init();
    HAL_IWDG_Refresh(&hiwdg);
    HAL_IWDG_Start(&hiwdg);
}

void Application_Task_Routine(void)
{
    // 执行应用任务
    Task_A();
    Task_B();
    Task_C();

    // 一轮任务完成后喂 IWDG(最终防线)
    HAL_IWDG_Refresh(&hiwdg);
}

// WWDG 窗口中断回调------确保在窗口内喂狗
void HAL_WWDG_WindowCallback(WWDG_HandleTypeDef *hwwdg)
{
    HAL_WWDG_Refresh(&hwwdg);  // 窗口内喂狗
}

七、常见陷阱与最佳实践

7.1 常见陷阱

陷阱 描述 解决方案
喂狗位置错误 喂狗代码放在可能阻塞的区域 放在主循环末尾或独立任务中
关闭调试信息后忘开看门狗 调试时关闭 WDG,发布时忘记重新开启 用宏定义统一控制:#ifdef DEBUG_DISABLE_WDG
中断中喂狗 中断优先级问题导致喂狗不及时 主循环中喂狗,中断中只做标志位置位
预分频计算错误 超时时间算错导致频繁复位 用表格或计算器预先算好参数
WWDG 过早喂狗 在 while(1) 中直接调用刷新函数 使用中断模式,在窗口回调中喂狗
看门狗初始化后未第一次喂狗 初始化完成后必须立即重载计数器 初始化后立即调用一次 Refresh

7.2 最佳实践清单

复制代码
[ ] 1. 用宏定义统一控制看门狗的开关,方便调试
[ ] 2. 喂狗代码放在主循环最底部,确保前面代码异常时不会喂狗
[ ] 3. 关键任务完成后设置标志位,主循环检查标志位后再喂狗
[ ] 4. 系统启动完成后延迟一段时间再开启看门狗(避免启动过程被复位)
[ ] 5. 低功耗模式下确保看门狗仍在工作(IWDG 由 LSI 驱动,休眠可用)
[ ] 6. 在串口调试时打印看门狗触发次数(可通过复位源寄存器读取)
[ ] 7. 生产环境建议启用双看门狗架构
[ ] 8. 代码审查时将"喂狗逻辑"作为必查项

7.3 调试技巧:如何判断是否由看门狗复位

复制代码
/**
 * 读取复位源寄存器,判断是否由看门狗触发复位
 */
void Check_Reset_Source(void)
{
    uint32_t reset_flag = LL_RCC_GetResetFlags();

    if (reset_flag & LL_RCC_GET_ICSR_WWDG1F_FLAG)
    {
        // 窗口看门狗复位
        printf("Reset source: WWDG1\n");
        LL_RCC_ClearRESETFlags(LL_RCC_GET_ICSR_WWDG1F_FLAG);
    }

    if (reset_flag & LL_RCC_GET_ICSR_IWDG1F_FLAG)
    {
        // 独立看门狗复位
        printf("Reset source: IWDG1\n");
        LL_RCC_ClearRESETFlags(LL_RCC_GET_ICSR_IWDG1F_FLAG);
    }

    if (reset_flag & LL_RCC_GET_ICSR_SFTRSTF_FLAG)
    {
        // 软件复位
        printf("Reset source: Software\n");
    }

    if (reset_flag & LL_RCC_GET_ICSR_PORSFTRSTF_FLAG)
    {
        // 电源上电/掉电复位
        printf("Reset source: Power\n");
    }
}

八、总结

要点 总结
看门狗是什么 一个硬件定时器,软件必须定期"喂狗",否则触发系统复位
为什么需要 自动恢复程序跑飞/死锁,提高系统可靠性和自恢复能力
STM32 提供什么 IWDG(独立看门狗)+ WWDG(窗口看门狗)两种
IWDG 怎么用 初始化 -> 设置分频和重载值 -> 启动 -> 主循环中定期喂狗
WWDG 怎么用 初始化 -> 设置窗口值 -> 以中断模式启动 -> 在窗口回调中喂狗
核心注意事项 喂狗位置、调试开关控制、LSI精度、双看门狗架构、复位源检测

一句话总结:看门狗是嵌入式系统的"最后一道防线"------它不能阻止程序出错,但能在程序出错后让系统自动恢复,让产品在现场少出"死机后需要断电重启"的尴尬状况。

相关推荐
恒锐丰科技林技术员1 小时前
EG2113D 高压半桥驱动芯片:高压功率变换的实用驱动方案
经验分享·嵌入式硬件·硬件工程
石马马户2 小时前
传感器的中断 Latch、中断状态寄存器、中断读取与保持时间
stm32·单片机·嵌入式硬件
fly_sunnn12 小时前
STM32-GPIO
stm32·单片机·嵌入式硬件
宵时待雨16 小时前
STM32基础1:嵌入式历史与生态
stm32·单片机·嵌入式硬件
zhangzhangkeji16 小时前
stm32 14-1 WDG 看门狗:理论部分
stm32
不会代码的小猴16 小时前
启动文件
stm32·嵌入式硬件
深圳老胡16 小时前
STM32CubeMX 生成 CMake 工程,用 VSCode 编译与调试的完整流程
笔记·stm32·单片机·代码规范
2401_8628808217 小时前
iMX6ULL 驱动工程搭建 + LED 灯驱动
c语言·驱动开发·嵌入式硬件
hahaha601617 小时前
FPGA+ARM实现AWB的全流程
人工智能·嵌入式硬件·算法·计算机视觉·fpga开发