type: raw source: bilibili lecture: "14-1 WDG看门狗, 14-2 独立看门狗 & 窗口看门狗" lecture_material: "14-1 WDG看门狗, 14-2 独立看门狗&窗口看门狗" project_code: "14-1 独立看门狗, 14-2 窗口看门狗"
看门狗(Watchdog)是一种硬件定时器,当程序卡死或跑飞时,自动复位 MCU。
工作原理:程序正常运行时周期性喂狗(重装计数器);若超时未被喂狗,定时器溢出 → 产生系统复位。
教师讲解:看门狗就像一个"监工"——你必须在规定时间内告诉他"我还活着"。如果你没来得及说,他就认为你死了,直接重启整个系统。在工业现场——电机控制、PLC、汽车电子——这是必备的安全机制。
如果不看门狗,设备会"僵死"在现场,需要人工断电重启。
上图:独立看门狗 IWDG 模块框图(来源:STM32入门教程 PPT 第188页)
| 参数 | 值 |
|---|---|
| 时钟源 | LSI(内部低速 RC,约 40kHz) |
| 计数器 | 12-bit 递减计数器 |
| 预分频 | 4 / 8 / 16 / 32 / 64 / 128 / 256 |
| 超时范围 | ~0.1ms ~ ~26.2s(配置决定) |
| 模式 | 只能复位 |
| 工作状态 | 待机/停止模式下仍持续计数 |
| 寄存器写保护 | 需要写入访问使能 |
$$T{out} = \frac{PR scaler \times (RLR + 1)}{f{LSI}}$$
示例:Prescaler = 16,RLR = 2499
$$T_{out} = \frac{16 \times (2499 + 1)}{40000} = 1.0s$$
教师强调:预分频越大的超时范围越大;但 LSI 本身精度不高(漂移 ±50%),所以实际超时时间需要留够余量。
| 预分频 | RLR | 理论超时(LSI=40kHz) |
|---|---|---|
| 16 | 2499 | 1.0 s |
| 64 | 2499 | 4.0 s |
| 128 | 4095 | 13.1 s |
| 256 | 4095 | 26.2 s |
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);IWDG_SetPrescaler(IWDG_Prescaler_16);IWDG_SetReload(2499);IWDG_ReloadCounter();IWDG_Enable();⚠ 使能后无法关闭,只能由复位停止。
int main(void)
{
OLED_Init();
Key_Init();
// 判断复位来源
if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) == SET)
{
OLED_ShowString(2, 1, "IWDGRST");
RCC_ClearFlag();
}
else
{
OLED_ShowString(3, 1, "RST");
}
// IWDG 配置:超时 1 秒
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
IWDG_SetPrescaler(IWDG_Prescaler_16); // 预分频 16
IWDG_SetReload(2499); // RLR = 2499
IWDG_ReloadCounter(); // 首次喂狗
IWDG_Enable(); // 使能 → cannot stop
while (1)
{
Key_GetNum(); // 阻塞式按键扫描(模拟主循环卡死时,按下按键不放会卡住)
IWDG_ReloadCounter(); // 喂狗
OLED_ShowString(4, 1, "FEED");
Delay_ms(200);
OLED_ShowString(4, 1, " ");
Delay_ms(600); // 喂狗间隔 800ms < 1000ms → 不会复位
}
}
IWDG_HandleTypeDef hiwdg;
void IWDG_Init_HAL(void)
{
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_64; // 40kHz / 64 = 625Hz
hiwdg.Init.Reload = 625; // 625 / 625Hz ≈ 1s 超时
HAL_IWDG_Init(&hiwdg);
}
// 喂狗
// HAL_IWDG_Refresh(&hiwdg);
// 复位来源判断(HAL 中通过 RCC 寄存器读取)
if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET)
{
__HAL_RCC_CLEAR_RESET_FLAGS();
}
实验现象:正常运行时 "FEED" 每秒闪烁一次;按住按键不放(卡死 2s)→ 系统超时复位 → OLED 显示 "IWDGRST"。
判定复位来源:
RCC_GetFlagStatus(RCC_FLAG_IWDGRST) → IWDG 复位RCC_GetFlagStatus(RCC_FLAG_WWDGRST) → WWDG 复位RCC_ClearFlag() → 清除所有复位标志教师特别提醒:有些同学把喂狗放在 SysTick 中断里,这是致命的!如果主循环死锁但中断仍然正常触发,看门狗永远不会复位——掩盖了真正的故障。
上图:窗口看门狗 WWDG 模块框图(来源:STM32入门教程 PPT 第191页)
| 参数 | 值 |
|---|---|
| 时钟源 | PCLK1(APB1 时钟,典型 36MHz) |
| 计数器 | 7-bit 递减计数器(CNT,T[6:0]) |
| 喂狗窗口 | 必须在上窗口和下窗口之间喂狗 |
| 预分频 | 1 / 2 / 4 / 8 |
| 超时范围 | ~0.05ms ~ ~36.86ms(PCLK1=36MHz) |
| 复位方式 | 复位 或 提前唤醒中断(EWI) |
| 工作模式 | 停止模式下停止计数 |
T[6:0] 寄存器
|
v
┌───────────────┐
│ 7F (127) │ ← 上窗口(启动值)
│ ↓ │
│ 窗口值 │ ← 必须在此之后喂狗
│ ↓ │
│ 3F (63) │ ← 下窗口(固定为 0x40)
│ ↓ │
│ 0 │ ← 计数器减到 0x3F 时复位
└───────────────┘
教师讲解:窗口看门狗比独立看门狗更"严苛"——不仅不能太晚喂狗,还不能太早喂狗。这可以检测"程序跑飞但碰巧喂了狗"的情况。比如代码在错误分支碰巧执行了喂狗指令,如果在窗口之外,照样触发复位。
$$T{out} = \frac{Prescaler \times (CNT{init} - 0x3F)}{f_{PCLK1}}$$
示例:PCLK1 = 36MHz,Prescaler = 8,CNT = 0x54(=84)
$$T_{out} = \frac{8 \times (84 - 63)}{36 \times 10^6} \approx 4.67\mu s$$
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);WWDG_SetPrescaler(WWDG_Prescaler_8);WWDG_SetWindowValue(0x40 | 21);(窗口值 = 21 + 63 = 84)WWDG_Enable(0x40 | 54);(CNT = 54 + 63 = 117)WWDG_SetCounter(0x40 | 54);⚠ WWDG 的寄存器和计数值使用
0x40 | xx组合:0x40=64 是下窗口,加上 63 后才等于 127。
int main(void)
{
OLED_Init();
Key_Init();
// 判断复位来源
if (RCC_GetFlagStatus(RCC_FLAG_WWDGRST) == SET)
{
OLED_ShowString(2, 1, "WWDGRST");
RCC_ClearFlag();
}
else
{
OLED_ShowString(3, 1, "RST");
}
// WWDG 初始化
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
WWDG_SetPrescaler(WWDG_Prescaler_8); // 预分频 8
WWDG_SetWindowValue(0x40 | 21); // 窗口值 84
WWDG_Enable(0x40 | 54); // CNT = 117,超时约 12us
while (1)
{
Key_GetNum(); // 阻塞式按键扫描
OLED_ShowString(4, 1, "FEED");
Delay_ms(20);
OLED_ShowString(4, 1, " ");
Delay_ms(20);
WWDG_SetCounter(0x40 | 54); // 喂狗
}
}
喂狗间隔 40ms。窗口值 84 ÷ 超时时间约 50us → 必须在 30~50us 内喂狗,否则复位。这就是 WWDG 的"窗口"约束。
WWDG_HandleTypeDef hwwdg;
void WWDG_Init_HAL(void)
{
hwwdg.Instance = WWDG;
hwwdg.Init.Prescaler = WWDG_PRESCALER_8;
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)
{
// 复位前保存关键数据
Store_Save();
}
EWI 可在计数器到 0x40 时产生中断,在复位前执行抢救操作(如保存数据、记录日志)。
void WWDG_IRQHandler(void)
{
if (WWDG_GetFlagStatus())
{
// 在复位前保存关键数据
Store_Save();
WWDG_ClearFlag();
}
}
| 对比项 | IWDG | WWDG |
|---|---|---|
| 时钟 | LSI 40kHz(专用) | PCLK1(与系统时钟共享) |
| 计数器宽度 | 12-bit | 7-bit |
| 超时范围 | 0.1ms ~ 26.2s | 0.05ms ~ 36.86ms |
| 喂狗条件 | 仅不能超时 | 窗口范围内 + 不超时 |
| 停止模式下 | 持续计数 | 停止计数 |
| 调试模式下 | 默认停止(可用 DBGMCU 配置) | 默认停止(可用 DBGMCU 配置) |
| 主要用途 | 系统级大周期保护 | 代码执行时间精确监控 |
教师总结:IWDG 适合"慢速保护"——主循环 1 秒内一定要跑完一圈;WWDG 适合"快速精准保护"——某个操作必须在 1 毫秒内完成,否则异常。实际项目中,两个看门狗可以同时使用(IWDG 兜底大循环、WWDG 监控关键任务)。
| 场景 | 推荐 | 原因 |
|---|---|---|
| 工业电机控制 | IWDG + WWDG | IWDG 保护主循环,WWDG 保护 FOC 算环境 |
| 电池供电传感器 | IWDG | 超时可设置较长(防止低频唤醒后未喂狗) |
| 通信网关 | WWDG | 对数据包处理时间有严格限制 |
| 汽车 ECU | 两者同时使用 | ISO 26262 功能安全有要求 |
⚠ 调试时:建议暂停调试器时关闭看门狗或延长超时,避免每次断点暂停都触发复位。