3. 看门狗机制原理

看门狗(Watchdog)是嵌入式与安全关键系统中最基础、也最有效的自恢复机制之一。它的核心思想非常朴素:系统必须在规定时间内主动“报到”,证明自己仍然活着;一旦超时未报到,看门狗就判定系统已经失控,并强制其复位或进入安全状态。理解看门狗,关键要抓住三个要素:定时器、喂狗动作和复位策略。这三者共同决定了看门狗是“救命稻草”还是“形同虚设”。

一、看门狗定时器:机制的心脏

看门狗定时器(WDT,Watchdog Timer)本质上是一个递减或递增的计数器。系统正常运行时,软件周期性地“喂狗”,即重新装载计数值,使计数器永远不会到达溢出点。如果程序跑飞、死循环、任务阻塞或中断被长期关闭,喂狗动作停止,计数器最终溢出,触发复位信号。

从参数角度看,看门狗定时器有几个关键量:

  • 超时时间(Timeout):计数器从初值到溢出所需的时间,决定了系统能容忍多长时间的“失联”。
  • 喂狗窗口:部分高级看门狗要求喂狗必须落在特定时间窗口内,过早或过晚都视为异常,可防止程序因跑飞而“疯狂喂狗”。
  • 时钟源:可来自独立低速振荡器(如 32.768kHz)、内部 RC 或总线时钟。独立时钟源抗干扰能力更强。
  • 复位输出:溢出后产生内部复位、外部引脚复位,或仅产生中断。
看门狗定时器的可靠性,首先取决于它的时钟是否独立。如果看门狗与 CPU 共用主时钟,一旦主时钟失效,看门狗也会一起“失明”。

二、硬件看门狗与软件看门狗

按实现载体,看门狗可分为硬件看门狗和软件看门狗,两者在可靠性、成本和灵活性上差异明显。

硬件看门狗通常由芯片内部独立模块或外部专用芯片实现。它拥有独立时钟源和复位电路,即使 CPU 内核完全死锁,只要时钟仍在运行,它依然能够触发复位。外部看门狗芯片(如 MAX6369、TPS3813 等)还可通过 GPIO 喂狗,具备更强的抗软件失效能力。其缺点是增加成本、占用板级空间,且超时时间通常由硬件固定或有限可调。

软件看门狗则依赖系统中的一个定时器中断或任务来计数。它实现简单、超时时间灵活,但致命弱点是:如果系统关中断、调度器崩溃或该任务本身被阻塞,软件看门狗也会随之失效。因此,软件看门狗更适合作为“任务级健康检查”,而不能替代硬件看门狗承担最终复位职责。

对比维度 硬件看门狗 软件看门狗
时钟独立性 独立时钟源,可靠性高 依赖系统时钟/中断
CPU 死锁时 仍可复位 通常失效
超时时间 固定或有限可调 灵活可配置
成本与复杂度 较高,需外部芯片或专用模块 低,仅需定时器资源
典型用途 最终安全复位 任务级健康监测

三、喂狗机制:不是“随便喂”

喂狗机制的设计质量,直接决定看门狗能否真正发现故障。常见的喂狗方式包括:

  1. 单点喂狗:在主循环中直接写寄存器。实现最简单,但若主循环中某段代码死循环,喂狗仍可能被执行,无法发现局部故障。
  2. 任务级喂狗:每个关键任务维护自己的“存活标志”,由监控任务统一检查后再喂狗。任一任务超时未更新标志,则停止喂狗,触发复位。
  3. 窗口喂狗:喂狗必须落在允许窗口内,防止程序跑飞后高频喂狗掩盖故障。
  4. 挑战-应答喂狗:看门狗给出随机数或序列,软件必须正确计算并回写,防止简单写固定值。

下面是一个任务级喂狗的伪代码示例,展示了“先检查、后喂狗”的基本逻辑:

// 各任务周期更新自己的心跳计数
volatile uint32_t task_a_heartbeat = 0;
volatile uint32_t task_b_heartbeat = 0;

void task_a(void) {
    while (1) {
        // 执行任务逻辑
        task_a_heartbeat++;
        delay_ms(10);
    }
}

void watchdog_monitor(void) {
    uint32_t last_a = 0, last_b = 0;
    while (1) {
        if (task_a_heartbeat == last_a ||
            task_b_heartbeat == last_b) {
            // 有任务未更新,停止喂狗,等待复位
            while (1) { }
        }
        last_a = task_a_heartbeat;
        last_b = task_b_heartbeat;
        feed_watchdog();   // 所有任务健康,才喂狗
        delay_ms(100);
    }
}
喂狗点应放在“所有关键任务都已完成一轮”之后,而不是放在某个单一循环里。喂狗的本质是“系统整体健康的投票”,不是“某个函数的例行公事”。

四、复位策略:复位之后怎么办

看门狗触发后,系统通常面临几种复位策略选择:

  • 整机复位:最彻底,重新初始化所有外设和任务,适合大多数失控场景。
  • 部分复位:仅复位异常子系统,保留关键状态,适合高可用系统。
  • 降级运行:关闭非关键功能,进入安全模式并告警,适合不能立即停机的场景。
  • 复位原因记录:在复位前将看门狗标志、任务状态写入非易失存储,便于事后定位。

一个常见的误区是:看门狗复位后,系统直接重新初始化并继续运行,却不记录复位原因。这样会导致同一故障反复发生却无法定位。正确的做法是在启动时读取复位状态寄存器,区分上电复位、看门狗复位、外部复位等,并写入日志。

if (RSTSRC & WDT_RESET_FLAG) {
    log_error("Reset caused by watchdog timeout");
    save_crash_context();
}
clear_reset_flags();
不要把看门狗当作“万能重启按钮”。如果喂狗逻辑本身有缺陷,看门狗可能永远不触发;如果复位后不记录原因,故障就会变成“偶发重启”,难以复盘。

五、典型场景与复盘要点

在工业控制器中,常见做法是:外部硬件看门狗负责最终复位,MCU 内部软件看门狗负责任务级监测,关键任务通过心跳标志向监控任务汇报。监控任务只有在所有心跳都正常时才翻转 GPIO 喂外部看门狗。这样,即使 RTOS 调度器崩溃,外部看门狗仍能复位系统。

复盘时建议重点检查以下清单:

  • 看门狗时钟是否独立于 CPU 主时钟?
  • 喂狗点是否覆盖所有关键任务,而非单一主循环?
  • 超时时间是否大于最坏情况下的任务执行时间,又足够短以快速恢复?
  • 是否启用了窗口喂狗或挑战-应答机制?
  • 复位原因是否被记录并可读?
  • 是否存在关中断时间过长导致喂狗被延迟的路径?

只有把定时器、喂狗机制和复位策略作为一个整体来设计,看门狗才能真正成为系统的“最后一道防线”,而不是一个被形式化绕过的摆设。

看门狗机制:定时器 · 喂狗 · 复位策略 硬件看门狗(独立时钟) 独立时钟源 递减计数器 溢出 → 复位信号 软件看门狗(任务级监测) 任务A心跳 任务B心跳 监控任务统一喂狗 喂狗动作 窗口内 · 挑战应答 · 心跳投票 复位策略 整机复位 部分复位 降级运行 记录复位原因