5.5 STM32驱动开发-通用与高级定时器
前言¶
这里主要讲解 Breeze Mini 的内部定时器,完整代码在工程目录下的 Drivers 子目录下的 stm32f10x_driver_timer.c 和 stm32f10x_driver_timer.h 中,该代码主要是驱动定时器产生不同时间长度的定时中断来控制main函数调用相应函数,进而实现 中断嵌套 功能。同时也是为了后面讲解PWM功能模块做铺垫。
相关知识¶
通用定时器¶
STM32F1系列的通用定时器是一个通过可编程预分频器 (PSC) 驱动的16位自动装载计数器 (CNT),其被用于:测量输入信号的脉冲长度 (输入捕获) 和产生输出波形 (输出比较和脉冲宽度调制),使用PSC和RCC时钟控制预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒之间调整。STM32的每个通用定时器都是独立的,可以单独使用,每个通用定时器功能包括:
-
具有一个16位向上、向下或向上向下自动装载计数器(TIMx_CNT)
-
具有一个16位可编程预分频器(TIMx_PSC),计数器时钟频率的分频系数范围为1~65535
-
拥有4个独立通道,可以被分别配置成如下功能:
-
输入捕获
-
输出比较
-
PWM输出(边缘或中间对齐模式)
-
单脉冲模式输出
-
-
具有可使用外部信号 (TIMx_ETR) 控制定时器与定时器互联的同步电路
-
具有事件中断响应能力,事件源有:
-
更新:计数器溢出和计数器初始化(通过软件或内部/外部触发)
-
触发事件:计数器启动、停止、初始化或由内部/外部触发计数
-
输入捕获
-
输出比较
-
针对定位的增量(正交)编码器和霍尔传感器电路
-
触发输入作为外部时钟或按周期的电流管理
-
常见问题¶
问题:定时器和计数器是什么关系?
答:定时器的定时功能本质上是通过计数器 "比着" 时钟脉冲计数来实现的,可以 "比着" 脉冲的上升沿,也可以是下降沿。正是由于每次计数器都是 "对齐" 时钟脉冲计数,所以将 计数的次数乘上时钟脉冲周期就可以得到定时的大小。
问题:定时器定时与时钟频率之间的关系是怎样的?
答:定时的大小=计数器计数值*时钟脉冲周期 (时钟频率倒数),由于计数器最大计数值是确定的,所以当时钟频率越小 (时钟周期越大) 时,定时越长,但同时也会使得 "定时分辨率" 降低。比如5MHz的时钟产生的单位定时为1 / 5 = 0.2us,而10MHz时钟产生的单位定时为1 / 10 = 0.1us。由此可见,10MHz的 "定时分辨率" 更高。
更多有关寄存器的内容可以直接参考 STM32寄存器手册 第253页通用定时器一章,为了能够更加清楚地了解通用定时器的功能,下面会简单介绍一些与定时功能密切相关的寄存器。
相关寄存器介绍¶
TIMx_CR1寄存器¶
TIMx_CR1是TIMx的控制寄存器,该寄存器的各位描述如下图所示:
从上图描述可以看出,TIMx_CR1的第0位控制计数使能,只有该位置1,才能开启定时器计数。第4位设置计数的方向,第5和6位设置计数对齐方式,第8和9位设置定时器时钟分频因子。
TIMx_DIER寄存器¶
TIMx_DIER是TIMx的DMA/中断使能寄存器,该寄存器各位描述如下图所示:
这里只需要关注该寄存器的第0位,该位是更新中断使能位,设置该位为1则允许更新事件产生中断。
TIMx_PSC寄存器¶
TIMx_PSC是TIMx的预分频寄存器,该寄存器用于对时钟进行分频,然后提供给计数器,以作为计数器的时钟。该寄存器的各位描述如下图所示:
这里定时器的时钟来源有4个:
-
内部时钟(CK_INT)
-
外部时钟模式1(外部输入脚TIx)
-
外部时钟模式2(外部触发输入ETR)
-
内部触发输入(使用另一个定时器给本定时器提供时钟)
具体选择哪个时钟源可以通过 TIMx_SMCR 寄存器的对应位来设置,这里的CK_INT时钟是从APB1总线时钟倍频得来的,而高级定时器的内部时钟来自于APB2总线时钟。
TIMx_CNT寄存器¶
TIMx_CNT是TIMx的计数寄存器,该寄存器存储有当前定时器的计数值。
TIMx_ARR寄存器¶
TIMx_ARR是TIMx的自动装载计数器,该寄存器在物理上对应着2个寄存器,1个是可以被操作的,被称为 预装载寄存器,还有1个是被操作不了的,这个操作不了的寄存器被称为 影子寄存器。实际上真正起作用的是影子寄存器,根据TIMx_CR1寄存器中APRE位的设置:
1 2 | APRE = 0 //预装载寄存器的内容可以随时送到影子寄存器中,两者是相连的 APRE = 1 //只有在每次更新事件时才把预装载寄存器的内容送到影子寄存器中 |
该寄存器的各位描述如下图所示:
TIMx_SR寄存器¶
TIMx_SR是TIMx的状态寄存器,该寄存器用来标记定时器事件和中断是否发生,该寄存器各位描述如下图所示:
关于定时器更多的详细描述请参考 STM32寄存器手册 第282页,只要对上面介绍的寄存器进行设置,就可以使用通用定时器产生要求的定时信号。
高级定时器¶
高级定时器在 Breeze Mini 飞控代码中没有被使用,所以这里就不再进行介绍。
固件库配置定时器¶
定时器相关的库函数在 FWLib 目录下的 stm32f10x_tim.c 和 stm32f10x_tim.h 中。这里以通用定时器TIM3为例来介绍如何通过官方固件库来操作、配置定时器。首先由于TIM3是APB1总线下的外设,所以需要先使能APB1外设对应于TIM3的时钟,调用的函数是:
1 | RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); |
然后初始化定时器参数,设置自动重装载值,分频系数和计数方式等,在库函数中定时器的初始化是通过函数 TIM_TimeBaseInit 实现的,其原型为:
1 2 | void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct) |
1 2 3 4 5 6 7 8 | 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个成员变量,对于通用定时器来说只有前4个参数有用,最后一个参数 TIM_RepetitionCounter 没用(该参数只对高级定时器有用)。
第一个成员变量 TIM_Prescaler 是用来设置时钟分频系数的,第二个成员变量 TIM_CounterMode 是用来设置计数方式的,可以设置成 向上、向下 和 中央对齐 计数模式。第3个是设置自动重载计数周期值,第4个是设置时钟分频因子,针对TIM3初始化的代码格式一般如下:
1 2 3 4 5 6 | 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); |
接着设置TIM3_DIER允许更新中断,在库函数中使能定时器中断的函数是 TIM_ITConfig,其原型为:
1 2 | void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState); |
第一个参数表示要被初始化的定时器标号,比如 TIM3,第二个参数表示要使能的定时器中断的类型,比如 更新中断TIM_IT_Update,触发中断TIM_IT_Trigger 等。比如要使能TIM3的更新中断,其代码为:
1 | TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE); |
接着是设置TIM3的中断优先级,因为是要通过中断的方式来产生定时,所以需要设置NVIC相关寄存器的值,NVIC具体设置的方法在前面 STM32驱动开发-中断向量控制 中有详细的介绍,这里就不再重复讲解。设置完成后还要编写中断任务函数,当相应定时器中断发生时,通过检测状态寄存器TIMx_SR的值来判断产生的中断类型,比如我们使用的是TIM3的 更新中断,则只需检测TIM3_SR的最低位即可,注意当执行完中断服务函数后要置 TIM3_SR的最低位为0来清除中断标志。在固件库中用来读取并判断SR寄存器的值的函数为:
1 | ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT); |
该函数用于判断定时器TIMx的中断类型 TIM_IT 是否发生,比如若要判断TIM3是否发生更新(溢出)中断,方法为:
1 | if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {} |
固件库中清除中断标志位的函数是:
1 | void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT); |
该函数使用起来非常简单,比如我们要清除TIM3的更新(溢出)中断的标志位的代码是:
1 | TIM_ClearITPendingBit(TIM3, TIM_IT_Update); |
通过调用以上介绍的固件库函数就可以实现定时器的定时功能了。
硬件连接¶
通用定时器属于 Breeze Mini 主微控制器STM32F103TBU6的内部外设,所以是没有外部电路连接的。
软件设计¶
参考¶
- STM32开发指南-库函数版本_V3.1.pdf, 正点原子, ALLENTEK.