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不断的闪烁。
我是这耀眼的瞬间,是划过天边的刹那火焰。