十五、WDG看門狗

7七柒發表於2024-03-07

十三、WDG看門狗

WDG簡介

  • WDG(Watchdog)看門狗
  • 看門狗可以監控程式的執行狀態,當程式因為設計漏洞、硬體故障、電磁干擾等原因,出現卡死或跑飛現象時,看門狗能及時復位程式,避免程式陷入長時間的罷工狀態,保證系統的可靠性和安全性
  • 看門狗本質上是一個定時器,當指定時間範圍內,程式沒有執行餵狗(重置計數器)操作時,看門狗硬體電路就自動產生復位訊號
  • WDG開啟後就無法關閉。這是防止程式意外關閉看門狗
  • IWDG開啟後會自動開啟LSI時鐘
  • RCC_GetFlagStatus(RCC_FLAG_IWDGRST);RCC_GetFlagStatus(RCC_FLAG_WWDGRST);可以判斷是否是看門狗產生的復位訊號

STM32內建兩個看門狗

  • 獨立看門狗(IWDG):獨立工作,對時間精度要求較低

    獨立看門狗的時鐘是專用的 LSI,內部低速時鐘(40KHz),即使主時鐘出現問題了,看門狗也能正常工作,這也是獨立看門狗,獨立的得名原因。

    對時間精度要求較低就是獨立看門狗只有一個最晚時間界限,你餵狗間隔只要不超過這個最晚界限就行了,

  • 視窗看門狗(WWDG):要求看門狗在精確計時視窗起作用

    視窗看門狗使用的是APB1時鐘(36MHz)

    要求看門狗在精確計時視窗起作用,意思就是餵狗的時間有個最晚的界限,也有個最早的界限,必須在這個界限的視窗內餵狗,這也是視窗看門狗,視窗的得名原因。

    因為對於獨立看門狗來說,可能程式就卡死在餵狗的部分了;或者程式跑飛,但是餵狗程式碼也意外執行了;或者程式有時候很快餵狗,有時候又比較慢餵狗,那這些狀態,獨立看門狗就檢測不到了,但是視窗看門狗是可以檢測到這些問題的,因為它對餵狗的時間,可以卡的很死,快了,慢了,都不行。

IWDG獨立看門狗框圖

image

與定時器的時基單元基本是一樣的結構,執行流程如下

  1. 在預分頻器之前,輸入時鐘是 LSI,內部低速時鐘,時脈頻率為 40KHz

  2. 時鐘進入預分頻器進行分頻,這個預分頻器只有 8 位,所以它最大隻能進行 256 分頻,上面這個預分頻暫存器 IWDG_PR,可以配置分頻係數,

  3. 經過預分頻器分頻之後,時鐘驅動遞減計數器,每來一個時鐘,自減一個數,另外這個計數器是 12 位的,所以最大值是 212 - 1 = 4095,然後,當自減到 0 之後,產生 IWDG 復位

  4. 正常執行時,為了避免復位呢,我們可以提前在重灌載暫存器IWDG_RLR中寫一個值,用來更新遞減計數器的值

  5. 在這個鍵暫存器裡寫一個特定資料,可以控制電路進行餵狗,這時重灌值就會複製到當前的計數器中,這樣計數器就會回到重灌值重新自減執行了

  6. 狀態暫存器 SR,這就是標誌電路執行的狀態了,其實這個 SR 裡沒什麼東西,就只有兩個更新同步位,基本不用看。

  7. 上面這些暫存器位於 1.8V 供電區,下面主要的工作電路,都位於 VDD 供電區,即在停機和待機模式時仍能正常工作,所以獨立看門狗,也是喚醒待機模式的 4 個條件之 1。

IWDG鍵暫存器

鍵暫存器本質上是控制暫存器,用於控制硬體電路的工作。

比如我們剛才說的餵狗操作,就是透過在鍵暫存器寫入 0xAAAA 完成的

在可能存在干擾的情況下,一般透過在整個鍵暫存器寫入特定值來代替控制暫存器寫入一位的功能,以降低硬體電路受到干擾的機率

為什麼能降低干擾呢。獨立看門狗工作的環境是程式跑飛,受到電磁干擾等等,程式做出任何操作都是有可能的,如果你只在暫存器中設定一個位,那這一位就有可能會在誤操作中,變成 1 或者變成 0,這個機率是比較大的,所以單獨設定 1 位就來執行控制,在這裡比較危險。這時,我們就可以透過在整個暫存器寫入一個特定值,來代替寫入一個位的操作。

寫入鍵暫存器的值 作用
0xCCCC 啟用獨立看門狗
0xAAAA IWDG_RLR中的值重新載入到計數器(餵狗)
0x5555 解除 IWDG_PR 和 IWDG_RLR 的防寫
0x5555之外的其他值 啟用 IWDG_PR 和 IWDG_RLR 的防寫

最後兩條,是防寫的邏輯,想對IWDG的暫存器執行寫指令,必須寫入指定的鍵值

PR、SR 和 RLR 三個暫存器,它們也要有防止誤操作的功能,SR 是隻讀的,這個不用保護;剩下的,對 PR 和 RLR 的寫操作,可以設定一個防寫措施,然後只有在鍵暫存器寫入 5555,才能解除防寫,一旦寫入其他值,PR 和 RLR 再次被保護,這樣 PR 和 RLR,就跟隨鍵暫存器一起被保護了起來,防止誤操作

IWDG最大餵狗時間

也就是定時器的溢位時間。

這裡有個公式,超時時間:TIWDG = TLSI × PR預分頻係數 × (RL + 1)
其中:TLSI = 1 / FLSI

  • FLSI:LSI時脈頻率
  • TLSI:LSI一個週期的時間
  • PR:預分頻係數
  • RL:重灌載值

例如預分頻係數為4,RL重灌載值為4096

那麼超時復位的時間為:1/40KHz * 4 * (4095 + 1) = 409.6ms

PR預分頻暫存器:PR寫0表示4分頻;寫1表示8分頻;寫2表示16分頻。。。

image

簡單來說,和定時器定時時間的計算是一樣的

WWDG視窗看門狗框圖

image

  • WWDG_CR:

    • WDGA位:WDGA=1時表示開啟視窗看門狗

    • T6位:溢位標誌位。T6=0時表示定時器溢位,沒有及時餵狗

    • T0~T5:6位遞減計數器。寫入最大餵狗時間,自減為0後產生復位訊號

    • 注意: 對於硬體電路來說,T6 位其實也是計數器的一部分,只不過 T6 位被單獨拎出來,當作標誌位了而已

      舉個例子,比如這個計數器,初始值,我們給 111 1111,那麼來一個計數脈衝,值減 1,變為 111 1110,再來一個,變為 111 1101,以此類推,不斷自減,直到減為 100 0000,減到這個數時,是一個關鍵節點,此時包括 T6 位在內的數,是 100 0000,轉為十六進位制是 0x40。也就是說,此時,如果把 T6 位也當作計數器的一部分,那計數器的值實際上才減一半;但是,如果我們把 T6 位剝離出去,當作溢位標誌位,低 6 位,當作計數器,那此時的狀態就是標誌位為 1,計數器為 00 0000,已經減到 0 了,再減一次,下一個值是什麼呢?就是 011 1111,這時最高位 T6,由 1 變為 0,即代表計數器溢位,這時,最高位 T6,就會透過線路產生復位訊號,這就是這個計數器的工作流程和溢位條件。

  • WWDG_CFR:餵狗時間的最早界限。 計算出一個最早界限的計數值,寫入到這裡的 W6~W0 中,這些值寫入之後是固定不變的

WWDG工作流程

  • 餵狗的時間超時

    1. 輸入時鐘是PCLK1,來自APB1的時鐘,時脈頻率為36MHz

    2. 經過看門狗預分頻器分頻後提供給計數器進行遞減計數

    3. WWDG_CR的T[5:0]6位遞減計數器開始遞減計數,T[5:0]=0後,再來一個時鐘週期,T6=0,經過非門取反後等於1,再經過或門輸出1,;如果開啟視窗看門狗檢測,WDGA=1,兩條線路都輸出1,經過與門輸出1產生復位訊號,復位電路

  • 過早餵狗

    1. 軟體寫入WWDG_CR暫存器表示餵狗,並輸出1到與門
    2. 在寫入WWDG_CR之前,將當前WWDG_CR的T[6:0]與WWDG_CFR的W[6:0]比較,如果T[6:0] > W[6:0],則說明餵狗的時間太早了,輸出比較結果為1
    3. 寫入WWDG_CR輸出1,過早餵狗輸出1,兩條線路經過與門的結果就是1。再透過或門輸出1。如果WDGA=1,經過與門後輸出1產生復位訊號,復位電路

WWDG 工作特性

  • 遞減計數器 T[6:0] 的值小於 0x40 時,WWDG 產生復位

    注意這裡寫的是 T[6:0],包含 T6 位,所以是值減到 0x40 之後,再減一次就復位。

  • 遞減計數器T[6:0]在視窗W[6:0]外被重新裝載時,WWDG產生復位

    這就是不能過早餵狗。

  • 遞減計數器 T[6:0] 等於 0x40 時可以產生早期喚醒中斷(EWI),用於重灌載計數器以避免 WWDG 復位

    注意這裡是等於 0x40 時,可以產生一個早期中斷,上面是小於 0x40 時,產生復位,0x40,這時 T6 還是 1

    這裡的意思是減到 0x40 時,產生中斷,這個中斷也可以稱作“死前中斷”,馬上就要溢位復位了,再提醒一下你,要不要乾點啥。所以在這個早期喚醒中斷裡,我們一般可以用來執行一些緊急操作,比如儲存重要資料、關閉危險裝置等等;或者,還有一種寫法,就是雖然超時餵狗了,但是我可以在中斷裡執行一些程式碼進行解決;或者這個任務不是很危險,超時了我就只想做一些提示,不想讓它復位了,這樣的話,我們就可以在這個早期喚醒中斷裡,直接執行餵狗,阻止系統復位,然後提示一些資訊就完事了

  • 定期寫入WWDG_CR暫存器(餵狗)以避免WWDG復位

WWDG時序

image

縱軸是 T[6:0],包含 T6 位的 CNT,遞減計數器,餵狗之後,在這個數值往下遞減,如果這個計數值還很大,高於這個 W[6:0] 視窗值了,你就不能急著餵狗,也就是在這一塊,是不允許重新整理的;

之後,當計數值小於視窗值了,進入到重新整理視窗,可以餵狗,當然餵狗也不能太晚,要在減到 3F 之前喂,3F 這個值,就是 T6 位剛等於 0 的時刻,所以下面這裡,T6 位,在這個時刻變為 0,同時復位訊號也在這個時刻傳送

WWDG餵狗時間

image

超時時間:TWWDG = TPCLK1 × 4096 × WDGTB預分頻係數 × (T[5:0] + 1)

餵狗的最晚時間。和獨立看門狗的基本一樣。

PCLK1 × 4096:PCLK1 進來之後,其實是先執行了一個固定的 4096 分頻,這裡框圖沒畫出來,因為 36 M 的頻率還是太快了,先來個固定分頻降一降。

WDGTB 預分頻係數:分頻係數 = 2^WDGTB,當 WDGTB = 0 時,就是 1 分頻;WDGTB = 1,就是 2 分頻;WDGTB = 2,就是 4 分頻;WDGTB = 3,就是 8 分頻,分頻係數只有這 4 種選擇

T[5:0]:寫入六位計數器的值

視窗時間:TWIN = TPCLK1 × 4096 × WDGTB預分頻係數 × (T[5:0] - W[5:0])

就是餵狗的最早時間。這個公式手冊裡並沒有給出,不過也總結出來了。這個和上面差不多,主要區別就是後面這裡,上面這個超時時間,是計數器減到 0 的時間,而這個,是計數器減到視窗值的時間,所以我們拿 T[5:0] - W[5:0] 就可以算出視窗時間了,同時也要注意,這個 W[5:0] 也是不包含 W6 這一位的。然後 WWDG 框圖這裡,從這裡可以看出,計數器等於視窗值時,就已經可以餵狗了,所以這裡公式後面,就不用再 +1 了,這就是視窗時間的公式。

其中:TPCLK1 = 1 / FPCLK1

IWDG和WWDG對比

IWDG獨立看門狗 WWDG視窗看門狗
復位 計數器減到0後 計數器T[5:0]減到0後、過早重灌計數器
中斷 早期喚醒中斷
時鐘源 LSI(40KHz) PCLK1(36MHz)
預分頻係數 4、8、32、64、128、256 1、2、4、8
計數器 12位 6位(有效計數)
超時時間 0.1ms~26214.4ms 113us~58.25ms
餵狗方式 寫入鍵暫存器,重灌固定值RLR 直接寫入計數器,寫多少重灌多少
防誤操作 鍵暫存器和防寫
用途 獨立工作,對時間精度要求較低 要求看門狗在精確計時視窗起作用

IWDG相關庫函式

// 啟用或禁用對IWDG_PR和IWDG_RLR暫存器的寫訪問。
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);

// 寫IWDG的預分頻值
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);

// 寫IWDG的重灌載值
void IWDG_SetReload(uint16_t Reload);

// 餵狗。將重灌載暫存器的值寫入自減計數器中
void IWDG_ReloadCounter(void);

// 使能IWDG
void IWDG_Enable(void);

// 獲取IWDG的相關標誌位
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG);

WWDG相關庫函式

// WWDG復位
void WWDG_DeInit(void);

// 寫WWDG的預分頻值
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);

// 寫WWDG的視窗值W[5:0]。最早餵狗的時間
void WWDG_SetWindowValue(uint8_t WindowValue);

// 使能中斷。自減計數器T[5:0]=0(下一個時鐘週期就要溢位產生復位訊號)時可以觸發一次中斷
void WWDG_EnableIT(void);

// 餵狗。寫自減計數器T[6:0]
void WWDG_SetCounter(uint8_t Counter);

// 使能WWDG。這裡要傳入的值會寫入自減計數器T[6:0]。為了防止使能WWDG時T6=0,直接產生復位訊號,所以使能前要先寫T[6:0]
void WWDG_Enable(uint8_t Counter);

// 獲取WWDG相關標誌位
FlagStatus WWDG_GetFlagStatus(void);

// 清除WWDG相關標誌位
void WWDG_ClearFlag(void);

案例

IWDG

image

示例程式碼

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Delay.h"

// 初始化按鍵,上拉輸入
void Key_Init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct);
}

void Key()
{
	// 當按鍵按住不放時進入死迴圈
	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET)
	{
		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET);
	}
}

int main()
{
	OLED_Init();
	Key_Init();
	OLED_ShowString(1,1,"IWDG:");
	
	// 判斷是否是獨立看門狗產生的復位
	if(RCC_GetFlagStatus(RCC_FLAG_IWDGRST) == SET)
	{
		OLED_ShowString(2,1,"IWDG_Reset");
		// 一定要記得清除標誌位,因為復位標誌位即使復位了也不會自動清除
		RCC_ClearFlag();
	}
	else
	{
		OLED_ShowString(2,1,"Normal_Reset");
	}
	
	// IWDG的暫存器寫使能
	IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
	// 寫預分頻暫存器。分頻係數16
	IWDG_SetPrescaler(IWDG_Prescaler_16);
	// 寫重灌載暫存器。
	IWDG_SetReload(2499 + 1);	// 最大餵狗時間為:1 / 40KHz * 16 * 2500 = 1000ms
	// 餵狗。將重灌載值寫入計數器。該函式底層是在鍵暫存器中寫入0xAAAA,正好開啟了IWDG暫存器的防寫
	// 在使能IWDG前喂一次狗防止計數器為0,直接復位
	IWDG_ReloadCounter();
	// 使能IWDG
	IWDG_Enable();
	
	while(1)
	{
		Key();
		Delay_ms(400);
		IWDG_ReloadCounter();
		OLED_ShowString(3,1,"WWW");
		Delay_ms(300);
		OLED_ShowString(3,1,"   ");
	}
}

WWDG

接線圖

image

示例程式碼

#include "stm32f10x.h"                  // Device header
#include "OLED.h"
#include "Delay.h"

// 初始化按鍵,上拉輸入
void Key_Init()
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStruct);
}

void Key()
{
	// 當按鍵按住不放時進入死迴圈
	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET)
	{
		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1) == Bit_RESET);
	}
}

int main()
{
	OLED_Init();
	Key_Init();
	OLED_ShowString(1,1,"WWDG:");
	
	// 判斷是否是視窗看門狗產生的復位
	if(RCC_GetFlagStatus(RCC_FLAG_WWDGRST) == SET)
	{
		OLED_ShowString(2,1,"WWDG_Reset");
		// 一定要記得清除標誌位,因為復位標誌位即使復位了也不會自動清除
		RCC_ClearFlag();
	}
	else
	{
		OLED_ShowString(2,1,"Normal_Reset");
	}
	
	// WWDG輸入APB1外設,開啟WWDG時鐘
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
	
	// 設定預分頻係數為8分頻
	WWDG_SetPrescaler(WWDG_Prescaler_8);
	
	// W[5:0]=11;加上0x40是將W6位置1。因為餵狗時是將T[6:0]與W[6:0]比大小判斷是否提前餵狗。在30ms前餵狗會產生復位訊號
	WWDG_SetWindowValue(0x40 + 21);		// 1 / 36000KHz * 4096 * 8 * (54 - 21) 約等於30ms
	
	// 使能WWDG。使能WWDG的同時寫入自減計數器。T[5:0]=54;加上0x40是將T6位復位標誌位置1。超過50ms沒有餵狗就會產生復位訊號
	WWDG_Enable(0x40 + 54);				// 1 / 36000KHz * 4096 * 8 * (54 + 1) 約等於50ms
	
	
	while(1)
	{
		Key();
		Delay_ms(20);
		OLED_ShowString(3,1,"WWW");
		Delay_ms(20);
		OLED_ShowString(3,1,"   ");
		
		// 餵狗。寫自減計數器
		WWDG_SetCounter(0x40 + 54);
	}
}

相關文章