STM32入门(五)定时器与中断

STM32入门(五)定时器与中断

STM32F103系列有11个定时器:

      2个高级定时器(TIM1,TIM8):高级定时器除了具有基本定时器和通用定时器的功能以外,还具有输出互补带死区的信号,以及刹车等功能。

     4个通用定时器(TIM2,TIM3,TIM4,TIM5):通用定时器(TIM2~TIM5)除了基本的定时功能以外,还具有输入捕获和输出比较,产生pwm波形的功能。

     2个基本定时器(TIM6,TIM7):基本定时器(TIM6,TIM7)的主要功能就是定时,由各自的可编程预分频器驱动。

     2个看门狗定时器(IWDG,WWDG):看门狗定时器用于监测程序是否故障的,在超出一定时间程序跑飞了或者CPU不工作了,会产生一次系统复位或者是中断(只有窗口看门狗有中断功能)。

     1个系统滴答定时器:系统滴答定时器是封装在cortex-M3中的一个定时器,在运行操作系统时滴答定时器周期性产生中断,基于时间片轮转的系统会在时间片里切换任务,还能用这个定时器实现精准的延时。

TIM1~TIM8这8个定时器都是16位的,最大能到65535,这个值我们称之为重装载值(arr),定时器计数模式有3种:

 (1)向上计数模式:从0开始计数,逐一递增到arr,产生溢出事件,重新从0开始计数;

 (2)向下计数模式: 从arr开始计数,逐一递减到0,产生溢出事件,重新从arr开始计数;

 (3)向上/向下模式:从0开始向上计数,计到arr产生溢出事件然后向下计数,计到0又产生溢出,再从0开始向上计数。

TIM1~TIM8中,除了基本定时器TIM6,TIM7只具有向上计数模式之外,其他6个都支持3种计数模式。定时器定时时间计算,假设定时时间T,预分频值为psc,时钟为SYS_CLK(时钟源为系统时钟),则T=((psc+1)/SYS_CLK)*(arr+1)。

接下来我们以通用定时器TIM3为实例,来说明要经过哪些步骤,才能达到这个要求,并产生中断。这里我们就对每个步骤通过库函数的实现方式来描述。首先要提到的是,定时器相关的库函数主要集中在固件库文件stm32f10x_tim.h和stm32f10x_tim.c文件中。

1)TIM3时钟使能
TIM3是挂载在APB1之下,所以我们通过APB1总线下的使能使能函数来使能TIM3。调用的函数是:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能

2)初始化定时器参数,设置自动重装值,分频系数,计数方式等。在库函数中,定时器的初始化参数是通过初始化函数TIM_TimeBaseInit实现的:

voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

第一个参数是确定是哪个定时器,这个比较容易理解。第二个参数是定时器初始化参数结构体指针,结构体类型为TIM_TimeBaseInitTypeDef,下面我们看看这个结构体的定义

typedef struct{
uint16_t TIM_Prescaler;     
uint16_t TIM_CounterMode;       
uint16_t TIM_Period;         
uint16_t TIM_ClockDivision;   
uint8_t TIM_RepetitionCounter;   
} TIM_TimeBaseInitTypeDef;  

这个结构体一共有5个成员变量,要说明的是,对于通用定时器只有前面四个参数有用,最后一个参数TIM_RepetitionCounter是高级定时器才有用的,这里不多解释。

第一个参数TIM_Prescaler是用来设置分频系数的,刚才上面有讲解。
第二个参数TIM_CounterMode是用来设置计数方式,上面讲解过,可以设置为向上计数,向下计数方式还有中央对齐计数方式,比较常用的是向上计数模式TIM_CounterMode_Up和向下计数模式TIM_CounterMode_Down。
第三个参数是设置自动重载计数周期值,这在前面也已经讲解过。
第四个参数是用来设置时钟分频因子。
针对TIM3初始化范例代码格式:

TIM_TimeBaseInitTypeDef    TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_Period = 5000;
TIM_TimeBaseStructure.TIM_Prescaler =7199; 
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

3)设置TIM3_DIER允许更新中断
因为我们要使用TIM3的更新中断,寄存器的相应位便可使能更新中断。在库函数里面定时器中断使能是通过TIM_ITConfig函数来实现的:

void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState)

第一个参数是选择定时器号,这个容易理解,取值为TIM1~TIM17。第二个参数非常关键,是用来指明我们使能的定时器中断的类型,定时器中断的类型有很多种,包括更新中断TIM_IT_Update,触发中断TIM_IT_Trigger,以及输入捕获中断等等。第三个参数就很简单了,就是失能还是使能。例如我们要使能TIM3的更新中断,格式为:

TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); 

4)TIM3中断优先级设置。
在定时器中断使能之后,因为要产生中断,必不可少的要设置NVIC相关寄存器,设置中断优先级。之前多次讲解到用NVIC_Init函数实现中断优先级的设置,这里就不重复讲解。

5)允许TIM3工作,也就是使能TIM3。光配置好定时器还不行,没有开启定时器,照样不能用。我们在配置完后要开启定时器,通过TIM3_CR1的CEN位来设置。在固件库里面使能定时器的函数是通过TIM_Cmd函数来实现的:

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)

这个函数非常简单,比如我们要使能定时器3,方法为:

TIM_Cmd(TIM3, ENABLE);    //使能TIMx外设

6)编写中断服务函数。
在最后,还是要编写定时器中断服务函数,通过该函数来处理定时器产生的相关中断。在中断产生后,通过状态寄存器的值来判断此次产生的中断属于什么类型。然后执行相关的操作,我们这里使用的是更新(溢出)中断,所以在状态寄存器SR的最低位。在处理完中断之后应该向TIM3_SR的最低位写0,来清除该中断标志。在固件库函数里面,用来读取中断状态寄存器的值判断中断类型的函数是:

ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t)

该函数的作用是,判断定时器TIMx的中断类型TIM_IT是否发生中断。比如,我们要判断定时器3是否发生更新(溢出)中断,方法为:

if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET){}

固件库中清除中断标志位的函数是:

void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT)

该函数的作用是,清除定时器TIMx的中断TIM_IT标志位。使用起来非常简单,比如我们在TIM3的溢出中断发生后,我们要清除中断标志位,方法是:

TIM_ClearITPendingBit(TIM3, TIM_IT_Update    );

这里需要说明一下,固件库还提供了两个函数用来判断定时器状态以及清除定时器状态标志位的函数TIM_GetFlagStatus和TIM_ClearFlag,他们的作用和前面两个函数的作用类似。只是在TIM_GetITStatus函数中会先判断这种中断是否使能,使能了才去判断中断标志位,而TIM_GetFlagStatus直接用来判断状态标志位。
通过以上几个步骤,我们就可以达到我们的目的了,使用通用定时器的更新中断,来控制DS1的亮灭。

time.c文件:

#include "timer.h"
#include "led.h"

//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
	
	//定时器TIM3初始化
	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
 
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断

	//中断优先级NVIC设置
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //初始化NVIC寄存器


	TIM_Cmd(TIM3, ENABLE);  //使能TIMx					 
}
//定时器3中断服务程序
void TIM3_IRQHandler(void)   //TIM3中断
{
	if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)  //检查TIM3更新中断发生与否
		{
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update  );  //清除TIMx更新中断标志 
		LED1=!LED1;
		}
}

main文件:

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "timer.h"
 
 int main(void)
 {		
 
	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 //串口初始化为115200
 	LED_Init();			     //LED端口初始化
	TIM3_Int_Init(4999,7199);//10Khz的计数频率,计数到5000为500ms  
   	while(1)
	{
		LED0=!LED0;
		delay_ms(200);		   
	}	 

 
}	 
 

此段代码对TIM3进行初始化之后,进入死循环等待TIM3溢出中断,当TIM3_CNT的值等于TIM3_ARR的值的时候,就会产生TIM3的更新中断,然后在中断里面取反LED1,TIM3_CNT再从0 开始计数。根据上面的公式,我们可以算出中断溢出时间为500ms,即
Tout= ((4999+1)*( 7199+1))/72=500000us=500ms

如果没有错误,我们将看DS0不停闪烁(每400ms闪烁一次),而DS1也是不停的闪烁,但是闪烁时间较DS0慢(1s一次)。

你可能感兴趣的:(单片机,嵌入式,stm32,定时器,中断)