为什么需要多任务看门狗
传统的看门狗只管”主循环有没有在跑”。但在多线程 RTOS 里,主循环早就不存在了——每个线程有自己的循环。如果只在某个线程里喂狗,其它线程死锁了完全检测不到。
PipeMonitor 需要一种机制:任一关键线程卡死,整个系统自动复位。
设计思路
不直接在业务线程里喂硬件看门狗,而是引入一个 看门狗监督线程,它周期性检查所有关键线程的”心跳”:
- 每个业务线程在每轮循环结束时调用
app_watchdog_mark_alive(TASK_ID) - 监督线程每秒检查一次:如果某个线程超过指定时间没上报,就认为它卡死了
- 只有所有线程都健康时才喂硬件 IWDG;否则让 IWDG 超时复位
static rt_bool_t app_watchdog_all_healthy(void)
{
rt_tick_t now = rt_tick_get();
for (rt_uint32_t i = 0; i < APP_WATCHDOG_TASK_COUNT; i++)
{
rt_tick_t timeout = rt_tick_from_millisecond(g_task_cfg[i].timeout_ms);
if ((rt_int32_t)(now - g_last_alive[i]) > (rt_int32_t)timeout)
{
rt_kprintf("[WDG] %s stale, wait for IWDG reset\n", g_task_cfg[i].name);
return RT_FALSE;
}
}
return RT_TRUE;
}
各线程的超时阈值
不同线程的工作周期不同,超时阈值也不同:
| 线程 | 超时 | 理由 |
|---|---|---|
measure | 20s | 采集周期 2s,留 10 倍余量应对 Modbus 偶发超时 |
display | 3s | LCD 刷新周期 200ms,3s 没响应基本是卡死了 |
heartbeat | 3s | 心跳周期 500ms,3s 无响应说明调度器挂了 |
uplink | 20s | 上行等待云端 ACK 最长 8s,加上补传可能更久 |
static const app_watchdog_task_cfg_t g_task_cfg[APP_WATCHDOG_TASK_COUNT] =
{
{ "measure", 20000U },
{ "display", 3000U },
{ "heartbeat", 3000U },
{ "uplink", 20000U },
};
IWDG 配置
硬件看门狗使用 STM32H562 的 IWDG,时钟源 LSI 32kHz:
/* IWDG 使用 LSI 32kHz:1250 * 256 / 32000 = 10s */
#define APP_IWDG_PRESCALER IWDG_PRESCALER_256
#define APP_IWDG_RELOAD 1249U
超时时间 = (1249+1) * 256 / 32000 = 10 秒。监督线程每秒检查一次,所以最坏情况下系统在检测到故障后 10 秒内复位。
复位原因记录
系统启动时读取 RCC 复位标志,打印到串口方便排查:
void app_watchdog_print_reset_reason(void)
{
rt_kprintf("[WDG] reset reason:");
if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)) rt_kprintf(" IWDG");
if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)) rt_kprintf(" WWDG");
if (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST)) rt_kprintf(" SW");
if (__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST)) rt_kprintf(" PIN");
if (__HAL_RCC_GET_FLAG(RCC_FLAG_BORRST)) rt_kprintf(" BOR");
if (__HAL_RCC_GET_FLAG(RCC_FLAG_LPWRRST)) rt_kprintf(" LPWR");
rt_kprintf("\n");
__HAL_RCC_CLEAR_RESET_FLAGS();
}
同时会上报一条 MCU_RESTART 报警事件到云端,让运维知道设备曾经重启过。
看门狗的启动时机
看门狗不是一开机就启动的。在 app_init 线程里,所有业务线程都跑起来之后才启动:
rt_uint32_t watchdog_mask = APP_WATCHDOG_MASK_MEASURE |
APP_WATCHDOG_MASK_DISPLAY |
APP_WATCHDOG_MASK_HEARTBEAT;
/* 上行服务启动成功后才监控它 */
if (uplink_service_start() == RT_EOK)
watchdog_mask |= APP_WATCHDOG_MASK_UPLINK;
app_watchdog_start(watchdog_mask);
这样避免了初始化阶段还没来得及上报就被误判为卡死。
故障场景分析
场景 1:Modbus 总线死锁
采集线程卡在 rt_mutex_take 等待串口锁。20 秒后看门狗检测到 measure 超时,停止喂 IWDG,系统自动复位。
场景 2:LCD FMC 总线异常
显示线程卡在 lcd_fill 等待 FSMC 应答。3 秒后看门狗检测到 display 超时,系统复位。
场景 3:云端长时间无响应
上行线程在 wait_cloud_ack 里等待 8 秒超时,然后继续运行。只要它还在调用 mark_alive,看门狗就不会误判。只有真正卡死(比如串口驱动死锁)才会触发复位。
场景 4:栈溢出
RT-Thread 的 RT_USING_OVERFLOW_CHECK 会在栈底放置哨兵值。上下文切换时如果检测到哨兵被踩,打印 stack overflow 并触发 HardFault。看门狗此时已经停止喂狗,10 秒后 IWDG 复位。
为什么不直接喂 IWDG
如果每个线程都直接调用 HAL_IWDG_Refresh,那只要有一个线程还活着,IWDG 就不会超时——完全失去了监控其它线程的能力。
分离”心跳上报”和”硬件喂狗”两层,才能实现”任一线程卡死就复位”的需求。
小结
看门狗的设计哲学是:信任但验证。每个线程按自己的节奏正常运行时上报心跳;监督线程只在异常时介入。这样既不会因为误判频繁复位,又能在真正故障时快速恢复。
对于工业现场来说,“自动复位恢复”比”卡死等人工干预”靠谱得多。