窗口看門狗(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 重啟 → 串口提示“窗口看門狗復位!”。