3、看门狗定时器原理:定时器工作原理、喂狗机制、超时处理流程
看门狗这东西,说白了就是一个「倒计时炸弹」。你必须在它炸掉之前,给它一个信号——「我还活着」。这个信号,就是我们常说的「喂狗」。
我刚开始做运动控制那会儿,总觉得看门狗就是个简单的定时器,没什么好研究的。直到有一次,一台六轴机器人半夜突然「死机」,第二天查了一上午才发现是看门狗配置不当导致的。嗯,从那以后,我再也不敢小看这个小小的定时器了。
3.1 定时器工作原理
看门狗定时器的核心,就是一个递减计数器。它从预设值开始,每个时钟周期减1。减到0的时候,就会触发复位信号。
你想想看,这个机制其实很简单:
- 预设值:你设定的超时时间,比如100ms
- 时钟源:通常是独立的RC振荡器,或者主时钟分频
- 递减过程:每个时钟周期,计数器减1
- 触发条件:计数器减到0,立即产生复位
我在项目中遇到过一个问题:有些芯片的看门狗时钟源和主时钟是同一个。这意味着如果主时钟挂了,看门狗也跟着失效。所以,我个人习惯选用有独立时钟源的看门狗,比如STM32的独立看门狗(IWDG)。
关键参数:看门狗的超时时间 = 预设值 × 时钟周期。举个例子,如果时钟频率是40kHz(周期25μs),预设值是4000,那么超时时间就是100ms。
3.2 喂狗机制
喂狗,就是在计数器减到0之前,重新写入预设值。说白了,就是告诉看门狗:「程序还在跑,别复位我」。
喂狗的方式通常有两种:
- 硬件喂狗:通过专用的引脚,给一个脉冲信号
- 软件喂狗:在程序中写入特定的寄存器值
我建议在运动控制系统中,喂狗操作要放在主循环的「关键路径」上。什么叫关键路径?就是程序必须经过的地方。比如:
void main_loop(void)
{
while(1)
{
// 读取编码器数据
read_encoder();
// 计算控制量
calculate_pid();
// 输出PWM
output_pwm();
// 喂狗——放在这里
feed_the_dog();
// 处理通信
handle_communication();
}
}
为什么要放在中间?因为如果放在开头,万一后面的代码卡死了,看门狗也救不了你。我曾经见过一个同事,把喂狗放在循环的第一行,结果通信模块死锁了,看门狗还在不停地被喂——程序永远不复位,机器就这么僵在那里。
小技巧:喂狗的位置,应该放在你确认「程序健康」的那个点之后。比如,你可以先检查一下关键变量的范围,再喂狗。这样能避免「假健康」的情况。
3.3 超时处理流程
当看门狗超时了,会发生什么?这取决于你的配置。常见的处理方式有三种:
| 处理方式 | 说明 | 适用场景 |
|---|---|---|
| 系统复位 | 直接复位整个MCU | 大多数嵌入式系统 |
| 中断+复位 | 先触发中断,再延时复位 | 需要记录错误信息的系统 |
| 仅中断 | 只触发中断,不复位 | 非关键系统(不推荐) |
我个人强烈推荐使用「中断+复位」的方式。为什么?因为你可以利用中断服务程序,把现场信息保存下来。比如:
void WDG_IRQHandler(void)
{
// 保存错误信息
save_error_log("Watchdog timeout!");
// 保存关键寄存器值
save_cpu_registers();
// 保存堆栈信息
save_stack_trace();
// 然后等待复位
// 硬件会自动复位
}
这样复位之后,你就能通过日志分析出问题出在哪里。否则,你只能看到「系统重启了」,但不知道为什么重启。
注意:中断服务程序里不要做太多事情。看门狗超时说明系统已经不稳定了,你只有很短的时间来保存关键信息。我一般只保存几个关键寄存器和错误码,不超过100个字节。
3.4 看门狗工作流程图
下面这张图,是我画的一个看门狗工作流程。它展示了从启动到超时处理的完整过程:
从这张图你可以看到,看门狗的工作其实就是一个「检查-喂狗-检查」的循环。如果程序卡在某个地方,没有及时喂狗,计数器就会一直减到0,然后触发复位。
经验之谈:看门狗的超时时间,一般设置为主循环周期的2-3倍。比如你的主循环是50ms,那看门狗就设100-150ms。太短容易误触发,太长又起不到保护作用。
好了,关于看门狗定时器的原理,我就讲到这里。记住一句话:看门狗不是用来「看门」的,而是用来「看程序」的。它确保你的程序在正确的时间、正确的位置,做正确的事情。