看门狗就是定期查看芯片内部的情况,一旦发生错误就向芯片发送重启信号,从而实现无人职守时持续工作。看门狗在程序中的中断拥有最高优先级。
工作原理:看门狗芯片与单片机的一个IO引脚相连,通过程序控制定时的往看门狗芯片发送脉冲(称为喂狗),当单片机由于其他原因导致跑飞或进入死循环,就不能执行喂狗,看门狗芯片没有接收到单片机的信号,则看门狗芯片向单片机复位引脚发送复位信号,则实现了单片机的自动复位。STM32 M4芯片集成了看门狗到内部,化身为一个计数器,当设定的计数值减法计数到0的时候,就会触发复位中断。需要定时的刷新计数值(喂狗),防止计数值到达0产生复位。
独立看门狗(IWDG):Independent Watch Dog
监测并解决软件导致的故障,当计数器达到超时值时,触发一个中断或产生系统复位。
独立看门狗由专用的低速时钟(LSI)驱动,因此在主时钟发生故障时,看门口仍然可以保持运行。
主时钟一般频率都比较高,比较容易收到外界条件的干扰,稳定性较差,低速时钟由于频率低,抗干扰能力比较强,能够保证在主时钟发生故障的情况下仍然能够运行,提高系统的稳定性。
IWDG主要特性:
1、自由运行递减计数器
2、时钟由独立的振荡器提供,
3、当递减计数器到达0x000时产生复位。
/*添加复位检测代码,反映出当前程序是否有问题,如果看门口复位太多,则需要检查程序*/
//检查当前复位是否由看门狗导致
if(RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET)
{
printf("iwdg reset cpu\r\n");
//清空标志位
RCC_ClearFlag();
}
else
{
//普通复位
printf("normal reset cpu\r\n");
}
//使能看门口寄存器
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
//设置看门狗的时钟,分频 32kHz/256=125Hz,就是一秒完成125次计数
IWDG_SetPrescaler(IWDG_Prescaler_256);
//设置看门狗计数值125 一秒内没有刷新计数值,就会复位一次
IWDG_SetReload(125);
//刷新独立看门狗的计数值,重新计数,就是喂狗动作
IWDG_ReloadCounter();
/* 使能独立看门狗 */
IWDG_Enable();
//建议:不要在main中喂狗,main中执行的代码比较多,难以确定喂狗的时刻。
//没有操作系统,利用定时器中断喂狗
//有操作系统,在任务中喂狗,任务相当于线程
定时器喂狗程序
void tim3_init(void)
{
//使能定时器1的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
/* 定时器的时间的配置,中断频率为1000Hz,也就是说中断周期时间为1ms
最大的定时器时间约为6.5535s
TIM_TimeBaseStructure.TIM_Period =65535;
如果要进行2秒的定时
TIM_TimeBaseStructure.TIM_Period = (10000*2)-1;
如果要进行1秒的定时
TIM_TimeBaseStructure.TIM_Period = (10000)-1;
如果要进行100毫秒的定时
TIM_TimeBaseStructure.TIM_Period = (10000/10)-1;
*/
//配置定时器3的属性,定时的时间 = 时钟源/频率
TIM_TimeBaseStructure.TIM_Period = (10000/10)-1; //定时时间100ms
TIM_TimeBaseStructure.TIM_Prescaler =8400-1; //第一分频,设置预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //第二分频,1分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//配置NVIC的中断优先级
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//定时器3的中断配置
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
//使能定时器3工作
TIM_Cmd(TIM3, ENABLE);
}
//独立看门狗的标志位
static volatile uint32_t g_iwdg_cnt=0;
int main(void)
{
volat g_iwdg_cnt
/* Check if the system has resumed from IWDG reset ,检查当前系统复位是否有看门狗复位导致*/
if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET)
{
/* IWDGRST flag set */
printf("iwdg reset cpu\r\n");
}
else if (RCC_GetFlagStatus(RCC_FLAG_WWDGRST) != RESET)
{
/* WWDGRST flag set */
printf("wwdg reset cpu\r\n");
}
else
{
/* IWDGRST flag is not set */
printf("normal reset cpu\r\n");
}
/* Clear reset flags,清空所有复位标记 */
RCC_ClearFlag();
/* Enable write access to IWDG_PR and IWDG_RLR registers,解锁 */
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
/* IWDG counter clock: LSI/256 ,独立看门狗的时钟频率=32KHz/256=125Hz,只要独立看门狗进行125次计数的时候,就代表1秒时间的到达*/
IWDG_SetPrescaler(IWDG_Prescaler_256);
//设置看门狗的计数值,计数值就决定独立看门狗的复位时刻,125:1秒复位一次,也告诉我们必须在1秒之内对看门狗进行喂狗(刷新计数值),否则CPU会复位。
IWDG_SetReload(125);
/* Reload IWDG counter,刷新独立看门狗的计数值 */
IWDG_ReloadCounter();
/* Enable IWDG (the LSI oscillator will be enabled by hardware) ,使能独立看门狗工作*/
IWDG_Enable();
//定时器3的初始化
tim3_init();
while(1)
{
#if 1 //1秒内喂狗
//刷新计数值
//如果在裸机代码当中,放在main函数里面运行的话,很难确定喂狗的时刻
//推荐1)看门狗喂狗是放在定时器中断进行喂狗
// 2)如果有实时操作系统的支持,可以放在任务(线程)里面进行喂狗
//IWDG_ReloadCounter();
delay_ms(900);
printf("main is running...\r\n");
//等待蓝牙数据
//播放音乐
//控制数码管
//......
//while(PEin(6)==0);
//在末尾添加g_iwdg_cnt
g_iwdg_cnt=0;
#else
//1秒之外进行喂狗
//刷新计数值
IWDG_ReloadCounter();
delay_ms(1100);
#endif
}
}
void TIM3_IRQHandler(void)
{
//检查是否有更新事件触发
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)
{
//添加代码
g_iwdg_cnt++;
//100*100ms=20s
if(g_iwdg_cnt >= 200)
{
//等待看门狗超时复位
while(1);
}
IWDG_ReloadCounter();
//清空标志位,告诉CPU,我已经处理完毕,可以接收新的一次中断请求
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
}
}
窗口看门狗(WWDG):Window Watch Dog
窗口看门狗常被用来监测,由外部干扰或不可预见的逻辑条件造成的应用程序背离正常的运行序列而造成的软件故障,必须在限定的时间窗口内刷新计数器,否则导致MCU复位。
窗口看门狗需要在实时操作系统下运行,可以监测系统错误
WWDG的主要特性:
1、可编程的自由运行计递减数器,
2、复位条件:
当递减计数器值小于0x40时复位,
在窗口外重载递减计数器时复位
3、提前唤醒中断(EWI):当递减计数器等于0x40时被触发
喂狗注意事项:必须在窗口范围内喂狗。
提前喂狗,cpu复位
超时喂狗,cpu复位
程序设计:
/* 检查是否窗口看门狗导致的复位,如果发现由窗口看门
狗导致的复位,输出打印信息*/
if (RCC_GetFlagStatus(RCC_FLAG_WWDGRST) != RESET)
{
/* WWDGRST flag set */
printf("wwdg reset cpu\r\n");
/* Clear reset flags */
RCC_ClearFlag();
}
else
{
/* WWDGRST flag is not set */
printf("normal reset cpu\r\n");
}
/* WWDG configuration ,窗口看门狗的配置*/
/* Enable WWDG clock ,使能看门狗的时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
/* 窗口看门狗的时钟 = (PCLK1 (42MHz)/4096)/8 = 1281 Hz (~780 us) */
WWDG_SetPrescaler(WWDG_Prescaler_8);
/* Set Window value to 80; WWDG counter should be refreshed only when the counter
is below 80 (and greater than 0x40) otherwise a reset will be generated
设置窗口的上限值为 80
*/
WWDG_SetWindowValue(80);
/* 设置计数值的初值为 127,则窗口看门狗的最大超时时间 = 780 us * 64 = 49.92 ms
这个时候窗口刷新时间如下
~780 * (127-80) = 36.6ms < refresh window < ~780 * 64 = 49.9ms
*/
WWDG_Enable(127);
//WWDG NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn; //窗口看门狗中断通道
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; //抢占优先级 0
NVIC_InitStructure.NVIC_IRQChannelSubPriority =0; //子优先级 0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 VIC 寄
存器
//清空提前唤醒中断标志位
WWDG_ClearFlag();
//使能提前唤醒中断
WWDG_EnableIT();
// 看门狗中断服务函数
void WWDG_IRQHandler(void)
{
if(WWDG_GetFlagStatus()==SET)
{
//进行喂狗
WWDG_SetCounter(127);
//清空提前唤醒中断标志位
WWDG_ClearFlag();
}
}
STM32代码
static volatile uint32_t g_wwdg_cnt=0;
int main(void)
{
int32_t rt = 0;
/* GPIOA GPIOE GPIOF硬件时钟使能,就是让GPIOA GPIOE GPIOF工作 */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
/* 配置PE13 PE14为输出模式,让这根引脚具有输出高低电平的功能 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14; //第13 14号引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,增强驱动能力,引脚的输出电流更大
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //引脚的速度最大为100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //没有使用内部上拉电阻
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* 配置PF9 PF10为输出模式,让这根引脚具有输出高低电平的功能 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10; //第9 10号引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,增强驱动能力,引脚的输出电流更大
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //引脚的速度最大为100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //没有使用内部上拉电阻
GPIO_Init(GPIOF, &GPIO_InitStructure);
GPIO_SetBits(GPIOF,GPIO_Pin_9|GPIO_Pin_10);
GPIO_SetBits(GPIOE,GPIO_Pin_13|GPIO_Pin_14);
//配置中断优先级分组选择第二组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//串口1初始化波特率为115200bps
usart1_init(115200);
delay_ms(500);
/* Check if the system has resumed from IWDG reset ,检查当前系统复位是否有看门狗复位导致*/
if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET)
{
/* IWDGRST flag set */
printf("iwdg reset cpu\r\n");
}
else if (RCC_GetFlagStatus(RCC_FLAG_WWDGRST) != RESET)
{
/* WWDGRST flag set */
printf("wwdg reset cpu\r\n");
}
else
{
/* IWDGRST flag is not set */
printf("normal reset cpu\r\n");
}
/* Clear reset flags,清空所有复位标记 */
RCC_ClearFlag();
/* WWDG configuration */
/* Enable WWDG clock ,使能窗口看门狗的时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
/* WWDG clock counter = (PCLK1 (42MHz)/4096)/8 = 1281 Hz (~780 us)
窗口看门狗的时钟频率= (PCLK1 (42MHz)/4096)/8 = 1281 Hz (-1,780 us T=1/f)
*/
WWDG_SetPrescaler(WWDG_Prescaler_8);
/* Set Window value to 80; WWDG counter should be refreshed only when the counter
is below 80 (and greater than 64) otherwise a reset will be generated */
WWDG_SetWindowValue(80);
/* Enable WWDG and set counter value to 127, WWDG timeout = ~780 us * 64 = 49.92 ms
In this case the refresh window is:
~780 * (127-80) = 36.6ms < refresh window < ~780 * 64 = 49.9ms
设置看门狗的初始值为127
*/
WWDG_Enable(127);
WWDG_ClearFlag();
//使能窗口看门狗唤醒中断
WWDG_EnableIT();
//配置窗口看门狗的中断优先级
NVIC_InitStructure.NVIC_IRQChannel = WWDG_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
while(1)
{
//末尾添加一行代码
g_wwdg_cnt=0;
}
}
void WWDG_IRQHandler(void)
{
if(WWDG_GetFlagStatus()==SET)
{
g_wwdg_cnt++;
//约10秒时间
if(g_wwdg_cnt >= 200)
{
while(1);
}
//喂狗
WWDG_SetCounter(127);
//清空标志位
WWDG_ClearFlag();
}
}