TIM(Timer)定时器是STM32中功能最强大,结构最复杂的一个外设,以下对其做一下简介(以stm32为例):
TIM可以对输入的时钟进行计数,并在数值达到设定值时触发中断。
在STM32中定时器的基准时钟一般都是主频72MHz,并且以16位计数器,预分频器,自动重装寄存器为时基单元,在72MHz计数时钟下可以实现最大59.65s的定时。同时STM32定时器支持级联模式,可实现更长时间的定时。(当两个定时器级联时就可产生8千年多的定时)
TIM不仅具备基本的定时中断功能,而且还包括内外时钟源选择,输入捕获,输出比较,编码器接口,主从触发模式等多种功能。
STM32的定时器,根据复杂程度好应用场景分为了高级定时器,通用定时器,基本定时器三种类型。
当然也存在TIM9,TIM10等只是一般情况下用不到,同时也不是所有的STM32单片机都有所有的TIM外设,如STM32F103C8T6只有TIM1~TIM4这4个定时器。
定时中断的基本结构
这里以配置TIM2(通用定时器为例),首先我们需要在keil软件中编写Timer的.c和.h文件。将基本结构中的每一步打通即可完成Timer的函数配置。
开启RCC时钟
选择时基单元的时钟源(对于定时中断选择内部时钟源即可)
配置时基单元(ARR,PSC,CNT模式等,用结构体即可配置)
配置输入中断控制,允许更新中断输出到NVIC
配置NVIC,在NVIC中打开定时器的中断通道,并配置一个优先级
运行控制
上面配置完成之后,还需要使能计数器,否则计数器无法运行,最后写一个中断函数,这样每个一段时间中断函数就可自动运行了(计数器有使能和失能两个状态,使能是开启的意思,失能则是关闭)
.c文件:
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//开启时钟
TIM_InternalClockConfig(TIM2); //开启内部定时器
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //TIM结构体
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //这个是分屏系数,不需要纠结
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //这个值和上一行的值可以自己改变玩玩,体会一下不同
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复定时器,这个是高级定时器才有
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);// 清除更新时的中断标志位,防止更新时程序直接进入中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //使能内部定时器
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure; //NVIC结构体
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选取中断路径
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//相应优先级
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE); //开启定时器
}
void TIM2_IRQHandler(void) //TIM2的中断函数名称,不能更改,不需要声明,同时这个函数也可放到main.c中
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //检查中断标志位
{ //括号里添加自己所需要的中断代码
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);//清除标志位
}
}
注意:TIM_TimeBaseStructure.TIM_ClockDivision = 0;这句话是什么作用?其实仔细看过技术手册后发现这句话与PWM输出实验其实是没关系的,这句话是设置定时器时钟(CK_INT)频率与数字滤波器(ETR,TIx)使用的采样频率之间的分频比例的(与输入捕获相关),0表示滤波器的频率和定时器的频率是一样的。
.h文件
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
#endif
通用定时器可以向上计数、向下计数、向上向下双向计数模式。
1)向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
2)向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
3)中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
简单地理解三种计数模式,可以通过下面的图形:
对于这个定时器框图,分成四部分来讲:最顶上的一部分(计数时钟的选择)、中间部分(时基单元)、左下部分(输入捕获)、右下部分(PWM输出)。这里主要介绍一下前两个,后两者的内容会在后面的文章中讲解到。
初始化定时器的时候指定我们分频系数psc,这里是将我们的系统时钟(72MHz)进行分频。然后指定重装载值arr,这个重装载值的意思就是当我们的定时器的计数值达到这个arr时,定时器就会重新装载其他值。例如当我们设置定时器为向上计数时,定时器计数的值等于arr之后就会被清0重新计数。定时器计数的值被重装载一次被就是一个更新(Update)
超出(溢出)时间计算:Tout=(ARR+1)(PSC+1)/TIMxCLK
其中:Tout的单位为us,TIMxCLK是定时器时钟源,在这里就是72Mhz。我们将分配的时钟进行分频,指定分频值为psc,就将我们的TIMxCLK分了psc+1,我们定时器的最终频率就是TIMxCLK/(psc+1) MHz。这里的频率的意思就是1s中记 TIMxCLK/(psc+1) M个数 (1M=10的6次方) ,每记一个数的时间为(psc+1)/Tclk ,很好理解频率的倒数是周期,这里每一个数的周期就是(psc+1)/TIMxCLK 秒。然后我们从0记到arr 就是 (arr+1)*(psc+1)/TIMxCLK。
这里需要注意的是:PSC预分频系数需要加1,同时自动重加载值也需要加1。
1)为什么自动重加载值需要加1,因为从ARR到0之间的数字是ARR+1个;
2)为什么预分频系数需要加1,因为为了避免预分频系数不设置的时候取0的情况,使之从1开始。
这里需要和之前的预分频进行区分:由于通用定时器的预分频系数为1~65535之间的任意数值,为了从1开始,所以当预分频系数寄存器为0的时候,代表的预分频系数为1。而之前的那些预分频系数都是固定的几个值,比如1、4、8、16、32、64等等,而且可能0x000代表1,0x001代表4,0x010代表8等等。也就是说,一边是随意的定义(要从1开始),另一边是宏定义了某些值(只有特定的一些值)。
比如,想要设置超出时间为1s,并配置中断,我们设置arr=7199,psc=9999。我们将72MHz (1M等于10的6次方) 分成了(9999+1)等于 7200Hz,就是一秒钟记录9000数,每记录一个数就是1/7200秒。我们这里记录9000个数进入定时器更新(7199+1)*(1/7200)=1s,也就是1s进入一次更新Update。
1个初始化函数
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);//定时器参数初始化
typedef struct
{
uint16_t TIM_Prescaler;//预分频系数的设置
uint16_t TIM_CounterMode;//计数模式
uint16_t TIM_Period;//自动装载值
uint16_t TIM_ClockDivision;//输入捕获会用到
uint8_t TIM_RepetitionCounter;//高级定时器会用到
} TIM_TimeBaseInitTypeDef;
作用:用于对预分频系数、计数方式、自动重装载计数值、时钟分频因子等参数的设置。
2个使能函数
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);//定时器使能函数
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);//定时器中断使能函数
作用:前者使能定时器,后者使能定时器中断。
4个状态标志位获取函数
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
作用:前两者获取(或清除)状态标志位,后两者为获取(或清除)中断状态标志位。
9.定时器中断的一般步骤
实例要求:通过TIM3的中断来控制DS1的亮灭,DS1是直接连接在PE5上的。
1)使能定时器时钟。调用函数:RCC_APB1PeriphClockCmd();
2)初始化定时器,配置ARR、PSC。调用函数:TIM_TimeBaseInit();
3)开启定时器中断,配置NVIC。调用函数:void TIM_ITConfig();NVIC_Init();
4)使能定时器。调用函数:TIM_Cmd();
5)编写中断服务函数。调用函数:TIMx_IRQHandler()。