WWDG/IWDG一旦开启不能关闭,可通过选项字节在上电时启动硬件看门狗,看门狗计数只能写入不能读取。看门狗启用时,T6bit必须置1,防止立即重置。
一、原理
独立看门狗-超时复位
窗口看门狗-喂狗(重置计数器,计数器最大12Bit,-1 = 4095)早了或者晚了都复位
1、IWDG结构
键寄存器负责键入重装载值,这个值是之前配置好的,每次喂狗会自动通过键寄存器使重装载寄存器的数值键入递减计数器。看门狗位于VDD供电区,所以在停机和待机模式时可以正常使用。
2、IWDG超时时间计算公式
超时频率 = Fiwdg = Flsi(LSI频率)/PR预分频系数/(RL+1)。
默认Tlsi=40khz,频率=1/40000=0.025ms。分频后时间增加。只有固定的分频系数,若为2(分频16)。若RL=99,则为0.025ms*16 = 0.4ms*100 = 40ms。
表格内说明:例如第一行4分频,
Tiwdg = 0.025ms*4*(RL=0x000,最短时间)1 = 0.1ms
Tiwdg = 0.025ms*4*(RL=0xFFF,最长时间)4096= 409.6ms
3、WWDG结构
WDGTB同独立看门狗的PR和定时器的PSC都是预分频器。
WWDG_CR相当于把独立看门狗的计数器、KR(控制寄存器)合二为一了。没有重装寄存器,在直接在CNT写入数据即可(6bit递减计数器,T5-T0,T6为溢出标志位(为1表示没有溢出,为0时表示计数器溢出,例如从111 1111->100 0000 -0x40时在减一 -> 011 1111表示溢出),若将T6位当作计数器的一部分,那么减到0x40开始溢出。若把第六位当作计数器,那么位第六位减到0溢出)。
CFR为喂狗的最早时间界限。WDGA窗口看门狗使能。
工作流程:PCLK1提供时钟APB1(36MHz),通过WDGA使能窗口看门狗,当WWDG_CR减到0时溢出,提供复位信号。或者当写入WWDG_CR时,WWDG_CR喂狗时的剩余的计数值>WWDG_CFR配置寄存器,提供复位信号。即限制了最早喂狗时间值。
4、WWDG工作特征
早期唤醒中断(EWI即死前复位),通过该中断提醒用户看门狗即将复位。可以做一些操作。
看门狗时序图显示了可刷新重装载看门狗时间的时间范围(W[6,0]>T[6,0]>0x3F)。
5、WWDG时间计算公式
Tpclk1 = 0.028us。Tpclk1提供的时钟先进性了4096分频,所以要×4096=114.688us。114.688us×2^WDGTB×(T[5:0]+1)为超时时间根据表格可以查看最大超时时间和最小超时时间。
超时时间计算直接通过T[5,0],当T[5,0]递减到0溢出。窗口超时时间是指,窗口CFR寄存器所表示的最低喂狗时间,当T[5,0]>W[5,0]喂狗时的复位时间。即复位时间 :超时时间>正常喂狗时间>窗口时间。2^6bit最大为63。
6、WWDG/IWDG的差别
窗口看门狗的递减计数器未使能也会一直递减。所以需要使能时进行喂狗。
二、实际程序
1、独立看门狗,超时复位
程序烧写入可以看到,程序正常执行,OLED不断显示喂狗操作,按下复位按键可以看到显示RET复位。
通过按住PB11按键(图中按钮接在PB11和地,和图上不一样)按住不停,使软件停在检测按键内。看门狗超时后OLED显示WWDGRET表示独立看门狗超时。
无需手动打开LSI
测试方式:
1、不进行按键按下操作,此时应该能正常喂狗,OLED显示 IWDG_Reload。
2、持续按下PB11按键,此时应该不能正常喂狗,OLED显示 IWDG RST,表示看门狗复位。
3、STM32按键复位,可以看到OTHER RST,说明复位显示正常。
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyIWDG.h"
#include "Button.h"
int main(void){
OLED_Init();
ButtonPB11_Init();
OLED_ShowString(1,1,"IWDG TEST:");//
if(RCC_GetFlagStatus(RCC_FLAG_IWDGRST) == SET){//判断是否是独立看门狗复位,该寄存器每次不会被清除
OLED_ShowString(2,1,"IWDG RST");//显示看门狗复位
Delay_ms(500);
OLED_ShowString(2,1," ");
RCC_ClearFlag();
}else{//其他复位
OLED_ShowString(2,1,"OTHER RST");//显示其他复位
Delay_ms(500);
OLED_ShowString(2,1," ");
}
MyIWDG_Init();//看门狗初始化 1000ms
while(1){
GetButtonPB11();//使用按钮延时来模拟程序异常
IWDG_ReloadCounter();//喂狗
OLED_ShowString(3,1,"IWDG_Reload");//显示喂狗操作
Delay_ms(600);
OLED_ShowString(3,1," ");
Delay_ms(200);
}
return 0;
}
MyWWDG.c
#include "stm32f10x.h" // Device header
void MyIWDG_Init(void){
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
//我们需要超时1s复位,根据手册选择最小16分频,充分利用寄存器防止除数有误差
IWDG_SetPrescaler(IWDG_Prescaler_16);
//根据公式:重装值 = ( 1000ms / Tlsi(4KHz = 0.025ms) / PR预分频数(16) )-1
IWDG_SetReload(2499);
IWDG_ReloadCounter();//重装值装载
IWDG_Enable();//独立看门狗启动
}
MyWWDG.h
#ifndef __MYIWDG_H
#define __MYIWDG_H
#include "stm32f10x.h" // Device header
void MyIWDG_Init(void);
#endif
Button.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
/**
* @brief 初始化引脚PB11地开信号接收Button,用于辅助测试看门狗
* @param
* @arg
* @param
* @arg
* @retval None
*/
void ButtonPB11_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
/**
* @brief 获取PB11的Button是否按下
* @param
* @arg
* @param
* @arg
* @retval None
*/
uint8_t GetButtonPB11(void){
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == RESET){
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == RESET);
Delay_ms(20);
return 1;
}
return 0;
}
Button.h
#ifndef __BUTTON_H
#define __BUTTON_H
#include "stm32f10x.h" // Device header
uint8_t GetButtonPB11(void);
void ButtonPB11_Init(void);
#endif
2、窗口看门狗,超时或提前都会复位。
读改写操作的优点:
1、可以单独改变寄存器的某几位,而不影响其他寄存器的值。
2、如果连续修改多次不同/相同的位,操作效率比较高。
3、所有更改的位,最终写回到寄存器时同时生效
程序测试方式:
1、将while内延时程序时间改成小于17ms,可以看到有窗口时间复位。
2、将while内延时程序时间改成大于35ms,可以看到有超时时间复位。
3、将while内延时程序时间改成(17,35),可以看到持续喂狗,没有复位产生。
4、通过STM32复位按键可以看到复位正常。通过按键持续按住PB11按键可以看到,持续产生窗口看门狗复位。
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "MyWWDG.h"
#include "Button.h"
int main(void){
OLED_Init();
ButtonPB11_Init();
OLED_ShowString(1,1,"WWDG TEST:");//
if(RCC_GetFlagStatus(RCC_FLAG_WWDGRST) == SET){//判断是否是独立看门狗复位,该寄存器每次不会被清除
OLED_ShowString(2,1,"WWDG RST");//显示看门狗复位
Delay_ms(500);
OLED_ShowString(2,1," ");
RCC_ClearFlag();
}else{//其他复位
OLED_ShowString(2,1,"OTHER RST");//显示其他复位
Delay_ms(500);
OLED_ShowString(2,1," ");
}
MyWWDG_Init();//窗口看门狗初始化 超时时间50ms,窗口时间30ms
while(1){
GetButtonPB11();//使用按钮延时来模拟程序异常
OLED_ShowString(3,1,"WWDG_Reload");//显示喂狗操作
Delay_ms(20);
OLED_ShowString(3,1," ");
Delay_ms(15);
WWDG_SetCounter(0x40 | 54);//喂狗
/*
经测试本程序和个人的硬件,
窗口时间30ms,但是使用延时小于17ms就会触发窗口时间复位。
超时时间50ms,但是使用延时大于35ms就会触发超时时间复位。
说明其他程序延时可以达到13ms-15ms。
也可注释所有程序,使用延时测试,经测试窗口时间30ms,超时时间50ms无异常。
*/
}
return 0;
}
MyWWDG.c
#include "stm32f10x.h" // Device header
void MyIWDG_Init(void){
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE);//开启窗口看门狗时钟
WWDG_SetPrescaler(WWDG_Prescaler_8);//设置预分频
/*设置窗口时间30ms
计算公式为:Twin = Tpclk1*4096*WWDGpcs*(T[5:0]-W[5:0])。
带入T[5:0] = 55 , Tpclk1 = 36MHz = 0.028us , WWDGpcs = 8
得到30ms = 917.504us*(55-W[5:0]) ; 54-W[5:0]=32.7 ;W[5:0]= 21.3。
*/
WWDG_SetWindowValue(0x40 | 21);//设置窗口时间,也需要|0x40,因为电路中对比超时时间和窗口时间使用的T[6:0]>W[6:0]
/*
超时时间是50ms
计算公式为:Twwdg = Tpclk1(36MHz = 0.028us)*4096*WWDGpcs(8)*(T[5:0]-1)。
50ms = 917.504us*(T[5:0]-1),T[5:0]=53.5=54。
T6溢出位位2^7=64 = 0x40。T7使能位函数自动置位。所以需要输入64+54=0x76=0x40 | 54
*/
WWDG_Enable(0x40 | 54);//设置超时时间,在使能时需要同时设置(溢出位也要置1 0x40),因为窗口看门狗递减计数器为自由递减,不使能也会递减
}
MyIWDG.h
#ifndef __MyIWDG_H
#define __MyIWDG_H
#include "stm32f10x.h" // Device header
void MyIWDG_Init(void);
#endif
Button.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
/**
* @brief 初始化引脚PB11地开信号接收Button,用于辅助测试看门狗
* @param
* @arg
* @param
* @arg
* @retval None
*/
void ButtonPB11_Init(void){
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
}
/**
* @brief 获取PB11的Button是否按下
* @param
* @arg
* @param
* @arg
* @retval None
*/
uint8_t GetButtonPB11(void){
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == RESET){
Delay_ms(20);
while(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_11) == RESET);
Delay_ms(20);
return 1;
}
return 0;
}
Button.h
#ifndef __BUTTON_H
#define __BUTTON_H
#include "stm32f10x.h" // Device header
uint8_t GetButtonPB11(void);
void ButtonPB11_Init(void);
#endif