看门狗定时器(WDT)从入门到实战 —— 单片机系统的"守护者"
一、一句话理解
看门狗定时器(Watch Dog Timer,WDT)是单片机的一个硬件组成部分,本质是一个递增(或递减)的定时器。程序正常运行时,它会定期被"喂"(重置);一旦程序跑飞、死循环,没人喂它,它就会超时触发系统复位,让程序重新从起点运行。
通俗类比:就像你设了一个每天早 8 点的闹钟,平时你 7:50 就醒来关掉它,闹钟永远不会响。但有一天你喝醉了睡过头,闹钟就响了——强行把你叫醒。看门狗就是单片机的"闹钟",一切正常就不叫,一出异常就叫。
二、看门狗的前世今生
2.1 为什么需要看门狗
在嵌入式系统中,软件 Bug 永远不可能 100% 测尽。即使代码本身正确,外部干扰也可能让 CPU 执行出错。最常见的故障模式有:
| 故障类型 | 现象 | 软件能否自救 |
|---|---|---|
| 死循环 | 程序卡在while(1){} | ❌ 不能 |
| 指针跑飞 | PC 指针跳到非法地址 | ❌ 不能 |
| 寄存器被改写 | 外设状态异常 | ⚠️ 部分能 |
| 中断丢失 | 关键事件未响应 | ⚠️ 部分能 |
| 时钟故障 | 主时钟停振 | ❌ 不能 |
| 电压跌落 | CPU 处于亚稳态 | ❌ 不能 |
这些故障的共同点是:软件已经无法依靠自身恢复。此时只有"硬件强制复位"这一招——这就是看门狗存在的意义。
2.2 一个真实的工业现场案例
某工厂产线上的 STM32 控制板,运行 3 个月后偶发性"死机",现场人员必须断电重启。复现条件苛刻:高湿度 + 变频器频繁启停。
排查发现:变频器启停瞬间产生强电磁干扰,偶尔让 CPU 取到错误指令进入死循环。未启用看门狗是根因。
启用 IWDG(超时 1s)后,连续运行 2 年未再出现死机。
结论:看门狗是工业产品的"兜底保险",不是可选项,是必选项。
三、核心作用:防止系统跑飞
在工业现场,单片机经常受到以下干扰:
- ⚡电气噪声— 电机、变频器产生的电磁干扰
- 🔌电源故障— 电压骤降、浪涌
- ⛈️静电放电(ESD)— 冲击可能让寄存器翻转
- 🔄软件 Bug— 程序进入死循环或死锁
- 🌡️温度漂移— 时钟频率偏移导致时序异常
- 📡射频干扰(RFI)— 无线设备、手机靠近时
这些干扰会导致程序跑飞、卡死。看门狗的作用就是兜底——当软件层面无法自救时,硬件层面强制复位,让系统恢复运行。
四、三个关键术语
4.1 喂狗(Feed / Kick the Dog)
含义:在程序中主动触发看门狗,计数器被刷新,重新开始计算。
实际操作就是调用一条指令(如WDT_RESET()或写入特定寄存器),告诉看门狗"我还活着"。
喂狗频率分为两种模式:
- 快狗(Fast-Mode):频繁喂狗,适合周期短的任务
- 慢狗(Slow-Mode):较少喂狗,适合初始化等耗时较长的阶段
实际开发中,通常在 WDT 初始化后先设为 Slow-Mode,等周期任务正式运行后再切换为 Fast-Mode。
4.2 杀狗(Disable WDT)
含义:禁用/关闭看门狗。调试阶段常用——你不想程序跑到一半被复位,就先关掉看门狗。
⚠️注意:产品正式发布时务必开启看门狗,杀狗只用于调试。一个常见的"血案"是:调试时关了看门狗,发版时忘了开,量产后死机投诉雪片般飞来。
4.3 咬狗(Dog Biting / Timeout)
含义:看门狗启动后,计数器自动递减。如果在规定时间内没有收到"喂狗"指令,计数器归零,看门狗就会对 CPU 产生复位信号,强制系统重启——俗称"被狗咬了"。
小知识:为什么叫"狗"?——早期工程师把这种"必须定期喂食否则就咬你(复位)"的定时器形象地比作看门狗,名字沿用至今。英文 “Kick the dog” 也是同样的隐喻。
五、四类看门狗
从软硬件控制类型划分,看门狗可分为以下四类:
5.1 硬件看门狗(Hardware WDT)
| 特征 | 说明 |
|---|---|
| 位置 | 独立于 CPU 之外,由专用硬件电路实现 |
| 时钟 | 由独立的时钟驱动,外部无法更改 |
| 复位方式 | 输出信号引脚直接连接 CPU 的 RESET 引脚 |
| 优势 | 即使 CPU 完全死机,硬件看门狗依然有效 |
典型芯片:MAX813、TPS3823、STWD100、SP706S
适用场景:高可靠性工业控制、汽车电子、航空航天等不允许死机的场景。
5.2 软件看门狗(Software WDT)
| 特征 | 说明 |
|---|---|
| 位置 | CPU 内置的一个计数器模块 |
| 时钟 | 由 CPU 本身的时钟决定 |
| 配置 | 计数器初值由软件设置,超时时间可在一定范围内变化 |
| 复位方式 | 通过中断或异常信号触发系统重启 |
适用场景:对时间精度要求不高的通用嵌入式应用。
5.3 独立看门狗(Independent WDT,IWDG)
| 特征 | 说明 |
|---|---|
| 时钟 | 由专用低速时钟驱动(如 LSI 32kHz) |
| 关键优势 | 即使主时钟发生故障,独立看门狗依然有效 |
| 独立性 | 完全独立于主程序运行 |
| 典型实现 | STM32 IWDG |
适用场景:主时钟可能不稳定的场景,如低功耗模式、时钟切换过程中。
5.4 窗口看门狗(Window WDT,WWDG)
| 特征 | 说明 |
|---|---|
| 时钟 | 由 APB1 时钟分频驱动 |
| 独特机制 | 设置了时间窗口——喂狗不能太早也不能太晚,必须在窗口内 |
| 检测范围 | 既能检测程序卡死,也能检测程序跑得太快(异常加速) |
适用场景:对程序执行时间有严格要求的精密控制场景,如电机控制、电力电子。
5.5 四类看门狗横向对比
| 维度 | 硬件 WDT | 软件 WDT | IWDG | WWDG |
|---|---|---|---|---|
| 独立性 | ★★★★★ | ★★ | ★★★★ | ★★★ |
| 主时钟故障有效 | ✓ | ✗ | ✓ | ✗ |
| 时间精度 | 中 | 低 | 中 | 高 |
| 可检测异常加速 | ✗ | ✗ | ✗ | ✓ |
| 成本 | 需外接芯片 | 免费 | 免费 | 免费 |
| 配置灵活度 | 低 | 高 | 中 | 中 |
| 适用场景 | 高可靠 | 通用 | 低功耗/抗主时钟故障 | 精密时间控制 |
六、工作流程
┌─────────────────────────┐ │ 看门狗初始化 │ │ 设置计数器初始值 │ ────────────┬────────────┘ ▼ ┌─────────────────────────┐ │ 计数器开始计数 │ │ 初始值递减 │ └────────────┬────────────┘ ▼ ┌──────────────┐ │ 定时器=0? │ ──┬────────┬── 是 │ │ 否 ▼ ▼ ┌──────────┐ ┌──────────────┐ │ 喂狗! │ │ 计数器继续 │ │ 重置计数 │ │ 递减 │ │ 回到起点 │ └─────────────┘ └────────── │ ▼ ┌──────────────┐ │ 定时器=0? │ └──┬────────┬──┘ 否 │ │ 是 ▼ ▼ (继续计数) ┌──────────────┐ │ 看门狗产生 │ │ 复位信号 │ └──────┬───────┘ ▼ ┌──────────────┐ │ 系统复位 │ │ 程序重新运行 │ └──────────────┘关键步骤解读:
- 初始化阶段:必须先设置计数器初值(决定超时时间),再启动看门狗。一旦启动,无法关闭(部分芯片支持)。
- 运行阶段:计数器自动递减,与主程序并行运行,互不干扰。
- 喂狗判断:每次喂狗,计数器被重装载回初值,重新开始递减。
- 超时触发:一旦计数器归零,立即产生复位信号。这个动作是硬件级别的,不需要 CPU 参与——这也是看门狗可靠性的核心。
七、STM32 看门狗实战详解
STM32 系列单片机内置两类看门狗:IWDG(独立看门狗)和WWDG(窗口看门狗)。下面分别讲解。
7.1 IWDG 独立看门狗
7.1.1 关键寄存器
| 寄存器 | 全称 | 作用 |
|---|---|---|
| KR | Key Register | 键寄存器。写0xCCCC启动 IWDG;写0xAAAA喂狗;写0x5555允许修改 PR/RLR |
| PR | Prescaler Register | 预分频寄存器。决定分频系数 /4、/8、/16…/256 |
| RLR | Reload Register | 重装载寄存器。12 位,0~4095,决定计数器初值 |
| SR | Status Register | 状态寄存器。PVU(PR 是否可改)、RVU(RLR 是否可改) |
7.1.2 超时时间计算
T_timeout = 4 × 2^(PR+2) × (RLR+1) / f_LSI其中:
PR= 0~6,对应分频系数 /4、/8、/16、/32、/64、/128、/256RLR= 0~4095f_LSI≈ 32kHz(STM32 内部低速 RC 振荡器,误差 ±5%)
举例:PR=4(分频 /64),RLR=624,f_LSI=32kHz
T = 4 × 2^6 × 625 / 32000 = 4 × 64 × 625 / 32000 = 5000ms = 5s7.1.3 HAL 库代码示例
#include"stm32f1xx_hal.h"IWDG_HandleTypeDef hiwdg;/* 初始化 IWDG,超时 5s */voidMX_IWDG_Init(void){hiwdg.Instance=IWDG;hiwdg.Init.Prescaler=IWDG_PRESCALER_64;// PR=4,分频 /64hiwdg.Init.Reload=624;// RLR=624hiwdg.Init.Window=IWDG_WINDOW_DISABLE;// 禁用窗口模式(普通 IWDG)if(HAL_IWDG_Init(&hiwdg)!=HAL_OK){Error_Handler();}}/* 喂狗 */voidWDT_Feed(void){HAL_IWDG_Refresh(&hiwdg);// 等价于写 KR = 0xAAAA}/* 主循环 */intmain(void){HAL_Init();SystemClock_Config();MX_IWDG_Init();while(1){task_sensor_read();WDT_Feed();// 喂狗task_can_send();WDT_Feed();// 喂狗task_uart_handle();WDT_Feed();// 喂狗}}7.1.4 寄存器级裸机操作
/* 启动并配置 IWDG(无 HAL 库) */voidIWDG_Init_BareMetal(void){/* 1. 启动 IWDG:写 KR = 0xCCCC */IWDG->KR=0xCCCC;/* 2. 允许修改 PR、RLR:写 KR = 0x5555 */IWDG->KR=0x5555;/* 3. 设置预分频:PR=4 → 分频 /64 */IWDG->PR=0x04;/* 4. 设置重装载值:RLR=624 → 5s 超时 */IWDG->RLR=624;/* 5. 重装载计数器:写 KR = 0xAAAA */IWDG->KR=0xAAAA;}/* 喂狗(重装载) */#defineIWDG_Feed()(IWDG->KR=0xAAAA)7.2 WWDG 窗口看门狗
7.2.1 与 IWDG 的核心区别
| 维度 | IWDG | WWDG |
|---|---|---|
| 时钟源 | LSI(32kHz 独立) | APB1(系统时钟分频) |
| 计数器位数 | 12 位 | 7 位 |
| 喂狗限制 | 任意时刻 | 必须在窗口内 |
| 检测异常加速 | ✗ | ✓ |
| 复位前中断 | ✗ | ✓(可触发早期唤醒中断 EWI) |
| 主时钟故障有效 | ✓ | ✗ |
7.2.2 窗口机制详解
WWDG 的计数器是 7 位(0~0x7F),递减计数。关键参数:
- 窗口上限值 W:写入
CFR寄存器的W[6:0],决定"最早可喂狗时刻" - 超时阈值 0x40:计数器递减到
0x3F时立即复位(即T[6]由 1→0 触发)
喂狗规则:
0x7F > 计数值 > W:禁止喂狗(喂了就复位)W ≥ 计数值 > 0x40:允许喂狗(喂狗重装载为 0x7F)计数值 = 0x3F:自动复位
为什么禁止"早喂"?——如果程序异常加速(比如跳过了某些耗时的初始化),会过早到达喂狗点。窗口机制可以捕捉到这种异常。
7.2.3 HAL 库代码示例
WWDG_HandleTypeDef hwwdg;voidMX_WWDG_Init(void){hwwdg.Instance=WWDG;hwwdg.Init.Prescaler=WWDG_PRESCALER_8;// 分频hwwdg.Init.Window=0x50;// 窗口值 Whwwdg.Init.Counter=0x7F;// 计数器初值hwwdg.Init.EWIMode=WWDG_EWI_ENABLE;// 使能早期唤醒中断if(HAL_WWDG_Init(&hwwdg)!=HAL_OK){Error_Handler();}}/* 早期唤醒中断回调:复位前的最后机会 */voidHAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef*hwwdg){/* 这里可以做紧急保存:写关键数据到 Flash/FRAM *//* 注意:到这里已经离复位只有几十个时钟周期,不能做长操作 */Save_Critical_Data();}/* 主循环喂狗(必须在窗口内) */voidWWDG_Feed_InWindow(void){/* 读 SR 状态判断当前是否在窗口内 */if((hwwdg.Instance->CR&0x7F)<=hwwdg.Init.Window){HAL_WWDG_Refresh(&hwwdg);}}八、两种运行状态
8.1 正常运行
程序按预期执行,在关键位置分散放置"喂狗指令"(如WDT_RESET())。每隔一段时间,CPU 主动喂狗,计数器重置,看门狗安静待命。
// 示例:正常喂狗流程voidmain_loop(void){WDT_Init(5000);// 初始化看门狗,超时时间 5 秒while(1){task_1();// 执行任务 1WDT_Reset();// 喂狗 ✅task_2();// 执行任务 2WDT_Reset();// 喂狗 ✅task_3();// 执行任务 3WDT_Reset();// 喂狗 ✅}}8.2 异常运行(被狗咬)
程序因干扰进入死循环,喂狗指令所在的代码段永远执行不到。看门狗等不到喂狗信号,计数器归零,自动产生复位信号,单片机从程序存储器的起始位置重新执行。
// 示例:死循环导致被咬voidmain_loop(void){WDT_Init(5000);// 初始化看门狗,超时时间 5 秒while(1){task_1();// 执行任务 1WDT_Reset();// 喂狗 ✅// task_2() 中发生死循环!task_2();// ← 卡在这里,永远出不来WDT_Reset();// ❌ 永远执行不到!task_3();WDT_Reset();}// 5 秒后 → 看门狗超时 → 系统自动复位 → 程序重新从 main() 开始}九、喂狗策略与常见陷阱
9.1 喂狗位置黄金法则
| ✅ 推荐位置 | ❌ 禁止位置 |
|---|---|
| 主循环每次任务结束后 | 中断服务程序(ISR)内 |
| 关键状态机切换时 | 阻塞型延时函数内 |
| 长任务分段完成点 | 死循环while(1){}内 |
while(1)顶部 | 看门狗初始化前 |
9.2 常见陷阱与避坑
陷阱 1:中断里喂狗
错误代码:
voidTIM2_IRQHandler(void){// 1ms 定时中断WDT_Feed();// ❌ 错!主程序死循环时中断仍会触发,看门狗永远不会咬}问题:即使主程序死循环,定时中断仍会正常触发并喂狗。看门狗形同虚设。
正确做法:喂狗放在主循环。中断只置标志位,主循环检查标志位再喂狗。
陷阱 2:超时时间设置过短
错误:超时设为 10ms,但某个 Flash 写操作就要 30ms。结果:每次写 Flash 都触发复位。
正确:超时时间应 ≥ 最长单任务时间 × 1.5 倍冗余。
陷阱 3:超时时间设置过长
错误:超时设为 10s。系统死机后要等 10s 才重启,用户体验极差。
正确:通用场景 100ms~1s,工控场景 1~5s,初始化阶段用慢狗(10s+)。
陷阱 4:单一喂狗点
错误:整个主循环只在末尾喂一次狗。如果某个任务卡住,整段循环都跑不完。
正确:每个长任务后都喂一次,分散喂狗点。
陷阱 5:调试时关狗,发版忘了开
典型血案:开发阶段#define WDT_ENABLE 0,量产代码忘了改回 1。
对策:用编译器宏强制检查——Release 构建配置中WDT_ENABLE必须为 1,否则编译报错。
9.3 多任务系统下的喂狗策略
在 RTOS(如 FreeRTOS)环境下,所有任务都"活着"才能喂狗,避免某个任务死掉却被其他任务喂狗掩盖。
/* FreeRTOS:监控任务喂狗策略 */voidTask_Watchdog(void*pvParameters){for(;;){/* 检查所有任务是否都"打卡"过 */if(task_sensor_alive&&task_comm_alive&&task_log_alive){HAL_IWDG_Refresh(&hiwdg);/* 清除打卡标志,下一轮重新检查 */task_sensor_alive=0;task_comm_alive=0;task_log_alive=0;}vTaskDelay(100);// 100ms 检查一次}}十、看门狗测试方法
10.1 故意触发测试
在产品验证阶段,必须证明看门狗真的有效。常见做法:
/* 测试代码:故意进入死循环验证看门狗 */voidTest_WDT(void){#ifdefWDT_TEST_MODEprintf("WDT test: entering infinite loop...\r\n");while(1){/* 故意什么都不做,等看门狗咬 */}#endif}验证标准:
- 触发死循环后,超时时间内系统自动复位
- 复位后能正常启动(看门狗不能"咬死"系统)
- 串口/日志能记录"上次复位原因 = IWDG_RESET"
10.2 复位原因识别
STM32 通过RCC->CSR寄存器可识别复位来源:
voidCheck_Reset_Source(void){if(__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)){printf("⚠️ Reset by IWDG! (Watchdog bite)\r\n");/* 可记录到 Flash 供后续分析 */}if(__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)){printf("⚠️ Reset by WWDG! (Window watchdog)\r\n");}if(__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST)){printf("Reset by NRST pin (power-on/manual)\r\n");}/* 清除复位标志 */__HAL_RCC_CLEAR_RESET_FLAGS();}十一、多看门狗组合策略
11.1 内部 IWDG + 外部硬件 WDT
最高可靠性的场景(汽车电子、医疗、工业安全)通常组合使用:
┌──────────────┐ │ STM32 CPU │──┬── 内部 IWDG(一级保护,超时 1s) │ │ │ └──────────────┘ └── 外部 MAX813(二级保护,超时 2s) │ ▼ 直接拉低 NRST 引脚为什么需要二级?——如果 STM32 内部时钟树完全故障(LSI 也挂了),IWDG 不会工作。外部 WDT 用独立晶振,能捕捉到这种极端情况。
11.2 IWDG + WWDG 互补
- IWDG:主时钟故障兜底
- WWDG:精密时间监控(防止异常加速)
两者同时启用,覆盖"卡死"和"加速"两种异常。
十二、实际工程案例
12.1 工业控制:PLC 远程 IO 模块
场景:STM32F4 + Modbus RTU + 8 路模拟量输出,部署在变电站。
方案:
- IWDG 超时 2s(主循环 100ms × 20 倍冗余)
- 主循环每次完成 Modbus 处理后喂狗
- 长操作(如 EEPROM 写)前临时切到慢狗
- 复位原因写入 EEPROM,维护人员可读取"死机次数"
12.2 汽车电子:TCU 程序烧录器
场景:DAM-C3241 通过 CANFD 给汽车 TCU 烧录程序,烧录过程不能中断。
方案:
- WWDG 监控烧录时序(每个 CAN 帧 100ms 间隔,窗口 50~150ms)
- IWDG 兜底保护(超时 5s,覆盖 WWDG 漏检场景)
- 烧录关键步骤在 Flash 备份"断点",复位后能续烧
12.3 光伏监测:DAM-3053DC 数据采集
场景:野外光伏面板电压电流监测,无人值守,必须自恢复。
方案:
- IWDG 超时 5s(采集周期 1s × 5 倍冗余)
- DTU-1101 无线传输失败时不喂狗,触发复位重连
- 复位计数器记录"通信故障次数",超阈值进入低功耗维护模式
十三、开发建议
| 场景 | 建议 |
|---|---|
| 调试阶段 | 先关闭看门狗(杀狗),方便排查问题 |
| 产品发布 | 必须开启看门狗,这是最后一道防线 |
| 初始化阶段 | 使用慢狗模式,给初始化留足时间 |
| 主循环阶段 | 切换为快狗模式,频繁喂狗 |
| 关键代码段 | 在重要任务完成后立即喂狗 |
| 中断服务程序 | 避免在 ISR 中喂狗,防止掩盖中断异常 |
| 超时时间设置 | ≥ 最长单任务 × 1.5 倍冗余 |
| 复位原因识别 | 启动时检查RCC->CSR,记录复位来源 |
| 量产测试 | 必须验证 WDT 真的能触发复位 |
| 多任务系统 | 用"任务打卡"机制,所有任务都活着才喂狗 |
附录 A:常用 MCU 看门狗对比
| MCU | 内置 WDT 类型 | 独立时钟 | 窗口模式 | 备注 |
|---|---|---|---|---|
| STM32F1/F4 | IWDG + WWDG | LSI 32kHz | ✓(WWDG) | 主流工业级 |
| STM32G0/G4 | IWDG + WWDG | LSI 32kHz | ✓(WWDG) | 新一代高性能 |
| STM32L4 | IWDG + WWDG | LSI 32kHz | ✓(WWDG) | 低功耗 |
| ESP32 | TWDT + IWDT | RTC 时钟 | ✗ | 双核,分任务看门狗 |
| Arduino AVR | 内置 WDT | WDTON fuse | ✗ | 8 位机,2s 上限 |
| NXP LPC1768 | WWDT | 内部 RC | ✓ | 类似 STM32 WWDG |
| GD32F1/F3 | FWDGT + WWDGT | LSI 40kHz | ✓ | 国产 STM32 兼容 |
| HC32F460 | WDT | LSI 32kHz | ✗ | 国产高性能 |
附录 B:术语速查表
| 术语 | 英文 | 含义 |
|---|---|---|
| 喂狗 | Feed / Kick | 重置计数器,告诉 WDT “我还活着” |
| 杀狗 | Disable | 关闭看门狗,仅调试用 |
| 咬狗 | Bite / Timeout | 计数器归零触发复位 |
| 快狗 | Fast-Mode | 短周期频繁喂狗 |
| 慢狗 | Slow-Mode | 长周期少喂狗,适合初始化 |
| IWDG | Independent Watchdog | 独立看门狗,LSI 驱动 |
| WWDG | Window Watchdog | 窗口看门狗,APB1 驱动 |
| EWI | Early Wakeup Interrupt | 早期唤醒中断,复位前最后机会 |
| LSI | Low-Speed Internal | 内部低速 RC 振荡器,约 32kHz |
| KR | Key Register | 键寄存器(IWDG 控制) |
| RLR | Reload Register | 重装载寄存器(IWDG 初值) |
附录 C:参考资源
- STM32F103 参考手册 RM0008(IWDG/WWDG 章节)
- STM32 HAL 库用户手册 UM1850
- ST 应用笔记 AN4065:Watchdog usage on STM32
- 原文:知乎专栏 https://zhuanlan.zhihu.com/p/654005577