STM32視窗看門狗(WWDG)
引言
之前講過了獨立看門狗,可以避免程式跑飛。這一節介紹的是視窗看門狗,他們雖然都是看門狗,但是也有許多的差別。例如視窗看門狗使用的時鐘是系統時鐘,而獨立看門狗則使用的是獨立的RC時鐘。關於兩個看門狗之間更多的不同,可以參考下面這張圖片:
視窗看門狗介紹
視窗,顧名思義,就像視窗比較器一樣,都會有一個上限值,和一個下限值。獨立看門狗只需要在計數器溢位之前,對其進行復位即可。而視窗看門狗不同,喂狗的時間點不能太早,也不能太遲。這樣做有什麼好處呢?
如果我們使用的是獨立看門狗,當程式跑飛的時候,可能會機緣巧合之下又回到了正軌,假如在計數器溢位之前回到正軌,或者在錯誤的環節中正好又執行了喂狗的操作,那我們就沒有辦法發現出錯的那一部分,那我們就會預設沒有出錯。而如果我們使用了視窗看門狗,我們可以讓其在一段時間後才能喂狗,這樣一來,程式若沒有按照正確的路徑執行,我們就能夠及時發現。
視窗看門狗相對於獨立看門狗另一個重要的區別是視窗看門狗擁有一箇中斷。這個中斷並不是用來日常喂狗的,而是在出現突發狀況時,能夠讓其儲存一些重要資料。類似讓微控制器寫一份“遺囑”,有一個緊急處理的機會。如果視窗看門狗的中斷只是用來日常喂狗的話,就和獨立看門狗沒什麼區別了,甚至還會埋下隱患。
視窗看門狗的配置過程如下:
- 使能WWDG時鐘
- 設定視窗值和分頻數
- 開啟WWDG中斷並分組
- 設定計數器初始值
- 使能看門狗
- 編寫中斷服務函式
視窗看門狗的上視窗值是由使用者確定的,而下視窗值是固定的0x40。當計數器在上視窗之外被重新整理或者低於下視窗,計數器都會產生復位。見下圖圖示:W[6:0]這七位是用來儲存上視窗值的暫存器的低七位,T[6:0]是計數器。
假設:
- Twwdg為超時時間(單位為ms)
- Fpclk1為APB1時鐘的頻率(單位為KHz)
- prer為預分頻係數
- T[5:0]為計數器的低六位
則視窗看門狗的超時公式為:
\[\text { Twwdg }=\frac{4096 \times 2^{prer} \times(\mathrm{T}[5: 0]+1)}{Fpclk1} \]
編碼
main.c
int main(void){ delay_init(); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); LED_Init(); LED0 = 0; delay_ms(300); WWDG_Init(0x7f, 0x5f, WWDG_Prescaler_8); //視窗看門狗初始化 while(1){ LED0 = 1; } }
wdg.h
#ifndef __WDG_H
#define __WDG_H
#include "sys.h"
void WWDG_Init(u8 tr,u8 wr,u32 fprer); //看門狗初始化
void WWDG_Set_Counter(u8 cnt); //喂狗函式
void WWDG_NVIC_Init(void); //看門狗中斷優先順序設定
#endif
wdg.c
#include "wdg.h"
#include "led.h"
u8 WWDG_CNT = 0x7f; //預設計數器的值為最大
void WWDG_Init(u8 tr, u8 wr, u32 fprer){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE); //開啟WWDG時鐘
WWDG_CNT = tr & WWDG_CNT; //初始化計數器的值
WWDG_SetPrescaler(fprer); //設定預分頻係數
WWDG_SetWindowValue(wr); //設定視窗值
WWDG_NVIC_Init(); //WWDG中斷分組初始化
WWDG_Enable(WWDG_CNT); //使能看門狗,並設定計數器初值
WWDG_ClearFlag(); //清除提前喚醒中斷標誌位
WWDG_EnableIT(); //開啟WWDG中斷
}
void WWDG_Set_Counter(u8 cnt){ //喂狗函式
WWDG_Enable(cnt);
}
void WWDG_NVIC_Init(){ //設定看門狗中斷優先順序
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void WWDG_IRQHandler(void){ //看門狗中斷服務函式
WWDG_SetCounter(WWDG_CNT);
WWDG_ClearFlag();
LED1 = !LED1;
}
至此,我們可以得到的效果就是LED0亮起300ms,然後熄滅。而LED1不斷的閃爍。