单片机
返回首页

STM32 独立看门狗定时器IWDG复位

2016-10-11 来源:eefocus

无论是什么微控制器,一般都会有看门狗模块。对于STM32来说,它具有两个看门狗:独立看门狗(IWDG)与窗口看门狗(WWDG)。这里就先讲讲独立看门狗。
看门狗能够检测和解决有软件错误引起的故障。当一个错误导致看门狗不能及时“喂狗”,那么它就会产生一个系统复位。独立看门狗,之所以“独立”,是因为它由专门的40kHz左右的低速时钟驱动的,及即时主时钟发生故障它也仍然有效。为它提供时钟的低速时钟LSI的频率虽然号称是40KHz,但实际上并不准确,它有MCU内部RC振荡产生,频率会会在30kHz~60kHz之间变化。所以,独立看门狗不能用来精确计时。如果想要实现准确计时,那还需要对LSI进行校准。独立看门狗最适合用于那些在一个主程序之外能够完全独立工作,并且对时间要求较低的场合。
下面就来实现下独立看门狗定时器的系统复位。还是基于我自己的规范工程。
1、工程的修改
1)当然要先添加stm32f10x_iwdg.c文件到STM32F10x_StdPeriph_Driver工程组中。因为这里我们要先校准LSI时钟频率,所以还要添加stm32f103_tim.c文件以便校验LSI时钟。
2)打开stm32f10x_conf.c文件,将其中原先屏蔽着的:#include 'stm32f10x_iwdg.h' 与 #include 'stm32f10x_tim.h'这两句话的屏蔽去掉。
3)新建IWDGRReset.c和IWDGReset.h两个文件,分别保存在BSP文件下的src与inc中,并将IWDGReset.c文件添加到BSP工作组中。
 
2、IWDGRReset.c和IWDGReset.h两个文件代码的编写
前面曾经说过,独立看门狗定时器的计数值不准确,因为它的时钟源是低速时钟LSI,所以在初始化独立看门狗定时器之前,需要先校准下LSI。校验LSI的步骤如下:
1)打开TIM5,设置通道4为输入捕获模式
2)设置AFIO_MAPR的TIM5_CH4_IREMAP位为‘1’,在内部吧LSI连接到TIM5的通道4.
3)通过TIM5的捕获/比较4事件或者中断来测量LSI的时钟频率。
所以在初始化独立看门狗定时器之前,需要配置下TIM5,将它的CH4配置成输入捕获,代码如下:

/*************************************************************
Function : IWDGReset_TIM5_Init
Description: 初始化TIM5,用来校准LSI时钟
Input : none
return : none
*************************************************************/
static void IWDGReset_TIM5_Init(void)
{
TIM_ICInitTypeDef TIM_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);//打开TIM5时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//打开AFIO时钟,引脚重映射一定要时,一定要有这句话
TIM_PrescalerConfig(TIM5, 0, TIM_PSCReloadMode_Immediate);//不对TIM5之中进行分频,所以TIM5这时候的时钟是72M
GPIO_PinRemapConfig(GPIO_Remap_TIM5CH4_LSI, ENABLE);//在内部吧LSI连接到TIM4的CH4上,用CH4来测量LSI时钟频率

TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;//TIM5的CH4
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;//检测上升沿
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;//管脚与寄存器直接对应
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV8;//输入捕获8预分频,即检测到8个上升沿才生成中断标志
TIM_ICInitStructure.TIM_ICFilter = 0;//不滤波
TIM_ICInit(TIM5, &TIM_ICInitStructure);//初始化TIM5的CH4

TIM_ITConfig(TIM5, TIM_IT_CC4, ENABLE);//打开CH4中断源
TIM5->SR = 0;//清除TIM5的所有标志位
TIM_Cmd(TIM5, ENABLE);//打开TIM5

NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;//设置TIM5的中断优先级为1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

首先要将TIM5的时钟打开。除此之外还要将AFIO的时钟打开,这个很重要,要不然就TIM5的引脚重映射就没有效果了。接下去就要将TIM5的CH4重映射到LSI上,也就是上面的GPIO_PinRemapConfig(GPIO_Remap_TIM5CH4_LSI, ENABLE)语句。再下去则要将TIM5的CH4配置成输入捕获,上升沿捕获,8预分频,不滤波。这里对CH4输入捕获进行8与分频是有目的,LSI的时钟频率在30kHz~60kHz,所以如果不分频的话,CH4的计数值变化范围为:(72M/30k)=1200~(72M/60k)=2400,它的变化才1200左右,相对于16为计数值而言只是小数目,这样计算出来的LSI频率误差就很大;而如果进行8预分频,CH4捕获到8次上升沿才产生中断标志位,也就是说让CH4采集8个周期,CH4的计数值变化范围为:9600~19200,变化有将近10000了,然后将计算出的频率处除与8,就是LSI的频率了,这样计算出LSI的频率误差就小了。接下去打开TIM5,打开CH4的苏茹捕获中断,同时清除TIM5所有的标志位。最后配置下TIM5的中断优先级为1。
TIM5配置好了,就要开始校准LSI时钟了,代码如下:

u32 LsiFreq = 40000; //LSI时钟频率默认为40kHz
extern u16 CaptureNumber; //捕获次数

/*************************************************************
Function : IWDGReset_LSICalibration
Description: 校准LSI时钟频率
Input : none
return : none
*************************************************************/
static void IWDGReset_LSICalibration(void)
{
RCC_LSICmd(ENABLE);//打开LSI时钟
while(RCC_GetFlagStatus(RCC_FLAG_LSIRDY)==RESET); //等待LSI时钟稳定

IWDGReset_TIM5_Init();//初始化TIM5
while(CaptureNumber != 2);//等待检测到LSI的2个上升沿
PRINTF('LSI freq: %dHz\r\n', LsiFreq);//将测量的LSI频率打印出来

TIM_ITConfig(TIM5, TIM_IT_CC4, DISABLE);//关闭CH4中断源
}

要校准LSI时钟,首先当然是先将LSI时钟打开,让内部的RC振荡器开始工作,等到振荡稳定后,再配置TIM5的CH4开始校准LSI。计算LSI频率的代码在stm32f10x_it.c的TIM5中断服务程序中,我之后再讲。等待知道捕获计数值CaptureNumber(在stm32f10x_it.c中定义)等于2,表示LSI的频率LsiFreq(默认情况下位40k)已经计算出来了。接下去将计算出来的LSI频率打印出来。最后在关闭TIM5 CH4的输入捕获中断。
接下去,就开始讲独立看门狗的初始化了,代码如下:

/*************************************************************
Function : IWDGReset_Init
Description: 独立看门狗初始化
Input : none
return : none
*************************************************************/
void IWDGReset_Init(void)
{
IWDGReset_LSICalibration(); //打开LSI,并校准LSI时钟

IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);//允许访问IWDG相关寄存器
IWDG_SetPrescaler(IWDG_Prescaler_32); //32分频
IWDG_SetReload(LsiFreq/128); //设置看门狗喂狗时间为250ms,装载值=0.25/(LsiFreq/32)=LsiFreq/128
IWDG_Enable(); //打开IWDG
}/*************************************************************
Function : IWDGReset_Init
Description: 独立看门狗初始化
Input : none
return : none
*************************************************************/
void IWDGReset_Init(void)
{
IWDGReset_LSICalibration();//打开LSI,并校准LSI时钟

IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);//允许访问IWDG相关寄存器
IWDG_SetPrescaler(IWDG_Prescaler_32);//32分频
IWDG_SetReload(LsiFreq/128);//设置看门狗喂狗时间为250ms,装载值=0.25/(LsiFreq/32)=LsiFreq/128
IWDG_Enable();//打开IWDG
}

这里先调用刚编写好的IWDGReset_LSICalibration()函数校准下LSI时钟,然后就可以开始配置看门狗了。调用库函数IWDG_WriteAccessCmd()让它允许范围IWDG的相关寄存器,再调用IWDG_SetPrescaler()设置32预分频,分频之后的频率为LsiFreq/32,再下去则要设置看门狗的超时时间了,调用IWDG_SetReload()函数来设置,看门狗的转载值设定(因为IWDG为12位计数器,所以最大的转载值为4095)可以使用公式:转载值=超时时间/(LsiFreq/32),上面代码中,我设定超时时间为250ms,所以要设定的看门狗转载值=0.25s/(LsiFreq/32)=LsiFreq/128。最后再打开独立看门狗。
接下去在编写IWDGReset.h的程序,程序非常简单,只是声明下独立看门狗的小初始化函数,代码如下:

#ifndef __IWDGRESET_H__
#define __IWDGRESET_H__
#include 'stm32f10x.h'

void IWDGReset_Init(void);

#endif

 
3、stm32f10x_it.c的修改
需要在stm32f10x_it.c文件中添加TIM5的中断服务函数,代码如下:

u16 CaptureNumber = 0;
extern u32 LsiFreq;
/*************************************************************
Function : TIM5_IRQHandler
Description: TIM5中断服务程序
Input : none
return : none
*************************************************************/
void TIM5_IRQHandler(void)
{
static u16 capture = 0;
static u16 IC1ReadValue1 = 0, IC1ReadValue2 = 0;
if (TIM_GetITStatus(TIM5, TIM_IT_CC4) != RESET)
{
if(CaptureNumber == 0)//第一次捕获到上升沿
{
IC1ReadValue1 = TIM_GetCapture4(TIM5);//获取当前计数值
}
else if(CaptureNumber == 1)//第二次捕获到上升沿
{
IC1ReadValue2 = TIM_GetCapture4(TIM5);//获取当前计数值

if (IC1ReadValue2 > IC1ReadValue1)//没有超过最大计数值
{
capture = (IC1ReadValue2 - IC1ReadValue1);//计算两个上升沿之间的计数值
}
else//超过了最大计数值
{
capture = ((0xFFFF - IC1ReadValue1) + IC1ReadValue2);//计算两个上升沿之间的计数值
}
LsiFreq = (uint32_t) SystemCoreClock / capture;
LsiFreq *= 8;//计算LSI频率=72M/capture*8 (乘上8是因为TIM5的CH4 8预分频)
}
CaptureNumber++;//捕获上升沿次数自增

TIM_ClearITPendingBit(TIM5, TIM_IT_CC4);//清除标志位
}
}

在这段代码中,计算出了LSI的时钟频率,它的思路是这样的:通过计算两次上升沿之间计数值差,进而计算出LSI的频率。由于之前配置CH4为8分频,这里的第一次检测到上升沿实际上是检测到了LSI时钟的第8个上升沿;这里的第二次检测到上升沿实际上是检测到LSI时钟的第16个上升沿。然后保存这两次检测到上升沿之间的计数值,分别用变量IC1ReadValue1和IC1ReadValue2保存,接着计算他们之间的差值。这里有两种情况:一种是,IC1ReadValue1IC1ReadValue2,这说明计数值已经溢出一次了,所以要用((0xFFFF - IC1ReadValue1) + IC1ReadValue2);这个计算公式计算得到,并保存在capture中。得到计数值差值后,才能计算LSI频率。调用公式:LsiFreq=SystemCoreClock/capture=72M/capture,这里得到的LsiFreq是8个周期所对应频率,所以实际上LSI时钟频率LsiFreq还要乘于8,即LsiFreq *=8这才是LSI的真实频率。
 
4、main函数的编写
main函数的代码很简单,如下:

/*************************************************************
Function : main
Description: main入口
Input : none
return : none
*************************************************************/
int main(void)
{
BSP_Init();
PRINTF('\nmain() is running!\r\n');
IWDGReset_Init();
while(1)
{
LED1_Toggle(); //LED闪烁
Delay_ms(200); //延时大改250ms以上,就会看门狗复位
IWDG_ReloadCounter();//喂狗
}
}

调用IWDGReset_Init()初始化独立看门狗,然后在while(1)中每次延时200ms后就调用喂狗函数IWDG_ReloadCounter()来重新转载下看门狗的转载值。
 
5、测试
依上见面的mian函数代码,每次都及时喂狗,程序只会执行一次而一直不会复位,在串口调试软件输出的信息中可以看到,如下图所示:
STM32 独立看门狗定时器IWDG复位 - ziye334 - ziye334的博客
 在将main函数中的延时改成Delay_ms(300),这样的话,看门 狗复无法及时喂狗,到时一直复位,如下图所示:
STM32 独立看门狗定时器IWDG复位 - ziye334 - ziye334的博客
 
下面在做做其他实验,看不看CH4的预分频对LSI频率的影响。
将配置TIM5 CH4的代码中的CH4d的8预分频改成不分频,即改成:

TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;

同时在stm32f10x_it.c中,将LsiFreq *= 8;这就话屏蔽掉,然后编译下载,有如下现象:
STM32 独立看门狗定时器IWDG复位 - ziye334 - ziye334的博客
 可以发现修改后的LSI时钟频率为53097Hz,跟原先的43920Hz相差甚大。要问哪个准确,当然是43920Hz比较准确了,所以在编写程序的时候,最好要将TIM5的CH4进行8分频。

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • 家用电源无载自动断电装置的设计与制作

  • 用数字电路CD4069制作的万能遥控轻触开关

  • 使用ESP8266从NTP服务器获取时间并在OLED显示器上显示

  • 开关电源的基本组成及工作原理

  • 用NE555制作定时器

  • 带有短路保护系统的5V直流稳压电源电路图

    相关电子头条文章