概述:
再由单片机为核心构成的微型计算机系统中,单片机常常会受到来自外界电磁场的干扰,造成程序跑飞,致使程序的正常运行状态被打断而陷入死循环,使得由单片机控制的系统无法继续正常工作,造成整个系统的停滞,发生不可预料的后果。
所以,出于对单片机运行状态进行实时检测的考虑,便产生了一种专门用于检测单片机程序运行状态的硬件结构,俗称“看门狗”。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的配置来说,最重要的无疑是其溢出时间和初始计数值之间的关系。现基于以上提出的几点要点来进行一次计算的示例。
- 上述要点提及WWDG属于APB1总线设备,即表示其时钟来自于PCLK1,最大为36MHz,因此PCLK1为36MHz。
- 在PCLK1驱动看门狗计时之前,首先要经过既定的4096分频,在经过Prescaler=8分频(上述第3点),由此不难得到看门狗的计数频率为: f = PCLK / 4096 / Prescaler = 36MHz / 4096 / 8 = 244Hz
- 则可以得到进行一次计数的时间约为:T = 1 / f = 4ms
- 上述第三点还提及将初始计数值设为0x7F,则由前面所知,当看门狗计数值从0x40跳变至0x3F时发生看门狗复位,则计算出了看门狗从启动计数到发生溢出复位的时间为:T1=4ms*(0x7F-0x3F)=264ms
- 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("\r\n The STM32 has been reset by WWDG \r\n");
RCC_ClearFlag(); //清除看门狗复位标志
}
else
{
WWDG_Configuration (); //设置WWDG
printf("\r\n 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("\r\n The Windows Watch Dog Has been flash \r\n");
}
外部中断0中断服务函数 EXTI0_IRQHandler
void EXTI0_IRQHandler (void)
{
while(1)
}
注意事项:
- 窗口看门狗是否产生复位操作,取决于定时计数器的值是否小于0x40,也就是窗口看门狗控制寄存器中的T6位是否为0,因此写入小于0x40的初始计数会马上发生一次复位操作。
- 窗口看门狗的复位相当于一次软复位,复位前WWDG各个寄存器的状态都将得到保留,因此在复位后,要首先将看门狗复位标志清除掉。
- 注意窗口看门狗启用之后在下一次复位事件产生之前不可以被禁用。
- 默认情况下,即使STM32进入调试状态窗口看门狗仍然会运行,这将导致调试出错。在开启窗口看门狗的情况下进行程序跟踪调试的读者应该注意这点。
- 如果程序中需要处理比较多的中断服务,请合理安排窗口看门狗的中断优先级。推荐的做法是:设置比较长的喂狗周期,同时赋予看门狗比较高的中断优先级。