看门狗定时器(WDT)从入门到实战 —— 单片机系统的"守护者"


一、一句话理解

看门狗定时器(Watch Dog Timer,WDT) 是单片机的一个硬件组成部分,本质是一个递增(或递减)的定时器。程序正常运行时,它会定期被"喂"(重置);一旦程序跑飞、死循环,没人喂它,它就会超时触发系统复位,让程序重新从起点运行。

通俗类比:就像你设了一个每天早 8 点的闹钟,平时你 7:50 就醒来关掉它,闹钟永远不会响。但有一天你喝醉了睡过头,闹钟就响了——强行把你叫醒。看门狗就是单片机的"闹钟",一切正常就不叫,一出异常就叫。

在这里插入图片描述


二、看门狗的前世今生

2.1 为什么需要看门狗

在嵌入式系统中,软件 Bug 永远不可能 100% 测尽。即使代码本身正确,外部干扰也可能让 CPU 执行出错。最常见的故障模式有:

故障类型 现象 软件能否自救
死循环 程序卡在 while(1){} ❌ 不能
指针跑飞 PC 指针跳到非法地址 ❌ 不能
寄存器被改写 外设状态异常 ⚠️ 部分能
中断丢失 关键事件未响应 ⚠️ 部分能
时钟故障 主时钟停振 ❌ 不能
电压跌落 CPU 处于亚稳态 ❌ 不能

这些故障的共同点是:软件已经无法依靠自身恢复。此时只有"硬件强制复位"这一招——这就是看门狗存在的意义。

2.2 一个真实的工业现场案例

某工厂产线上的 STM32 控制板,运行 3 个月后偶发性"死机",现场人员必须断电重启。复现条件苛刻:高湿度 + 变频器频繁启停。

排查发现:变频器启停瞬间产生强电磁干扰,偶尔让 CPU 取到错误指令进入死循环。未启用看门狗是根因。

启用 IWDG(超时 1s)后,连续运行 2 年未再出现死机。

结论:看门狗是工业产品的"兜底保险",不是可选项,是必选项。


三、核心作用:防止系统跑飞

在工业现场,单片机经常受到以下干扰:

  • 电气噪声 — 电机、变频器产生的电磁干扰
  • 🔌 电源故障 — 电压骤降、浪涌
  • ⛈️ 静电放电(ESD) — 冲击可能让寄存器翻转
  • 🔄 软件 Bug — 程序进入死循环或死锁
  • 🌡️ 温度漂移 — 时钟频率偏移导致时序异常
  • 📡 射频干扰(RFI) — 无线设备、手机靠近时

这些干扰会导致程序跑飞、卡死。看门狗的作用就是兜底——当软件层面无法自救时,硬件层面强制复位,让系统恢复运行。


四、三个关键术语

4.1 喂狗(Feed / Kick the Dog)

含义:在程序中主动触发看门狗,计数器被刷新,重新开始计算。

实际操作就是调用一条指令(如 WDT_RESET() 或写入特定寄存器),告诉看门狗"我还活着"。

喂狗频率 分为两种模式:

  • 快狗(Fast-Mode):频繁喂狗,适合周期短的任务
  • 慢狗(Slow-Mode):较少喂狗,适合初始化等耗时较长的阶段

实际开发中,通常在 WDT 初始化后先设为 Slow-Mode,等周期任务正式运行后再切换为 Fast-Mode。

4.2 杀狗(Disable WDT)

含义:禁用/关闭看门狗。调试阶段常用——你不想程序跑到一半被复位,就先关掉看门狗。

⚠️ 注意:产品正式发布时务必开启看门狗,杀狗只用于调试。一个常见的"血案"是:调试时关了看门狗,发版时忘了开,量产后死机投诉雪片般飞来。

4.3 咬狗(Dog Biting / Timeout)

含义:看门狗启动后,计数器自动递减。如果在规定时间内没有收到"喂狗"指令,计数器归零,看门狗就会对 CPU 产生复位信号,强制系统重启——俗称"被狗咬了"。

在这里插入图片描述

小知识:为什么叫"狗"?——早期工程师把这种"必须定期喂食否则就咬你(复位)"的定时器形象地比作看门狗,名字沿用至今。英文 “Kick the dog” 也是同样的隐喻。


五、四类看门狗

从软硬件控制类型划分,看门狗可分为以下四类:

在这里插入图片描述

5.1 硬件看门狗(Hardware WDT)

特征 说明
位置 独立于 CPU 之外,由专用硬件电路实现
时钟 由独立的时钟驱动,外部无法更改
复位方式 输出信号引脚直接连接 CPU 的 RESET 引脚
优势 即使 CPU 完全死机,硬件看门狗依然有效

典型芯片:MAX813、TPS3823、STWD100、SP706S

适用场景:高可靠性工业控制、汽车电子、航空航天等不允许死机的场景。

5.2 软件看门狗(Software WDT)

特征 说明
位置 CPU 内置的一个计数器模块
时钟 由 CPU 本身的时钟决定
配置 计数器初值由软件设置,超时时间可在一定范围内变化
复位方式 通过中断或异常信号触发系统重启

适用场景:对时间精度要求不高的通用嵌入式应用。

5.3 独立看门狗(Independent WDT,IWDG)

特征 说明
时钟 专用低速时钟驱动(如 LSI 32kHz)
关键优势 即使主时钟发生故障,独立看门狗依然有效
独立性 完全独立于主程序运行
典型实现 STM32 IWDG

适用场景:主时钟可能不稳定的场景,如低功耗模式、时钟切换过程中。

5.4 窗口看门狗(Window WDT,WWDG)

特征 说明
时钟 由 APB1 时钟分频驱动
独特机制 设置了时间窗口——喂狗不能太早也不能太晚,必须在窗口内
检测范围 既能检测程序卡死,也能检测程序跑得太快(异常加速)

适用场景:对程序执行时间有严格要求的精密控制场景,如电机控制、电力电子。

5.5 四类看门狗横向对比

维度 硬件 WDT 软件 WDT IWDG WWDG
独立性 ★★★★★ ★★ ★★★★ ★★★
主时钟故障有效
时间精度
可检测异常加速
成本 需外接芯片 免费 免费 免费
配置灵活度
适用场景 高可靠 通用 低功耗/抗主时钟故障 精密时间控制

六、工作流程

┌─────────────────────────┐
│  看门狗初始化             │
│  设置计数器初始值          │
────────────┬────────────┘
             ▼
┌─────────────────────────┐
│  计数器开始计数            │
│  初始值递减               │
└────────────┬────────────┘
             ▼
      ┌──────────────┐
      │  定时器=0?    │
      ──┬────────┬──
     是  │        │ 否
         ▼        ▼
┌──────────┐  ┌──────────────┐
│ 喂狗!    │  │ 计数器继续    │
│ 重置计数  │  │ 递减          │
│ 回到起点  │  └─────────────┘
└──────────         │
                     ▼
              ┌──────────────┐
              │  定时器=0?   │
              └──┬────────┬──┘
             否  │        │ 是
                 ▼        ▼
          (继续计数)  ┌──────────────┐
                      │ 看门狗产生    │
                      │ 复位信号      │
                      └──────┬───────┘
                             ▼
                      ┌──────────────┐
                      │ 系统复位      │
                      │ 程序重新运行  │
                      └──────────────┘

关键步骤解读

  1. 初始化阶段:必须先设置计数器初值(决定超时时间),再启动看门狗。一旦启动,无法关闭(部分芯片支持)。
  2. 运行阶段:计数器自动递减,与主程序并行运行,互不干扰。
  3. 喂狗判断:每次喂狗,计数器被重装载回初值,重新开始递减。
  4. 超时触发:一旦计数器归零,立即产生复位信号。这个动作是硬件级别的,不需要 CPU 参与——这也是看门狗可靠性的核心。

七、STM32 看门狗实战详解

STM32 系列单片机内置两类看门狗:IWDG(独立看门狗)WWDG(窗口看门狗)。下面分别讲解。

7.1 IWDG 独立看门狗

在这里插入图片描述

7.1.1 关键寄存器
寄存器 全称 作用
KR Key Register 键寄存器。写 0xCCCC 启动 IWDG;写 0xAAAA 喂狗;写 0x5555 允许修改 PR/RLR
PR Prescaler Register 预分频寄存器。决定分频系数 /4、/8、/16…/256
RLR Reload Register 重装载寄存器。12 位,0~4095,决定计数器初值
SR Status Register 状态寄存器。PVU(PR 是否可改)、RVU(RLR 是否可改)
7.1.2 超时时间计算
T_timeout = 4 × 2^(PR+2) × (RLR+1) / f_LSI

其中:

  • PR = 0~6,对应分频系数 /4、/8、/16、/32、/64、/128、/256
  • RLR = 0~4095
  • f_LSI ≈ 32kHz(STM32 内部低速 RC 振荡器,误差 ±5%)

举例:PR=4(分频 /64),RLR=624,f_LSI=32kHz

T = 4 × 2^6 × 625 / 32000 = 4 × 64 × 625 / 32000 = 5000ms = 5s
7.1.3 HAL 库代码示例
#include "stm32f1xx_hal.h"

IWDG_HandleTypeDef hiwdg;

/* 初始化 IWDG,超时 5s */
void MX_IWDG_Init(void)
{
    hiwdg.Instance = IWDG;
    hiwdg.Init.Prescaler = IWDG_PRESCALER_64;   // PR=4,分频 /64
    hiwdg.Init.Reload = 624;                     // RLR=624
    hiwdg.Init.Window = IWDG_WINDOW_DISABLE;     // 禁用窗口模式(普通 IWDG)

    if (HAL_IWDG_Init(&hiwdg) != HAL_OK) {
        Error_Handler();
    }
}

/* 喂狗 */
void WDT_Feed(void)
{
    HAL_IWDG_Refresh(&hiwdg);   // 等价于写 KR = 0xAAAA
}

/* 主循环 */
int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_IWDG_Init();

    while (1) {
        task_sensor_read();
        WDT_Feed();              // 喂狗
        task_can_send();
        WDT_Feed();              // 喂狗
        task_uart_handle();
        WDT_Feed();              // 喂狗
    }
}
7.1.4 寄存器级裸机操作
/* 启动并配置 IWDG(无 HAL 库) */
void IWDG_Init_BareMetal(void)
{
    /* 1. 启动 IWDG:写 KR = 0xCCCC */
    IWDG->KR = 0xCCCC;

    /* 2. 允许修改 PR、RLR:写 KR = 0x5555 */
    IWDG->KR = 0x5555;

    /* 3. 设置预分频:PR=4 → 分频 /64 */
    IWDG->PR = 0x04;

    /* 4. 设置重装载值:RLR=624 → 5s 超时 */
    IWDG->RLR = 624;

    /* 5. 重装载计数器:写 KR = 0xAAAA */
    IWDG->KR = 0xAAAA;
}

/* 喂狗(重装载) */
#define IWDG_Feed()  (IWDG->KR = 0xAAAA)

7.2 WWDG 窗口看门狗

在这里插入图片描述

7.2.1 与 IWDG 的核心区别
维度 IWDG WWDG
时钟源 LSI(32kHz 独立) APB1(系统时钟分频)
计数器位数 12 位 7 位
喂狗限制 任意时刻 必须在窗口内
检测异常加速
复位前中断 ✓(可触发早期唤醒中断 EWI)
主时钟故障有效
7.2.2 窗口机制详解

WWDG 的计数器是 7 位(0~0x7F),递减计数。关键参数:

  • 窗口上限值 W:写入 CFR 寄存器的 W[6:0],决定"最早可喂狗时刻"
  • 超时阈值 0x40:计数器递减到 0x3F 时立即复位(即 T[6] 由 1→0 触发)

喂狗规则:

  • 0x7F > 计数值 > W禁止喂狗(喂了就复位)
  • W ≥ 计数值 > 0x40允许喂狗(喂狗重装载为 0x7F)
  • 计数值 = 0x3F自动复位

为什么禁止"早喂"?——如果程序异常加速(比如跳过了某些耗时的初始化),会过早到达喂狗点。窗口机制可以捕捉到这种异常。

7.2.3 HAL 库代码示例
WWDG_HandleTypeDef hwwdg;

void MX_WWDG_Init(void)
{
    hwwdg.Instance = WWDG;
    hwwdg.Init.Prescaler = WWDG_PRESCALER_8;     // 分频
    hwwdg.Init.Window = 0x50;                     // 窗口值 W
    hwwdg.Init.Counter = 0x7F;                    // 计数器初值
    hwwdg.Init.EWIMode = WWDG_EWI_ENABLE;         // 使能早期唤醒中断

    if (HAL_WWDG_Init(&hwwdg) != HAL_OK) {
        Error_Handler();
    }
}

/* 早期唤醒中断回调:复位前的最后机会 */
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
    /* 这里可以做紧急保存:写关键数据到 Flash/FRAM */
    /* 注意:到这里已经离复位只有几十个时钟周期,不能做长操作 */
    Save_Critical_Data();
}

/* 主循环喂狗(必须在窗口内) */
void WWDG_Feed_InWindow(void)
{
    /* 读 SR 状态判断当前是否在窗口内 */
    if ((hwwdg.Instance->CR & 0x7F) <= hwwdg.Init.Window) {
        HAL_WWDG_Refresh(&hwwdg);
    }
}

八、两种运行状态

8.1 正常运行

程序按预期执行,在关键位置分散放置"喂狗指令"(如 WDT_RESET())。每隔一段时间,CPU 主动喂狗,计数器重置,看门狗安静待命。

// 示例:正常喂狗流程
void main_loop(void) {
    WDT_Init(5000);       // 初始化看门狗,超时时间 5 秒
    while (1) {
        task_1();         // 执行任务 1
        WDT_Reset();      // 喂狗 ✅
        task_2();         // 执行任务 2
        WDT_Reset();      // 喂狗 ✅
        task_3();         // 执行任务 3
        WDT_Reset();      // 喂狗 ✅
    }
}

8.2 异常运行(被狗咬)

程序因干扰进入死循环,喂狗指令所在的代码段永远执行不到。看门狗等不到喂狗信号,计数器归零,自动产生复位信号,单片机从程序存储器的起始位置重新执行。

// 示例:死循环导致被咬
void main_loop(void) {
    WDT_Init(5000);       // 初始化看门狗,超时时间 5 秒
    while (1) {
        task_1();         // 执行任务 1
        WDT_Reset();      // 喂狗 ✅
        
        // task_2() 中发生死循环!
        task_2();         // ← 卡在这里,永远出不来
        
        WDT_Reset();      // ❌ 永远执行不到!
        task_3();
        WDT_Reset();
    }
    // 5 秒后 → 看门狗超时 → 系统自动复位 → 程序重新从 main() 开始
}

九、喂狗策略与常见陷阱

9.1 喂狗位置黄金法则

✅ 推荐位置 ❌ 禁止位置
主循环每次任务结束后 中断服务程序(ISR)内
关键状态机切换时 阻塞型延时函数内
长任务分段完成点 死循环 while(1){}
while(1) 顶部 看门狗初始化前

9.2 常见陷阱与避坑

陷阱 1:中断里喂狗

错误代码

void TIM2_IRQHandler(void) {   // 1ms 定时中断
    WDT_Feed();                // ❌ 错!主程序死循环时中断仍会触发,看门狗永远不会咬
}

问题:即使主程序死循环,定时中断仍会正常触发并喂狗。看门狗形同虚设。

正确做法:喂狗放在主循环。中断只置标志位,主循环检查标志位再喂狗。

陷阱 2:超时时间设置过短

错误:超时设为 10ms,但某个 Flash 写操作就要 30ms。结果:每次写 Flash 都触发复位。

正确:超时时间应 ≥ 最长单任务时间 × 1.5 倍冗余。

陷阱 3:超时时间设置过长

错误:超时设为 10s。系统死机后要等 10s 才重启,用户体验极差。

正确:通用场景 100ms~1s,工控场景 1~5s,初始化阶段用慢狗(10s+)。

陷阱 4:单一喂狗点

错误:整个主循环只在末尾喂一次狗。如果某个任务卡住,整段循环都跑不完。

正确:每个长任务后都喂一次,分散喂狗点。

陷阱 5:调试时关狗,发版忘了开

典型血案:开发阶段 #define WDT_ENABLE 0,量产代码忘了改回 1。

对策:用编译器宏强制检查——Release 构建配置中 WDT_ENABLE 必须为 1,否则编译报错。

9.3 多任务系统下的喂狗策略

在 RTOS(如 FreeRTOS)环境下,所有任务都"活着"才能喂狗,避免某个任务死掉却被其他任务喂狗掩盖。

/* FreeRTOS:监控任务喂狗策略 */
void Task_Watchdog(void *pvParameters)
{
    for (;;) {
        /* 检查所有任务是否都"打卡"过 */
        if (task_sensor_alive && task_comm_alive && task_log_alive) {
            HAL_IWDG_Refresh(&hiwdg);

            /* 清除打卡标志,下一轮重新检查 */
            task_sensor_alive = 0;
            task_comm_alive = 0;
            task_log_alive  = 0;
        }
        vTaskDelay(100);   // 100ms 检查一次
    }
}

十、看门狗测试方法

10.1 故意触发测试

在产品验证阶段,必须证明看门狗真的有效。常见做法:

/* 测试代码:故意进入死循环验证看门狗 */
void Test_WDT(void)
{
    #ifdef WDT_TEST_MODE
    printf("WDT test: entering infinite loop...\r\n");
    while (1) {
        /* 故意什么都不做,等看门狗咬 */
    }
    #endif
}

验证标准

  • 触发死循环后,超时时间内系统自动复位
  • 复位后能正常启动(看门狗不能"咬死"系统)
  • 串口/日志能记录"上次复位原因 = IWDG_RESET"

10.2 复位原因识别

STM32 通过 RCC->CSR 寄存器可识别复位来源:

void Check_Reset_Source(void)
{
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST)) {
        printf("⚠️ Reset by IWDG! (Watchdog bite)\r\n");
        /* 可记录到 Flash 供后续分析 */
    }
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)) {
        printf("⚠️ Reset by WWDG! (Window watchdog)\r\n");
    }
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST)) {
        printf("Reset by NRST pin (power-on/manual)\r\n");
    }
    /* 清除复位标志 */
    __HAL_RCC_CLEAR_RESET_FLAGS();
}

十一、多看门狗组合策略

11.1 内部 IWDG + 外部硬件 WDT

最高可靠性的场景(汽车电子、医疗、工业安全)通常组合使用:

┌──────────────┐
│   STM32 CPU  │──┬── 内部 IWDG(一级保护,超时 1s)
│              │  │
└──────────────┘  └── 外部 MAX813(二级保护,超时 2s)
                              │
                              ▼
                       直接拉低 NRST 引脚

为什么需要二级?——如果 STM32 内部时钟树完全故障(LSI 也挂了),IWDG 不会工作。外部 WDT 用独立晶振,能捕捉到这种极端情况。

11.2 IWDG + WWDG 互补

  • IWDG:主时钟故障兜底
  • WWDG:精密时间监控(防止异常加速)

两者同时启用,覆盖"卡死"和"加速"两种异常。


十二、实际工程案例

12.1 工业控制:PLC 远程 IO 模块

场景:STM32F4 + Modbus RTU + 8 路模拟量输出,部署在变电站。

方案

  • IWDG 超时 2s(主循环 100ms × 20 倍冗余)
  • 主循环每次完成 Modbus 处理后喂狗
  • 长操作(如 EEPROM 写)前临时切到慢狗
  • 复位原因写入 EEPROM,维护人员可读取"死机次数"

12.2 汽车电子:TCU 程序烧录器

场景:DAM-C3241 通过 CANFD 给汽车 TCU 烧录程序,烧录过程不能中断。

方案

  • WWDG 监控烧录时序(每个 CAN 帧 100ms 间隔,窗口 50~150ms)
  • IWDG 兜底保护(超时 5s,覆盖 WWDG 漏检场景)
  • 烧录关键步骤在 Flash 备份"断点",复位后能续烧

12.3 光伏监测:DAM-3053DC 数据采集

场景:野外光伏面板电压电流监测,无人值守,必须自恢复。

方案

  • IWDG 超时 5s(采集周期 1s × 5 倍冗余)
  • DTU-1101 无线传输失败时不喂狗,触发复位重连
  • 复位计数器记录"通信故障次数",超阈值进入低功耗维护模式

十三、开发建议

场景 建议
调试阶段 先关闭看门狗(杀狗),方便排查问题
产品发布 必须开启看门狗,这是最后一道防线
初始化阶段 使用慢狗模式,给初始化留足时间
主循环阶段 切换为快狗模式,频繁喂狗
关键代码段 在重要任务完成后立即喂狗
中断服务程序 避免在 ISR 中喂狗,防止掩盖中断异常
超时时间设置 ≥ 最长单任务 × 1.5 倍冗余
复位原因识别 启动时检查 RCC->CSR,记录复位来源
量产测试 必须验证 WDT 真的能触发复位
多任务系统 用"任务打卡"机制,所有任务都活着才喂狗

附录 A:常用 MCU 看门狗对比

MCU 内置 WDT 类型 独立时钟 窗口模式 备注
STM32F1/F4 IWDG + WWDG LSI 32kHz ✓(WWDG) 主流工业级
STM32G0/G4 IWDG + WWDG LSI 32kHz ✓(WWDG) 新一代高性能
STM32L4 IWDG + WWDG LSI 32kHz ✓(WWDG) 低功耗
ESP32 TWDT + IWDT RTC 时钟 双核,分任务看门狗
Arduino AVR 内置 WDT WDTON fuse 8 位机,2s 上限
NXP LPC1768 WWDT 内部 RC 类似 STM32 WWDG
GD32F1/F3 FWDGT + WWDGT LSI 40kHz 国产 STM32 兼容
HC32F460 WDT LSI 32kHz 国产高性能

附录 B:术语速查表

术语 英文 含义
喂狗 Feed / Kick 重置计数器,告诉 WDT “我还活着”
杀狗 Disable 关闭看门狗,仅调试用
咬狗 Bite / Timeout 计数器归零触发复位
快狗 Fast-Mode 短周期频繁喂狗
慢狗 Slow-Mode 长周期少喂狗,适合初始化
IWDG Independent Watchdog 独立看门狗,LSI 驱动
WWDG Window Watchdog 窗口看门狗,APB1 驱动
EWI Early Wakeup Interrupt 早期唤醒中断,复位前最后机会
LSI Low-Speed Internal 内部低速 RC 振荡器,约 32kHz
KR Key Register 键寄存器(IWDG 控制)
RLR Reload Register 重装载寄存器(IWDG 初值)

附录 C:参考资源

  • STM32F103 参考手册 RM0008(IWDG/WWDG 章节)
  • STM32 HAL 库用户手册 UM1850
  • ST 应用笔记 AN4065:Watchdog usage on STM32
  • 原文:知乎专栏 https://zhuanlan.zhihu.com/p/654005577

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐