记录一下,方便以后翻阅~
主要内容:
1) 窗口看门狗概述;
2) 常用寄存器和库函数配置;
3) 窗口看门狗实验。
窗口看门狗实验内容:
为了对之前的知识进行总结复习,本人在教学案例的基础上又“意淫”了一些附加要求,具体内容为,启动后,
独立看门狗每次自动复位时,LED0先灭后亮,蜂鸣器不叫。按KEY2键可对独立看门狗进行手动喂狗,按下时,LED0灭,蜂鸣器叫,指令内容通过串口传至PC端,松开时,LED0亮,蜂鸣器不叫,长按KEY2时,LED0一直灭,蜂鸣器一直叫,但指令只传一次;
窗口看门狗启动提前唤醒中断,每次因中断自动喂狗时,LED1翻转一次(即由0至1或由1至0),蜂鸣器不叫,若喂狗同时KEY1按下,指令内容通过串口传至PC端。
官方资料:《STM32中文参考手册V10》第18章——窗口看门狗
1. 窗口看门狗的概念
由于喂狗时间是一个有上下限的范围内(窗口),通过设定相关寄存器,设定其上限时间(下限固定),喂狗的时间不能过早也不能过晚,所以称之为窗口看门狗;
独立看门狗限制喂狗时间在0-x内,x由键寄存器的低16位决定。喂狗的时间不能过晚。
2. 窗口看门狗工作示意图
2.1 由上图所示,喂狗时间只能在刷新窗口间;
2.2 3Fh用二进制表示是0011 1111;
2.3 T6位至到达3Fh(0011 1111)前一时刻(0100 0000),当下一时刻到达3Fh时,变为0011 1111,即第六位由1变为0,执行复位。
3. 窗口看门狗框图
3.1 窗口看门狗的时钟来源于PCLK1,PCLK1最高频率可达36MHz,PLCK1先除以4096,再进入看门狗预分频器,最后计算出的值作为窗口看门狗的时钟频率;;
3.2 控制寄存器WWDG_CR的第0~5位用来设置计数值,当第6位(即T6)由1变为0时,变产生一个复位,第7位WDGA为激活位,使能窗口看门狗;
3.3 通过比较器,当控制寄存器WWDG_CR的第0~6位(即T6:0)>配置寄存器WWDG_CFR的第0 ~6位(即W6:0),则比较结果为1;
3.5 因此,上图中,产生看门狗复位的条件有:
3.5.1 当满足3.3条件,即(T6:0)>(W6:0),此时,对控制寄存器WWDG_CFR进行写入,则经过或运算后,产生复位(因为处于不允许刷新的区域,不能喂狗);
3.5.2 当计数器的数值从0x40减到0x3F时(即T6位由1跳变到0),经过非运算,再经过或运算后,产生复位。
3.6 如果启动了看门狗并且允许中断,当递减计数器等于0x40时产生早期唤醒中断(EWI),它可以用于喂狗以避免WWDG复位;
3.7 上窗口值W[6:0]必须大于下窗口值0x40。否则就无窗口了。
4. 窗口看门狗超时时间
根据上述公式,假设 Fpclk1为最大值36MHz,那么可以得到最小~最大超时时间表如下所示:
5. 窗口看门狗作用
对于一般的看门狗,程序可在它产生复位前的任意时刻刷新看门狗,但是,有可能程序跑乱了又跑回到正常的地方,或跑乱的程序正好执行了刷新看门狗操作,这样的情况下一般的看门狗就检测不出来了;
若使用窗口看门狗,可以根据程序正常执行的时间设置刷新看门狗的一个时间窗口,保证不会提前刷新看门狗也不会滞后刷新看门狗,这样可以检测出程序没有按照正常的路径运行非正常地跳过了某些程序段的情况。
6. 窗口看门狗常用寄存器
6.1 控制寄存器WWDG_CR
void WWDG_Enable(uint8_t Counter); //启动并设置初始值//
void WWDG_SetCounter(uint8_t Counter); //喂狗//
6.2 配置寄存器WWDG_CFR
void WWDG_EnableIT(void); //使能提前唤醒中断//
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler); // 设置分频系数//
void WWDG_SetWindowValue(uint8_t WindowValue); //设置上窗口值//
6.3 控制寄存器WWDG_CR
FlagStatus WWDG_GetFlagStatus(void);
void WWDG_ClearFlag(void);
7. 窗口看门狗配置过程
7.1 使能看门狗时钟:
RCC_APB1PeriphClockCmd();
7.2 设置分频系数:
WWDG_SetPrescaler();
7.3 设置上窗口值:
WWDG_SetWindowValue();
7.4 开启提前唤醒中断并分组(可选):
WWDG_EnableIT();
NVIC_Init();
7.5 使能看门狗:
WWDG_Enable();
7.6 喂狗:
WWDG_SetCounter();
7.7 编写中断服务函数
WWDG_IRQHandler();
8. 实验代码部分解读
跑马灯、蜂鸣器、按键输入、串口等相关代码可参考以前的文章。
8.1 exti.c代码解读
将KEY2键作为外部中断,按下时,
#include "led.h"
#include "beep.h"
#include "key.h"
#include "exti.h"
#include "usart.h"
#include "sys.h"
//编写EXTIx_Init初始化函数,包括中断线配置和每个中断线的优先级配置//
void EXTIx_Init(void)
{
//*申明一个结构体,名字为EXTI_InitStructure,结构体原型由EXTI_InitTypeDef确定*//
EXTI_InitTypeDef EXTI_InitStructure;
//*申明一个结构体,名字为NVIC_InitStructure,结构体原型由NVIC_InitTypeDef确定*//
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); //使能复用功能时钟AFIO//
//设置I/O口与中断线的映射关系//
GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource2); //PE2对应KEY2//
//PE2中断线初始化配置,中断模式,下降沿触发,使能//
EXTI_InitStructure.EXTI_Line=EXTI_Line2; //GPIOx,Pin2对应EXTI2中断线//
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; //中断模式//
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling; //双边沿触发//
EXTI_InitStructure.EXTI_LineCmd = ENABLE; //使能//
//中断线初始化,GPIOE,Pin2映射到EXTI2中断线,中断模式,下降沿触发,使能//
EXTI_Init(&EXTI_InitStructure);
//对上述配置好的每个中断进行优先级初始化//
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; //选择EXTI2外部中断通道,即PE2,即KEY2
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //响应优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能//
NVIC_Init(&NVIC_InitStructure);
}
//编写EXTI2中断服务函数,对应KEY2按键,对独立看门狗的相应工作//
void EXTI2_IRQHandler(void)
{
if(KEY2==0) //判断KEY2按键是按下还是松开,按下则KEY2==0,if(1)
{
BEEP=1; //按下时蜂鸣器叫//
LED0=1; //按下时LED0灭//
printf("KEY2按下,对独立看门狗进行手动喂狗(在main.c中执行),LED0灭,蜂鸣器叫\r\n");
}
else
{
BEEP=0; //松开时蜂鸣器不叫//
LED0=0; //松开时LED0亮//
IWDG_ReloadCounter();
printf("KEY2松开,对独立看门狗进行手动喂狗,LED0亮,蜂鸣器不叫\r\n");
}
EXTI_ClearITPendingBit(EXTI_Line2); //清除LINE0上的中断标志位
}
8.2 wwdg.c代码解读
包括IWDG_Init和WWDG_Init初始化函数,并编写了窗口看门狗早期唤醒中断服务函数。
#include "wdg.h"
#include "led.h"
#include "key.h"
#include "usart.h"
//编写IWDG_Init初始化函数//
void IWDG_Init(u8 IWDG_PR,u16 IWDG_RLR)
{
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);//取消寄存器写保护//
IWDG_SetPrescaler(IWDG_PR); //设置独立看门狗的预分频系数//
IWDG_SetReload(IWDG_RLR); //设置看门狗重装载值//
IWDG_ReloadCounter(); //喂狗,否则将从0xFFF算起//
IWDG_Enable(); //使能独立看门狗//
}
//定WWDG_CR控制寄存器第0~6位值,默认为最大0111 1111//
u8 WWDG_CNT=0x7f;
//窗口看门狗计数频率的计算公式为:Fwwdg=PCLK1/(4096*2^fprer)//
//编写WWDG_Init初始化函数,WWDG_TV(CR寄存器0~6位计数值),WWDG_WV(用来设置上窗口值)和WWDG_PR(用来设置预分频系数)//
void WWDG_Init(u8 WWDG_TV,u8 WWDG_WV,u32 WWDG_PR)
{
//先WWDG时钟使能//
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
WWDG_CNT=WWDG_TV&WWDG_CNT; //初始化WWDG_CNT,一般0x40≤WWDG_TV≤0x7F,则实际意思是将WWDG_TV赋值给WWDG_CNT//
WWDG_SetPrescaler(WWDG_PR); //设置WWDG预分频值//
WWDG_SetWindowValue(WWDG_WV); //设置上窗口值,且0x40≤WWDG_WV≤0x7F//
WWDG_Enable(WWDG_CNT); //使能看门狗,并载入计数值,且0x40≤WWDG_CNT≤0x7F//
WWDG_ClearFlag(); //清除提前唤醒中断标志位
WWDG_NVIC_Init(); //初始化窗口看门狗中断服务函数//
WWDG_EnableIT(); //开启窗口看门狗中断,即到计数到0x40时,发生一次中断//
}
//重置WWDG计数器的值,即执行WWDG_Enable(cnt)函数//
void WWDG_Set_Counter(u8 cnt)
{
WWDG_Enable(cnt); //使能看门狗,并载入计数值,且0x40≤cnt≤0x7F//
}
//编写窗口看门狗中断服务初始化函数//
void WWDG_NVIC_Init()
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn; //WWDG中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //响应优先级为3
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//编写窗口看门狗中断服务函数//
void WWDG_IRQHandler(void)
{
//喂狗,实际执行 WWDG_Enable(cnt)函数//
WWDG_SetCounter(WWDG_CNT); //当禁掉此句后,窗口看门狗将产生复位//
WWDG_ClearFlag(); //清除提前唤醒中断标志位//
LED1=!LED1; //LED状态翻转
if(KEY1==0) //判断KEY1按键是否按下,按下则KEY2==0,if(1)
{
printf("对窗口看门狗进行手动喂狗,LED1闪烁\r\n");
}
}
8.3 main.c代码解读
主函数中,死循环里写了KEY2按下时,对独立看门狗的喂狗方式。
#include "led.h"
#include "beep.h"
#include "key.h"
#include "wdg.h"
#include "exti.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
int main(void)
{
u8 key;
delay_init(); //延时函数初始化//
//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级//
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//usart1串口初始化,波特率为115200//
LED_Init(); //LED端口初始化,LED0和LED0默认灭//
BEEP_Init(); //初始化蜂鸣器端口,不叫//
KEY_Init(); //按键初始化//
EXTIx_Init(); //外部中断初始化//
uart_init(115200); //串口USART1初始化//
delay_ms(300);
LED0=0; //LED0点亮//
IWDG_Init(4,3125); //根据预分配值和重装载值确定溢出时间,根据参数,溢出时间大约为//
WWDG_Init(0X7F,0X4F,WWDG_Prescaler_8); //设置计数器值,上窗口值,分频系数为8//
//系统进入死循环后,LED0是亮的//
while(1)
{
key=KEY_Scan(1); //支持连按//
if(key)
{
switch(key)
{
case 3: //若KEY2按下,则对独立看门狗进行喂狗//
IWDG_ReloadCounter();
break;
}
}
}
}
最后调试效果如下图
9. 旧知识点
1)复习如何新建工程模板,可参考STM32学习心得二:新建工程模板;
2)复习基于库函数的初始化函数的一般格式,可参考STM32学习心得三:GPIO实验-基于库函数;
3)复习寄存器地址,可参考STM32学习心得四:GPIO实验-基于寄存器;
4)复习位操作,可参考STM32学习心得五:GPIO实验-基于位操作;
5)复习寄存器地址名称映射,可参考STM32学习心得六:相关C语言学习及寄存器地址名称映射解读;
6)复习时钟系统框图,可参考STM32学习心得七:STM32时钟系统框图解读及相关函数;
7)复习延迟函数,可参考STM32学习心得九:Systick滴答定时器和延时函数解读;
8)复习ST-LINK仿真器的参数配置,可参考STM32学习心得十:在Keil MDK软件中配置ST-LINK仿真器;
9)复习ST-LINK调试方法,可参考STM32学习心得十一:ST-LINK调试原理+软硬件仿真调试方法;
10)复习如何对GPIO进行复用,可参考STM32学习心得十二:端口复用和重映射;
11)复习中断相关知识,可参考STM32学习心得十三:NVIC中断优先级管理;
12)复习串口通信相关知识,可参考STM32学习心得十四:串口通信相关知识及配置方法;
13)复习外部中断一般配置,可参考STM32学习心得十五:外部中断实验;
14)复习独立看门狗相关知识,可参考STM32学习心得十六:独立看门狗实验。