STM32-自学笔记(15.窗口看门狗)

2019-04-15 17:06发布

概述: 再由单片机为核心构成的微型计算机系统中,单片机常常会受到来自外界电磁场的干扰,造成程序跑飞,致使程序的正常运行状态被打断陷入死循环,使得由单片机控制的系统无法继续正常工作,造成整个系统的停滞,发生不可预料的后果。 所以,出于对单片机运行状态进行实时检测的考虑,便产生了一种专门用于检测单片机程序运行状态的硬件结构,俗称“看门狗”。STM32微控制配备了2只看门狗,分别是窗口看门狗独立看门狗。 窗口看门狗简称WWDG,是Window Watch Dog的缩写。WWDG的核心是一个6位定时计数器,其特性如下:
  • 内置一个可编程的、自由运行的递减计数器。
  • 复位条件:当递减计数器的值小于0x40,(若看门狗已被启用)则产生复位;当递减计数器在窗口外被重新装载,(若看门狗被启动)则产生复位。
  • 如果启动了看门狗并且允许中断,当递减计数器等于0x40时产生早期唤醒中断(EWI),此中断服务可以被用于重装载计数器以避免发生WWDG复位。
WWDG结构简图 看门狗控制寄存器中的T[6:0]存放的是WWDG当前计数值,其会在PCLK1经过分频器之后所产生的时钟驱动下进行递减计数。当即数值递减至0x40,则会请求一次看门狗早期唤醒中断(可以在该中断服务中进行喂狗操作)。而当计数值继续递减至0x3F时,就会产生一次WWDG复位。而W[6:0]存放的是WWDG计数比较值,当T[6:0]中存放的值大于W[6:0]中存放的值时进行喂狗操作,同样会产生一次看门狗中断。这就是“窗口”的含义:喂狗操作必须是当前计数值在W[6:0]与0x3F之间进行才不会发生看门狗复位。从程序的角度来说,即无论是过早还是过晚的进行喂狗操作,都将引发一次看门狗复位。这正是STM32的WWDG最大的特点。 WWDG的驱动时钟来自PCLK1。这就是WWDG正常运行的必要条件——当PCLK1发生故障,则看门狗就停止了工作。因此WWDG一般用于整个程序中某个局部的检测。 实验设计 验证STM32微控制器窗口看门狗的复用功能。初始化各个设备之后,在看门狗早期唤醒中断服务中进行喂狗操作。同时配置一个外部中断EXTI0,并赋予其比窗口看门狗早期唤醒中断更为高级的先占优先级。当EXTI0触发即可停止喂狗操作,则理应很快发生一次窗口看门狗复位事件。以上信息使用串口向上位机打印。 硬件电路 软件设计(程序设计) 注意要点
  • 配置RCC寄存器组,设置PCLK1频率为36MHz(即PLL输出72MHz后进行2分频)。
  • 打开WWDG时钟,注意WWDG属于APB1总线设备(最大速度36MHz)。
  • 配置WWDG,预分频值为8,并写入初始计数值(本次实验写入0x7F)。
  • 配置GPIO、EXTI、USART等外设。
  • 给WWDG的早期唤醒中断赋予较低先占优先级,同时给予EXTI中断赋予较高先占优先级。
对于WWDG的配置来说,最重要的无疑是其溢出时间初始计数值之间的关系。现基于以上提出的几点要点来进行一次计算的示例。
  1. 上述要点提及WWDG属于APB1总线设备,即表示其时钟来自于PCLK1,最大为36MHz,因此PCLK1为36MHz。
  2. 在PCLK1驱动看门狗计时之前,首先要经过既定的4096分频,在经过Prescaler=8分频(上述第3点),由此不难得到看门狗的计数频率为:          f = PCLK / 4096 / Prescaler = 36MHz / 4096 / 8 = 244Hz
  3. 则可以得到进行一次计数的时间约为:T = 1 / f = 4ms
  4. 上述第三点还提及将初始计数值设为0x7F,则由前面所知,当看门狗计数值从0x40跳变至0x3F时发生看门狗复位,则计算出了看门狗从启动计数到发生溢出复位的时间为:T1=4ms*(0x7F-0x3F)=264ms
  5. T1便是本次软件设计所设定的看门狗溢出复位时间,所以用户程序的喂狗周期不能大于264ms,否则将发生看门狗复位。
主函数  main.c #include "stm32f10x_lib.h" #include "stdio.h" void RCC_Configuration (void); void NVIC_Configuration (void); void GPIO_Configuration (void); void EXTI_Configuration (void); void USART_Configuration (void); void WWDG_Configuration (void); int main (void) { RCC_Configuration (); //设置系统时钟 NVIC_Configuration (); //设置GPIO端口 GPIO_Configuration (); //设置NVIC EXTI_Configuration (); //设置EXTI USART_Configuration (); //设置USART //检查是否发生过窗口看门狗复位,是则进入if()内部,否则进入else内部 if (RCC_GetFlagStatus(RCC_FLAG_WWDGRST)!=RESET) { printf(" The STM32 has been reset by WWDG "); RCC_ClearFlag(); //清除看门狗复位标志 } else { WWDG_Configuration (); //设置WWDG printf(" The STM32 has been reset by WWDG before /r/n"); } while(1); } 设置系统各部分时钟   RCC_Configuration void RCC_Configuration(void) { ErrorStatus HSEStartUpStatus; //定义枚举类型变量 HSEStartUpStatus RCC_DeInit(); //复位系统时钟设置 RCC_HSEConfig(RCC_HSE_ON); //开启HSE HSEStatrtUpStatus=RCC_WaitForHSEStartUp(); //等待HSE起振并稳定 if(HSEStatrtUpStatus==SUCCESS) //判断HSE是否起振成功,是则进入if()内部 { RCC_HCLKConfig(RCC_SYSCLK_Div1); //选择HCLK(AHB)时钟源为SYSCLK分频 RCC_PCLK2Config(RCC_HCLK_Div1); //选择PCLK2时钟源为HCLK(AHB)1分频 RCC_PCLK1Config(RCC_HCLK_Div2); //选择PCLK1时钟源为HCLK(AHB)2分频 FLASH_SetLatency(FLASH_Latency_2); //设置Flash延时周期数为2 FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); //使能Flash预取缓存 //选择PLL时钟源为 HSE 1 分频,倍频数为9,则PLL=8MHz *9=72MHz RCC_PLLConfig(RCC_PLLSource_HSE_Div1,RCC_PLLMul_9); RCC_PLLCmd(ENABLE); //使能PLL while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY)==RESET); //等待PLL输出稳定 RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //选择SYSCLK时钟源为PLL while(RCC_GetSYSCLKSource()!=0x08); //等待PLL成为SYSCLK时钟源 } RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE); //打开APB1总线上的窗口看门狗时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1,ENABLE); //打开APB2总线上的GPIOA和USART1时钟 } 设置各GPIO端口功能    GPIO_Configuration (void) void GPIO_Configuration(void) { GPIO_InitTypeDef GPIO_InitStructure; //设置PA.0为上拉输入(EXTI Line0) GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode=GPIO_Mode__IPU; GPIO_Init(GPIOA,&GPIO_InitStructure); //定义PA.0为外部中断0输入通道(EXTI0) GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); //设置USART1的Tx脚(PA.9)为第2功能推挽输出功能 GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; GPIO_Init(GPIOA,&GPIO_InitStructure); //设置USART1的Rx脚(PA.10)为浮空输入脚 GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2|GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA,&GPIO_InitStructure); } 设置EXTI参数    EXTI_Configuration void EXTI_Configuration(void) { //定义EXTI初始化结构体EXTI_InitStructure EXTI_InitTypeDef EXTI_InitStructure; //设置外部中断0通道(EXTI_Line0)在下降沿时触发中断 EXTI_InitStructure.EXTI_Line=EXTI_Line0; EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd=ENABLE; EXTI_Init(&EXTI_InitStructure); //定义PA.0为外部中断0输入通道(EXIT0) GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource0); } 设置WWDG参数   WWDG_Configuration void WWDG_Configuration(void) { 设置WWDG预分频值为8,WWDG时钟频率=(PCLK1/4096)/8=244Hz(约4ms) WWDG_SetPrescaler(WWDG_Prescaler_8); //设置WWDG初始计数值为0x7F并启动WWDG,此时WWDG超时时间为4ms*(0x7F-0x3F)=264ms WWDG_Enable(0x7F); WWDG_ClearFlag(); //清除WWDG早期唤醒中断(EWI)标志 WWDG_EnableIT(); //使能WWDG早期唤醒中断(EWI) } 设置NVIC参数   NVIC_Configuration void NVIC_Configuration(void) { //定义NVIC初始化结构体NVIC_InitStructure NVIC_InitTypeDef NVIC_InitStructure; //使用优先级分组2 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //使能外部中断0通道(EXIT0),0级先占优先级,0级次占优先级 NVIC_InitStructure.NVIC_IRQChannel=EXTI0_IRQChannel; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0; NVIC_InitStructure.NVIC_IRQChannelSubPriority=0; NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE; NVIC_Init(&NVIC_InitStructure); //使能窗口看门狗(WWDG)中断,1级先占优先级 NVIC_InitStructure.NVIC_IRQChannel=WWDG_IRQChannel; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1; NVIC_InitStructure.NVIC_IRQChannelSubPriority=0; NVIC_Init(&NVIC_InitStructure); } 设置USART1  USART_Configuration void USART_Configuration(void) { USART_InitTypeDef USART_InitStructure; //定义USART初始化结构体USART_InitStructure USART_ClockInitTypeDef USART_ClockInitStructure; //定义USART初始化结构体USART_ClockInitStructure //波特率为9600bps;8位数据长度,1个停止位,无检验位;禁用硬件流控制;禁止USART时钟;时钟极性低;在第2个边沿捕获数据;最后一位数据的时钟脉冲不从SCLK输出 USART_InitStructure.USART_BaudRate=9600; USART_InitStructure.USART_WordLength=USART_WordLength_8b; USART_InitStructure.USART_StopBits=USART_StopBits_1; USART_InitStructure.USART_Parity=USART_Parity_NO; USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode=USART_Mode_Rx|USART_Mode_Tx; USART_Init(USART1,&USART_InitStructure); USART_Cmd(USART1,ENABLE); //使能USART1 } 将printf函数重定位到USART1     fputc int fputc (int ch,FILE*f) UASRT_SendData(USART1,(u8)ch); while(USART_GetFlagStatus(USART1,USART_FLAG_TC)==RESET); return ch; 中断服务程序 头文件 #include "stm32f10x_it.h" #include"stdio.h" 看门狗早期唤醒中断服务函数  WWDG_IRQHandler void WWDG_IRQHandler (void) { WWDG_SetCounter(0x7F); //更新WWDG计数器 WWDG_ClearFlag(); //清除WWDG早期唤醒中断(EWI)标志 printf(" The Windows Watch Dog Has been flash "); } 外部中断0中断服务函数     EXTI0_IRQHandler void EXTI0_IRQHandler (void) { while(1) } 注意事项:
  1. 窗口看门狗是否产生复位操作,取决于定时计数器的值是否小于0x40,也就是窗口看门狗控制寄存器中的T6位是否为0,因此写入小于0x40的初始计数会马上发生一次复位操作。
  2. 窗口看门狗的复位相当于一次软复位,复位前WWDG各个寄存器的状态都将得到保留,因此在复位后,要首先将看门狗复位标志清除掉。
  3. 注意窗口看门狗启用之后在下一次复位事件产生之前不可以被禁用。
  4. 默认情况下,即使STM32进入调试状态窗口看门狗仍然会运行,这将导致调试出错。在开启窗口看门狗的情况下进行程序跟踪调试的读者应该注意这点。
  5. 如果程序中需要处理比较多的中断服务,请合理安排窗口看门狗的中断优先级。推荐的做法是:设置比较长的喂狗周期,同时赋予看门狗比较高的中断优先级。