--- tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(扩展篇)V1.0.0 — 看门狗章节 + 配套代码27~28" author: "尚硅谷研究院" date: 2026-07-16 created: 2026-07-15 --- # 看门狗IWDG与WWDG > **用生活理解**:看门狗就像社区里的保安——你得定时去保安亭刷卡(喂狗),证明你还在正常工作。如果你一直没来刷卡(程序跑飞/卡死),保安就拉响警报(系统复位)。**IWDG** 是独立供电的保安(不怕停电),**WWDG** 是严格规定只能在特定时间窗口内刷卡的保安。 --- ## 为什么要看门狗 嵌入式系统运行中可能因电磁干扰、电源波动、软件 bug 等原因导致程序**跑飞或死循环**。看门狗可以在程序异常时自动复位系统,保证设备不会"死机"。 --- ## 独立看门狗 IWDG IWDG(Independent Watchdog)使用**独立的 LSI 时钟(~40KHz)**,与主时钟独立——即使主时钟停振,IWDG 仍然工作。 ### IWDG 框图 ``` LSI(~40KHz) → 8位预分频器 → 12位递减计数器(RL[11:0]) → 复位 ↑ 键寄存器(KR)写 0xAAAA 重装 ``` ### IWDG 寄存器 | 寄存器 | 功能 | | ---------------------- | ------------------------------------------------------- | | **KR** (键寄存器) | 写 0xCCCC 启动、写 0xAAAA 喂狗、写 0x5555 允许写 PR/RLR | | **PR** (预分频器) | 分频系数(3 位) | | **RLR** (重装载寄存器) | 12 位重装值(0~4095) | ### IWDG 超时时间计算 ``` Tout = (RLR + 1) × 4 × 2^PR / LSI LSI ≈ 40KHz ``` **常见超时时间表(LSI=40KHz)**: | PR[2:0] | 分频系数 | RLR=4095 超时 | 分辨率 | | ------- | -------- | ------------- | ------ | | 000 | 4 | 0.41s | 0.1ms | | 001 | 8 | 0.82s | 0.2ms | | 010 | 16 | 1.64s | 0.4ms | | 011 | 32 | 3.28s | 0.8ms | | 100 | 64 | 6.55s | 1.6ms | | 101 | 128 | 13.11s | 3.2ms | | 110 | 256 | 26.21s | 6.4ms | | 111 | 256 | 26.21s | 6.4ms | **实验配置**:PR=4(分频64), RLR=2499 ``` Tout = 2500 × 64 × 4 / 40000 = 16s ``` ### 实验 1:寄存器版 IWDG **项目路径**:`stm32/27_iwdg_test_register` **文件:`stm32/27_iwdg_test_register/Hardware/IWDG/iwdg.h`** ```c #ifndef __IWDG_H #define __IWDG_H #include "stm32f10x.h" void IWDG_Init(void); void IWDG_Refresh(void); // 喂狗 #endif ``` **文件:`stm32/27_iwdg_test_register/Hardware/IWDG/iwdg.c`**(实际代码:先启动,再配置,最后喂狗) ```c #include "iwdg.h" void IWDG_Init(void) { IWDG->KR = 0xCCCC; // 启动看门狗 IWDG->KR = 0x5555; // 解除寄存器保护 IWDG->PR = 4; // 预分频 64 IWDG->RLR = 2499; // 重装载值 2499 → 超时约 16s IWDG_Refresh(); // 重新加载计数值 } void IWDG_Refresh(void) { IWDG->KR = 0xAAAA; // 喂狗 } ``` **文件:`stm32/27_iwdg_test_register/User/main.c`** ```c #include "usart.h" #include "delay.h" #include "key.h" #include "iwdg.h" uint8_t flag; int main(void) { USART_Init(); Key_Init(); IWDG_Init(); printf("尚硅谷独立看门狗实验...\n"); while (1) { printf("正常执行程序...\n"); Delay_ms(3000); // 模拟主循环 3s 耗时 if (flag) // 按键中断中设置的标志位 { Delay_ms(3000); // 模拟额外耗时 3s(累计 6s,超时 16s 以内,不会复位) flag = 0; } IWDG_Refresh(); // 喂狗 printf("程序执行结束,喂狗成功!\n"); } } ``` ### 实验 2:HAL 库版 IWDG **项目路径**:`stm32/28_iwdg_test_hal` **文件:`stm32/28_iwdg_test_hal/Core/Src/main.c`** ```c #include "main.h" #include "iwdg.h" #include "usart.h" #include "gpio.h" uint8_t flag; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_IWDG_Init(); // CubeMX 生成的 IWDG 初始化 MX_USART1_UART_Init(); printf("尚硅谷独立看门狗实验...\n"); while (1) { printf("正常执行程序...\n"); HAL_Delay(3000); if (flag) { HAL_Delay(3000); flag = 0; } HAL_IWDG_Refresh(&hiwdg); // HAL 库喂狗 printf("程序执行结束,喂狗成功!\n"); } } void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { flag = 1; printf("按键按下...\n"); } ``` HAL 库 SystemClock_Config 需使能 LSI: ```c RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI | RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.LSIState = RCC_LSI_ON; ``` --- ## 窗口看门狗 WWDG WWDG(Window Watchdog)更加灵活——它要求必须在特定的**时间窗口内**喂狗: | 特性 | 说明 | | ------------------ | -------------------------------------------- | | 时钟源 | APB1(36MHz),不是独立时钟 | | 计数器 | 7 位递减(T[6:0],最大值 127) | | 窗口上限 | 由 CFR 寄存器 W[6:0] 设定 | | 喂狗时机 | **必须在 CNT > W[6:0] 且 CNT ≠ 0x3F 时喂狗** | | 早期唤醒中断 (EWI) | CNT=0x40 时产生中断(可提前预警) | ### WWDG 工作时序 ``` 窗口区(允许喂狗) 禁止区 CNT: 127 ← ... ← [W+1] ← ... ← 0x40 ← 0x3F → 复位 ↑ ↑ 最早喂狗时机 最晚喂狗时机 ``` - 喂狗太早(CNT > W+1):复位 - 喂狗太晚(CNT < 0x40/H):复位 - 必须在 **W < CNT ≤ 0x7F** 范围内喂狗 → 正常 ### WWDG 超时计算 ``` Tout = (CNT - W) × (PSC + 1) / APB1 ``` ### WWDG 配置示例(寄存器版) ```c void WWDG_Init(void) { RCC->APB1ENR |= RCC_APB1ENR_WWDGEN; WWDG->CFR |= WWDG_CFR_WDGTB_1 | WWDG_CFR_WDGTB_0; // WDGTB=11: 分频 8 WWDG->CFR &= ~WWDG_CFR_W; WWDG->CFR |= (0x50 << 0); // 窗口值 0x50 WWDG->CR = 0x7F; // 计数器初始值 127 WWDG->CR |= WWDG_CR_WDGA; // 使能 WWDG } void WWDG_Feed(void) { WWDG->CR = 0x7F; // 重装计数器 } void WWDG_IRQHandler(void) { if (WWDG->SR & WWDG_SR_EWIF) { // 距离复位还有 0x40 个计数周期(约 284μs @ 36MHz/8) WWDG->SR = 0x00; } } ``` ### HAL 版 WWDG ```c WWDG_HandleTypeDef hwwdg; hwwdg.Instance = WWDG; hwwdg.Init.Prescaler = WWDG_PRESCALER_8; // WDGTB=11 hwwdg.Init.Window = 0x50; // 窗口值 hwwdg.Init.Counter = 0x7F; // 计数值初始值 hwwdg.Init.EWIMode = WWDG_EWI_ENABLE; HAL_WWDG_Init(&hwwdg); HAL_WWDG_Refresh(&hwwdg); // 喂狗 void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg) { // 距离复位还剩 64 个计数周期 } ``` --- ## IWDG vs WWDG 对比 | 对比项 | IWDG | WWDG | | ------------ | ------------------------ | ------------------------------ | | **时钟源** | LSI (~40KHz) 独立 | APB1(依赖主时钟) | | **计数器** | 12 位递减 | 7 位递减 | | **超时范围** | ~0.1ms ~ 26s | ~0.1ms ~ 若干 ms | | **喂狗窗口** | 无窗口(任意时间喂即可) | **有窗口:太早太晚都复位** | | **可停止** | ❌ 启动后不可停 | ✅ 可通过软件停止 | | **中断** | ❌ 无 | ✅ 有 EWI 早期唤醒中断 | | **独立供电** | ✅ 完全独立 | ❌ 依赖主电源 | | **选型建议** | 需要高可靠性的系统 | 需要精确检测程序执行时序的系统 | ## 核心速查表 | IWDG 操作 | 寄存器 | HAL 库 | | ----------- | ---------------- | -------------------- | | 启动 | `KR = 0xCCCC` | `HAL_IWDG_Init()` | | 配置 PR/RLR | 先 `KR = 0x5555` | 通过 `Init` 结构体 | | 喂狗 | `KR = 0xAAAA` | `HAL_IWDG_Refresh()` | | WWDG 操作 | 寄存器 | HAL 库 | | --------- | --------------- | -------------------------------- | | 启动 | `CR | = WDGA` | `HAL_WWDG_Init()` | | 喂狗 | `CR = 0x7F` | `HAL_WWDG_Refresh()` | | 配窗口 | `CFR.W = value` | `Init.Window = value` | | EWI 回调 | 手动中断函数 | `HAL_WWDG_EarlyWakeupCallback()` | ## 常见问题与避坑 1. **IWDG 一直在复位** → main 循环中忘了喂狗、喂狗间隔超过超时时间 2. **IWDG 启动后无法调试** → IWDG 一旦启动无法暂停,调试时需在断点前禁用 IWDG(Keil 中配置 DBGMCU) 3. **WWDG 喂狗太早也复位** → 检查窗口值配置(W 值必须小于 CNT 初始值) 4. **WWDG 应用场景**:适合检测程序的**执行时序是否正常**(太早或太晚说明程序逻辑异常) 5. **LSI 频率不准** → LSI 约 30~60KHz(典型 40KHz),实际超时时间 ±50%,重要场合需实测 6. **Keil 调试关闭 IWDG**:在 Debug → Settings → Debug 选项卡中配置 DBGMCU 寄存器