概述:独立看门狗(IWDG)
窗口看门狗主要用于对某个局部应用程序进行监控,防止其过早或过晚地执行,其正常工作的前提是STM32的主时钟正常工作。因此窗口看门狗“触手能及”的范围是有限的,很有必要再配备一个能对全局应用程序进行监控的看门狗,与窗口看门狗形成功能上的互补,为STM32应用程序的运行稳定与可靠性再添一层保险。这就是IWDG的由来。
功能上的差异必然是以硬件结构上的差异来达成的。
IWDG的特性:从本实质上分析,IWDG仍然遵循一般看门狗的结构,及其核心仍应该是一个定时计数电路。其次,窗口看门狗之所以具有局限性,最主要原因是他的驱动时钟来自于APB1总线。IWDG既然被要求用以从全局的角度监控应用程序的运行,则其一定在某种程度上是“脱离”STM32内部时钟总线的。
从STM32的技术参考文档可知,STM32的IWDG具有以下基本特性:
- 拥有完全自由运行的递减计数器
- 驱动时钟由独立的RC振荡器提供(可在停止和待机模式下工作)。
- 看门狗被激活后,在计数器计数至0x000时产生复位。
可以看到,IWDG使用独立的RC振荡器提供时钟驱动,其运行不在依赖于STM32的时钟总线。IWDG的硬件结构如下图:
IWDG位于VDD供电区,使用VDD供给工作所需电源。这意味着即便STM32的ARM CorTex-M3内核停止工作(内核工作于1.8V供电区,停止工作的情况不仅包括内核断电,还包括内核进入停机模式和待机模式),IWDG仍然能够正常工作。综上所述,STM32的IWDG模块在时钟驱动源于供电源上较窗口看门狗进行了改动,达到了与STM32在一定程度上“隔离”的效果,从而能完成其监控全局程序的任务。
实验设计:
软件设计(程序设计):
- 配置RCC、GPIO、EXTI、USART寄存器组。
- 配置IWDG,预分频为32分频,重载值为349。
- IWDG没有提供类似窗口看门狗的“早期唤醒中断”的中断源,所以要配置SysTick定时器,产生250ms时间间隔,用以进行周期性地喂狗操作。
- 配置NVIC,赋予EXIT0较高级的先占优先级,同时赋予SysTick较低的先占优先级。对于IWDG的使用,读者应该关注以下两个要点。
(1). IWDG的配置流程
为了增加程序的安全与稳定性,STM32的IWDG拥有寄存器读/写保护功能,所以必须按照既定的操作原则才能正确地读/写IWDG地各个寄存器。操作原则如下:
- 在设置IWDG的预分频值和重载值之前,必须向IWDG的键寄存器写入0x5555。
- 重载IWDG计数值的方法是向键寄存器中写入0xAAAA。
- 启用IWDG的方法是向键寄存器中写入0xCCCC。
总结起来就是,初始化IWDG必须以0x5555>>重载计数值>>0xAAAA>>0xCCCC的数据流顺序写入。任何不同于此操作流程的数据写入操作,都将使IWDG回到受保护状态,要重新写入0x5555才能再次对其进行操作。
(2). IWDG的溢出时间计算
与WWDG类似,假设STM32的内部RC振荡器频率为32kHz(实际上STM32内部RC振荡器的频率并不稳定,依环境因素为30~60kHz不等,官方给出的值为40kHz,此处为方便计算取32kHz),而IWDG分频值为32分频,那么容易得到IWDG的单次计数周期为:P=1*32/32khz=1ms
而程序设置IWDG的初始值为349,因此可知IWDG的溢出时间为(注意从349递减计数至0时发生下溢):T=(349+1)*P=350ms
这就是本小节程序设计中IWDG发生一次溢出的大概时间间隔,应用程序必须以小于这一事件的周期进行喂狗操作,否则将发生IWDG复位事件。
主函数(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 SysTick_Configuration(void);
void IWDG_Configuration(void);
void USART_Configuration(void);
int main(void)
{
RCC_Configuration();
NVIC_Configuration();
GPIO_Configuration();
EXTI_Configuration();
SysTick_Configuration();
IWDG_Configuration();
USART_Configuration();
if(RCC_GetFlagStatus(RCC_FLAG_IWDGRST)!=RESET)
{
printf("\r\n The STM32 has been reset by IWDG \r\n");
RCC_ClearFlag(); //清除看门狗复位标志
}
else
{
printf("\r\n The STM32 has been reset by IWDG before \r\n");
SysTick_Configuration(); //设置Systick定时器
IWDG_Configuration(); //设置IWDG
}
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_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_USART1,ENABLE); //打开APB2总线上的GPIOA和USART1时钟
}
设置各GPIO端口功能 GPIO_Configuration
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_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStructure);
//定义PA.0为外部中断0输入通道(EXIT0)
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_10;
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOA,&GPIO_InitStructure);
}
设置EXIT参数 EXIT_Configuration
void EXTI_Configuration(void);
{
//定义初始化结构体EXTI_InitStructure
EXTI_InitTypeDef EXTI_InitStructure;
//设置外部中断0通道(EXIT_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);
}
设置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);
//设置SysTick中断为1级先占优先级,0级次占优先级
NVIC_SystemHandlerPriorityConfig(SystemHandler_SysTick,1,0);
}
设置Systick定时器,重装载时间为250ms Systick_Configuration
void Systick_Configuration(void)
{
//选择HCLK/8为Systick时钟源
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
//主频为72/8MHZ,配置计数值为9000*250可以得到250ms定时间隔
SysTick_SetReload(9000*250);
SysTick_CounterCmd(SysTick_Counter_Enable); //启动SysTick计数
SysTick_ITConfig(ENABLE); //使能SysTick中断
}
设置IWDG,超时时间为350ms IWDG_Counfiguration
void IWDG_Counfiguration(void)
{
//使能对寄存器IWDG_PR和IWDG_RLRD的写操作
IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);
//设置IWDG时钟为LSI经32分频,则IWDG计数器时钟=32kHz(LSI)/32=1kHz
IWDG_SetPrescaler(IWDG_Prescaler_32);
IWDG_SetReload(349); //设置IWDG计数值为349
IWDG_ReloadCounter(); //重载IWDG计数值
IWDG_Enable(); //启动IWDG
}
设置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"
SysTick定时器中断服务函数 SysTickHandler
void SysTickHandler(void)
{
IWDG_ReloadCounter(); //重载IWDG计数器
printf("\r\n The IWDG has been flashed \r\n");
}
外部中断0中断服务函数 EXTI0_IRQHandler
void EXTI0_IRQHandler(void)
{
while(1);
}
注意事项:
- IWDG使用的是STM32内部的RC振荡器供给驱动时钟,因此并无所谓“打开IWDG时钟”这类操作。
- STM32内部的RC振荡器频率并不稳定在某个值,甚至可以说变动的幅度比较大。因此读者在计算看门狗重装值的时候,请以RC震荡器运行在可达到的最低工作频率的情况计算,并将重装值设置到比所需的计算值稍微大一些为妙。
- 默认情况下,即便ARM Cortex-M3内核停止工作,IWDG仍将保持工作。
- IWDG复位仍然等同于一次软复位。
- IWDG在一次开启之后下次复位之前,不可以再被禁止。建议读者不要在IWDG工作时尝试改变他的工作参数。
- 读者应该已经注意到,和WWDG不同的是,IWDG的复位标志位于RCC寄存器组里(而WWDG的复位标志位于自身寄存器组里)。