窗口看门狗(WWDG)
窗口看门狗(WWDG)
1. WWDG 简介
作用:在应用跑飞、死循环、长时间被中断占用等异常时,强制复位 MCU,提高系统可靠性。
时钟来源:来自 APB1 时钟 (PCLK1) 的分频(与 IWDG 的 LSI 独立时钟不同)。
核心特性:
7 位递减计数器 T[6:0]:从
0x7F
递减到0x40
,再到0x3F
时复位。窗口值 W[6:0]:喂狗必须在“窗口”内进行(T ≤ W 且 T > 0x3F);
喂早了(T > W)立刻复位;
喂晚了(T ≤ 0x3F)复位。
EWI(Early Wakeup Interrupt)提前唤醒中断:当 T 递减到
0x40
时产生中断,给你“最后一次机会”喂狗。
2. 工作原理与简化框图
计数与窗口规则
T: 0x7F ──… 递减 …── 0x5F ──…── 0x40 ── 0x3F↑允许最早喂的位置=W (例如0x5F) ↑EWI中断 ↑复位点
必须在 T∈(0x3F, W] 这个区间喂狗;早于W立即复位;晚于0x3F也复位
时钟链路与计数
PCLK1 ──÷4096──÷(1/2/4/8)──> 计数时钟tick ──> 7位递减计数器 T[6:0]├─ T==0x40 → 触发EWI└─ T==0x3F → 复位MCU
3. 寄存器与 HAL 函数
关键寄存器(STM32F1 系列)
WWDG_CR(控制/计数器寄存器)
WDGA
(bit7):1=启动 WWDGT[6:0]
:当前计数值(写入相当于刷新/喂狗)
WWDG_CFR(配置寄存器)
W[6:0]
:窗口值WDGTB[1:0]
:预分频(1/2/4/8)EWI
(bit9):提前唤醒中断使能
WWDG_SR(状态寄存器)
EWIF
(bit0):EWI 标志位(到0x40
时置位,软件写 0 清除)
HAL 屏蔽了直接位操作,底层完成上述写寄存器逻辑。
HAL 相关 API
HAL_WWDG_Init(&h)
:配置并启动 WWDG(设定 Counter、Window、Prescaler、EWI)。HAL_WWDG_Refresh(&h)
:喂狗(刷新计数器)。HAL_WWDG_EarlyWakeupCallback(&h)
:EWI 回调(T==0x40 时被调用)。HAL_WWDG_IRQHandler(&h)
:在WWDG_IRQHandler
里调用它,才能触发回调。复位来源判断:
__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)
:是否 WWDG 复位;__HAL_RCC_CLEAR_RESET_FLAGS()
:清除复位标志。
4. 溢出时间与“喂狗窗口”计算
计数器递减 tick 周期
Prescaler ∈ {1,2,4,8}
(HAL:WWDG_PRESCALER_1/2/4/8
)例:72 MHz 系统 + 默认 APB1=36 MHz,若 Prescaler=8:
代入你代码的参数
T_init=0x7F
,W=0x5F
,Prescaler=8
,PCLK1≈36 MHz
t_tick ≈ 0.910 ms
t_min = (0x7F-0x5F)*t_tick = 32*tick ≈ 29.1 ms
t_EWI = (0x7F-0x40)*t_tick = 63*tick ≈ 57.3 ms
t_reset = (0x7F-0x3F)*t_tick = 64*tick ≈ 58.3 ms
允许喂狗窗口:
[29.1 ms, 58.3 ms)
,窗口宽度约 29.1 ms你主循环
delay_ms(50)
→ 50 ms 恰好落在窗口内,所以正常不会复位。若偶尔卡住,EWI 在 ~57.3 ms 触发回调,回调里你又
wwdg_feed()
,可兜底避免复位。
5. 配置步骤(HAL 版)
打开 WWDG 时钟 + 中断(在
HAL_WWDG_MspInit
里)
__HAL_RCC_WWDG_CLK_ENABLE();
HAL_NVIC_SetPriority(WWDG_IRQn, 2, 2);
HAL_NVIC_EnableIRQ(WWDG_IRQn);
2.初始化并启动
wwdg_handle.Instance = WWDG;
wwdg_handle.Init.Counter = 0x7F; // 初值(7位)
wwdg_handle.Init.Window = 0x5F; // 窗口
wwdg_handle.Init.Prescaler= WWDG_PRESCALER_8; // 预分频
wwdg_handle.Init.EWIMode = WWDG_EWI_ENABLE; // 开EWI
HAL_WWDG_Init(&wwdg_handle);
3.中断服务与回调
void WWDG_IRQHandler(void){ HAL_WWDG_IRQHandler(&wwdg_handle); }void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *h){wwdg_feed(); // 最后时刻兜底喂狗led2_toggle(); // 观测EWI是否发生
}
4.周期性喂狗(窗口内)
HAL_WWDG_Refresh(&wwdg_handle);
5.复位来源识别
if(__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST)) { /* ... */ }
__HAL_RCC_CLEAR_RESET_FLAGS();
调试注意:单步/断点会让时间停住。建议在调试时冻结 WWDG:
DBGMCU->APB1FZ |= DBGMCU_APB1_FZ_DBG_WWDG_STOP;
(CubeMX 可勾选)
6.HAL 可选值速查
wwdg_handle.Init.Prescaler
:WWDG_PRESCALER_1 / _2 / _4 / _8
wwdg_handle.Init.EWIMode
:WWDG_EWI_DISABLE / WWDG_EWI_ENABLE
复位标志(
__HAL_RCC_GET_FLAG()
):RCC_FLAG_WWDGRST / IWDGRST / PINRST / PORRST / SFTRST
等
计数/窗口范围:
Counter
、Window
都是 7 位有效:0x40 ~ 0x7F
7. IWDG vs WWDG(差异对照)
特性 | IWDG(独立看门狗) | WWDG(窗口看门狗) |
---|---|---|
时钟源 | LSI(约40kHz,独立) | APB1(PCLK1)/4096/Presc |
能否停表 | 一旦启动不可停 | 可在调试时冻结(DBGMCU) |
喂狗限制 | 只要周期内喂即可 | 必须在窗口内喂(太早/太晚都复位) |
预警 | 无 | EWI(T==0x40) |
典型用途 | 无人值守、兜底安全 | 需要“节拍约束”的系统(防止过早喂狗掩盖问题) |
main.c
#include "sys.h" // 系统时钟初始化函数
#include "delay.h" // 延时函数
#include "led.h" // LED 控制
#include "uart1.h" // 串口1
#include "wwdg.h" // 窗口看门狗驱动int main(void)
{HAL_Init(); /* 初始化 HAL 库(NVIC 分组、SysTick 定时器等) */stm32_clock_init(RCC_PLL_MUL9); /* 设置系统时钟为 72MHz(HSE=8MHz * 9) */led_init(); /* 初始化 LED GPIO */uart1_init(115200); /* 初始化串口1,波特率 115200 *//* 初始化窗口看门狗* 参数1 tr:计数器初值(0x40 ~ 0x7F,最高7位有效)* 参数2 wr:窗口值(0x40 ~ 0x7F),喂狗时计数器必须 <= wr 才有效* 参数3 psc:分频器* 本例:Counter=0x7F (127),Window=0x5F (95),Prescaler=8*/wwdg_init(0x7f, 0x5f, WWDG_PRESCALER_8);printf("hello world!\r\n");/* 判断上次复位原因 */if(__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST) != RESET){printf("窗口看门狗复位!\r\n");__HAL_RCC_CLEAR_RESET_FLAGS(); /* 清除复位标志 */}elseprintf("外部复位!\r\n");while(1){ delay_ms(50); /* 延时 50ms */wwdg_feed(); /* 喂狗(刷新计数器) */led1_toggle(); /* 翻转 LED1,观察程序是否在运行 */}
}
wwdg.c
#include "wwdg.h"
#include "led.h"WWDG_HandleTypeDef wwdg_handle = {0}; // 窗口看门狗句柄/* 初始化窗口看门狗* tr: Counter 初值 (0x40 ~ 0x7F)* wr: Window 窗口值 (0x40 ~ 0x7F)* psc: 预分频系数*/
void wwdg_init(uint8_t tr, uint8_t wr, uint32_t psc)
{wwdg_handle.Instance = WWDG; // 指定实例 = 窗口看门狗外设wwdg_handle.Init.Counter = tr; // 设置计数器初值wwdg_handle.Init.Window = wr; // 设置窗口值wwdg_handle.Init.Prescaler = psc; // 设置分频系数wwdg_handle.Init.EWIMode = WWDG_EWI_ENABLE; // 使能提前唤醒中断(EWI)HAL_WWDG_Init(&wwdg_handle); // 初始化硬件
}/* MSP 初始化:底层硬件配置 */
void HAL_WWDG_MspInit(WWDG_HandleTypeDef *hwwdg)
{__HAL_RCC_WWDG_CLK_ENABLE(); // 打开 WWDG 时钟/* 配置中断优先级 */HAL_NVIC_SetPriority(WWDG_IRQn, 2, 2); // 抢占优先级2,子优先级2HAL_NVIC_EnableIRQ(WWDG_IRQn); // 使能 WWDG 中断
}/* 窗口看门狗中断服务函数 */
void WWDG_IRQHandler(void)
{HAL_WWDG_IRQHandler(&wwdg_handle); // 调用 HAL 库中断处理
}/* 提前唤醒回调函数* 当计数器降到 0x40 时产生 EWI 中断* 在这里可以做一些预处理(比如打印信息、刷新寄存器等)*/
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{wwdg_feed(); // 喂狗(刷新计数器)led2_toggle(); // 翻转 LED2,表示进入了回调
}/* 喂狗函数:刷新 WWDG 计数器 */
void wwdg_feed(void)
{HAL_WWDG_Refresh(&wwdg_handle);
}
实验现象
程序启动时
串口打印 "hello world!"。
判断复位来源,如果是看门狗复位,会打印“窗口看门狗复位!”。
正常情况(延时 50ms < 看门狗溢出时间)
主循环中不断
wwdg_feed()
,LED1 每 50ms 翻转一次。程序一直运行,LED1 闪烁。
提前唤醒中断现象
当计数器下降到 0x40,触发 EWI 中断,执行
HAL_WWDG_EarlyWakeupCallback
:喂狗(防止复位)
LED2 翻转,表示进入了回调。
异常情况(如果不喂狗,或者喂狗太早/太晚)
WWDG 会复位 MCU,串口下次启动会打印“窗口看门狗复位!”。
和独立看门狗 (IWDG) 不同,WWDG 多了“窗口限制”:喂狗必须在合适的范围内,太早/太晚都不行。
可选参数
A. wwdg_handle.Init.Counter
范围:
0x40 ~ 0x7F
(7 位有效)功能:设置计数器初值,从该值递减计数,减到 0x3F 时复位。
B. wwdg_handle.Init.Window
范围:
0x40 ~ 0x7F
功能:定义一个“窗口值”。
喂狗必须在计数器 ≤ Window 时才有效。
如果喂狗时计数器 > Window → 立即复位!
C. wwdg_handle.Init.Prescaler
可选值(分频系数):
WWDG_PRESCALER_1
WWDG_PRESCALER_2
WWDG_PRESCALER_4
WWDG_PRESCALER_8
作用:控制计数器递减的速度。
D. wwdg_handle.Init.EWIMode
WWDG_EWI_DISABLE
:不启用提前唤醒中断WWDG_EWI_ENABLE
:启用 EWI,当计数器降到 0x40 时产生中断(可在中断中喂狗)。
E. 复位标志
通过 __HAL_RCC_GET_FLAG()
获取:
RCC_FLAG_WWDGRST
→ 窗口看门狗复位RCC_FLAG_IWDGRST
→ 独立看门狗复位RCC_FLAG_PINRST
→ 外部复位RCC_FLAG_PORRST
→ 上电复位RCC_FLAG_SFTRST
→ 软件复位
总结
WWDG 与 IWDG 的区别:
IWDG:只要定期喂狗就行,没有窗口要求,独立时钟(LSI),一旦开启不可关闭。
WWDG:有窗口限制,喂狗太早/太晚都复位,用系统时钟分频,支持提前唤醒中断。
本实验现象:
LED1 周期闪烁,LED2 在回调中翻转;
如果关闭喂狗 → MCU 重启 → 串口提示“窗口看门狗复位!”。