STM32F4软件看门狗喂狗防止程序死循环
在工业控制现场,你有没有遇到过这样的情况:设备莫名其妙“卡死”,远程无法唤醒,只能派人去现场手动重启?😅 尤其是那些部署在偏远地区的智能终端——比如风力发电机的监控模块、农田里的灌溉控制器,一旦程序陷入一个没有延时也没有中断退出机制的
while(1)
循环,整个系统就等于“活死人”状态。
这时候,硬件看门狗(IWDG)确实能救场。但问题来了: 调试时一接上JTAG,它照样计数不停,咔嚓一下给你复位了 …… 调试体验直接拉满 frustration 😤。更别说 LSI 时钟不准、启动后不能关闭这些老毛病。
所以,很多工程师转而选择一种更灵活、更聪明的办法—— 软件看门狗 。特别是在使用 STM32F4 这类性能强劲 yet 开发自由度高的 MCU 时,用片上定时器搭一套“软狗”,反而成了高可靠系统的标配操作 ✅。
咱们不整虚的,直接上实战思路:
想象你的主循环是一个勤劳的快递员,每天按路线挨家挨户送货。如果某天他在某一户门前卡住了(比如死循环),后续任务全停摆。那怎么知道他“失联”了呢?
答案很简单: 每隔一段时间检查他是否来签到 。没签到?说明出事了,赶紧派直升机空投支援(复位)!
这就是软件看门狗的核心逻辑 🚀。
我们拿 STM32F4 的 TIM6 定时器当“计时员”,每 50ms 打一次卡;主循环每跑完一圈就清零一次计数器——这叫“喂狗”。要是连续 100 次都没人来喂(也就是 5 秒钟),那就判定系统已“失联”,立刻执行
NVIC_SystemReset()
自动重启。
听起来像不像一个微型监控系统?👀 其实它的实现成本低得惊人:
- 只需要一个基本定时器(TIM6/TIM7)
-
一个全局变量
watchdog_counter - 几行中断回调 + 主循环里的喂狗语句
资源占用几乎可以忽略,却能极大提升系统鲁棒性 💪。
来看关键代码怎么写:
#include "stm32f4xx_hal.h"
static uint32_t watchdog_counter = 0;
#define WATCHDOG_TIMEOUT_COUNT 100 // 100 × 50ms = 5秒超时
#define FEED_INTERVAL_MS 50
void Watchdog_Timer_Init(void)
{
__HAL_RCC_TIM6_CLK_ENABLE();
TIM_HandleTypeDef htim6;
htim6.Instance = TIM6;
htim6.Init.Prescaler = (SystemCoreClock / 1000) - 1; // 1kHz
htim6.Init.Period = (FEED_INTERVAL_MS * 1) - 1; // 50ms
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_Base_Start_IT(&htim6);
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 10, 0);
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
}
这段初始化把 TIM6 配置成每 50ms 触发一次中断。注意这里用了
SystemCoreClock
动态计算分频值,确保不同主频下都能精准定时 👌。
然后是中断服务函数:
void TIM6_DAC_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim6);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM6) {
watchdog_counter++; // 每次中断+1,表示等待喂狗的时间又过去一段
}
}
别小看这一行
watchdog_counter++
,它是整个机制的生命线。只要主循环还能流转,这个计数就会被定期清零;一旦清零动作消失,它就开始默默倒计时……
接下来就是“喂狗”环节,通常放在主循环末尾:
int main(void)
{
HAL_Init();
SystemClock_Config();
Watchdog_Timer_Init();
while (1)
{
Task_LED_Toggle();
Task_Sensor_Read();
Task_Communicate();
watchdog_counter = 0; // ✅ 喂狗!证明我还活着
HAL_Delay(10); // 防止循环过快(视需求可选)
}
}
但等等!有个细节很多人忽略: 应该先检查是否超时,再执行任务 。否则如果某次任务耗时太久,还没走到喂狗语句就被判死了,岂不是冤枉?
所以更稳妥的做法是把检测提到最前面:
void CheckAndFeedWatchdog(void)
{
if (watchdog_counter >= WATCHDOG_TIMEOUT_COUNT)
{
NVIC_SystemReset(); // CPU级软复位
while (1); // 理论上不会走到这里
}
}
// 主循环中:
while (1)
{
CheckAndFeedWatchdog(); // 🔍 先查有没有超时
Task_LED_Toggle();
Task_Sensor_Read();
Task_Communicate();
watchdog_counter = 0; // 🐶 安全完成一轮,喂一口
}
这样即使某个任务异常延长,也能在下次进入循环时第一时间响应故障,避免“死后复活”的尴尬局面。
这种设计特别适合哪些场景呢?举几个真实例子👇:
🔧 案例1:忘加延时的死循环
while (sensor_ready() == 0) {
// 忘记加HAL_Delay(1);
}
这种低级错误新手常犯。CPU 全速空转,看似活跃,实则主线程已瘫痪。硬件看门狗可能仍能正常喂(如果在其他地方触发),但软件看门狗会立刻发现主循环没流转,果断复位。
⚡ 案例2:高优先级中断霸占CPU
假设有一个高频 ADC 中断或通信中断频繁触发,导致主循环长时间得不到执行。虽然系统没死,但业务逻辑停滞。这种情况对用户来说就是“设备无响应”。
软件看门狗正好弥补这一点:它监测的是 主流程的活性 ,而不是单纯的 CPU 是否在跑指令。
🛠️ 案例3:RTOS迁移前的过渡方案
你在做一个裸机项目,但未来打算上 FreeRTOS。提前加入软件看门狗,相当于为每个“伪任务”建立健康检查机制。等以后换成任务心跳检测(task watchdog)时,思想模型完全一致,平滑过渡 ✔️。
当然,任何技术都有使用边界,软件看门狗也不例外。下面这几个坑,建议 mark 下 ⚠️:
❌ 别在中断里喂狗!
void USART1_IRQHandler() {
parse_data();
watchdog_counter = 0; // 🚫 错误示范!
}
这么做会导致即使主循环卡死,只要有串口中断进来就能续命——那还监控个啥?🐶 相当于保安天天代打卡,老板以为员工都在上班,其实办公室早就没人了。
记住: 喂狗必须发生在主上下文 ,才能真实反映主流程运行状态。
⏱️ 超时时间设置要合理
太短?容易误杀正常长任务(比如读 SD 卡花了几百毫秒)。
太长?等5分钟才复位,用户体验还不如拔电源。
推荐公式:
最大单轮主循环耗时 × 3~5 倍
你可以先加日志测一下典型循环周期,再定阈值。例如平均 80ms,峰值 120ms,那设个 500ms~1s 的窗口就很安全。
🧩 多分支路径都要覆盖喂狗
如果你的主循环是状态机驱动的:
switch(state) {
case STATE_A: run_a(); break;
case STATE_B: run_b(); break;
default: break;
}
记得每个分支最后都要喂狗,或者统一放在 switch 后面。别出现某个状态忘了喂,结果系统反复重启,查半天才发现是漏了一句赋值 😓。
说到这儿,你可能会问:能不能和硬件看门狗一起用?
当然可以!而且这是高端玩法 👑。
设想这样一个双保险架构:
- 第一层:软件看门狗,负责智能判断,可记录崩溃前的状态、上传日志、进入安全模式;
- 第二层:IWDG 硬件看门狗,作为最后防线,哪怕软件逻辑彻底失控也能强制复位。
两者配合,就像汽车的安全带 + 安全气囊,层层防护 🛡️。
甚至还可以玩点花的:让软件看门狗定期去喂 IWDG。这样既能享受调试友好性(暂停时不复位),又能保留硬件级可靠性。
最后提一句工程哲学 💬:
软件看门狗的本质,是一种 对程序行为的契约式监督 。你告诉系统:“我保证每隔一段时间回来报到一次,如果我没来,你就当我出事了。”
这种“自我证言 + 超时惩罚”的机制,在分布式系统、微服务健康检查中也随处可见。嵌入式里的这一小步,其实是通往高可用系统的一大步。
所以在你的下一个 STM32F4 项目里,不妨从现在开始,给主循环加上这行小小的
watchdog_counter = 0;
吧 🐾。
毕竟,谁都不想半夜被电话叫醒:“领导,设备又不动了……”
“预防胜于治疗”,尤其是在没人看得见的地方 🌌。
graph TD
A[上电启动] --> B[初始化系统与时钟]
B --> C[启动TIM6定时器]
C --> D[进入主循环]
D --> E{执行各项任务}
E --> F[检查看门狗是否超时]
F -->|未超时| G[清零计数器喂狗]
G --> H[延时/同步]
H --> D
F -->|已超时| I[触发系统复位]
I --> J[NVIC_SystemReset()]
这套机制虽轻量,但足够坚韧。
它不会阻止你写出 bug,但它会确保——
即使出了问题,设备也能自己爬起来继续战斗
💥。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
1612

被折叠的 条评论
为什么被折叠?



