一、什么是看门狗
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精度、双看门狗架构、复位源检测 |
一句话总结:看门狗是嵌入式系统的"最后一道防线"------它不能阻止程序出错,但能在程序出错后让系统自动恢复,让产品在现场少出"死机后需要断电重启"的尴尬状况。