TIM(Timer)定时器是STM32系列微控制器中常用的外设之一,定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断。本章开始介绍定时器的相关内容。
19.1 定时器简介
STM32定时器根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型。在前面《第17章.RCC-STM32时钟配置》的图17.1-1 STM32时钟树中,我们可以看到有8个定时器,其中定时器1和8挂载在APB2总线上,定时器2-7挂载在APB1总线上。 注意不同型号的STM单片机的资源是不一样的,我们教程用的STM32F103C8T6定时器资源有TIM1/TIM2/TIM3/TIM4,即一个高级定时器和3个通用定时器,没有基本定时器。
图19.1-1 定时器分类
19.1.1 基本定时器
概述:基本定时器TIM6和TIM7是一个16位的只能向上计数的定时器,只能定时,没有外部IO。
作用:基本定时器用于基本的定时功能。不支持输入捕获和输出比较功能,但可以用于驱动DAC(数模转换器)或产生基本的定时中断。
19.1.2 通用定时器
概述:通用定时器TIM2/3/4/5是一个16位的可以向上/下计数的定时器,可以定时,可以输出比较,可以输入捕捉,每个定时器有4个外部IO(可参考前面第三章中的《图3.3-2 STM32F103C8T6各引脚定义》)。
作用:除具备基本定时器所有功能外,还具有输入捕获、输出比较功能。输入捕获功能可用于测量输入信号的频率和脉宽,输出比较功能可产生PWM波形用于电机、舵机控制等场景。
19.1.3 高级定时器
概述:高级定时器TIM1/8是一个16位的可以向上/下计数的定时器,除了具有通用定时器的所有功能外,还支持互补输出、死区时间控制等高级功能,每个定时器有8个外部IO。
作用:主要用于三相电机控制。
19.2 基本定时器框图
学习基本定时器框图会对定时器有一个很好的整体了解,之后的编程也会有一个清晰的思路。
图19.2-1 基本定时器框图
19.2.1 时钟源
如图中标号①部分,定时器要实现计数功能,首先要给它一个时钟源:定时器时钟TIMxCLK,即内部时钟CK_INT。基本定时器时钟挂载在APB1总线,所以它的时钟来自于APB1总线,但是基本定时器时钟不是直接由APB1总线直接提供,而是先经过一个倍频器。当APB1的预分频器系数为1时,这个倍频器系数为1,即定时器的时钟频率等于APB1总线时钟频率;当APB1的预分频器系数≥2分频时,这个倍频器系数就为2,即定时器的时钟频率等于APB1总线时钟频率的两倍,库函数中APB1 预分频的系数是2,即PCLK1=36Mhz,所以定时器时钟TIMxCLK=36*2=72Mhz。
19.2.2 控制器
控制器除了控制定时器复位、使能、计数等功能之外,还可以用于触发DAC转换。
19.2.3 时基单元
时基单元包括:预分频器寄存器(TIMx_PSC)、计数器寄存器(TIMx_CNT)、自动重载寄存器(TIMx_ARR)。基本定时器的这三个寄存器都是16位有效数字,即可设置值范围是0~65535。
PSC 预分频器:时基单元中的预分频器PSC,输入CK_PSC来源于控制器部分,实际上就是来自于内部时钟(CK_INT),输出CK_CNT是分频后的时钟,它是计数器实际的计数时钟,通过设置预分频器寄存器(TIMx_PSC)的值可以得到不同频率的CK_CNT,计算公式如下,公式中PSC是写入预分频器寄存器(TIMx_PSC)的值。
预分频器寄存器(TIMx_PSC)可以在运行过程中修改它的数值,新的预分频数值将在下一个更新事件时起作用。更新事件发生时,会把TIMx_PSC寄存器值更新到其影子寄存器中,才会起作用。
影子寄存器:从上框图看,预分频器PSC后面有一个影子,自动重载寄存器也有个影子,这就表示这些寄存器有影子寄存器。影子寄存器是一个实际起作用的寄存器,不可直接访问。我们可以把预分频系数写入预分频器寄存器(TIMx_PSC),但是预分频器寄存器只是起到缓存数据的作用,只有等到更新事件发生时,预分频器寄存器的值才会被自动写入其影子寄存器中,这时才真正起作用。
自动重载寄存器:自动重装载寄存器ARR是一个16位的寄存器,这里面装着计数器能计数的最大值。当计数到这个值的时候,如果使能了中断的话,定时器就产生溢出中断。该寄存器及其影子寄存器和PSC预分频器有所不同,自动重载寄存器是否具有缓冲作用还受到ARPE位的控制。当该位置0时,ARR寄存器不进行缓冲,我们写入新的ARR值时,该值会马上被写入ARR影子寄存器中,从而直接生效;当该位置1时,ARR寄存器进行缓冲,我们写入新的ARR值时,该值不会马上被写入ARR影子寄存器中,而是要等到更新事件发生才会被写入ARR影子寄存器,这时才生效。预分频器寄存器则没有这样相关的控制位。
计数器:基本定时器的计数器(CNT)是一个16位递增的计数器,当寄存器(TIMx_CR1)的CEN位置1,即使能定时器,每来一个CK_CNT脉冲,TIMx_CNT的值就会递增加1。当TIMx_CNT值与TIMx_ARR的设定值相等时,TIMx_CNT的值就会被自动清零并且会生成更新事件(如果开启相应的功能,就会产生DMA请求、产生中断信号或者触发DAC同步),如此循环。TIMx_CNT等于TIMx_ARR时,称之为定时器溢出,定时器溢出就伴随着更新事件的发生。
19.3 定时时间的计算
计数器在CK_CNT的驱动下,计一个数的时间则是CK_CNK的倒数,而根据前面公式CK_CNT=TIMxCLK/(PSC+1),那么记一个数的时间为:(PSC+1)/TIMxCLK,产生一次中断的时间则等于:
其中ARR即为自动重载寄存器的值,因为是从0开始计数的,因此公式中需要+1.
下面我们举例说明。我们设置一个10ms周期的定时器更新中断,一般思路是先设置预分频寄存器,然后才是自动重载寄存器。我们内部时钟TIMxCLK为72MHz,我们把预分频系数设置为7200,即写入预分频寄存器的值PSC为7199,那么CK_CNT=72MHz/7200=10KHz。这样就得到计数器的计数频率为10KHz,计数器1秒钟可以计10000个数。所以我们让计数器计数100个数就能满足要求,即需要设置自动重载寄存器的值ARR为99。如果1秒中断的话,那ARR为10000-1=9999即可。
19.4 基本定时器控制寄存器
下面介绍基本定时器(TIM6/TIM7) 的几个重要的寄存器,完整的可以参考《STM32F10x-中文参考手册》。
19.4.1控制寄存器1(TIMx_CR1)
图19.4-1 TIMx_CR1 寄存器
位7(APRE)用于控制自动重载寄存器ARR是否具有缓冲作用,如果ARPE位置1,ARR起缓冲作用,即只有在更新事件发生时才会把ARR的值写入其影子寄存器里;如果ARPE位置0,那么修改自动重载寄存器的值时,该值会马上被写入其影子寄存器中立即生效。
位0(CEN)用于使能或者禁止计数器,该位置1计数器开始工作,置0停止工作。
19.4.2 DMA/中断使能寄存器(TIMx_DIER)
图19.4-2 TIMx_DIER 寄存器
位8(UDE)用于使能或者禁止更新DMA请求,DMA相关内容,我们后面会再单独介绍,这里我们暂且用不到,置0即可。
位0(UIE)用于使能或者禁止更新中断,我们用到中断时,该位需要置1。
19.4.3 状态寄存器(TIMx_SR)
图19.4-3 TIMx_SR 寄存器
位0(UIF)是中断更新的标志位,当发生中断时由硬件置1,然后就会执行中断服务函数,需要软件清零,所以我们必须在中断服务函数里把该位清零。如果中断后,不把该位清零,那么系统会一直进入中断服务函数。
19.4.4 计数器寄存器(TIMx_CNT)
图19.4-4 TIMx_CNT 寄存器
该寄存器就是计数器的实时计数值。
19.4.5 预分频寄存器(TIMx_PSC)
图19.4-5 TIMx_PSC 寄存器
该寄存器是基本寄存器TIM6/TIM7的16位预分频寄存器,写入数值范围0到65535,分频系数为1到65536。
19.5.6 自动重载寄存器(TIMx_ARR)
图19.4-6 TIMx_ARR寄存器
该寄存器可以由前面寄存器TIMx_CR1的APRE位设置是否进行缓冲。计数器的值会和ARR寄存器影子寄存器进行比较,当两者相等,定时器就会溢出,发生更新事件,如果打开更新中断,还会发生更新中断。
19.5 基本定时器相关库函数
基本定时器功能相对较少,后面通用定时器我们还会介绍相关库函数。这里我们先只介绍基本定时器定时中断的相关库函数,DMA等相关的,会在后续DMA等相关章节再介绍。
19.5.1 内部时钟选择函数:TIM_InternalClockConfig
函数名:TIM_InternalClockConfig
函数原形:TIM_InternalClockConfig(TIM_TypeDef*TIMx)
功能描述:设置 TIMx 内部时钟
输入参数:TIMx:x可以是2,3或者4(注:STM32F103C8T6只有4个定时器,没有基本定时器)
举例:
TIM_InternalClockConfig(TIM2);
|
19.5.2 时基单元配置函数:TIM_TimeBaseInit
函数名:TIM_TimeBaseInit
函数原形:
void TIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct);
|
功能描述:根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时基单元,即时基单元初始化;
输入参数:
TIM_TypeDef* TIMx:指向定时器实例的指针。STM32系列微控制器包含多个定时器(如TIM1、TIM2等),每个定时器都有自己的一组寄存器。通过该指针,可以指定要初始化的定时器。
TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct:指向TIM_TimeBaseInitTypeDef结构体的指针,该结构体包含了定时器时间基数单元的配置信息。
时基单元初始化结构体介绍如下:
uint16_t TIM_CounterMode; uint16_t TIM_ClockDivision; uint8_t TIM_RepetitionCounter; } TIM_TimeBaseInitTypeDef;
|
(1)TIM_Prescaler:定时器预分频器设置,时钟源经预分频器PSC后才是定时器时钟,它设定TIMx_PSC寄存器的值。可设置范围为0至65535,实现1至65536分频。
(2)TIM_CounterMode:STM32定时器支持多种计数模式,如向上计数、向下计数、中央对齐计数(通用定时器章节会介绍)等。基本定时器只能是向上计数,即TIMx_CNT只能从0开始递增,并且无需初始化。
(3)TIM_Period:定时器周期,即设定自动重载寄存器的值,在事件生成时更新到影子寄存器。可设置范围为0至65535。
(4)TIM_ClockDivision:时钟分频,设置定时器时钟CK_INT频率与数字滤波器采样时钟频率分频比,基本定时器没有此功能,不用设置。这个值主要有1,2,4分频,扥别对应TIM_CKD_DIV1 ,TIM_CKD_DIV2,TIM_CKD_DIV4.
(5)TIM_RepetitionCounter:重复计数器,属于高级控制寄存器专用寄存器位,利用它可以非常容易控制输出PWM的个数。基本定时器这里不用设置。
虽然基本初始化结构体有5个成员,但对于基本定时器只需设置预分频器TIM_Prescaler和自动重载寄存器TIM_Period两个就可以。
举例:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_TimeBaseStructure.TIM_Period = 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler = 0xF; TIM_TimeBaseStructure.TIM_ClockDivision = 0x0; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, & TIM_TimeBaseStructure);
|
19.5.3 定时器状态标志位清除函数TIM_ClearFlag
函数名: TIM_ClearFlag
函数原形:
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
|
功能描述:TIM_ClearFlag函数用于清除指定的定时器状态标志位。在STM32的定时器中,状态标志位用于指示定时器当前的状态或发生的事件。例如,当定时器计数到自动重装载寄存器(ARR)的值时,会触发更新事件,并设置相应的状态标志位。通过调用TIM_ClearFlag函数,可以清除这些状态标志位,以便进行后续的状态检测或事件处理。
在定时器的中断服务程序或轮询程序中,可以通过检查状态标志位来判断定时器是否发生了特定的事件。在事件处理完毕后,可以使用TIM_ClearFlag函数清除状态标志位,以避免重复处理同一事件。
参数说明:
TIM_TypeDef* TIMx:指向定时器实例的指针。STM32中有多个定时器(如TIM1、TIM2等),每个定时器都有自己的一组寄存器。通过该指针,可以指定要操作的定时器。
uint16_t TIM_FLAG:要清除的状态标志位。不同的定时器有不同的状态标志位,用于指示定时器的不同状态(如更新事件、输入捕获事件等)。对于基本定时器主要是更新事件TIM_FLAG_Update,对于其他的状态在后续定时器章节再介绍。
举例:
TIM_TypeDef* TIMx = TIM3; uint16_t TIM_FLAG = TIM_FLAG_Update; TIM_ClearFlag(TIMx, TIM_FLAG);
|
19.5.4 中断功能配置TIM_ITConfig
函数名:TIM_ITConfig
函数原形:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
|
参数说明:
TIM_TypeDef* TIMx:指向要配置的定时器的指针。STM32系列微控制器包含多个定时器,如TIM1、TIM2等,此参数用于选择具体的定时器。
uint16_t TIM_IT:指定要配置的中断源。STM32定时器的中断源包括更新中断(Update Interrupt)、捕获/比较中断(Capture Compare Interrupt)等。具体的中断源可以通过宏定义来选择,如TIM_IT_Update、TIM_IT_CCx(x=1,2,3,4)等。常见的如下,对于基本定时器来说,只有更新中断TIM_IT_Update。
①TIM_IT_Update:更新中断(或称溢出中断)。当定时器的计数器达到其预设的自动重装载值(ARR)并溢出时,将触发此中断。这是定时器中最基本且常用的中断类型,常用于实现周期性任务或时间测量。
②TIM_IT_CCx(x=1, 2, 3, 4):捕获/比较中断。STM32定时器具有多个捕获/比较通道(通常为4个),每个通道都可以配置为输入捕获或输出比较模式。当捕获/比较通道的输入信号达到预设的阈值或输出信号与预设值匹配时,将触发此中断。这种中断类型常用于信号测量、PWM输出控制等场景。
③TIM_IT_Trigger:触发中断。当定时器的外部触发输入(如ETR、TIx等)满足预设条件时,将触发此中断。这种中断类型通常用于外部信号的同步或触发其他外设的操作。
④TIM_IT_Break:刹车中断(高级定时器)。当定时器的刹车输入信号有效时,将触发此中断。刹车功能通常用于安全相关的应用,如电机控制中的紧急停车。
3.FunctionalState NewState:指定中断的新状态。这个参数是枚举类型FunctionalState,通常有两个取值:ENABLE(启用中断)和DISABLE(禁用中断)。
功能描述:TIM_ITConfig函数用于启用或禁用指定定时器的一个或多个中断源。通过配置这个函数,用户可以在定时器发生特定事件时产生中断,从而在中断服务程序中执行相应的操作。
举例:
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
|
19.5.5 定时器中断状态获取函数TIM_GetITStatus
STM32定时器的TIM_GetITStatus函数是一个用于检查定时器中断标志位是否被置位的函数。
函数原型:
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
|
参数说明:
TIM_TypeDef* TIMx:指向要检查的定时器的指针。STM32系列微控制器包含多个定时器,如TIM1、TIM2等,此参数用于选择具体的定时器。
uint16_t TIM_IT:指定要检查的中断标志位。STM32定时器的中断标志位包括更新中断标志(TIM_IT_Update)、捕获/比较中断标志(TIM_IT_CCx,x=1, 2, 3, 4)等。
返回值:函数返回ITStatus类型的值,表示中断标志位的状态。ITStatus通常是一个枚举类型,包含两个取值:SET(表示中断标志位被置位)和RESET(表示中断标志位未被置位)。
功能描述:TIM_GetITStatus函数用于检查指定定时器的指定中断标志位是否被置位。如果中断标志位被置位,则表示相应的中断事件已经发生,此时可以在中断服务程序中执行相应的中断处理代码。
举例:
void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { TIM_ClearFlag(TIM2, TIM_IT_Update);
|
19.5.6 定时器使能函数TIM_Cmd
TIM_Cmd是用于控制定时器(TIM)外设的使能(ENABLE)或失能(DISABLE)状态的函数。
函数原型:
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
|
参数说明:
TIM_TypeDef* TIMx:这是一个指向定时器结构体的指针,用于选择要操作的定时器。STM32微控制器通常有多个定时器(如TIM1、TIM2等),通过该参数可以选择具体的定时器。
FunctionalState NewState:这是一个枚举类型的参数,用于指定定时器的状态。它通常有两个取值:
ENABLE:使能定时器,即启动定时器的工作。
DISABLE:失能定时器,即停止定时器的工作。
功能描述:TIM_Cmd函数的主要功能是控制定时器的使能或失能状态。当NewState参数为ENABLE时,函数将启动指定的定时器,使其开始计数或执行其他配置的任务。当NewState参数为DISABLE时,函数将停止指定的定时器,使其停止计数或执行其他任务。
举例:
19.5.7 清除中断待处理标志位函数TIM_ClearITPendingBit
函数原形:
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
|
参数说明:
TIM_TypeDef* TIMx:指向定时器实例的指针。STM32中有多个定时器实例,如TIM1、TIM2等,通过此参数可以指定要操作的定时器。
uint16_t TIM_IT:指定要清除的中断标志位。这个参数通常是一个宏定义,代表特定的中断类型,如更新中断、捕获比较中断等。
功能描述:TIM_ClearITPendingBit函数的功能是清除指定定时器的指定中断待处理标志位。当定时器发生某个中断事件并启用了相应的中断时,中断待处理标志位会被置位。通过调用此函数,可以清除相应的中断待处理标志位,从而告知中断控制器该中断事件已经被处理。
TIM_ClearITPendingBit函数通常在定时器中断服务程序(ISR)中使用。当中断服务程序被调用时,表示定时器已经发生了某个中断事件。在中断服务程序中,除了处理中断事件外,还需要清除相应的中断待处理标志位,以确保中断控制器能够正常接收后续的中断请求。
举例:
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
|
19.6 基本定时器应用案例
本章我们主要学习基本定时器的定时中断功能,基本定时器没有外部IO口,只能用内部时钟。我们练习的案例也很简单,就是通过基本定时器的定时中断,实现LED灯以1秒为周期进行亮灭。我们直接在前面章节EXTI的例程上修改即可,LED接在PB1口上。由于我这边用的开发板没有基本定时器TIM6-7,因此我们用基本定时器TIM2模拟该功能即可。
本例程大致思路如下,通过定时器10ms触发一次中断,并进行计次,主函数判断记次为100(1秒时间)时反转LED灯。
19.6.1 编程步骤
(1)开定时器时钟;
(2)初始化时基单元初始化结构体;
(3)使能定时器TIMx,的更新update中断;
(4)打开定时器;
(5)编写中断服务函数;
19.6.2 硬件相关宏定义配置
LED灯部分这里就不讲了,直接全部copy 《第15章.GPIO输入-按键检测》中的LED.c和LED.h.
我们硬件相关配置推荐使用宏,这样是方便后期更改的,只用改头文件,其他函数都不用修改。除了硬件相关的配置,参数配置也推荐用宏。同时我们前面知道这几个定时器,APB1和APB2上都有,这样一些配置函数也是可得通过宏来指定,如RCC_APB1PeriphClockCmd函数名可以指定给一个宏。这样不管硬件怎么变,都可以只修改宏定义即可,非常便于移植。我们建立BaseTIM.h头文件,代码如下:
#define BASIC_TIM_APBxClock_FUN RCC_APB1PeriphClockCmd #define BASIC_TIM_CLK RCC_APB1Periph_TIM2 #define BASIC_TIM_Period 100-1 #define BASIC_TIM_Prescaler 7200-1 #define BASIC_TIM_IRQ TIM2_IRQn #define BASIC_TIM_IRQHandler TIM2_IRQHandler void BASIC_TIM_Init(void);
|
19.6.3 定时器相关配置函数
在BaseTIM.c文件主要进行定时器初始化配置,NVIC配置。如下代码:BASIC_TIM_Init()函数包含了NVIC和定时器初始化配置函数,这样后续调用这一个函数就可以实现初始化;
static void BASIC_TIM_NVIC_Config(void) NVIC_InitTypeDef NVIC_InitStructure; NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); NVIC_InitStructure.NVIC_IRQChannel = BASIC_TIM_IRQ ; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); static void BASIC_TIM_Mode_Config(void) TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; BASIC_TIM_APBxClock_FUN(BASIC_TIM_CLK, ENABLE); TIM_TimeBaseStructure.TIM_Period = BASIC_TIM_Period; TIM_TimeBaseStructure.TIM_Prescaler= BASIC_TIM_Prescaler; TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; TIM_TimeBaseInit(BASIC_TIM, &TIM_TimeBaseStructure); TIM_ClearFlag(BASIC_TIM, TIM_FLAG_Update); TIM_ITConfig(BASIC_TIM,TIM_IT_Update,ENABLE); TIM_Cmd(BASIC_TIM, ENABLE); void BASIC_TIM_Init(void)
|
19.6.4 定时器中断服务函数
在中断服务文件stm32f10x_it.c文件中加入如下中断服务函数:
void BASIC_TIM_IRQHandler (void) if ( TIM_GetITStatus( BASIC_TIM, TIM_IT_Update) != RESET ) TIM_ClearITPendingBit(BASIC_TIM , TIM_FLAG_Update);
|
相关例程文件已经上传
|