1.看門狗簡介
看門狗起始就是一個定時器,從功能上說它可以讓微控制器在程序發生意外(程序進入死循環或跑飛)的時候,能重新恢復到系統剛上電狀態,以保障系統出問題的時候可以重啟一次。說的簡單一點,看門狗就是能讓程序出問題時能重新啟動系統。STM32有兩個看門狗,獨立看門狗和窗口看門狗。
1.1獨立看門狗(Independent Watchdog,IWDG)
獨立看門狗號稱寵物狗,它有一個12位的遞減計數器,當計數器的值從某個值一直減到0的時候,系統就會產生一個復位信號,即IWDG_RESET。如果在計數器沒有減到0之前,刷新了計數器的值,那么就不會產生復位信號,這個動作就是我們經常說的喂狗。
1.2獨立看門狗時鐘
獨立看門狗的時候由獨立的RC振蕩器LSI提供,即使主時鐘發生故障它仍然有效,非常獨立。LSI的頻率一般在30~60KHz之間,所以獨立看門狗的定時時間并不一定非常準確,只適用于對時間精度要求比較低的場合。
上圖中,配置的IWDG時鐘CK_IWDG=32KHz。
1.3獨立看門狗計數器時鐘
遞減計數器的時鐘由LSI經過一個8位的預分頻器得到,我們可以操作預分頻器寄存器IWDG_PR來設置分頻因子,分頻因子可以是:[4,8,16,32,64,128,256],計數器時鐘CK_CNT=CK_IWDG/IWDG_PR。
1.4重裝載寄存器
重裝載寄存器是一個12位的寄存器,里面裝著要刷新到計數器的值,這個值的大小決定著獨立看門狗的溢出時間。超時時間Tout=1/CK_CNT*rlv,rlv是重裝載寄存器的值。
若IWDG_PR=32,rlv=2000,所以CK_CNT=32KHz/32=1KHz,Tout=1/1000*2000=2s。意味著2s之內我們就得喂狗,不然系統就會重啟。
1.5標準庫演示
#ifndef __BSP_IWDG_H
#define __BSP_IWDG_H#ifdef __cplusplus
extern "C"{#endif#include "stm32f4xx.h"void Init_IWDG(uint8_t prv,uint16_t rlv);
void IWDG_Feed(void);#ifdef __cplusplus
}
#endif#endifc
#include "bsp_iwdg.h"/*
* 設置 IWDG 的超時時間
* Tout = prv/LSICLK * rlv (s)
* prv可以是[4,8,16,32,64,128,256]
* prv:預分頻器值,取值如下:
* @arg IWDG_Prescaler_4: IWDG prescaler set to 4
* @arg IWDG_Prescaler_8: IWDG prescaler set to 8
* @arg IWDG_Prescaler_16: IWDG prescaler set to 16
* @arg IWDG_Prescaler_32: IWDG prescaler set to 32
* @arg IWDG_Prescaler_64: IWDG prescaler set to 64
* @arg IWDG_Prescaler_128: IWDG prescaler set to 128
* @arg IWDG_Prescaler_256: IWDG prescaler set to 256
*
* 獨立看門狗使用LSI作為時鐘。
* LSI 的頻率一般在 30~60KHZ 之間,根據溫度和工作場合會有一定的漂移,我
* 們的STM32F407中為32KHz,所以獨立看門狗的定時時間并不一定非常精確,只適用于對時間精度
* 要求比較低的場合。
*
* rlv:預分頻器值,取值范圍為:0-0XFFF
* 函數調用舉例:
* Init_IWDG(IWDG_Prescaler_32 ,1000); // IWDG 1s 超時溢出
* (32/LSICLK)*1000 = 1s
*/
void Init_IWDG(uint8_t prv,uint16_t rlv)
{// 使能 預分頻寄存器PR和重裝載寄存器RLR可寫IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);// 設置預分頻器值IWDG_SetPrescaler(prv);// 設置重裝載寄存器值IWDG_SetReload(rlv);// 把重裝載寄存器的值放到計數器中IWDG_ReloadCounter();// 使能 IWDGIWDG_Enable();
}void IWDG_Feed(void)
{// 把重裝載寄存器的值放到計數器中,喂狗,防止IWDG復位// 當計數器的值減到0的時候會產生系統復位IWDG_ReloadCounter();
}
int main(void)
{Init_LED();// //設置中斷分組
// NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
//
// Init_USART();Init_IWDG(IWDG_Prescaler_32,1000);/* Infinite loop */while (1){}
}
上述代碼思路為:Init_LED()中會將LED點亮,然后開啟獨立看門狗,設置的溢出時間為1s,那么1s后由于沒有喂狗,mcu會復位,LED也會滅,但是由于時間太快了,肉眼應該無法看到。所以,我們使用了示波器進行測試LED的輸入電平。
int main(void)
{Init_LED();// //設置中斷分組
// NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
//
// Init_USART();Init_IWDG(IWDG_Prescaler_32,1000);/* Infinite loop */while (1){IWDG_Feed();}
}
如果我們在巡航中一直喂狗,那么mcu就不會復位。
2.窗口看門狗(Window Watchdog,WWDG)
窗口看門狗號稱警犬,它也有一個遞減計數器不斷的往下遞減計數,當減到一個固定值0x40時還不喂狗的話,就會產生復位,這個值叫窗口的下限,是固定值,不能改變。不同的是,窗口看門狗的計數器在減到某一個數之前喂狗也會產生復位,這個值叫窗口的上限,上限值由用戶獨立設置。窗口看門狗計數器的值必須在上窗口和下窗口之間才可以喂狗,這就是窗口看門狗中窗口兩個字的含義。
RLR是重裝載寄存器,用來設置獨立看門狗的計數器的值。TR是窗口看門狗的計數器的值,由用戶獨立設置,WR是窗口看門狗的上窗口值,由用戶獨立設置。
在出現下述兩種情況之一時產生看門狗復位:
- 當喂狗的時候如果計數器的值大于窗口上限值。
- 當計數器的數值從0x40減到0x3F。
如果啟動了看門狗并且使能中斷,當遞減計數器等于0x40時產生早期喚醒中斷(EWI),這個中斷我們稱它為死前中斷或者叫遺囑中斷, 在中斷函數里面我們應該出來最重要的事情,而且必須得快,因為遞減計數器再減一次,就會產生系統復位。
注意事項:
- 上限值必須大于0x40,否則就無窗口了。
- 窗口看門狗時鐘來源PCLK1(APB1總線時鐘)分頻后。
2.1標準庫演示
#ifndef __BSP_WWDG_H
#define __BSP_WWDG_H#ifdef __cplusplus
extern "C"{#endif#include "stm32f4xx.h"#define WWDG_CNT 0x7Fvoid Init_WWDG(uint8_t tr, uint8_t wr, uint32_t prv);
void WWDG_Feed(void);#ifdef __cplusplus
}
#endif#endif
#include "bsp_wwdg.h"
#include "stdio.h"/* WWDG 配置函數
* tr :遞減計時器的值, 取值范圍為:0x7f~0x40
* wr :窗口值,取值范圍為:0x7f~0x40
* prv:預分頻器值,取值可以是
* @arg WWDG_Prescaler_1: WWDG counter clock = (PCLK1/4096)/1
* @arg WWDG_Prescaler_2: WWDG counter clock = (PCLK1/4096)/2
* @arg WWDG_Prescaler_4: WWDG counter clock = (PCLK1/4096)/4
* @arg WWDG_Prescaler_8: WWDG counter clock = (PCLK1/4096)/8
*/
void Init_WWDG(uint8_t tr, uint8_t wr, uint32_t prv)
{//使能窗口看門狗時鐘RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE);// 設置預分頻器值WWDG_SetPrescaler(prv);// 設置重裝載寄存器值WWDG_SetWindowValue(wr);// 使能 WWDGWWDG_Enable(WWDG_CNT&tr);//配置中斷控制器并使能中斷NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel=WWDG_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=3;NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;NVIC_Init(&NVIC_InitStruct);WWDG_ClearFlag();//清楚標志位WWDG_EnableIT();//使能中斷
}void WWDG_IRQHandler(void)
{WWDG_SetCounter(WWDG_CNT);WWDG_ClearFlag();
}void WWDG_Feed(void)
{printf("WWDG_Feed\r\n");// 把重裝載寄存器的值放到計數器中,WWDG_SetCounter(WWDG_CNT);
}
int main(void)
{Init_USART();Init_LED();//設置中斷分組NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);Init_WWDG(0x7f,0x5f,WWDG_Prescaler_8);printf("hello,this is stm32f407\r\n");/* Infinite loop */while (1){//-----------------------------------------------------// 這部分應該寫需要被WWDG監控的程序,這段程序運行的時間// 決定了窗口值應該設置成多大。//-----------------------------------------------------// 計時器值,初始化成最大0X7F,當開啟WWDG時候,這個值會不斷減小// 當計數器的值大于窗口值時喂狗的話,會復位,當計數器減少到0X40// 還沒有喂狗的話就非常非常危險了,計數器再減一次到了0X3F時就復位// 所以要當計數器的值在窗口值和0X40之間的時候喂狗,其中0X40是固定的。if ( (WWDG->CR & 0X7F) < 0x5f ){// 喂狗,重新設置計數器的值為最大0X7FWWDG_Feed();}}
}
注意,我們試過了在死前中斷中喂狗,但是好像來不及,mcu還是重啟了。所以,我們在main函數中進行了計數判斷喂狗。
主函數中我們把WWDG的計數器的值設置 為0X7F,上窗口值設置為0X5F,分頻系數為8分頻。在while死循環中,我們不斷讀取計數器的值, 當計數器的值減小到小于上窗口值的時候,我們喂狗,讓計數器重新計數。
在while死循環中,一般是我們需要監控的程序,這部分代碼的運行時間,決定了上窗口值應該設置為多少,當監控的程序運行完畢之后, 我們需要執行喂狗程序,比起獨立看門狗,這個喂狗的窗口時間是非常短的,對時間要求很精確。如果沒有在這個窗口時間內喂狗的話, 那就說明程序出故障了,會產生提前喚醒中斷,最后系統復位。