第三章 软件看门狗实现:基于定时器的软件看门狗设计、任务超时监控机制、心跳包机制
各位同学,咱们接着聊。上一章讲了硬件看门狗,说白了就是一颗独立的小芯片,你喂它就老实,不喂它就咬人。但硬件看门狗有个问题——它太“死心眼”了。它只知道你有没有在规定时间内拉一下引脚,至于你的程序是不是真的在干活,它根本不知道。
所以,这一章我们来聊聊软件看门狗。嗯,这才是真正考验嵌入式工程师功力的地方。
3.1 基于定时器的软件看门狗设计
软件看门狗,说白了就是利用MCU内部的定时器资源,自己搭一个“监控系统”。我个人习惯用系统滴答定时器(SysTick)或者通用定时器来做这件事。为什么?因为不用额外花钱买芯片,而且灵活性极高。
我刚开始做智能手表那会儿,觉得硬件看门狗就够用了。直到有一次,手表在待机模式下死机了,硬件看门狗居然没复位——后来查了半天,发现是低功耗模式下,硬件看门狗的时钟源被切断了。从那以后,我就开始重视软件看门狗的设计。
来看一个最简单的软件看门狗实现:
// 软件看门狗结构体
typedef struct {
uint32_t reload_value; // 重装载值
uint32_t current_count; // 当前计数值
uint8_t enabled; // 使能标志
void (*timeout_callback)(void); // 超时回调函数
} sw_watchdog_t;
// 全局软件看门狗实例
static sw_watchdog_t sw_wdg;
// 初始化软件看门狗
void sw_watchdog_init(uint32_t timeout_ms) {
sw_wdg.reload_value = timeout_ms;
sw_wdg.current_count = timeout_ms;
sw_wdg.enabled = 1;
sw_wdg.timeout_callback = system_reset; // 默认复位
// 启动一个1ms周期的定时器中断
timer_start(TIMER_ID, 1); // 1ms中断一次
}
// 定时器中断服务函数(1ms调用一次)
void timer_isr(void) {
if (sw_wdg.enabled) {
if (sw_wdg.current_count > 0) {
sw_wdg.current_count--;
} else {
// 超时了!
if (sw_wdg.timeout_callback) {
sw_wdg.timeout_callback();
}
}
}
}
// 喂狗函数
void sw_watchdog_feed(void) {
sw_wdg.current_count = sw_wdg.reload_value;
}
你看,代码其实很简单。核心思想就是:定时器每1ms减一次计数,如果计数减到0还没人喂狗,就执行超时回调。这个回调可以是系统复位,也可以是错误记录,甚至可以是进入安全模式。
3.2 任务超时监控机制
好了,有了基础的软件看门狗,我们得想想怎么用它来监控任务。你想想看,一个智能手表里跑着多少任务?显示、传感器采集、蓝牙通信、触控处理...每个任务都有可能卡死。
我设计过一个“任务超时监控”机制,说白了就是给每个任务分配一个“时间片”,如果某个任务在规定时间内没有完成,就认为它出问题了。
具体实现是这样的:
// 任务监控结构体
typedef struct {
uint32_t task_id; // 任务ID
uint32_t max_exec_time; // 最大执行时间(ms)
uint32_t start_time; // 任务开始时间
uint8_t is_running; // 是否正在运行
char task_name[16]; // 任务名称,方便调试
} task_monitor_t;
// 任务监控表
static task_monitor_t task_table[] = {
{0, 10, 0, 0, "Touch"}, // 触控任务,最多10ms
{1, 50, 0, 0, "Display"}, // 显示任务,最多50ms
{2, 100, 0, 0, "Sensor"}, // 传感器任务,最多100ms
{3, 200, 0, 0, "BLE"}, // 蓝牙任务,最多200ms
};
// 任务开始执行时调用
void task_start_monitor(uint32_t task_id) {
if (task_id < sizeof(task_table)/sizeof(task_table[0])) {
task_table[task_id].start_time = get_tick_ms();
task_table[task_id].is_running = 1;
}
}
// 任务执行完毕时调用
void task_stop_monitor(uint32_t task_id) {
if (task_id < sizeof(task_table)/sizeof(task_table[0])) {
task_table[task_id].is_running = 0;
uint32_t elapsed = get_tick_ms() - task_table[task_id].start_time;
if (elapsed > task_table[task_id].max_exec_time) {
// 记录超时警告
log_warning("Task %s timeout! Expected %dms, actual %dms",
task_table[task_id].task_name,
task_table[task_id].max_exec_time,
elapsed);
}
}
}
// 系统监控任务(周期性调用)
void system_monitor_task(void) {
for (int i = 0; i < sizeof(task_table)/sizeof(task_table[0]); i++) {
if (task_table[i].is_running) {
uint32_t elapsed = get_tick_ms() - task_table[i].start_time;
if (elapsed > task_table[i].max_exec_time * 2) {
// 严重超时!任务可能卡死了
log_error("Task %s hung! Force reset.", task_table[i].task_name);
system_reset();
}
}
}
}
3.3 心跳包机制
心跳包,这个名字很形象。就像人的心跳一样,系统每隔一段时间发出一个“我还活着”的信号。在智能手表里,心跳包机制有两个作用:一是告诉外部设备(比如手机)手表还在正常运行;二是作为系统内部健康检查的依据。
我设计的心跳包机制是这样的:
// 心跳包结构
typedef struct {
uint32_t heartbeat_id; // 心跳序号
uint32_t uptime_ms; // 系统运行时间
uint8_t battery_level; // 电池电量
uint8_t task_status; // 任务状态位图
uint16_t error_count; // 错误计数
} heartbeat_packet_t;
// 心跳包全局变量
static heartbeat_packet_t heartbeat;
static uint32_t last_heartbeat_tick = 0;
// 生成心跳包
void heartbeat_generate(void) {
heartbeat.heartbeat_id++;
heartbeat.uptime_ms = get_tick_ms();
heartbeat.battery_level = battery_get_level();
heartbeat.task_status = get_task_status_bitmap();
heartbeat.error_count = get_error_count();
}
// 发送心跳包(通过蓝牙发送到手机)
void heartbeat_send(void) {
heartbeat_generate();
ble_send_data((uint8_t*)&heartbeat, sizeof(heartbeat_packet_t));
}
// 心跳包监控(检查是否按时发送)
void heartbeat_monitor(void) {
uint32_t now = get_tick_ms();
// 如果超过5秒没有发送心跳包
if (now - last_heartbeat_tick > 5000) {
log_warning("Heartbeat lost! Last sent %dms ago",
now - last_heartbeat_tick);
// 尝试重新发送
heartbeat_send();
last_heartbeat_tick = now;
// 如果连续3次丢失心跳,触发系统检查
static uint8_t lost_count = 0;
lost_count++;
if (lost_count >= 3) {
log_error("Heartbeat lost 3 times! System check needed.");
system_health_check();
lost_count = 0;
}
}
}
这里有个细节我想强调一下。心跳包不仅仅是“我还活着”的信号,它还可以携带系统状态信息。比如电池电量、任务状态、错误计数等。这样手机端就能实时了解手表的状态,一旦发现异常可以及时提醒用户。
实战经验: 我在做某款运动手表时,发现用户跑步时手表经常死机。通过分析心跳包数据,发现是GPS模块在信号弱时耗电过大,导致电压跌落触发了复位。后来我们在心跳包里加入了电压监控,一旦发现电压低于阈值就主动降低GPS采样率,问题就解决了。
3.4 三种机制的协同工作
好了,现在我们有三个工具:软件看门狗、任务超时监控、心跳包机制。它们不是孤立的,而是需要协同工作。
我一般这样设计它们的协作关系:
| 机制 | 监控对象 | 响应时间 | 处理方式 |
|---|---|---|---|
| 软件看门狗 | 整个系统 | 毫秒级 | 立即复位 |
| 任务超时监控 | 单个任务 | 秒级 | 记录日志+尝试恢复 |
| 心跳包机制 | 系统与外部通信 | 秒级到分钟级 | 通知用户+系统检查 |
你看,这三个机制覆盖了不同的时间尺度和监控范围。软件看门狗是最后一道防线,任务超时监控是日常巡检,心跳包是对外沟通的窗口。
举个例子:如果某个传感器任务卡死了,任务超时监控会先发现,记录日志并尝试恢复。如果恢复失败导致系统整体卡死,软件看门狗会触发复位。复位后,手机端因为收不到心跳包,会提醒用户“手表可能异常重启了”。
嗯,这一章的内容就到这里。下一章我们会聊聊如何在实际项目中调试和优化这些机制。记住,看门狗不是万能的,但没有看门狗是万万不能的。