做嵌入式开发的同学,几乎都逃不开「看门狗」这个话题。设备现场跑飞、程序死锁、任务卡死,无人值守的时候总不能天天派人去按复位键。看门狗就是解决这类问题的核心方案,但很多新手一直搞不清:硬件看门狗和软件看门狗到底有什么区别?分别用在什么场景?能不能一起用?
本文结合真实业务场景和可直接复用的代码,一次性讲透。
一、硬件看门狗:芯片自带的最后一道安全防线
1. 到底是什么?
硬件看门狗是MCU 芯片内部集成的硬件外设,最典型的就是 STM32 的 IWDG(独立看门狗)。 它最大的特点是:自带独立的内部低速 RC 时钟,完全不依赖 CPU 主系统时钟。哪怕 CPU 内核彻底卡死、主时钟停振,看门狗的计数器依然在自己跑。
2. 工作原理
- 初始化时设定一个超时时间(比如 1 秒),看门狗的计数器从初始值开始递减;
- 程序正常运行时,必须在超时到期之前,主动「喂狗」(重装载计数器值),让计数器重新开始计数;
- 一旦程序卡死、死循环、跑飞,无法按时喂狗,计数器减到 0,硬件会直接触发整个 MCU 复位,让设备强制重启恢复。
3. 实战代码(STM32 独立看门狗)
#include "stm32f10x_iwdg.h" /** * @brief 初始化独立看门狗 * @param prer: 预分频系数 * @param rlr: 重装载值 * @note 示例:IWDG_Init(IWDG_Prescaler_64, 625) → 溢出时间约1秒 */ void IWDG_Init(uint8_t prer, uint16_t rlr) { IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); // 开启寄存器写权限 IWDG_SetPrescaler(prer); // 设置预分频值 IWDG_SetReload(rlr); // 设置重装载值 IWDG_ReloadCounter(); // 首次喂狗,重装计数器 IWDG_Enable(); // 使能独立看门狗 } /** * @brief 喂狗:重装载计数器,防止溢出复位 */ void IWDG_Feed(void) { IWDG_ReloadCounter(); } int main(void) { // 系统时钟、外设初始化... IWDG_Init(IWDG_Prescaler_64, 625); while(1) { user_main_loop(); // 业务主循环 IWDG_Feed(); // 每次循环正常结束就喂狗 } }4. 典型应用场景
户外温湿度采集节点、简单工业传感器、小家电控制器这类裸机单任务设备。
- 功能逻辑简单,不需要精确定位故障;
- 核心诉求是「卡死了能自己重启,继续干活」;
- 纯硬件兜底,几乎不占用 CPU 资源。
二、软件看门狗:多任务系统的精细监控器
1. 到底是什么?
软件看门狗不是硬件外设,是完全用代码 / 线程 / 定时器实现的逻辑监控机制。它基于系统时钟和线程调度,用来精准监控单个任务、单个模块是否正常运行。
2. 工作原理
- 给每一个需要监控的业务线程,设置一个「心跳计数器」和对应的超时时间;
- 业务线程正常运行时,会定时给自己的心跳计数器刷新数值(相当于「喂软狗」);
- 专门的监控线程 / 定时器定期巡检所有心跳计数器,如果某个计数器超过设定时间没刷新,就判定对应的线程死锁 / 卡死;
- 触发后可以自定义处理逻辑:打印错误日志、保存故障现场、重启对应线程,极端情况再触发系统复位。
3. 实战代码(RT-Thread 线程级软件看门狗)
#include <rtthread.h> #define TASK_NUM 2 // 监控的任务数量 #define HEARTBEAT_TIMEOUT 3 // 超时时间:秒 // 心跳结构体:每个任务对应一个 typedef struct { rt_uint32_t heartbeat; rt_uint8_t is_valid; const char* task_name; } wdt_soft_t; static wdt_soft_t soft_wdt[TASK_NUM]; /** * @brief 任务上报心跳(喂软狗) * @param task_id: 任务ID */ void soft_wdt_feed(uint8_t task_id) { if(task_id >= TASK_NUM) return; soft_wdt[task_id].heartbeat = rt_tick_get(); } /** * @brief 监控线程:每秒巡检一次所有任务心跳 */ static void wdt_monitor_thread(void *parameter) { while(1) { rt_thread_mdelay(1000); for(uint8_t i = 0; i < TASK_NUM; i++) { if(!soft_wdt[i].is_valid) continue; // 计算超时 rt_tick_t delta = rt_tick_get() - soft_wdt[i].heartbeat; if(delta > HEARTBEAT_TIMEOUT * RT_TICK_PER_SECOND) { rt_kprintf("[WARN] 任务%s超时卡死!\n", soft_wdt[i].task_name); // 自定义处理:打印日志、保存现场、尝试重启线程 // 极端情况:调用NVIC_SystemReset()触发硬件复位 } } } } // -------------- 业务线程示例 -------------- // 运动识别线程 void motion_task(void *arg) { soft_wdt[0].task_name = "motion_task"; soft_wdt[0].is_valid = 1; while(1) { motion_detect_process(); // 运动识别业务逻辑 soft_wdt_feed(0); // 正常跑完一轮就喂狗 rt_thread_mdelay(10); } } // 蓝牙通信线程 void ble_task(void *arg) { soft_wdt[1].task_name = "ble_task"; soft_wdt[1].is_valid = 1; while(1) { ble_data_process(); // 蓝牙数据处理 soft_wdt_feed(1); // 正常跑完一轮就喂狗 rt_thread_mdelay(20); } }4. 典型应用场景
智能手表、智能家居、工业控制器这类带操作系统的多任务设备。
- 系统线程多,不能因为一个功能卡死就整个设备复位;
- 需要精确定位哪个模块出了问题,保留故障日志;
- 优先尝试恢复线程,不直接重启整机,提升用户体验。
三、一张表看懂核心区别
表格
| 对比维度 | 硬件看门狗(硬狗) | 软件看门狗(软狗) |
|---|---|---|
| 实现本质 | MCU 内置硬件外设 | 代码 / 线程实现的逻辑 |
| 时钟依赖 | 独立内部 RC 时钟,与主时钟无关 | 依赖系统主时钟和线程调度 |
| 复位能力 | CPU 彻底死机也能触发硬件复位 | CPU 全卡死时自身也失效,只能触发软件逻辑 |
| 检测粒度 | 只能检测整个系统是否存活 | 可精确到单个线程、单个模块 |
| 处理方式 | 只有复位一种动作 | 可自定义:打日志、存现场、重启线程、复位 |
| 资源占用 | 几乎不占用 CPU 资源 | 需要占用线程、定时器资源 |
| 定位 | 系统最后一道安全兜底 | 日常业务监控、故障定位 |
四、企业级真实用法:双看门狗组合架构
真正的商用设备(比如智能穿戴、车载设备),从来不是二选一,而是两者搭配使用:
- 软件看门狗在前:监控每个业务线程,出问题先打日志、尝试重启线程,尽量不复位整机,保证用户体验;
- 硬件看门狗在后:做最终兜底。如果软件看门狗都失效了(整个系统内核卡死、调度崩溃),硬狗超时触发硬件复位,保证设备最终能自己恢复。
举个智能手表的真实例子:
- 运动识别线程死锁 → 软狗先检测到,打印异常日志,重启运动线程,用户完全感知不到;
- 内核崩溃、整个系统调度挂死 → 软狗也跑不动了,1 秒后 IWDG 硬狗触发复位,手表重启恢复。
五、新手常见误区
- 有硬狗就够了,不用软狗错。多任务系统里,一个线程卡死就整机复位,用户体验极差;而且硬狗没法告诉你哪里出了问题,排查故障全靠猜。
- 软狗能替代硬狗错。如果整个系统彻底卡死、总线故障、主时钟停振,软件看门狗自己也跑不起来,必须靠硬狗做最终兜底。
- 喂狗放在主循环末尾就万事大吉错。如果主循环里某个函数阻塞很久,但最终还是返回了,喂狗依然会执行,硬狗检测不出来。这种场景必须靠软狗做细粒度监控。
六、总结
- 硬件看门狗是底线:负责极端情况下的整机复位,保证设备不死机;
- 软件看门狗是精细化管理:负责单任务监控、故障定位,提升系统稳定性和可维护性;
- 真正的工业级、消费级设备,都是软硬结合,双看门狗架构。
如果觉得文章对你有帮助,欢迎点赞收藏~有不同观点也欢迎评论区交流。