STM32 双看门狗实战:IWDG 防卡死,WWDG 管节奏
STM32 双看门狗实战:IWDG 防卡死,WWDG 管节奏
做 demo 和做产品,最大的区别是"有没有人看着"。实验室里的板子死机了,我伸手按一下复位就行;可户外气象站挂在山顶、共享充电宝躺在商场角落、智能门锁嵌在防盗门里——这些设备的卡死,没有任何人能去按复位。嵌入式系统的第一课就是:凡是指望"人来救"的设计,在无人值守场景里都是不合格的。看门狗(Watchdog)就是那个替你按复位键的机制——一段独立的硬件电路,盯着程序的心跳,心跳停了就自动复位重启。
今天在洋桃 1 号开发板(STM32F103C8T6)上把两只看门狗都跑了一遍:demo44_IWDG 和 demo45_WWDG,顺手做了一个复位溯源的小改造——让 OLED 在每次开机时告诉你"上次是怎么死的"。两只狗分工不同:IWDG 防卡死,WWDG 管节奏。演示都来自洋桃配套例程,现象朴素,但往产品方向多想一层,能挖出的东西比 demo 本身多得多——这也是这篇复盘的主线:从"会喂狗"到"会用狗"。
#一、IWDG 官方演示:按住按键 1 秒,自动复位
#硬件现象
烧录 demo44_IWDG,上电后 OLED 显示 "IWDG TEST",程序正常运行。此时按住 KEY1 约 1 秒——模拟主循环被卡住 1 秒——MCU 自动复位,OLED 重新刷出开机画面。整个过程没有任何人工干预,这就是"卡死自动重启"的最小演示:按住按键让主循环停摆,狗饿了就咬,咬了就重启。
#原理:LSI 独立时钟 + 倒数计数
IWDG 的全名叫 Independent Watchdog,"独立"两个字是它的全部灵魂:它的时钟不来自主时钟,而来自 LSI——一颗 40kHz 的内部低速 RC 振荡器。主频再高、主程序再卡、甚至主时钟崩溃,看门狗的倒数永远不停。这个"独立性"决定了它的定位:最后一道防线。
超时时间的计算公式很朴素:
textTout = 预分频系数 × 重装载值 / 40 (LSI = 40kHz,每毫秒 40 个计数)
demo 的参数是预分频 64、重装载 625:64 × 625 / 40 = 1000ms。也就是程序必须每 1 秒至少喂一次狗,超过 1 秒没喂,硬件复位。
#三条铁律
IWDG 的寄存器操作浓缩成三条铁律,对应 KR 寄存器的三个关键值:
c/* IWDG 初始化三步 + 一条喂狗 */
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); // KR = 0x5555:解锁,之后才能改配置
IWDG_SetPrescaler(IWDG_Prescaler_64); // 预分频 64
IWDG_SetReload(625); // 重装载值 625
IWDG_ReloadCounter(); // KR = 0xAAAA:喂狗,计数器回满
IWDG_Enable(); // KR = 0xCCCC:启动,启动后不可关闭
- KR 写 0x5555:解锁。 预分频和重装载寄存器默认写保护,先解锁才能配置。
- KR 写 0xCCCC:启动。 注意启动之后没有任何寄存器操作能关闭它,只能靠复位停下来——硬件在这件事上非常较真,这是刻意的安全设计:防止程序跑飞后把狗"关掉"。
- KR 写 0xAAAA:喂狗。 计数器重新从重装载值倒数,相当于把表拨回满格。
主循环末尾那一行 IWDG_ReloadCounter() 就是喂狗的全部代码。至此 demo 的机制很清楚了:主循环每圈喂一次,按住 KEY1 让主循环停摆超过 1 秒,狗咬人复位。
顺带记一个做产品的注意点:LSI 是 RC 振荡器,误差可达 ±10%,算出来的 1000ms 只是名义值,实际可能在 900~1100ms 之间晃。设计时要把这个余量算进去——喂狗周期别贴着标称超时跑,留足精度余量,否则低温环境或器件老化的板子会莫名误复位,而且这类问题在实验室永远复现不出来。
#二、实操改造:复位溯源,让设备说出死因
demo 只演示了"复位了",但没回答"为什么复位"。真实产品里,复位原因是最有价值的诊断信息。于是这次实验动手做了个小改造:开机第一件事读 RCC 的复位标志位,把死因显示在 OLED 上。
c/* 开机第一件事:判断这次复位是谁干的 */
if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) == SET) {
OLED_ShowString("WDG RESET!"); // 看门狗复位:程序卡死过
} else if (RCC_GetFlagStatus(RCC_FLAG_PORRST) == SET) {
OLED_ShowString("POWER ON!"); // 上电复位:拔电重插
} else if (RCC_GetFlagStatus(RCC_FLAG_PINRST) == SET) {
OLED_ShowString("PIN/SW RESET!"); // 复位引脚复位:按复位键(或调试器复位)
}
RCC_ClearFlag(); // 清标志:不清的话,下次开机读到的是旧账
烧进去实测,三种操作对应三种显示,清清楚楚:
| 操作 | OLED 显示 | 含义 |
|---|---|---|
| 长按 KEY1 超过 1 秒 | WDG RESET! | 主循环被卡,IWDG 咬人 |
| 拔电重插 | POWER ON! | 正常上电 |
| 按复位键 | PIN/SW RESET! | 人工复位 |
实测时还有个有意思的观察:按住 KEY1 期间,OLED 上的内容并不会消失;复位发生的一瞬间屏幕闪一下(外设重新初始化),随后才刷出 WDG RESET!。复位是硬件级动作,瞬间完成,死掉的程序没有任何收尾和申辩的机会——这也是看门狗复位和软件主动重启最大的区别:软件重启可以善后,看门狗复位只管翻篇。
这段十几行的代码里藏着三个工程细节,都是这次实验的真实收获:
1. 判断顺序有讲究。 特异性强的标志要放在前面判。IWDG 复位会连带置位其他标志(比如 PINRST 也被拉高),如果把 PINRST 放在第一个判断,看门狗复位就会被误判成"按了复位键"。顺序必须是 IWDG → POR → PIN,从最特异到最一般。
2. 读完必须 RCC_ClearFlag()。 这些标志位不会自己消失,不清除会一直挂着——下一次开机读到的就是上一次的旧账,溯源逻辑直接失效。这个坑不亲脚踩一次很难有体感,而且它坏得很隐蔽:平时看不出问题,直到某天你发现"每次开机都报 WDG RESET",其实那只是两天前某次卡死的残留标志。
3. 看门狗初始化要放在所有外设就绪之后。 如果 IWDG 一上电就启动,而 OLED、传感器这些"启动慢"的外设初始化加起来超过 1 秒,设备就会陷入"复位 → 重新初始化 → 还没初始化完 → 又复位"的死循环。整机表现为永远起不来,而且没有任何报错——这是看门狗系统里最阴险的故障形态。喂狗放主循环末尾,确保完整跑完一圈才喂。
#三、喂狗的学问:心跳必须来自主循环
看完 demo 很容易冒出一个"聪明"的想法:喂狗嘛,找个定时器中断,每 100ms 自动喂一次,主循环想干啥干啥,多省心。
这是错的,而且是新手最典型的错误。
c/* 错误示范:把喂狗放进定时器中断 */
void TIM3_IRQHandler(void) {
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {
IWDG_ReloadCounter(); // 中断每 100ms 喂一次狗
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
int main(void) {
/* ...初始化... */
while (1) {
/* 万一主循环在这里死循环:定时器中断照常运行,
狗照常被喂,看门狗永远不超时——防卡死形同虚设 */
}
}
问题的本质是:喂狗的意义不是"证明定时器活着",而是"证明主循环活着"。把喂狗藏进中断,等于让一个不被监控的东西替被监控的东西打卡——考勤制度直接作废:狗还在,但它永远不会咬那个真正该咬的人。主循环死了,中断还在勤勤恳恳地喂狗,设备彻底卡死却永远不重启,看门狗形同虚设。
正确做法只有一种:喂狗放在主循环末尾,作为主循环活着的心跳证明。主循环完整转一圈才喂一次,卡死即断粮,断粮即复位。
大型 RTOS 里这个思想会升级成"检查点喂狗"(task monitoring):每个任务定期置位自己的存活标志,喂狗前检查所有任务的标志是否齐全——齐全才喂,缺任何一个就放狗复位。
c/* 检查点喂狗(RTOS 场景示意):所有任务都报到才喂 */
if (task_alive[0] && task_alive[1] && task_alive[2]) {
IWDG_ReloadCounter(); // 全员存活,才允许喂狗
memset(task_alive, 0, sizeof(task_alive)); // 清零,下一轮重新报到
}
/* 缺任何一个标志:这一轮不放狗粮,等超时复位 */
顺带的好处是:喂狗失败时把缺失的标志位也写进日志,重启后一读就知道是哪个任务先失联的。单循环裸机的心跳是主循环本身,多任务系统的心跳是所有任务标志的与运算,逻辑一脉相承。
#四、产品级应用:设备黑匣子
前面那段复位溯源的 if-else,是我认为这次实验里最有"产品味"的部分——它就是设备黑匣子的雏形:每次开机,先弄清楚上次是怎么死的。复位原因的记录有三个层次的用途,一层比一层深。
#用途一:上云报警,看群体故障率曲线
单台设备的复位原因是一条样本,万台设备的是一条曲线。设备开机时把上次复位原因上报云平台,后台就能画出复位率趋势:某批次设备的看门狗复位率从 0.1% 悄悄爬到 5%——大概率是某次 OTA 推的固件引入了死循环,或者某批电源器件出了问题。在用户大规模投诉之前,厂商已经能主动定位、推修复补丁甚至启动召回。这就是"数据先于投诉"的运维逻辑:黑匣子让故障从"被动挨打"变成"主动发现"。
#用途二:维修读取,告别"用户说死机"
返修是嵌入式产品绕不开的环节。用户口中的"死机"是个黑盒:可能是程序卡死,可能是电源断了,也可能只是屏幕背光坏了。返修机接上工具读一次日志:复位原因 + 时间戳 + 死前状态,和"用户说死机"是天壤之别。日志显示历次都是 WDG RESET,往软件方向查;每次都是 POWER ON,先怀疑用户家插座接触不良。同一个"死机"投诉,维修方向完全不同——一次读日志,省掉一整轮反复拆装和"换个板子试试"的猜谜。
#用途三:死前遗言,HardFault 里留 PC/LR
程序死得最惨的方式是 HardFault(硬件错误,比如野指针访问了非法地址)。默认的 HardFault_Handler 就是个死循环——程序死了,但什么都没留下,事后连复现都无从下手。产品级的做法是在死亡现场留下"遗言":
cvoid HardFault_Handler(void) {
u32 sp = __get_MSP(); // 出错瞬间 MSP 指向硬件自动压栈的 8 个寄存器
u32 pc = ((u32 *)sp)[6]; // 压栈帧 [6] 是 PC:死在哪条指令
u32 lr = ((u32 *)sp)[5]; // 压栈帧 [5] 是 LR:从哪个函数调用过来
EEPROM_WriteLog(pc, lr); // 把"遗言"写进非易失存储
NVIC_SystemReset(); // 主动复位,尽量恢复服务
}
售后拿到 PC 值,对照 .map 文件一查,直接定位到死机的那一行 C 代码。这份日志和"死机复现不了"的售后工单相比,是天壤之别。
#黑匣子存哪里:三种介质
| 介质 | 容量 / 寿命 | 特点 |
|---|---|---|
| BKP 备份寄存器 | 42 字节,无擦写寿命限制 | VBAT 供电,断电不丢,容量最小 |
| 片内 Flash 末页 | 1KB,约 1 万次擦写 | 不加外部器件,但写前要整页擦除 |
| 外挂 EEPROM(24C02) | 256 字节,约百万次擦写 | 字节级写入,寿命最长,多花几毛钱 |
产品里常见三种并用:BKP 存最新一条复位原因(开机立读、上报、清位),Flash 末页存事件日志(低频追加),EEPROM 存高频记录(比如 HardFault 遗言)。42 字节听着小,存一条"复位原因 + 时间戳 + 状态字"绰绰有余;而它最大的优点是没有擦写寿命焦虑——毕竟备份寄存器本质是 RAM 单元,靠 VBAT 维持,随便写。
黑匣子还有两个工程细节值得记。一是记录要防"半截":写入前先落一个约定魔数,写完再覆盖掉,万一断电打断写入,下次开机就知道这条记录不完整,不能信。二是写入要有节制:Flash 末页只有一万次擦写寿命,不能每圈主循环都写,通常只在"事件"发生时写——复位、故障、OTA 升级,或者攒够一批再落盘。
#五、WWDG:窗口看门狗,管的是节奏
如果说 IWDG 是"最后防线",WWDG(Window Watchdog,窗口看门狗)就是"过程监控"。demo45_WWDG 的现象和 demo44 几乎一样:长按 KEY1 复位。但机制完全相反——它不只关心你有没有喂狗,还关心你什么时候喂。
#窗口机制:早喂晚喂都算违规
WWDG 的计数器从 0x7F 往下倒数,掉过 0x3F 就复位(下边界 = 超时)。中间开了一扇"窗"(窗口值可设,本实验为 0x50):计数器值还高于窗口值时喂狗,属于"太早"——违规,直接复位;掉过 0x3F 才喂,属于"太晚"——超时复位。只有计数器落在窗口区间内的喂狗才合法。
text计数值
0x7F ┬ 递减开始
│
│ ← 这个区间喂狗 = 太早(狗还不饿,硬喂 = 违规复位)
0x50 ┼ - - - - - - 窗口值(本实验设 0x50)
│
│ ← 只有这个区间喂狗 = 合法
0x3F ┴ 掉过这条线 = 太晚(超时复位)
本实验参数:预分频 8、计数初值 0x7F、窗口值 0x50,换算下来上窗口约 48ms、下窗口约 64ms——也就是说,喂狗动作必须落在上一个 64ms 周期内的第 48ms 到 64ms 之间。主循环里的 delay_ms(54) 正好卡在窗口正中间,稳稳合法:
cWWDG_SetPrescaler(WWDG_Prescaler_8); // 预分频 8
WWDG_SetWindowValue(0x50); // 窗口值 0x50
WWDG_SetCounter(0x7F); // 计数初值 0x7F
while (1) {
delay_ms(54); // 54ms:卡在 48~64ms 窗口正中间
WWDG_SetCounter(0x7F); // 喂狗
}
这个设计的用意很"狠":程序跑飞之后如果进了某个意外的快速分支,主循环转得飞快,喂狗间隔远小于 48ms——IWDG 根本发现不了(它只管你不超过 1 秒没喂,喂得再勤它都高兴),WWDG 却能立刻逮住"喂得太勤"这个异常。一句话:程序不仅活着,还得按时活着。
为什么"喂得太早"也要复位?因为正常程序喂狗的时机由业务节奏决定,是高度稳定的;喂狗时机突然变快,说明程序已经跑进了不该跑的分支——要么被中断风暴裹挟,要么主循环结构被改乱。"早"不是勤快,是异常信号,就像人心跳过速也是病。
#六、双狗对比:一张表看懂分工
| 维度 | IWDG 独立看门狗 | WWDG 窗口看门狗 |
|---|---|---|
| 时钟源 | LSI 40kHz 独立 RC 振荡 | PCLK1 36MHz 主时钟派生 |
| 精度 | 差(RC 误差 ±10%,"1 秒"只是大概) | 高(晶振派生) |
| 时间量级 | 秒级 | 毫秒级 |
| 喂狗规则 | 不超时即可,早喂晚喂都行 | 必须不早不晚,窗外喂就复位 |
| 中断 | 无 | 有 EWI 早期唤醒中断 |
| 主时钟死了 | 照常工作 | 一起死 |
| 启动后可否关闭 | 不可 | 可 |
| 定位 | 防卡死,最后防线 | 防时序乱,过程监控 |
两点值得单独展开:
时钟源差异是本质差异。 IWDG 用独立的 LSI,主时钟崩溃它照常倒数——所以它才有资格承担"最后防线";WWDG 挂在主时钟上,主时钟死了它也跟着死——所以它永远不承担"防死"职责,只承担"监控节奏"职责。两者不是竞争关系,是分工关系。
EWI 是 WWDG 的遗言机制。 计数器掉到 0x3F 前一刻可以触发 EWI(Early Wakeup Interrupt,早期唤醒中断),程序能在中断里抢救现场——把关键状态写进日志——再迎接复位。这是 WWDG 唯一一次"留下遗言"的机会,也解释了为什么它在产品里常和日志系统绑定使用。
选窗口参数的实操心得:先把主循环一圈的实测耗时摸清楚(翻转一个 GPIO 用示波器量最直接),然后两头留余量——窗口上界要略大于主循环最快一圈的耗时,防止正常的快圈被误判成"喂太早";窗口下界(超时时间)要略大于最慢一圈的耗时,防止正常的慢圈被误判成"超时"。余量都留够,WWDG 才能做到"只咬跑飞的,不咬正常的"。
#双狗并用的真实场景
电机控制是最典型的双狗并用场景:
- IWDG 设 2 秒:整机卡死的兜底。秒级、不敏感,是保险丝而不是监控器。
- WWDG 设 20ms 窗口:电机控制主循环(PWM 更新节拍)必须每 20ms 准时跑一圈。主循环一旦跑飞进某个阻塞调用,IWDG 还没反应过来,WWDG 先复位,电机在毫秒级内停转,避免失控。
同样的思路也适用于通信帧间隔监控、ADC 采样周期监控——凡是有"节拍"要求的循环,WWDG 都是精准的节拍监督员。一句话总结:IWDG 管"活着没有",WWDG 管"活着的节奏对不对"。
另外也不是所有产品都需要双狗:消费类小家电、玩具,IWDG 一个就够;工业控制、电机驱动、有严格实时节拍要求的场景,才值得让 WWDG 上场——狗越多,初始化和喂狗纪律的维护成本越高。
#七、调试与量产:一条条件编译
最后一个实用建议:调试期用条件编译把看门狗关掉。
c#define ENABLE_WDG 0 // 调试期置 0,量产前改 1
#if ENABLE_WDG
IWDG_Config(); // 量产版本才打开
#endif
原因很直接:调试器打断点时 CPU 停住,狗没人喂,几秒后直接复位——你正在单步跟踪一个 bug,芯片突然重启,那个令人抓狂的"断点莫名丢失、程序自己跑飞",多半就是看门狗在背后咬人。调试期关狗、量产开狗,是嵌入式工程里约定俗成的纪律。还有一个相关纪律:看门狗超时宁可设松一点,超时设得比主循环最坏情况的耗时还紧,正常波动就会触发误复位——IWDG 的定位是兜底,兜底不需要灵敏,可靠比快更重要。
下一步实验计划是 RTC 实时时钟——这次黑匣子里露过面的 BKP 备份寄存器和 VBAT 电池,正好在"断电走时"里接着玩。
#八、应用价值
这次实验的价值不止于两只狗的用法。复位溯源和黑匣子,是"可观测性"在嵌入式里最原始也最有效的形态:单台设备的复位原因只是一条日志,万台设备的复位原因是一条产品健康曲线——它能驱动 OTA 决策、召回决策,也能把售后从猜谜变成读数。IWDG 防卡死、WWDG 防时序乱,回答的是嵌入式可靠性的两个根本问题:"死了怎么办"和"活着但不守规矩怎么办"——前者保下限,后者保质量。demo 里它们只是一行喂狗代码,产品里它们是售后成本、召回时机和品牌信誉。
#九、相关阅读
- STM32 实战复盘:一次烧录验证串起的整条工具链——同属「产品级素养」系列,一篇讲防抄(UID),一篇讲防死(看门狗)
- 定时器与中断:1 秒心跳与五阶段状态机——看门狗本质是一个特殊的定时器,原理见此
#九、更新日志
| 日期 | 类型 | 更新内容 |
|---|---|---|
| 2026-09-16 | 新增 | 首次建档:IWDG 演示与三条铁律 → 复位溯源改造(三种死因显示 + 三个工程细节)→ 喂狗心法 → 设备黑匣子三用途 → WWDG 窗口机制 → 双狗对比与并用场景 |