在由单片机构成的微型计算机系统中,由于单片机的工作常常会受到来自外界电磁场的干扰,造 成程序的跑飞,而陷入死循环,程序的正常运行被打断,由单片机控制的系统无法继续工作,会 造成整个系统的陷入停滞状态,发生不可预料的后果,所以出于对单片机运行状态进行实时监测 的考虑,便产生了一种专门用于监测单片机程序运行状态的模块或者芯片,俗称“看门狗” (watchdog) 。
先说一下这篇文章的思路,这表篇文章将分为3个部分来写。第一部分写基于手册和stm32cube介绍独立看门狗的使用,然后书写代码,通过硬件实现喂狗;第二部分写基于手册和stm32cube介绍窗口狗的使用,然后书写代码,通过软件实现喂狗;第三部分是两个狗的比较。
一、简介以及本质
1.简介
独立看门狗(IWDG)由专用的低速时钟(LSI)驱动,即使主时钟发生故障它也仍然有效,IWDG最适合应用于那些需要看门狗作为一个在主程序之外,能够完全独立工作。
这个介绍啥意思?还是不懂,没关系,我用两张图给你讲讲啥意思。
我们知道单片机的许多外设工作时都需要内部时钟的参与,否则就没法精准工作。下图中我把内部的高速与低速时钟都关掉了,我们来看看会是什么情况。
发现了吗?在内部时钟都关闭的情况下,外部LS RC时钟还是可以提供40HKz的时钟让独立看门狗使用,怎么样,够独立吧,他就是一点不依靠内部时钟,有专属的外部时钟,所以这个名字有“独立”两个字当之无愧吧。
2.本质
本质是一个 12 位的递减计数器,当计数器的值从某个值一直减到0的时候,系统就会产生一个复位信号,即 IWDG_RESET 。如果在计数没减到0之前,刷新了计数器的值的话,那么就不会产生复位信号,这个动作就是我们经常说的喂狗。
通俗来说就是你要在独立看门狗倒计时结束之前,人为通过外部的触发给它的计数器刷新到原来的最大值,这样你的程序还可以继续执行。如果你不进行干涉,等到它自己倒计时结束,这时候,它也会自己复位,给自己的计数器刷新到原来的最大值,但是你的程序就要从头开始执行了。就行你看的那些循环的电影,主人公明明今天没了,但是第二天又活了,接着又是同样的场景,周而复始。这里规定时间内你不喂狗,一到规定的时间,狗就把单片机“咬死了”(也就是重启单片机)。然后接着就是单片机“死后重生”。只要你在规定的时间不喂狗,单片机就也会陷入“被咬死在重生“的循环之中。
二、关注的相关寄存器
1.预分频寄存器(IWDG_PR)
我不直到大家觉得.预分频是干啥用的,直白的说,预分频就是就是用来减小记一个数的频率。你想一下如果我们不分频,原来计数一次的频率是40Hz,现在预分频因子=4,那么计数一次的频率就是40/4=10Hz。频率越大,计一个数时间越短;频率越小,计一个数时间越长。我们的预分频因子由IWDG_PR的最后三位决定,共有八种情况。
2.重装载寄存器(IWDG_RLR)
这里的重装寄存器相比于定时器那里的重装寄存器,功能比较简单,直白的说就是你要设置一个计数的上限值,这个上限制不超过12位的RL[11:0]即4096就可以了,手册上告诉我们计数的下限是0。当我们配置好之后,每发生复位时,重装载寄存器里的值都会变成我们设的最大值。
3.键寄存器(IWDG_KR)
我们这里使用的是独立看门狗,并不是硬件看门狗。这里有关写入的寄存器操作不必太在意,我们配置stm32cube和HAL库函数的时候其实就用到了。
4.溢出时间计算公式
这里的溢出时间和定时器的算法是一样的,都是用“计一个数的时间*计得个数”,最后就得出了总时间,也就是溢出时间。
三、stm32cube的配置如下图
SYS
IWDG
四、代码部分
我们要开启独立看门狗,溢出时间为1秒,使用按键2进行喂狗。
#include "main.h"
#include "iwdg.h"
#include "usart.h"
#include "gpio.h"
#include "string.h"
void SystemClock_Config(void);
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_IWDG_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_UART_Transmit(&huart1, "记得喂狗\r\n", strlen("记得喂狗\r\n"), 100);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_RESET)
HAL_IWDG_Refresh(&hiwdg);//喂狗函数,将重装载寄存器变为设置的625
HAL_Delay(50);
}
/* USER CODE END 3 */
}
烧录进单片机,打开串口助手,接可以看到每隔一秒发送"记得喂狗",当我在在一秒之内喂狗或者一直喂狗(一直按着KEY2),就不会重启跳出循环,而是一直调用喂狗函数,将重装载寄存器变为设置的625。
一、简介以及本质
1.手册上的简介
窗口看门狗通常被用来监测,由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运 行序列而产生的软件故障。除非递减计数器的值在T6位变成0前被刷新,看门狗电路在达到预置的时间周期时,会产生一个MCU复位。在递减计数器达到窗口寄存器数值之前,如果7位的递减计数器数值(在控制寄存器中)被刷新, 那么也将产生一个MCU复位。这表明递减计数器需要在 一个有限的时间窗口中被刷新。很明显,看的很懵比,没关系继续往下看,接着分析
2.本质
是一个能产生系统复位信号和提前唤醒中断的6位计数器。
产生复位条件: 当递减计数器值从 0x40 减到 0x3F 时复位(即T6位跳变到0(程序重头再来))
计数器的值大于 W[6:0] 值时喂狗会复位(程序重头再来)。
产生中断条件: 当递减计数器等于 0x40 时可产生提前唤醒中断 (EWI)。
在窗口期内重装载计数器的值,防止复位(程序不会重头再来),也就是所谓的喂狗。
看看图再看看文字理解一下
不出意外的话,对上面的说法,结合图片你已经差不多理解了,也明白为啥这条狗的名字里含有“窗口”二字,但是我估计你对图片上的纵轴计数值比较懵逼,没关系,继续往下看。
二、关注的相关寄存器
1.控制寄存器(WWDG_CR)
我们可以看到第七位是控制窗口开门狗开启的,这个我们在stm32cube中可以配置,接着是0到6位,也就是7个位。手册里说的啥意思呢?意思就是说计数器的计数上限最大是2^7也就是127,因为0~6一共7位;计数器的计数下限固定是0x3F,因为一但到了0x3F就会发生复位,将计数器的值再次变为最大值。
说白了,就是说控制寄存器决定了窗口开门狗是否开启以及图片中纵轴计数值的最大值
2.配置寄存器(WWDG_CFR)
这里一共10位可配置,第9位通过stm32cube配置,第八位与第七位是用来配置预分频的,第0~6位是来设置一个值当计数器的值与其相等时,作为窗口与非窗口的分界点,并且这个值要小于等于计数器最大值大于0x3F。
好了,介绍完这些,我们在回到刚刚说的让你很懵逼的计数值纵轴上,我新建一个二维坐标系给你看,你就理解了。
T[6:0]的值作为计数器最大值,W[6:0]的值作为分窗判断值。下现在没有那么懵逼了吧。
3.超时时间计算
Tout是WWDG超时时间(没喂狗)
Fwwdg是WWDG的时钟源频率(最大36M)
4096是WWDG固定的预分频系数 2^WDGTB是WWDG_CFR寄存器设置的预分频系数值
T[5:0]是WWDG计数器低6位,最多63
三、stm32cube的配置如下图
开启窗口看门狗,计数器值设置为 0X7F ,窗口值设置为 0X5F ,预分频系数为 8 。程序启动时点 亮 LED1 ,300ms 后熄灭,随后每隔三秒翻转LED1状态。。在提前唤醒中断服务函数进行喂狗,同时翻转LED2状态。
SYS
RCC
WWDG
GPIO(B89和B9都默认配置为高电平)
四、代码部分
#include "main.h"
#include "wwdg.h"
#include "gpio.h"
void SystemClock_Config(void);
void HAL_WWDG_EarlyWakeupCallback(WWDG_HandleTypeDef *hwwdg)
{
HAL_WWDG_Refresh(hwwdg);//计数值等于0x40进入中断,然后喂狗,防止复位
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_9);
}
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_8, GPIO_PIN_RESET);
HAL_Delay(300);
MX_WWDG_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_8);
HAL_Delay(3000);
}
/* USER CODE END 3 */
}