第13章 WDG看门狗
13.1 WDG看门狗
13.1.1 WDG简介
看门狗就是程序运行的一个保障措施,我们得在程序中定期地喂狗,如果程序卡死了,没有在规定的时间里喂狗,那么看门狗硬件电路就会自动帮我们复位一下,防止程序长时间卡死,起到重启的作用。
WDG(Watchdog)看门狗;
看门狗可以监控程序的运行状态,当程序因为设计漏洞、硬件故障、电磁干扰等原因,出现卡死或跑飞现象时,看门狗能及时复位程序,避免程序陷入长时间的罢工状态,保证系统的可靠性和安全性;
看门狗本质上是一个定时器,当指定时间范围内,程序没有执行喂狗(重置计数器)操作时,看门狗硬件电路就自动产生复位信号;
STM32内置两个看门狗:
独立看门狗(IWDG):独立工作,对时间精度要求较低
窗口看门狗(WWDG):要求看门狗在精确计时窗口起作用
13.1.2 IWDG框图
预分频器之前,输入时钟是LSI,内部低速时钟, 时钟频率为40KHz,之后时钟进入预分频器进行分频,这个分频器只有8位,所以最大只能进行256分频。上面这个预分频寄存器IWDG_PR,可以配置分频系数,这个PR和定时器的PSC是一个意思,它们都是Prescale的缩写,可能不是一个人设计的,所以手册里很多缩写都不太一样。之后后面,时钟经过预分频器分频之后,时钟驱动递减计数器,每来一个时钟,自减一个数,另外这个数是12位的,所以最大值是2^12-1=4095,然后,当自减到0之后,产生IWDG复位,正常运行时,为了避免复位,我们可以提前在重装寄存器写一个值,IWDG_RLR,和定时器的ARR是一样的,RLR是Reloader,ARR是Auto Reloader,那当我们预先写好值之后,在运行过程中,我们在这个键寄存器里写一个特定数据,控制电路进行喂狗,这时重装值就会复制到当前的计数器中,这样计数器就会回到重装值,重新自减运行了。状态寄存器IWDG_SR,这就是标志电路运行的状态了,其实这个SR里没什么东西,只有两个更新同步位,最后,上面这些寄存器,位于1.8V供电区,下面主要的工作电路,都位于VDD供电区。
13.1.3 IWDG键寄存器
键寄存器本质上是控制寄存器,用于控制硬件电路的工作;
在可能存在干扰的情况下,一般通过在整个键寄存器写入特定值来代替控制寄存器写入一位的功能,以降低硬件电路受到干扰的概率。
如果只在控制寄存器中设置一个位,那这一位就有可能在误操作中,变成1或者变成0,这个概率是比较大的,所以单独设置一位来进行控制比较危险。比如程序跑飞,胡乱地设置各个寄存器,寄存器收到影响,可能会变成0x0000,0xFFFF,它可以随机变为任何数,但它恰好变成0xAAAA这个数的概率比较小。
写入键寄存器的值 | 作用 |
0xCCCC | 启用独立看门狗 |
0xAAAA | IWDG_RLR中的值重新加载到计数器(喂狗) |
0x5555 | 解除IWDG_PR和IWDG_RLR的写保护 |
0x5555之外的其他值 | 启用IWDG_PR和IWDG_RLR的写保护 |
13.1.4 IWDG超时时间
13.1.5 WWDG框图
窗口看门狗没有重装寄存器, 那如何重装寄存器喂狗呢?我们直接在CNT写入数据就行了,想写多少就写多少。喂狗的最早时间界限,就写到看门狗配置寄存器(WWDG_CFR)存起来。左边就是输出信号的操作逻辑了,什么情况下会产生复位,就有这几个逻辑门来确定。
时钟来源是PCLK1,也就是APB1的时钟,这个时钟默认是36MHz,所以就是36MHz的时钟进来,进来之后,还是先经过一个预分频器进行分频,这个和独立看门狗的预分频器,定时器的预分频器,都是一个作用,就是灵活地调节后面计数器地时钟频率,同时预分频系数也是计算计数器溢出时间的重要参数。接着,分频之后的时钟,驱动这个计数器进行计数,这个计数器和独立看门狗一样,也是一个递减计数器,没来一个时钟,自减一次,不过这个计数器比较特殊,从图上看,这里写了T6~T0,总共是7个位,但下面确写的是6位递减计数器,最高位T6,这里用来当作溢出标志位,T6位等于1时,表示计数器没溢出,T6位等于0时,表示计数器溢出,不过对于硬件电路来说,T6位也是计数器的一部分。只不过T6位被单独拎出来,当作标志位了而已。举个例子,比如这个计数器,我们给初始值111 1111,那么来一个计数脉冲,值减1,变为111 1110,再来一个变为111 1101,直到减为100 0000这一个数值,是一个关键节点,此时包括T6位在内的数,是100 0000,转为16进制0x40,那就是说,若果把T6位的值也当作计数器的一部分,那计数器的值实际上才减一半,但是如果把T6位剥离出去,当作溢出标志位,低6位,当作计数器,那此时的状态就是,标志位为1,计数器为000 0000,已经减到0了,再减一次,下一个值就是011 1111,这时最高位T6由1变为0,即代表计数器溢出,这时最高位T6,就会通过这个线路,产生复位信号。
这就是这个计数器的工作流程和溢出条件。
13.1.6 WWDG工作特性
递减计数器T[6:0]的值小于0x40时,WWDG产生复位;
递减计数器T[6:0]在窗口W[6:0]外被重新装载时,WWDG产生复位;
递减计数器T[6:0]等于0x40时可以产生早期唤醒中断(EWI),用于重装载计数器以避免WWDG复位;
定期写入WWDG_CR寄存器(喂狗)以避免WWDG复位。
13.1.7 WWDG超时时间
13.1.8 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 | 直接写入计数器,写多少重装多少 |
防误操作 | 键寄存器和写保护 | 无 |
用途 | 独立工作,对时间精度要求较低 | 要求看门狗在精确计时窗口起作用 |
13.2 独立看门狗
13.2.1 硬件电路
13.2.2 软件部分
(1)复制《OLED显示屏》并改名为《独立看门狗》
(2)IWDG库函数
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess); //写使能控制,操作键寄存器
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler); //写预分频器
void IWDG_SetReload(uint16_t Reload); //写重装值
void IWDG_ReloadCounter(void); //重新装在寄存器,喂狗
void IWDG_Enable(void); //启动独立看门狗
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG); //获取标志位状态
(3)main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Key_Init(); //按键初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "IWDG TEST");
/*判断复位信号来源*/
if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) == SET) //如果是独立看门狗复位
{
OLED_ShowString(2, 1, "IWDGRST"); //OLED闪烁IWDGRST字符串
Delay_ms(500);
OLED_ShowString(2, 1, " ");
Delay_ms(100);
RCC_ClearFlag(); //清除标志位
}
else //否则,即为其他复位
{
OLED_ShowString(3, 1, "RST"); //OLED闪烁RST字符串
Delay_ms(500);
OLED_ShowString(3, 1, " ");
Delay_ms(100);
}
/*IWDG初始化*/
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); //独立看门狗写使能
IWDG_SetPrescaler(IWDG_Prescaler_16); //设置预分频为16
IWDG_SetReload(2499); //设置重装值为2499,独立看门狗的超时时间为1000ms
IWDG_ReloadCounter(); //重装计数器,喂狗
IWDG_Enable(); //独立看门狗使能
while (1)
{
Key_GetNum(); //调用阻塞式的按键扫描函数,模拟主循环卡死
IWDG_ReloadCounter(); //重装计数器,喂狗
OLED_ShowString(4, 1, "FEED"); //OLED闪烁FEED字符串
Delay_ms(200); //喂狗间隔为200+600=800ms
OLED_ShowString(4, 1, " ");
Delay_ms(600);
}
}
13.3 窗口看门狗
13.3.1 硬件电路
13.2.2 软件部分
(1)复制《独立看门狗》工程并改名为《窗口看门狗》
(2)WWDG库函数
void WWDG_DeInit(void); //恢复缺省配置
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler); //写入预分频器
void WWDG_SetWindowValue(uint8_t WindowValue); //写入窗口值
void WWDG_EnableIT(void); //使能中断
void WWDG_SetCounter(uint8_t Counter); //写入计数器
void WWDG_Enable(uint8_t Counter); //使能窗口看门狗
FlagStatus WWDG_GetFlagStatus(void); //获取标志位
void WWDG_ClearFlag(void); //清除标志位
(3)main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Key.h"
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Key_Init(); //按键初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "WWDG TEST");
/*判断复位信号来源*/
if (RCC_GetFlagStatus(RCC_FLAG_WWDGRST) == SET) //如果是窗口看门狗复位
{
OLED_ShowString(2, 1, "WWDGRST"); //OLED闪烁WWDGRST字符串
Delay_ms(500);
OLED_ShowString(2, 1, " ");
Delay_ms(100);
RCC_ClearFlag(); //清除标志位
}
else //否则,即为其他复位
{
OLED_ShowString(3, 1, "RST"); //OLED闪烁RST字符串
Delay_ms(500);
OLED_ShowString(3, 1, " ");
Delay_ms(100);
}
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE); //开启WWDG的时钟
/*WWDG初始化*/
WWDG_SetPrescaler(WWDG_Prescaler_8); //设置预分频为8
WWDG_SetWindowValue(0x40 | 21); //设置窗口值,窗口时间为30ms
WWDG_Enable(0x40 | 54); //使能并第一次喂狗,超时时间为50ms
while (1)
{
Key_GetNum(); //调用阻塞式的按键扫描函数,模拟主循环卡死
OLED_ShowString(4, 1, "FEED"); //OLED闪烁FEED字符串
Delay_ms(20); //喂狗间隔为20+20=40ms
OLED_ShowString(4, 1, " ");
Delay_ms(20);
WWDG_SetCounter(0x40 | 54); //重装计数器,喂狗
}
}