基于野火教程的看门狗。
实验器材:stm32c8t6,LED灯,按键一个。
在进入正题之前,我们先了解一下什么是看门狗。看门狗用于检测和解决由软件错误引起的故障,当计数器达到给定的超时值时,触发一个中断(仅适用于窗口看门狗)或系统复位。通俗的来讲,就是当程序跑飞,在规定的时间内系统复位,或产生中断。看门狗分为独立看门狗和窗口看门狗。独立看门狗用通俗一点的话来解释就是一个 12 位的递减计数器,当计数器的值从某个值一直减到 0 的时候,系统就会产生一个复位信号,即 IWDG_RESET。如果在计数没减到 0 之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们经常说的喂狗。 看门狗功能由 VDD 电压域供电,在停止模式和待机模式下仍能工作 。窗口看门狗跟独立看门狗一样,也是一个递减计数器不断的往下递减计数,当减到一个固定值 0X40时还不喂狗的话,产生复位,这个值叫窗口的下限, 是固定的值,不能改变。 这个是跟独立看门狗类似的地方,不同的地方是窗口看门狗的计数器的值在减到某一个数之前喂狗的话也会产生复位,这个值叫窗口的上限,上限值由用户独立设置。窗口看门狗计数器的值必须在上窗口和下窗口之间才可以喂狗,这就是窗口看门狗中窗口两个字的含义 。这是野火教程给的解释。
记下来我们进入正题:
功能框图如下:
①:独立看门狗的时钟由独立的RC振荡器提供LSI提供。主时钟发生故障时依然有效。另外就是LSI的频率在不同的温度和工作场合有漂移,因此看门狗适用于时间精确度低的场合。
②:递减寄存器的时钟由LSI经过寄存器IWDG_PR 分频得到。计数器时钟CK_CNT= 40/ 42^PRV,一个计数器时钟计数器就减一。
③:
④:重装载寄存器里面装着要刷新到计数器的值。这个值决定着独立看门狗的溢出时间。超时时间 Tout = (42^prv) / 40 * rlv (s) , prv 是预分频器寄存器的值, rlv 是重装载寄存器的值。
⑤:键寄存器作为看门狗的控制寄存器有三种模式:
要说明的是:写0XCCCC时是软件启动,一旦启动看门狗,只有复位才能关闭。
⑥:略。
看门狗的使用:一个程序运行的时间是50ms,我们设置的溢出时间是60ms,如果超过60ms还没有喂狗,就意味着程序跑飞了,系统就会自动复位。而后重新进行。
.c中的代码如下
#include "bsp_iwdg.h"
// 超时时间计算:Tout = prv / 40 * rlv
// prv 可以取下列值:4,8,16,32,64,128,256,256
// IWDG_Prescaler_4: IWDG prescaler set to 4
// IWDG_Prescaler_8: IWDG prescaler set to 8
// IWDG_Prescaler_16: IWDG prescaler set to 16
// IWDG_Prescaler_32: IWDG prescaler set to 32
// IWDG_Prescaler_64: IWDG prescaler set to 64
// IWDG_Prescaler_128: IWDG prescaler set to 128
// IWDG_Prescaler_256: IWDG prescaler set to 256
// 形参 rlv用来设置重装载寄存器 IWDG_RLR的 值 , 取 值 范 围 为0~0XFFF 。
//初始化函数
void IWDG_Config(uint8_t prv, uint16_t rlv)
{
// 使能 预分频器PR 和 重装载寄存器RLR 可写。
IWDG_WriteAccessCmd( IWDG_WriteAccess_Enable );
// 设置 预分频器值
IWDG_SetPrescaler( prv );
// 设置 重装载寄存器的值
IWDG_SetReload( rlv );
// 把重装载寄存器的值放到计数器中
IWDG_ReloadCounter();
// 使能 IWDG
IWDG_Enable();
}
// 喂狗函数
void IWDG_Feed(void)
{
//把重装载寄存器的值放到计数器中,防止IWDG复位
//当计数器的值减到零时,系统会复位
IWDG_ReloadCounter();
}
.h中的代码如下:
#ifndef __BSP_IWDG_H
#define __BSP_IWDG_H
#include "stm32f10x.h"
void IWDG_Feed(void);
void IWDG_Config(uint8_t prv,uint16_t rlv);
#endif /* __BSP_IWDG_H */
main.c中的文件如下:
//来到这里的时候,系统的时钟已经配置成72M。
LED1_GPIO_Config();
LED2_GPIO_Config();
GPIO_SetBits(LED2_G_GPIO_PORT, LED2_G_GPIO_PIN); //开始时,关闭绿灯
SysTick_Delay_ms(1500); // 延时 1.5 毫秒
// 检查看门狗是否复位
if( RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET )
{
// 看门狗复位时亮绿灯
LED2_OFF;
// 清除标志
RCC_ClearFlag();
// 如果一直不喂狗,会一直复位,加上前面的延时,会看到红灯闪烁
}
else
{
// 不是独立看门狗复位时(可能为上电复位或手动复位),
LED2_ON;
SysTick_Delay_ms(500);
LED2_OFF;
SysTick_Delay_ms(500);
}
KEY1_GPIO_Config();
IWDG_Config( IWDG_Prescaler_64 , 625 ); // prv = IWDG_Prescaler_64 ,rlv = 625 1秒的溢出时间
while( 1 )
{
// 以上代码为被监控的代码
if(Key1_Scan(KEY1_GPIO_PORT ,KEY1_GPIO_PIN) == KEY1_ON )
{
//喂狗操作,如果不喂狗,系统会复位,LED1则会灭一次,如果在一秒的时间内准时喂狗,绿灯会常亮
IWDG_Feed();
// 绿灯会常亮
LED2_OFF;
}
}
我们设置的溢出时间是1秒,如果在1秒的时间内没有按下按键,那么程序就会在main.c中执行while(1)之前的程序,一直循环,我们就会看到绿灯闪烁的情况。如果在1秒内不停按下了按键,(阻止了递减计数器减到零,程序就不会复位。)那么绿灯就会一直亮着。(限于材料有限,if和else中的程序均由绿灯完成,请多包含!)
开始之前,先来了解一下什么是窗口看门狗:
TR是窗口看门狗的计数器的值, WR 是窗口看门狗的上窗口值,均由用户独立设置。
功能框图:
①窗口看门狗的时钟:窗口看门狗的时钟PCLK1,PCLK1最大是36M。
②计数器的时钟:计数器的时钟由CK计时器经过预分频得到,分频系数由配置寄存器CFR-WDGTB配置,CK计时器的时钟=PCLK1/4096。计数器时钟CNT_CK=PCLK1/4096/(2*WDGTB),那么计数器递减一个数的时间T=1/CNT_CK。
③计数器:计数器手册上讲解的比我想说的更清楚:
④窗口值:下限我们无法改变,能改变的只有上限。而这个值必须介于0X7F到0X40之间。我们要监控的程序段 A 运行的时间为 Ta,当执行完这段程序之
后就要进行喂狗,如果在窗口时间内没有喂狗的话,那程序就肯定是出问题了。一般计数器的值TR设置成最大 0X7F,窗口值为 WR,计数器减一个数的时间为 T,那么时间: (TRWR)*T 应该稍微大于 Ta 即可。
⑤计算看门狗超时时间:
窗口看门狗适用于外部不可抗干扰或者程序逻辑错误造成的程序跑飞情况。
.c 文件代码如下:
#include "bsp_wwdg.h"
// 中断优先级初始化
static void WWDG_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
// WWDG 配置函数
// tr 递减计数器的值,取值范围是0X7F-0X40
// wr 窗口值,取值范围为0X7F-0X40
// prv 预分频器值,有四种选择:
// WWDG_Prescaler_1
// WWDG_Prescaler_2
// WWDG_Prescaler_4
// WWDG_Prescaler_8
void WWDG_Config( uint8_t tr,uint8_t wr,uint32_t prv)
{
// 开启WWDG时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE);
// 设置递减计数器的值
WWDG_SetCounter(tr);
// 设置预分频器的值
WWDG_SetPrescaler(prv);
// 设置上窗口的值
WWDG_SetWindowValue(wr);
// 设置计数器的值,使能WWDG
WWDG_Enable(WWDG_CNT);
// 清除提前唤醒中断标志位
WWDG_ClearFlag();
// 配置WWDG中断优先级
WWDG_NVIC_Config();
// 开 WWDG中断
WWDG_EnableIT();
}
void WWDG_Feed(void)
{
// 喂狗,刷新递减计数器的值,设置成最大WDG_CNT = 0X7F
WWDG_SetCounter(WWDG_CNT);
}
stm32f10x.it.c文件如下:
// WWDG 中断服务函数程序,如果发生了此中断,表示程序已经出现了故障,
// 这是一个死前中断,在此中断服务函数中应该干最重要的事,
// 这个时间具体有多长,看WDGTB的值决定:
// WDGTB:0 113us
// WDGTB:1 227us
// WDGTB:2 445us
// WDGTB:3 910us
void WWDG_IRQHandler(void)
{
//清除中断标志位
WWDG_ClearFlag();
// LED2亮,点亮LED只是示意性操作
// 真正使用的时候,应该是做最重要的事情
LED2_ON; // 这里以绿灯LED2常亮为最重要的事
}
// WWDG 中断服务函数程序,如果发生了此中断,表示程序已经出现了故障,
// 这是一个死前中断,在此中断服务函数中应该干最重要的事,
// 这个时间具体有多长,看WDGTB的值决定:
// WDGTB:0 113us
// WDGTB:1 227us
// WDGTB:2 445us
// WDGTB:3 910us
void WWDG_IRQHandler(void)
{
//清除中断标志位
WWDG_ClearFlag();
// LED2亮,点亮LED只是示意性操作
// 真正使用的时候,应该是做最重要的事情
LED2_ON; // 这里以绿灯LED2常亮为最重要的事
}
main.c文件如下:
uint8_t wwdg_tr, wwdg_wr;
//来到这里的时候,系统的时钟已经配置成72M。
LED1_GPIO_Config();
LED2_GPIO_Config();
// 开始之前先关闭LED1
LED1_ON; // 红灯关
// 延时1秒
SysTick_Delay_ms(1000);
// 初始化 WWDG ,配置计数器的初始值,配置上窗口值,启动WWDG,使能提前唤醒中断
WWDG_Config( 0X7F,0X5F,WWDG_Prescaler_8 );
// 窗口值我们在初始化的时候设置为0X5F,这个值不会改变
wwdg_wr = WWDG->CFR & 0X7F;
while( 1 )
{
LED1_OFF;
wwdg_tr = WWDG->CR & 0X7F;
if( wwdg_tr < wwdg_wr )
{
// 喂狗,重新设置计数器的值为最大0X7F
WWDG_Feed();
}
}
程序执行后LED1亮一段时间后会熄灭,之后就不会亮,而中断函数中的LED2不会灭(LED2指的是板子上的绿灯)。
刚入门,如果有错误,请及时指出,大佬勿喷!
欢迎交流学习,共同进步!