第2章:看门狗工作原理:内部定时器机制、喂狗操作、超时复位流程
大家好,我是你们的老朋友。上一章我们聊了看门狗为什么重要,这一章咱们深入内部,看看它到底是怎么工作的。
说白了,看门狗就是一个会倒计时的定时器。你必须在它归零之前告诉它:“我还活着,别复位!”——这就是喂狗。如果你没来得及喂,它就认为系统卡死了,然后强制复位。
听起来简单吧?但里面的门道可不少。我当年第一次用看门狗,就因为它“咬”了我一口,搞得我排查了整整两天。
2.1 内部定时器机制
看门狗的核心,就是一个递减计数器。它从你设定的初始值开始,每个时钟周期减1。减到0的时候,就触发复位。
你想想看,这个机制其实很朴素。但为什么有的看门狗准,有的不准?问题出在时钟源上。
| 时钟源类型 | 典型精度 | 我的评价 |
|---|---|---|
| 内部RC振荡器 | ±10% ~ ±30% | 便宜但飘得厉害,温度一变它就变 |
| 外部晶振 | ±50ppm | 稳,但多一个引脚和成本 |
| PLL分频时钟 | 取决于源时钟 | 小心PLL失锁,我踩过这个坑 |
我个人习惯,只要成本允许,一定用外部晶振做看门狗时钟。为什么?因为看门狗本身就是用来兜底的,如果它的时钟都不靠谱,那还兜什么底?
2.2 喂狗操作
喂狗,就是往看门狗的一个特殊寄存器里写入一个特定序列。比如STM32的独立看门狗,你需要先写0xAAAA,再写0x5555,最后写0xCCCC。
为什么要搞这么复杂?直接写一个位不行吗?
嗯,这里要注意:这是为了防止程序跑飞时“意外喂狗”。如果只是写一个位,飞掉的程序万一碰巧写到了那个位,看门狗就被误喂了,系统死机了也不知道。
// 典型的喂狗代码(以STM32 IWDG为例)
void IWDG_Feed(void)
{
IWDG->KR = 0xAAAA; // 使能访问重装载寄存器
IWDG->KR = 0x5555; // 写入重装载值
IWDG->KR = 0xCCCC; // 启动重装载
}
我曾经在一个项目中,把喂狗代码放在了中断服务函数里。结果主循环卡死了,中断还能正常触发,看门狗一直被喂着,系统死活不复位。排查了三天才发现——喂狗位置选错了。
- 喂狗代码只放在主循环的关键路径上
- 不要在中断里喂狗(除非你很清楚自己在做什么)
- 喂狗前检查关键任务是否完成
2.3 超时复位流程
当计数器减到0时,看门狗会做什么?
不同芯片略有差异,但大体流程是这样的:
- 计数器归零:递减到0的那一刻,触发复位事件
- 复位信号产生:内部产生一个持续若干时钟周期的复位脉冲
- 系统复位:CPU、外设全部回到初始状态
- 复位标志置位:有个寄存器会记录“这次复位是看门狗引起的”
第4点特别重要。为什么?因为你要区分是上电复位还是看门狗复位。如果是看门狗复位,说明系统之前出过问题,你需要记录下来,甚至进入安全模式。
// 检查复位原因(以某MCU为例)
if (RCC->CSR & RCC_CSR_WDGRSTF)
{
// 看门狗复位!
Log_Error("系统因看门狗超时而复位");
Enter_SafeMode();
// 清除标志位
RCC->CSR |= RCC_CSR_RMVF;
}
else
{
// 正常上电复位
Init_Normal();
}
2.4 知识体系总览
下面这张图,是我自己总结的看门狗工作原理全景图。你看一遍,应该就能把整个流程串起来了。
2.5 避坑指南
最后,分享几个我亲身踩过的坑:
- 坑1:喂狗代码放在中断里 —— 前面说过了,主循环卡死但中断还能跑,看门狗失效
- 坑2:超时时间设得太短 —— 系统正常运行时偶尔也会超过,导致误复位。我一般留50%的余量
- 坑3:没检查复位标志 —— 上电和看门狗复位用同一套初始化,问题被掩盖了
- 坑4:在调试器暂停时喂狗 —— 调试时断点停下,看门狗还在跑,等你单步执行完,它已经复位了
#ifdef DEBUG
#define WDT_TIMEOUT_MS 10000 // 调试时10秒
#else
#define WDT_TIMEOUT_MS 2000 // 正式版2秒
#endif
好了,这一章的内容就到这里。看门狗的工作原理,说白了就是一个会咬人的定时器。你按时喂它,它就乖乖的;你不喂它,它就咬你一口——让系统重启。
下一章我们聊聊怎么选型,不同应用场景该用什么样的看门狗。嗯,到时候见。
公众号:蓝海资料掘金营,微信deep3321