STM32中的看门狗

先复制一段度娘的东西:
在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会造成整个系统的陷入停滞状态,发生不可预料的后果,所以出于对单片机运行状态进行实时监测的考虑,便产生了一种专门用于监测单片机程序运行状态的模块或者芯片,俗称“看门狗”(watchdog) 。
简单来说,看门狗就是在不停地对我们正在运行的程序进行监视,我们必须在规定的时间的有效的时间内去进行“喂狗”操作,这样看门狗就知道我们的程序正在正常的运行,如果我们程序由于外界原因跑飞了,那么就无法进行喂狗操作,那么看门狗由于饥饿难耐,他就会使我们的单片机进行复位,以便让程序重新开始运行。
看门狗分为独立看门狗和窗口看门狗,独立看门狗比较简单,当然它的安全性没有窗口看门狗高。IWDG最适合应用于那些需要看门狗作为一个在主程序之外,能够完全独立工作,并且对时间精度要求较低的场合。WWDG最适合那些要求看门狗在精确计时窗口起作用的应用程序。

一、独立看门狗(IWDOG)

  1. IWDG简介:
    独立看门狗用通俗一点的话来解释就是一个 12 位的递减计数器,当计数器的值从某个值一直减到 0 的时候,系统就会产生一个复位信号,即 IWDG_RESET。如果在计数没减到 0 之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们经常说的喂狗。看门狗功能由 VDD 电压域供电,在停止模式和待机模式下仍能工作。  

  2. IWDG功能框图剖析

  • LSI独立看门狗时钟:
    独立看门狗的时钟由独立的 RC振荡器 LSI提供,即使主时钟发生故障它仍然有效,非常独立。LSI的频率一般在 30~60KHZ之间,根据温度和工作场合会有一定的漂移,我们一般取 40KHZ,所以独立看门狗的定时时间并一定非常精确,只适用于对时间精度要求比较低的场合。有不明白的小伙伴可以参考STM32时钟系统的配置寄存器和源码分析

  • 键寄存器(IWDG_KR):

    键值寄存器 IWDG_KR 可以说是独立看门狗的一个控制寄存器,主要有三种控制方式

    键值 作用
    0XAAAA 把寄存器IWDG_RLR的值重新装载到计数器中
    0X5555 允许访问IWDG_PR和IWDG_RLR寄存器
    0XCCCC 启动看门狗
  • 预分频寄存器(IWDG_PR):

    递减计数器的时钟由 LSI经过一个 8位的预分频器得到,我们可以操作预分频器寄存器 IWDG_PR 来设置分频因子,分频因子可以是:[4,8,16,32,64,128,256,256],计数器时钟CK_CNT= 40/ 4*2^PRV,一个计数器时钟计数器就减一。

  • 重装载寄存器(IWDG_RLR):
    重装载寄存器是一个 12 位的寄存器,里面装着要刷新到计数器的值,这个值的大小决定着独立看门狗的溢出时间。超时时间 Tout = (4*2^prv) / 40 * rlv (s) ,prv是预分频器寄存器的值,rlv是重装载寄存器的值。

  • 状态寄存器(IWDG_SR):

    注意:如果在应用程序中使用了多个重装载值或预分频值,则必须在RVU位被清除后才能重新改变预装载值,在PVU位被清除后才能重新改变预分频值。然而,在预分频和/或重装值更新后,不必等待RVU或PVU复位,可继续执行下面的代码。(即是在低功耗模式下,此写操作仍会被继续执行完成。)

  • IWDG寄存器映像:

  1. 库函数分析
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);//取消写保护:0x5555使能
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);//设置预分频系数:写PR
void IWDG_SetReload(uint16_t Reload);//设置重装载值:写RLR
void IWDG_ReloadCounter(void);//喂狗:写0xAAAA到KR
void IWDG_Enable(void);//使能看门狗:写0xCCCC到KR
FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG);//状态:重装载/预分频 更新

  1. IWDG超时实验
    通过按键进行喂狗,程序正常运行时led灯常亮,未进行喂狗时程序闪烁。
void IWDG_Init(u8 prer,u16 rlr) 
{	
    IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);  //使能对寄存器IWDG_PR和IWDG_RLR的写操作
    IWDG_SetPrescaler(prer);  //设置IWDG预分频值:设置IWDG预分频值为64
    IWDG_SetReload(rlr);  //设置IWDG重装载值
    IWDG_ReloadCounter();  //按照IWDG重装载寄存器的值重装载IWDG计数器
    IWDG_Enable();  //使能IWDG
}

//喂独立看门狗
void IWDG_Feed(void)
{   
 	IWDG_ReloadCounter();//reload										   
}
	
  1. main.c文件
#include "stm32f10x.h"
#include "stm32f10x_exti.h"
#include "misc.h"
#include "stm32f10x_iwdg.h"

 /***************  延时函数 *******************/
void Delay(__IO u32 nCount)
{
	for(; nCount != 0; nCount--);
} 

 /***************  配置LED用到的I/O口 *******************/
void LED_GPIO_Config(void)	
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); // 使能PC端口时钟  
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;	//选择对应的引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;       
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);  //初始化PC端口
	GPIO_SetBits(GPIOB, GPIO_Pin_12 );	 // 关闭LED
}


//将GPIOA_0设置为下拉输入
void KEY_Init(void) //IO初始化
{ 
 	GPIO_InitTypeDef GPIO_InitStructure;
 
 	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);    //使能PORTB,PORTE时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);	//使能AFIO复用功能时钟

	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;    // 0 pin
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;     // 设置成下拉输入
 	GPIO_Init(GPIOA, &GPIO_InitStructure);    // 初始化GPIOA_0

}

void IWDG_Init(u8 prer,u16 rlr) 
{	
    IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);  //使能对寄存器IWDG_PR和IWDG_RLR的写操作
    IWDG_SetPrescaler(prer);  //设置IWDG预分频值:设置IWDG预分频值为64
    IWDG_SetReload(rlr);  //设置IWDG重装载值
    IWDG_ReloadCounter();  //按照IWDG重装载寄存器的值重装载IWDG计数器
    IWDG_Enable();  //使能IWDG
}
//喂独立看门狗
void IWDG_Feed(void)
{   
 	IWDG_ReloadCounter();//reload										   
}


int main(void)
{
	SystemInit();
	LED_GPIO_Config(); //LED 端口初始化  
	KEY_Init();
	
	GPIO_SetBits(GPIOB, GPIO_Pin_12 );	 // 关闭LED
	Delay(1000000);  
	
	IWDG_Init(4,625);    //与分频数为64,重载值为625,溢出时间为1s
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);

	while (1)
	{
		if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == 1)
		{
			Delay(100);
			if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) == 1)
			{
				IWDG_Feed();//如果WK_UP按下,则喂狗
			}
		}
		Delay(10000);
	}
		
}


二、窗口看门狗(WWDOG)

  1. WWDOG简介
    窗口看门狗通常被用来监测,由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。简单的和独立看门狗对比一下区别应该就能明白什么是窗口看门狗了。
    在独立看门狗中,是计数器的值一直减到 0 时都可以进行喂狗,只有当计数器到达 0 时会产生复位信号。独立看门狗的时钟不是精准时钟。
    在窗口看门狗中,只有在设定的区间内才能进行喂狗,否则都会产生相应的复位信号使系统复位,并且独立看门狗采用的是PCLK1时钟,对时钟还有疑惑的小伙伴可以看我之前的笔记STM32时钟系统的配置寄存器和源码分析。从这可以看出来,窗口看门狗的安全性比独立看门狗要高一些,毕竟它喂狗的时间段缩短了,程序跑飞而误打误撞的去执行喂狗操作的几率了就降低了。
    注意:PCLK1的时钟频率最大36MHz
  • 窗口看门狗的时序图

    从图中可以看出来,喂狗时间只能在W[6:0]与0X3F之间。
  1. WWDG功能描述
  • 看门狗框图

    从图中可以看出当①和②同事发生复位信号时,系统才会复位。现在可能看不明白这个框图,记住下面这两个复位条件,便可看明白这个框图的流程了。
  • 当7位(T[6:0])递减计数器从0x40翻转到0x3F(T6位清零)时,产生一个复位。
  • 窗口寄存器中的数值被重新装载时,将产生一个复位。
    注意:发生复位的前提是启动门狗,也就是说WWDG_CR寄存器中的WDGA位被置’1’。
    现在来分析就比较简单了,当计数器T6:0>W6:0时重装计数器系统便会发生复位,同理当计数器小于0X3F时,重装计数器系统也会发生复位,所以计数器只能在W[6:0]与0X3F之间重装。
  1. WWDG使用
    应用程序在正常运行过程中必须定期地写入WWDG_CR寄存器以防止MCU发生复位。只有当计数器值小于窗口寄存器的值时,才能进行写操作。储存在WWDG_CR寄存器中的数值必须在0xFF和0xC0之间:
  • 启动看门狗
    在系统复位后,看门狗总是处于关闭状态,设置WWDG_CR寄存器的WDGA位能够开启看门狗,随后它不能再被关闭,除非发生复位。

  • 控制递减计数器
    递减计数器处于自由运行状态,即使看门狗被禁止,递减计数器仍继续递减计数。当看门
    狗被启用时,T6位必须被设置,以防止立即产生一个复位。
    T[5:0]位包含了看门狗产生复位之前的计时数目;复位前的延时时间在一个最小值和一个最
    大值之间变化,这是因为写入WWDG_CR寄存器时,预分频值是未知的。
    配置寄存器(WWDG_CFR) 中包含窗口的上限值:要避免产生复位,递减计数器必须在其值小于窗口寄存器的数值并且大于0x3F时被重新装载,0描述了窗口寄存器的工作过程。另一个重装载计数器的方法是利用早期唤醒中断(EWI)。设置WWDG_CFR寄存器中的WEI位开启该中断。当递减计数器到达0x40时,则产生此中断,相应的中断服务程序(ISR)可以用来加载计数器以防止WWDG复位。在WWDG_SR寄存器中写’0’可以清除该中断。

  • 控制寄存器(WWDG_CR)

  • 配置寄存器(WWDG_CFR)

  • 状态寄存器(WWDG_SR)

  1. 库函数分析
    RCC_APB1PeriphClockCmd();    // WWDG时钟使能  
    WWDG_SetPrescaler();        // 设置IWDG预分频值
    WWDG_SetWindowValue();      // 设置窗口值
    WWDG_Enable();              // 使能看门狗           
    WWDG_ClearFlag();          // 清除提前唤醒中断标志位 
    WWDG_EnableIT();           // 开启窗口看门狗中断
    WWDG_IRQHandler();          // 中断服务函数

看门狗超时计算公式

其中IWDG预分频值提供的头文件是1、2、4、8也试试2的次方

  1. main.c文件
#include "stm32f10x.h"
#include "stm32f10x_exti.h"
#include "misc.h"
#include "stm32f10x_wwdg.h"

//保存WWDG计数器的设置值,默认为最大. 
uint8_t WWDG_CNT=0x7f; 


 /***************  延时函数 *******************/
void Delay(__IO u32 nCount)
{
	for(; nCount != 0; nCount--);
} 

 /***************  配置LED用到的I/O口 *******************/
void LED_GPIO_Config(void)	
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE); // 使能PC端口时钟  
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;	//选择对应的引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;       
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);  //初始化PC端口
	GPIO_SetBits(GPIOB, GPIO_Pin_12 );	 // 关闭LED
}

//窗口看门狗中断服务程序
void WWDG_NVIC_Init()
{
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn;    //WWDG中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;   //抢占2,子优先级3,组2	
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;	 //抢占2,子优先级3,组2	
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; 
	NVIC_Init(&NVIC_InitStructure);//NVIC初始化
}

//初始化窗口看门狗
void WWDG_Init(uint8_t tr,uint8_t wr,uint32_t fprer)
{ 
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);  //   WWDG时钟使能
	WWDG_SetPrescaler(fprer);		//设置IWDG预分频值
	WWDG_SetWindowValue(wr);//设置窗口值
	WWDG_Enable(tr);	 //使能看门狗                 
	WWDG_ClearFlag();//清除提前唤醒中断标志位 

	WWDG_NVIC_Init();//初始化窗口看门狗 NVIC
	WWDG_EnableIT(); //开启窗口看门狗中断
} 


int main(void)
{
	SystemInit();
	LED_GPIO_Config(); //LED 端口初始化  
	
	WWDG_Init(WWDG_CNT,0X5F,WWDG_Prescaler_8);//计数器值为7f,窗口寄存器为5f,分频数为8	
	GPIO_ResetBits(GPIOB,GPIO_Pin_12);

	while (1)
	{

	}
		
}

void WWDG_IRQHandler(void)
{

	WWDG_SetCounter(WWDG_CNT);	  //喂狗

	WWDG_ClearFlag();	  //清除提前唤醒中断标志位
	if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_12) == 1)
	{
		GPIO_ResetBits(GPIOB,GPIO_Pin_12);
	}else{
		GPIO_SetBits(GPIOB, GPIO_Pin_12 );
	}

}

参考文献

STM32中的看门狗:https://blog.csdn.net/qq_36554582/article/details/82224648
STM32的IWDG(独立看门狗)详细用法:https://blog.csdn.net/weibo1230123/article/details/80705866

posted @ 2022-01-13 16:03  浇筑菜鸟  阅读(685)  评论(0)    收藏  举报