STM32F103五分钟入门系列(十四)窗口看门狗WWDG
Posted 自信且爱笑‘
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STM32F103五分钟入门系列(十四)窗口看门狗WWDG相关的知识,希望对你有一定的参考价值。
学习板:STM32F103ZET6
参考:STM32F103五分钟入门系列(十三)独立看门狗IWDG
一、窗口看门狗(WWDG)简介
1、什么是窗口看门狗
看门狗的概念上一博客已经总结过了,之所以被称为“窗口”,是因为“喂狗”时间有一个范围,可以通过相关寄存器设置上限时间,而下限是固定的。喂狗时间不能过早,也不能过晚。
2、窗口看门狗原理
上图所示,随着时间的推移,计数器的值一直在减小。窗口看门狗的喂狗上限为W[6:0](因为寄存器是低7位有效),这个上限值需要自己定义,但是不能小于0x3F,不能大于0x7F。
当前计数器的值大于自定义的W[6:0],且“喂狗”时,会发生中断;当计数值到达0x3F时,不管有没有“喂狗”,都发生中断;所以“喂狗”的计数范围为W[6:0]~0x3F,在W[6:0]之前、0x3F之后都不能“喂狗”,有上下限,谓之“窗口”。
当“可喂狗区间”没有喂狗,计数到0x40,即:0x01000000时,再倒计数一次,就变为0x00111111,即0x3F,此时会产生唤醒中断,产生复位信号。所以不难理解上图中当计数到3F时,T6为由1——>0,即0x01000000——>0x00111111。
3、窗口看门狗与独立看门狗区别
①喂狗范围不同
窗口看门狗“喂狗”范围有个上限和下限,独立看门狗没有下限,只有上限。
②产生复位信号时间不同
窗口看门狗在大于W[6:0]、倒计数到0x3F都产生复位信号;独立看门狗只有倒计数到0才产生复位信号
③时钟不同
窗口看门狗的时钟来源为APB1预分频后的PCLK1,为36MHZ。独立看门狗的时钟为40KHZ的LSI时钟
④窗口看门狗有自己的中断服务函数WWDG_IRQHandler,而独立看门狗没有
4、窗口看门狗应用场合
窗口看门狗(WWDG)通常被用来监测由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而产生的软件故障。因为窗口看门狗计数值寄存器只有7位,即最大为127,而还有下限0x40,即64,所以可喂狗计数区间在64~127(不一定能到达127,看这个窗口上限设置在哪里),所以很短时间内就需要喂狗,在某些时候,程序虽然跑飞,但是短暂时间后,又回归正常,如果用独立看门狗,可能无法检测出来,此时使用窗口看门狗比较合适。
二、窗口看门狗的相关寄存器
1、控制寄存器(WWDG_CR)
该寄存器是低8位有效的32位寄存器。位6:0用来存储看门狗的计数器值。当计数值从0x40变为3F时(到达下限),产生一个复位信号。位7为1时启动看门狗
2、配置寄存器(WWDG_CFR)
该寄存器是低10位有效的32位寄存器,位6:0包含与计数器比较的窗口值,即这7位是用来存储窗口看门狗上限值的。
位8:7设置窗口看门狗的时钟,将窗口看门狗时钟设置为(PCLK1/4096)/(2^位[8:7])
位9设置提前唤醒中断,当该位设置为1时,计数器达到0x40,跳变到0x3F时产生复位信号。即设置下限产生复位信号。
3、状态寄存器(WWDG_SR)
当计数器到达下限后无论是否产生复位信号(即无论CFR寄存器位9是否置1允许中断),SR寄存器的位0都会被硬件置1。所以可以根据该寄存器位0是否被置1来获取计数器是否到达下限。
三、窗口看门狗编程顺序
1、使能WWDG时钟
WWDG时钟用的是APB1,所以使能APB1时钟即可。
由之前博客STM32F103五分钟入门系列(七)SystemInit()函数、SetSysClock()函数总结,可以知道在72MHZ默认系统时钟下,APB1预分频系数为2、AHB预分频系数为1,所以PCLK1的时钟为36MHZ。
故WWDG时钟为36MHZ
2、设置窗口值和分频因子
3、开启 WWDG 中断并分组
4、 设置计数器初始值并使能看门狗
5、编写中断服务函数
四、例子(寄存器版+测试)
1、led代码
例:用LED0来指示程序复位,复位后LED0亮0.5s。在中断服务函数中清除中断标志,同时用LED1来指示进入中断服务函数情况。
LED代码上一博客STM32F103五分钟入门系列(十三)独立看门狗IWDG用到过,直接附代码:
//led.h
#ifndef LED_H
#define LED_H
void LED_Init(void);
#endif
//led.c
#include "sys.h"
#include "stm32f10x.h"
#include "led.h"
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct_B;
GPIO_InitTypeDef GPIO_InitStruct_E;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE ,ENABLE);
GPIO_InitStruct_B.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct_B.GPIO_Pin=GPIO_Pin_5;
GPIO_InitStruct_B.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct_B);
GPIO_InitStruct_E.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_InitStruct_E.GPIO_Pin=GPIO_Pin_5;
GPIO_InitStruct_E.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOE,&GPIO_InitStruct_E);
GPIO_SetBits(GPIOB, GPIO_Pin_5);//PB5置高电平
//GPIO_WriteBit(GPIOB, GPIO_Pin_5,1);
//GPIO_Write(GPIOB,0x0020); //慎用
//PBout(5)=1;
GPIO_SetBits(GPIOE, GPIO_Pin_5);//PE5置高电平
//GPIO_WriteBit(GPIOE, GPIO_Pin_5,1);
//GPIO_Write(GPIOE,0x0020); //慎用
//PEout(5)=1;
}
接下来就是窗口看门狗代码编写。
2、wwdg.h
wwdg.h文件编写,固定格式:
//wwdg.h
#ifndef WWDG_H
#define WWDG_H
void WWDG_Init(void);
#endif
wwdg.c文件代码编写:
3、wwdg.c
(1)使能WWDG时钟
RCC->APB1ENR|=1<<11;//使能WWDG时钟
(2)设置窗口值和分频数(有个重要总结)
窗口值下限为0x40(0x3F),为固定值,不需要设置。上限值设置:
PCLK1时钟为36MHZ,为了在有限的计数次数下,可喂狗时间间隔尽量长一点,WWDG时钟可以小一点,即分频系数可以大一点。
选用PCLK1除以4096再除以8位WWDG的时钟,此时时钟为:36MHZ/(4096*8)≈1.1KHZ
因为CR寄存器位6:0储存计数器的值、CFR的位6:0储存窗口上限值,所以这俩个值最大为:2^7-1=127(0x7F)。所以上限值的范围是127~65(>0x40),范围很小。当WWDG时钟最小时,可计数时间(最大可不喂狗时差)最长,为(127-65)/1100≈0.056s,这个时间已经是WWDG喂狗的最大时间了。
因为"可喂狗时间"范围比较小,所以就不变强求0.5s喂狗还是1s为喂狗了,如窗口上限0x6F
综上:
预分频器采用:CK计时器时钟(PCLK1除以4096)除以8
窗口上限赋值:0x6F
故CFR寄存器的值:0x1eF
WWDG->CFR|=0x1ef;//设置 CK计时器时钟(PCLK1除以4096)除以8、0x6f窗口上限
(3)开启 WWDG 中断并分组
WWDG->CFR|=1<<9;//设置提前唤醒中断
在主函数中设置中断优先级分组
//主函数中
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
再回到wwdg.c中设置优先级分组初始化函数:
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=WWDG_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
(4)设置计数器初始值并使能看门狗
最大装载值上限为1111111=0x7F,不妨设成这个:同时使能看门狗
WWDG->CR=0xff;//设置计数器初始值并使能看门狗
(5)编写中断服务函数
在中断服务函数中重装载初值和清除中断标志:
void WWDG_IRQHandler(void)
{
WWDG->CR=0x7f;
WWDG->SR=0;
PEout(5)=~PEout(5);
}
4、完整代码+测试
(1)不唤醒提前中断
//wwdg.c
#include"wwdg.h"
#include "stm32f10x.h"
#include "led.h"
#include "sys.h"
void WWDG_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
RCC->APB1ENR|=1<<11;//使能WWDG时钟
//WWDG->CFR&=0xfc00;//清空
WWDG->CFR|=0x1ef;//设置 CK计时器时钟(PCLK1除以4096)除以8、0x6f窗口上限
//WWDG->CFR|=1<<9;//设置提前唤醒中断
NVIC_InitStructure.NVIC_IRQChannel=WWDG_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
WWDG->CR=0x7f;
WWDG->CR|=1<<7;//设置计数器初始值并使能看门狗
}
void WWDG_IRQHandler(void)
{
WWDG->CR=0x7f;
WWDG->SR=0;
PEout(5)=~PEout(5);
}
//main.c
#include "stm32f10x.h"
#include "led.h"
#include "wwdg.h"
#include "sys.h"
#include "delay.h"
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init();
delay_init();
PBout(5)=0;
delay_ms(500);//通过LED0的亮灭来指示程序是否复位
WWDG_Init();
while(1)
{
PBout(5)=1;
}
}
注意:在wwdg.c中这行代码:WWDG->CFR|=1<<9;//设置提前唤醒中断
被注释掉,此时没有唤醒提前中断,即会计数到0x40,并到0x3f时产生复位信号,程序不会进入中断服务函数,程序直接复位了,所有只有LED0每隔0.5s闪烁一次。
测试效果:
(2)唤醒提前中断
当wwdg.c中代码WWDG->CFR|=1<<9;//设置提前唤醒中断
未被注释,即允许提前唤醒中断时,快要计数到0x40时,唤醒中断,进入中断服务函数,此时执行中断服务函数的重装载、清除中断标志代码,程序不会复位,并且中断服务函数中的LED1一直取反。表现为手动复位后,LED0闪烁一次,LED1一直闪烁,而之后不会再不复位,所以LED0一直处于熄灭状态。
注意是计数到0x40——>0x3f跳变时,程序复位,提前唤醒中断后,进入中断服务函数重装载初值,就不会计数到0x3F了,程序也不会复位!
去掉注释后代码效果:
五、窗口看门狗常用库函数
窗口看门狗库函数定义在stm32f10x_wwdg.h中:
1、失能窗口看门狗函数WWDG_DeInit()
参数: 无
返回值: 无
从函数体中也能看到,该函数只是对WWDG的时钟失能。
2、设置预分频系数函数WWDG_SetPrescaler()
参数:
表示PLCK1/4096/(1、2、4、8)
然后将该值赋值给CFR寄存器。
返回值: 无
3、窗口上限值设置函数WWDG_SetWindowValue()
参数:
自定义的一个小于0x7f的值,因为装载寄存器只有7位,且这个值不能小于0x40,所以应该在0x41~0x7f之间
返回值: 无
操作:
对CFR寄存器赋值
4、唤醒提前中断函数WWDG_EnableIT()
参数: 无
返回值: 无
操作:
直接把1赋值给CFR寄存器的位9,这里用的是基地址+偏移地址来访问该位,可以把库函数改为以下代码,更直观:
void WWDG_EnableIT(void)
{
WWDG->CFR|=1<<9;
}
5、设置重装载值函数WWDG_SetCounter()
参数:
该参数是自定义参数,必须小于窗口值上限,大于窗口值下限。
操作:
对CR寄存器赋值
6、窗口看门狗使能函数WWDG_Enable()(加一个注意事项)
参数:
这里传递了一个参数Counter,其实是没有用的,用的时候可以直接把参数置为0。
不过仔细看这个参数:
如果把这个参数设置为重装载值,那么这个函数即使能了WWDG,又同时重装载了初值!这样的话WWDG_SetCounter()
函数就可以用本函数替代了。
操作:
将0x80与Counter或运算后赋值给CR寄存器,不仅对CR寄存器位7置1,使能WWDG,还对CR寄存器的位6:0赋值,操作了装载值。
注意这里对CR寄存器的操作是赋值,不是位或运算,甚至就算是位或运算,对低6:0操作后,会对WWDG的重装载值产生影响,如果在本函数之前使用了WWDG_SetCounter()
函数进行重装载值,执行本函数后,重装载值会发生变化!
所以为了防止程序出现错误,本函数的参数最好是我们自定义的那个重装载值。这也是这个库函数不好的地方,库函数修改为如下代码就好用多了:
void WWDG_Enable()
{
WWDG->CR|=0x80;
//WWDG->CR|=CR_WDGA_Set
}
7、获取中断状态标志函数WWDG_GetFlagStatus()
参数: 无
返回值: RESET 或SET
操作:
直接返回SR寄存器的值,由于该寄存器只有位0有效,所以上电后其它位为0,若发生中断,SR寄存器位0变为1,该函数强制类型转换为FlagStatus类型,即{RESET = 0, SET = !RESET}
8、清除中断标志函数WWDG_ClearFlag()
参数: 无
返回值: 无
操作: 对SR寄存器直接赋0,清除中断标志
六、例子(库函数版+测试)
就前面的那个例子,用库函数写一遍,直接把源代码注释掉换成库函数:
只在wwdg.c中修改代码,其它代码保持不变
//wwdg.c
#include"wwdg.h"
#include "stm32f10x.h"
#include "led.h"
#include "sys.h"
void WWDG_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
//RCC->APB1ENR|=1<<11;//使能WWDG时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
//WWDG->CFR|=0x1ef;//设置 CK计时器时钟(PCLK1除以4096)除以8、0x6f窗口上限
WWDG_SetPrescaler(WWDG_Prescaler_8);
WWDG_SetWindowValue(0x6f);
//WWDG->CFR|=1<<9;//设置提前唤醒中断/**********注意注释本行,可以进入中断服务函数******/
WWDG_EnableIT();//设置提前唤醒中断/**********注意注释本行,可以进入中断服务函数******/
NVIC_InitStructure.NVIC_IRQChannel=WWDG_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
//WWDG->CR=0x7f;
WWDG_SetCounter(0x7f);
//WWDG->CR|=1<<7;//设置计数器初始值并使能看门狗
WWDG_Enable(0x7f);//注意参数,为重装载值,或者把上一行代码注释掉
}
void WWDG_IRQHandler(void)
{
//WWDG->CR=0x7f;
WWDG_SetCounter(0x7f);
//WWDG->SR=0;
WWDG_ClearFlag();
PEout(5)=~PEout(5);
}
测试效果与之前例子一致:
注意:在wwdg.c中这行代码:WWDG_EnableIT();
被注释掉,此时没有唤醒提前中断,即会计数到0x40,并到0x3f时产生复位信号,程序不会进入中断服务函数,程序直接复位了,所有只有LED0每隔0.5s闪烁一次
测试效果:
当wwdg.c中代码WWDG_EnableIT()
未被注释,即允许提前唤醒中断时,快要计数到0x40时,唤醒中断,进入中断服务函数,此时执行中断服务函数的重装载、清除中断标志代码,程序不会复位,并且中断服务函数中的LED1一直取反。表现为手动复位后,LED0闪烁一次,LED1一直闪烁,而之后不会再不复位,所以LED0一直处于熄灭状态。
注意是计数到0x40——>0x3f跳变时,程序复位,提前唤醒中断后,进入中断服务函数重装载初值,就不会计数到0x3F了,程序也不会复位!
去掉注释后代码效果:
以上是关于STM32F103五分钟入门系列(十四)窗口看门狗WWDG的主要内容,如果未能解决你的问题,请参考以下文章