独立看门狗—IWDG&窗口看门狗—WWDG
- 看门狗可以监控程序的运行状态,当程序因为设计漏洞、硬件故障、电磁干扰等原因,出现卡死或者跑飞现象时,看门狗能及时复位程序,避免程序陷入长时间的罢工状态,保证系统的可靠性和安全性
- 看门狗本质上是一个定时器,当指定时间范围内,程序没有执行喂狗(重置计数器)操作时,看门狗硬件电路就会自动产生复位信号
- STM32内置两个看门狗
独立看门狗(IWDG):独立工作,对时间精度要求较低
窗口看门狗(WWDG):要求看门狗在精确计时窗口起作用
独立看门狗
独立看门狗结构图
上半部分是寄存器核心,下半部分是工作流程
①时钟:独立看门狗的时钟源来自LSI(内部低速时钟),意味着不受外部晶振电路影响,同时就算系统主时钟发生故障时,也可以正常工作。使用内部晶振,也就以为i精度并不高,因此只适合应用在对时间精度要求比较低的场合。
②计数:独立看门狗的计数器是一个12位的递减计数器,计数最大值为0xFFF,当计数器递减到0时,就会产生一个复位信号,重启整个系统。如果在递减到0之前,将重装载数值写入递减计数器,就会由重装载数值开始递减到0,如此反复,就永远不会到0,也就不会产生复位信号,这个重装载计数值写入递减计数器的过程叫”喂狗“。
重装载数值来自重装载寄存器(IWDG_RLR),这个值大小决定独立看门狗的溢出时间(复位倒计时),溢出时间为:
其中,pre为预分频寄存器的值,范围:0<=pre<=6,rlr为重载寄存器的值,范围:0~0xFFF。
键值寄存器(IWDG_KY),往该写一些关键值,以实现不同的效果,这里关键值共有三个:
-
写0xAAAA:将重载寄存器(IWDG_RLR)的值更新到计数器;
-
写0x5555:默认预分频寄存器(IWDG_PR)和重装载寄存器(IWDG_RLR)有写保护,不允许直接写值,需要先向键值寄存器(IWDG_KY)写0x5555去保护,才能写预分频寄存器(IWDG_PR)和重装载寄存器(IWDG_RLR)。写完后,向键值寄存器(IWDG_KY)写入其它任意值,将重新启用写保护;
-
写0xCCCC:启动独立看门狗,一旦启动,无法通过其它方式关闭,只能复位后恢复默认关闭状态;
③独立看门狗复位:当独立看门狗的递减计数器递减到0,将发生复位。假设当前预分频寄存器值为6(256分频),重装载寄存器的值为15,则溢出时间为:
即启动独立看门狗,需要载0.96s内喂狗一次,否则系统将复位。
④状态寄存器:预分频器和重装载数值的工作状态,可通过状态寄存器(IWDG_SR)获取。该寄存器只有最低两位有效,含义如下:
- Bit[1] Reload Value Update,RVU:独立看门狗计数器重载值更新状态,读出为1表示重装载值正在更新,读出为0表示更新完毕;
- Bit[0] Prescaler Value Update,PVU: 独立看门狗预分频值更新状态,出为1表示预分频值正在更新,读出为0表示更新完毕;
因此,只有在RVU和PVU为0的情况下,才能分别设置重装载寄存器和预分频寄存器的值。
软件设计
通过按键切换自动喂狗模式和不自动喂狗模式
- 初始独立看门狗相关参数:时钟预分频、设置重装载值等;
- 主函数编写控制逻辑:通过按键切换不自动喂狗和自动喂狗模式。不自动喂狗模式中,不做任何操作,观察打印,何时复位重启。自动喂狗模式中,不断喂狗,观察打印,是否复位重启。
Step1:初始化独立看门狗喂狗时间
driver_iwdg.c
#include <stdio.h>
#include "main.h"
#include "driver_iwdg.h"
IWDG_HandleTypeDef hiwdg;
/*
* 函数名:void IWDG_Init(uint16_t period)
* 输入参数:period-设置喂狗周期,单位ms
* 输出参数:无
* 返回值:无
* 函数作用:初始化独立看门狗的喂狗时间
* 刷新时间计算:Prescaler/LSI*Reload
*/
void IWDG_Init(uint16_t period)
{
hiwdg.Instance = IWDG; // 选择独立看门狗
hiwdg.Init.Prescaler = IWDG_PRESCALER_256; // 设置预分频
hiwdg.Init.Reload = 40000/256*period/1000; // 设置重装载值
if (HAL_IWDG_Init(&hiwdg) != HAL_OK) // 初始化独立看门狗
{
Error_Handler();
}
}
喂狗:HAL库提供刷新独立看门狗函数”HAL_IWDG_Refresh()“
driver_iwdg.c
/*
* 函数名:void ClearIWDG(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:刷新独立看门狗的计数器,俗称“喂狗”
*/
void ClearIWDG(void)
{
if (HAL_IWDG_Refresh(&hiwdg) != HAL_OK) // 将重载寄存器的值更新到计数器
{
Error_Handler();
}
printf("--------- 喂狗 --------\n\r");
}
Step2:主函数控制逻辑,初始化设置喂狗周期时间,接着通过按键切换喂狗/不喂狗模式
main.c
#include <stdio.h>
#include "main.h"
#include "driver_usart.h"
#include "driver_iwdg.h"
#include "driver_key.h"
int main(void)
{
uint32_t run_cnt = 1;
// 初始化HAL库函数必须要调用此函数
HAL_Init();
/*
* 系统时钟即AHB/APB时钟配置
* 使用外部高速时钟HSE(8MHz)配置系统时钟,经过PLL放大9倍,得到72MHz
*/
SystemClock_Config();
// 初始化USART1,设置波特率为115200 bps
UsartInit(115200);
printf("**********************************************\n\r");
printf("-->独立看门狗IWDG实验\n\r");
printf("**********************************************\n\r");
// 初始化按键
KeyInit();
// 初始化IWDG,设置喂狗周期1000ms=1s
IWDG_Init(1000);
while(1)
{
if(up_flag != true) // 不喂狗,喂狗周期结束后将复位重启
printf("*当前模式:不自动喂狗模式 %d \n\r", run_cnt);
else
{
printf("*当前模式:自动喂狗模式 %d \n\r", run_cnt);
ClearIWDG(); // 喂狗周期内喂狗,不会复位重启
}
run_cnt++;
HAL_Delay(100);
}
}
/*
* 函数名:void Error_Handler(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:程序错误处理函数,此处暂时设为死循环,不做任何动作
*/
void Error_Handler(void)
{
while(1)
{
}
}
窗口看门狗—WWDG
独立看门狗和窗口看门狗的效果类似,都是检测系统发生软件错误或死机时,通过复位重启使系统重新正常工作。
独立看门狗包含一个12位递减计数器,从用户定义的t开始递减到0,必须载t~0之间喂狗,否则复位重启。
窗口看门狗包含一个7位递减计数器,从用户定义的t开始递减到64,必须在t~64之间喂狗,在t之前或者64之后喂狗,也会导致复位重启。这里的t值,称之为窗口上限,由用户自定义设置;这里的64,称之为窗口下限,是系统固定的。窗口看门狗计数器必须在上窗口和下窗口之间被刷新(喂狗),不能过早,也不能过晚,这也就窗口看门狗中的“窗口”含义。
窗口看门狗的窗口下限值为63,而窗口看门狗递减计数器又是7位,因此用户可设置的窗口上限范围为63<x<128。假设递减计数器初值为127,随着时间的递增,它的值将逐渐减少。现在还需要定一个窗口上边界,这个上边界位于下边界和递减计数器初值之间,假设为80。那么计数器从127递减到80这个时间段,是不允许喂狗的,一旦喂狗将复位重启;80递减到63这个时间段,则必须喂狗;63这个下边界是固定的,一旦递减计数器小于等于这个值,系统将复位。可以看到,整个过程,有两个关键数值,一个是递减计数器当前计数值,对应寄存器WWDG_CR[6:0],一个是窗口上边界值,对应寄存器WWDG_CFG[6:0]。
看门狗结构图
①时钟:窗口看门狗的时钟来自PCLK1(最高36HMz),经过4096分频,再经过WWDG_CFG的Bits[8 :7]位WDGTB分频得到,WDGTB支持2^n分频(0<=n<=3),由此可以得出计数器时钟为:
其中,pre为预分频寄存器的值,范围:0<=pre<=3。
②计数:窗口看门狗的计数器是一个7位的递减计数器,计数最大值为0x7F(01111111)(127),当计数器递减到0x3F(00111111)(63)时,就会产生一个复位信号,重启整个系统。当递减计数器递减到0x40(01000000)(64)时,如果使能了提前唤醒中断(WWDG_CFG的Bits[9]位EWI设置为1),则会产生提前唤醒中断,在该中断可以保存重要数据或者向WWDG_CR重新写入新计数器值,完成喂狗操作。一旦0x40变为0x39,系统将进行复位,因此必须在一个窗口看门狗计数周期内完成喂狗操作。WWDG_CR的Bits[7]位WDGA为窗口看门狗使能位,当为1时,窗口看门狗才工作。
③窗口:窗口看门狗的WWDG_CFG的Bits[6 :0]位为窗口上边界值,该值应小于计数器最大值0x7F,大于窗口下边界值0x3F。
④逻辑与或
独立/窗口看门狗的相同点和不同点
软件设计
通过按键切换不同的场景:不自动喂狗模式、自动喂狗模式和中断喂狗模式
- 初始窗口看门狗相关参数:时钟预分频、设置窗口值和计数值等;
- 初始化窗口看门狗硬件相关参数:时钟使能、设置中断优先级和使能;
- 编写窗口看门狗提前唤醒中断回调函数;
- 主函数编写控制逻辑:通过按键切换三种模式。不自动喂狗模式中,不做任何操作,观察打印,何时复位重启;自动喂狗模式中,先延时一段时间,再喂狗,观察打印,是否复位重启;中断喂狗模式中,使能看门狗中断,再提前唤醒中断里喂狗,观察打印,是否复位重启。
Step1:初始化窗口看门狗
driver_wwdg.c
#include <stdio.h>
#include "main.h"
#include "driver_wwdg.h"
WWDG_HandleTypeDef hwwdg;
/*
* 函数名:void WWDG_Init(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:初始化窗口看门狗
* 计算方式:窗口看门狗时钟:PCLK1(36MHz)/4096/Prescaler(8) ≈ 1098Hz ≈ 0.9ms
|--------|----------|-------|
窗口看门狗计数器: 120 80 63 0
36ms 15.3ms
即:启动看门狗后,36ms内不能喂狗,36ms~15.3ms需要喂狗
*/
void WWDG_Init(void)
{
hwwdg.Instance = WWDG; // 指定窗口看门狗
hwwdg.Init.Prescaler = WWDG_PRESCALER_8; // 时钟预分频, 支持2^n分频(0≤n≤3)
hwwdg.Init.Window = 80; // 设置窗口上边界值,范围:0x40~0x7F
hwwdg.Init.Counter = 120; // 设置计数值,范围:0x40~0x7F
hwwdg.Init.EWIMode = WWDG_EWI_ENABLE; // 提前唤醒中断使能
if (HAL_WWDG_Init(&hwwdg) != HAL_OK)
{
Error_Handler();
}
}
“HAL_WWDG_Init()”函数会回调“HAL_WWDG_MspInit()”进行硬件相关初始化
driver_wwdg.c
/*
* 函数名:void HAL_WWDG_MspInit(WWDG_HandleTypeDef* wwdgHandle)
* 输入参数:wwdgHandle-WWDG句柄
* 输出参数:无
* 返回值:无
* 函数作用:HAL_WWDG_Init回调硬件初始化
*/
void HAL_WWDG_MspInit(WWDG_HandleTypeDef* wwdgHandle)
{
if(wwdgHandle->Instance==WWDG)
{
__HAL_RCC_WWDG_CLK_ENABLE(); // 使能WWDG时钟
HAL_NVIC_SetPriority(WWDG_IRQn,0,0); // 设置WWDG中断优先级
HAL_NVIC_EnableIRQ(WWDG_IRQn); // 使能WWDG中断
}
}
Step2:窗口看门狗中断处理
使能中断后,当窗口看门狗计数到0x40时,会进去提前唤醒中断,在该中断处理函数里,用户可以保存数据或喂狗
driver_wwdg.c
/*
* 函数名:void WWDG_IRQHandler(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:WWDG的中断处理函数
*/
void WWDG_IRQHandler(void)
{
HAL_WWDG_IRQHandler(&hwwdg);
}
/*
* 函数名:void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef* hwwdg)
* 输入参数:hwwdg-WWDG句柄
* 输出参数:无
* 返回值:无
* 函数作用:提前唤醒中断回调函数
*/
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef* hwwdg)
{
ClearWWDG();
printf("-------复位前保存数据------\n\r");
printf("--------- 软件喂狗 --------\n\r");
}
Step3:窗口看门狗喂狗
调用HAL库提供的“HAL_WWDG_Refresh()”刷新计数器值,实现喂狗操作
driver_wwdg.c
/*
* 函数名:void ClearIWDG(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:刷新独立看门狗的计数器,俗称“喂狗”
*/
void ClearWWDG(void)
{
if (HAL_WWDG_Refresh(&hwwdg) != HAL_OK)
{
Error_Handler();
}
}
Step4:主函数控制逻辑
main.c
#include <stdio.h>
#include "main.h"
#include "driver_key.h"
#include "driver_usart.h"
#include "driver_wwdg.h"
int main(void)
{
uint32_t run_cnt = 1;
uint8_t windows_value = 0;
uint8_t counter_value = 0;
uint8_t delay_value = 0;
// 初始化HAL库函数必须要调用此函数
HAL_Init();
/*
* 系统时钟即AHB/APB时钟配置
* 使用外部高速时钟HSE(8MHz)配置系统时钟,经过PLL放大9倍,得到72MHz
*/
SystemClock_Config();
// 初始化USART1,设置波特率为115200 bps
UsartInit(115200);
printf("\n\r");
printf("**********************************************\n\r");
printf("-->窗口看门狗WWDG实验\n\r");
printf("**********************************************\n\r");
KeyInit(); // 初始化按键
WWDG_Init(); // 初始化WWDG
counter_value = WWDG->CR & 0X7F; // 获取计数值
windows_value = WWDG->CFR & 0x7F; // 获取窗口值
delay_value = (counter_value - windows_value)*0.9; // 计算多少ms后可喂狗
while(1)
{
if(up_flag == 0)
{
printf("*当前模式:不自动喂狗模式 %d \n\r", run_cnt);
}
else if(up_flag == 1)
{
HAL_Delay(delay_value);
ClearWWDG();
printf("*当前模式:自动喂狗模式 %d \n\r", run_cnt);
}
else if(up_flag == 2)
{
HAL_NVIC_EnableIRQ(WWDG_IRQn); // 使能WWDG中断
printf("*当前模式:中断喂狗模式 %d \n\r", run_cnt);
}
HAL_NVIC_DisableIRQ(WWDG_IRQn); // 去能WWDG中断
run_cnt++;
HAL_Delay(1);
}
}
/*
* 函数名:void Error_Handler(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:程序错误处理函数,此处暂时设为死循环,不做任何动作
*/
void Error_Handler(void)
{
while(1)
{
}
}