(一)看门狗
看门狗的本质是一个自减计数器,自减到零或其他情况时可以触发复位操作,可以防止我们的程序出现卡死等情况,要想看门狗这个计数器不自减到零,我们就需要在程序合适的位置执行计数值重装,也叫喂狗
看门狗分为独立看门狗和窗口看门狗,两者有一些区别
看门狗 | 时钟源 | 计数 | 复位机制 | 中断功能 |
独立看门狗 | 低速时钟LSI(40KHz) | 计数时间长 | 只有最慢喂狗时间 | 不具备中断功能,只能在计数器溢出时产生复位信号 |
窗口看门狗 | APB1分频时钟(36MHz/4096) | 计数时间短 | 喂狗时间不能小于小值和大于大值 | 提供了早期唤醒中断(EWI)的功能,当计数器减到0x40时可以触发中断而非直接复位 |
(二)独立看门狗
独立看门狗模块的函数有这几个
void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess);
// 写使能,写入看门狗之前要先写使能
void IWDG_SetPrescaler(uint8_t IWDG_Prescaler);
// 设置预分频
void IWDG_SetReload(uint16_t Reload);
// 设置自动重装值
void IWDG_ReloadCounter(void);
// 自动重装,也称喂狗,要在程序中合适时间调取,否则复位
void IWDG_Enable(void);
// 使能看门狗,开始工作
我们使用按键来模拟程序卡死,按键在按下不放时程序会进入循环等待,从而没有在规定的时间内喂狗而产生复位
void key_init()
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef gpio_init;
gpio_init.GPIO_Mode = GPIO_Mode_IPU;
gpio_init.GPIO_Pin = GPIO_Pin_6;
gpio_init.GPIO_Speed = GPIO_Speed_10MHz;
GPIO_Init(GPIOA, &gpio_init);
}
unsigned char get_key()
{
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 0)
{
Delay_ms(20);
while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 0);
Delay_ms(20);
return 1;
}
return 0;
}
我们要判断复位是看门狗导致的复位,还是手动复位或其他复位方式,可以使用获取标志位的函数获取独立看门狗复位标志位或窗口看门狗复位标志位
在开启独立看门狗时,我们要打开LSI时钟,其实这可以省略,因为看门狗打开时会强制开启这个时钟
和定时器一样,我们通过配置预分频和自动重装值来配置看门狗的最长时间,其中
可以对照表格来大概确定要配置的预分频和自动重装值
这里频率为40000Hz,我们想要喂狗时间在1s以内,我们可以设置预分频为16分频,可以计算自动重装值为2500,这样就配置好了看门狗
int main()
{
unsigned char key;
OLED_Init();
key_init();
RCC_LSICmd(ENABLE);
if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) == SET)
{
OLED_ShowString(1, 1, "IWDGRST");
RCC_ClearFlag();
}
else
{
OLED_ShowString(2, 1, "RST");
}
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
IWDG_SetPrescaler(IWDG_Prescaler_16);
IWDG_SetReload(2500-1); // T=(1/40)*16*2500=1000ms
IWDG_Enable();
while(1)
{
IWDG_ReloadCounter();
Delay_ms(500);
key = get_key();
}
return 0;
}
我们在循环中不断喂狗,由于循环中已经有一个500ms的延时,如果我们按下按键超过500ms,程序就会产生看门狗复位
(三)窗口看门狗
窗口看门狗和独立看门狗的不同在于其有一个窗口值,其要设置一个值用来计数器自减,自减到零后复位,这是最大超时值,其还要设置一个时间用来和计数器值进行比较,若计数器值大于此值时候喂狗,也会产生复位,这就是最小超时值
设置窗口看门狗的函数有这些
void WWDG_SetPrescaler(uint32_t WWDG_Prescaler);
// 设置预分频
void WWDG_SetWindowValue(uint8_t WindowValue);
// 设置窗口值
void WWDG_SetCounter(uint8_t Counter);
// 设置计数器值,也用来喂狗
void WWDG_Enable(uint8_t Counter);
// 使能看门狗,同时写入重装值
窗口看门狗的框图如图
这里实际自减的只有前六位,因为第七位在由1变为0时会产生复位,所以我们在给重装值时第七位一定要给1,否则会一直复位
和独立看门狗的计算公式一样,可以参考表格来确定预分频和重装值
这里选择最大超时为50ms,因此预分频要8分频,重装值通过公式可知为55,让窗口时间为20ms,即在30ms~50ms内喂狗才是正常,其他时间喂狗都会产生复位,这样窗口值根据公式为22,由于窗口看门狗没有自动重装,我们在喂狗时执行的代码就是设置计数器值即可
int main()
{
unsigned char flag = 0;
OLED_Init();
if (RCC_GetFlagStatus(RCC_FLAG_WWDGRST) == SET)
{
OLED_ShowString(1, 1, "WWDGRST");
RCC_ClearFlag();
}
else
{
OLED_ShowString(2, 1, "RST");
}
RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG, ENABLE);
WWDG_SetPrescaler(WWDG_Prescaler_8); // 36000000/4096/8
WWDG_SetCounter((55-1) | 0x40); // T = (1/36000000)*4096*8*55 = 50ms
WWDG_SetWindowValue((22-1)|0x40); // T = (1/36000000)*4096*8*22 = 20ms
// feed time: 30~50
while (1)
{
if (flag == 0)
{
WWDG_Enable((55-1)|0x40);
flag = 1;
}
else
{
WWDG_SetCounter((55-1)|0x40);
}
Delay_ms(29);
}
return 0;
}
这里值得注意的是,在使能窗口看门狗时会填上重装值,如果我们喂狗代码和使能代码过短,就可能会触发因为喂狗时间过短而产生的复位
(四)总结
通过独立看门狗和窗口看门狗两个程序,了解看门狗的工作原理和流程,对看门狗的作用有了基本了解