跳转至

5.5 STM32驱动开发-通用与高级定时器

前言

这里主要讲解 Breeze Mini 的内部定时器,完整代码在工程目录下的 Drivers 子目录下的 stm32f10x_driver_timer.cstm32f10x_driver_timer.h 中,该代码主要是驱动定时器产生不同时间长度的定时中断来控制main函数调用相应函数,进而实现 中断嵌套 功能。同时也是为了后面讲解PWM功能模块做铺垫。

相关知识

通用定时器

STM32F1系列的通用定时器是一个通过可编程预分频器 (PSC) 驱动的16位自动装载计数器 (CNT),其被用于:测量输入信号的脉冲长度 (输入捕获) 和产生输出波形 (输出比较和脉冲宽度调制),使用PSC和RCC时钟控制预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒之间调整。STM32的每个通用定时器都是独立的,可以单独使用,每个通用定时器功能包括:

  1. 具有一个16位向上、向下或向上向下自动装载计数器(TIMx_CNT)

  2. 具有一个16位可编程预分频器(TIMx_PSC),计数器时钟频率的分频系数范围为1~65535

  3. 拥有4个独立通道,可以被分别配置成如下功能:

    1. 输入捕获

    2. 输出比较

    3. PWM输出(边缘或中间对齐模式)

    4. 单脉冲模式输出

  4. 具有可使用外部信号 (TIMx_ETR) 控制定时器与定时器互联的同步电路

  5. 具有事件中断响应能力,事件源有:

    1. 更新:计数器溢出和计数器初始化(通过软件或内部/外部触发)

    2. 触发事件:计数器启动、停止、初始化或由内部/外部触发计数

    3. 输入捕获

    4. 输出比较

    5. 针对定位的增量(正交)编码器和霍尔传感器电路

    6. 触发输入作为外部时钟或按周期的电流管理

常见问题

问题:定时器和计数器是什么关系?

答:定时器的定时功能本质上是通过计数器 "比着" 时钟脉冲计数来实现的,可以 "比着" 脉冲的上升沿,也可以是下降沿。正是由于每次计数器都是 "对齐" 时钟脉冲计数,所以将 计数的次数乘上时钟脉冲周期就可以得到定时的大小

问题:定时器定时与时钟频率之间的关系是怎样的?

答:定时的大小=计数器计数值*时钟脉冲周期 (时钟频率倒数),由于计数器最大计数值是确定的,所以当时钟频率越小 (时钟周期越大) 时,定时越长,但同时也会使得 "定时分辨率" 降低。比如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个:

  1. 内部时钟(CK_INT)

  2. 外部时钟模式1(外部输入脚TIx)

  3. 外部时钟模式2(外部触发输入ETR)

  4. 内部触发输入(使用另一个定时器给本定时器提供时钟)

具体选择哪个时钟源可以通过 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.cstm32f10x_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的内部外设,所以是没有外部电路连接的。

软件设计

参考