欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第二部分 基础篇 - 第4章 定时器

程序员文章站 2022-03-13 23:40:56
...

4.1定时器的工作原理

4.1.1基本定时器

TIM6和TIM7定时器的主要功能包括:
● 16位自动重装载累加计数器
● 16位可编程(可实时修改)预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频
● 触发DAC的同步电路
● 在更新事件(计数器溢出)时产生中断/DMA请求
总的说来,基本定时器 TIM6 和 TIM7 只具备最基本的定时功能,就是累加的时钟脉冲数超过预定值时,能触发中断或触发 DMA 请求。由于在芯片内部与 DAC 外设相连,可通过触发输出驱动 DAC,也可以作为其他通用定时器的时钟基准。
这两个基本定时器使用的时钟源都是 TIMxCLK,时钟源经过 PSC 预分频器输入至脉冲计数器 TIMx_CNT,基本定时器只能工作在向上计数模式,在重载寄存器 TIMx_ARR 中保存的是定时器的溢出值。
工作时,脉冲计数器 TIMx_CNT 由时钟触发进行计数,当 TIMx_CNT 的计数值 X 等于重载寄存器 TIMx_ARR 中保存的数值 N 时,产生溢出事件,可触发中断或 DMA 请求。然后 TIMx_CNT 的值重新被置为 0,重新向上计数。

第二部分 基础篇 - 第4章 定时器

图4-1基本定时器框图

4.1.2通用定时器

通用TIMx (TIM2、 TIM3、 TIM4和TIM5)定时器功能包括:
● 16位向上、向下、向上/向下自动装载计数器;
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意数值;
● 4个独立通道:输入捕获,输出比较,PWM生成(边缘或中间对齐模式),单脉冲模式输出;
● 使用外部信号控制定时器和定时器互连的同步电路;
● 如下事件发生时产生中断/DMA:更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发), 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数),输入捕获,输出比较;
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
相比之下,通用定时器 TIM2 ~ TIM5 就比基本定时器复杂得多了。除了基本的定时,它主要用在测量输入脉冲的频率、脉冲宽与输出 PWM 脉冲的场合,还具有编码器的接口。

第二部分 基础篇 - 第4章 定时器

图4-2通用定时器框图

1. 捕获 / 比较寄存器
通用定时器的基本计时功能与基本定时器的工作方式是一样的,同样把时钟源经过预分频器输出到脉冲计数器 TIMx_CNT 累加,溢出时就产生中断或 DMA 请求。而通用定时器比基本定时器多出的强大功能,就是因为通用定时器多出了一种寄存器——捕获 / 比较寄存器 TIMx_CCR(capture/compare register),它在输入时被用于捕获(存储) 输入脉冲在电平发生翻转时脉冲计数器 TIMx_CNT 的当前计数值,从而实现脉冲的频率测量 ;在输出时被用来存储一个脉冲数值,把这个数值用于与脉冲计数器TIMx_CNT 的当前计数值进行比较,根据比较结果进行不同的电平输出。
2. PWM 输出过程分析
通用定时器可以利用 GPIO 引脚进行脉冲输出,在配置为比较输出、PWM 输出功能时,捕获 /比较寄存器 TIMx_CCR 被用作比较功能,下面把它简称为比较寄存器。这里直接举例说明定时器的 PWM 输出工作过程 :若配置脉冲计数器 TIMx_CNT 为向上计数,而重载寄存器 TIMx_ARR 被配置为 N,即 TIMx_CNT 的当前计数值数值 X 在TIMxCLK 时钟源的驱动下不断累加,当 TIMx_CNT 的数值 X 大于 N 时,会重置TIMx_CNT 数值为 0 并重新计数。
而在 TIMx_CNT 计数的同时,TIMx_CNT 的计数值 X 会与比较寄存器 TIMx_CCR 预先存储的数值 A 进行比较。当脉冲计数器 TIMx_CNT 的数值 X 小于比较寄存器TIMx_CCR 的值 A 时,输出高电平(或低电平);相反地,当脉冲计数器的数值 X 大于或等于比较寄存器的值 A 时,输出低电平(或高电平)。如此循环,得到的输出脉冲周期就为重载寄存器 TIMx_ARR 存储的数值(N+1)乘以触发脉冲的时钟周期,其脉冲宽度则为比较寄存器 TIMx_CCR 的值 A 乘以触发脉冲的时钟周期,即输出 PWM 的占空比为 A/(N+1) 。见图 4-3 PWM 输出模式,图中为重载寄存器 TIMx_ARR 被配置为 N=8,向上计数 ;比较寄存器 TIMx_CCR 的值被设置为 4、8、大于 8、等于 0 时的输出时序图。图中OCxREF 即为 GPIO 引脚的输出时序、CCxIF 为触发中断的时序。
第二部分 基础篇 - 第4章 定时器

图 4- 3 PWM 输出模式

3. PWM 输入过程分析
而当定时器被配置为输入功能时,可以用于检测输入到 GPIO 引脚的信号(频率检测、输入 PWM 检测),此时捕获 / 比较寄存器 TIMx_CCR 被用作捕获功能,下面把它简称为捕获寄存器。见图 4- 4,为 PWM 输入时的脉冲宽检测时序图。
第二部分 基础篇 - 第4章 定时器

图4-4PWM 输入检测

按照图 4- 4 所示时序图来分析 PWM 输入脉冲宽检测的工作过程 :要测量的 PWM 脉冲通过 GPIO 引脚输入到定时器的脉冲检测通道,其时序为图中的 TI1。把脉冲计数器TIMx_CNT 配置为向上计数,重载寄存器 TIMx_ARR 的 N 值配置为足够大。
在输入脉冲 TI1 的上升沿到达时,触发 IC1 和 IC2 输入捕获中断,这时把脉冲计数器TIMx_CNT 的计数值复位为 0,于是 TIMx_CNT 的计数值 X 在 TIMxCLK 的驱动下从 0 开始不断累加,直到 TI1 出现下降沿,触发 IC2 捕获事件,此时捕获寄存器 TIMx_CCR2 把脉冲计数器 TIMx_CNT 的当前值 2 存储起来,而 TIMx_CNT 继续累加,直到 TI1 出现第二 个 上 升 沿 , 触 发 了 IC1 捕 获 事 件 , 此 时 TIMx_CNT 的 当 前 计 数 值 4 被 保 存 到TIMx_CCR1。
很明显 TIMx_CCR1(加 1)的值乘以 TIMxCLK 的周期,即为待检测的 PWM 输入脉冲周期,TIMx_CCR2(加 1)的值乘以 TIMxCLK 的周期,就是待检测的 PWM 输入脉冲的高电平时间,有了这两个数值就可以计算出 PWM 脉冲的频率、占空比了。可以看出,正因为捕获 / 比较寄存器的存在,才使得通用定时器变得如此强大。
4. 定时器的时钟源
从时钟源方面来说,通用定时器比基本定时器多了一个选择,它可以使用外部脉冲作为定时器的时钟源。使用外部时钟源时,要使用寄存器进行触发边沿、滤波器带宽的配置。如果选择内部时钟源的话则与基本定时器一样,也为 TIMxCLK。但要注意的是,所有定时器(包括基本、通用和高级)使用内部时钟时,定时器的时钟源都被称为TIMxCLK,但 TIMxCLK 的时钟来源并不是完全一样的,见图 4-5。
第二部分 基础篇 - 第4章 定时器

图4-5时钟树(TIMxCLK 部分)

TIM2 ~ 7 也 就 是 基 本 定 时 器 和 通 用定时器,TIMxCLK 的时钟来源是 APB1 预分频器的输出。当 APB1 的分频系数为 1 时,则 TIM2 ~ 7 的 TIMxCLK 直接等于该APB1 预分频器的输出,而 APB1 的分频系数 不 为 1 时,TIM2 ~ 7 的 TIMxCLK 则 为APB1 预分频器输出的 2 倍。
如在常见的配置中,AHB=72 MHz,而 APB1 预分频器的分频系数被配置为 2,则PCLK1 刚好达到最大值 36 MHz,而此时 APB1 的分频系数不为 1,则 TIM2 ~ TIM7的时钟 TIMxCLK = (AHB/2)×2 = 72 MHz。
而对于 TIM1 和 TIM8 这两个高级定时器,TIMxCLK 的时钟来源则是 APB2 预分频器的输出,同样它也根据分频系数分为两种情况。
常见的配置中 AHB=72 MHz,APB2 预分频器的分频系数被配置为1, 此时PCLK2刚好达到最大值72 MHz,而 TIMxCLK 则直接等于APB2分频器的输出,即TIM1和 TIM8 的时钟 TIMxCLK=AHB=72 MHz。
虽然这种配置下最终 TIMxCLK 的时钟频率相等,但必须清楚实质上它们的时钟来源是有区别的。还要强调的是 :TIMxCLK 是定时器内部的时钟源,但在时钟输出到脉冲计数器 TIMx_CNT 前,还经过一个预分频器 PSC,最终用于驱动脉冲计数器 TIMx_CNT 的时钟频率根据预分频器 PSC 的配置而定。

4.1.3高级定时器

TIM1和TIM8定时器的功能包括:
● 16位向上、向下、向上/下自动装载计数器
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意数值
● 多达4个独立通道:输入捕获,输出比较,PWM生成(边缘或中间对齐模式),单脉冲模式输出;
● 死区时间可编程的互补输出;
● 使用外部信号控制定时器和定时器互联的同步电路;
● 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器;
● 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态;
● 如下事件发生时产生中断/DMA:更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发),触发事件(计数器启动、停止、初始化或者由内部/外部触发计数),输入捕获,输出比较,刹车信号输入;
● 支持针对定位的增量(正交)编码器和霍尔传感器电路;
● 触发输入作为外部时钟或者按周期的电流管理。

第二部分 基础篇 - 第4章 定时器

图4-6高级控制定时器框图

总的来说,TIM1 和 TIM8 是两个高级定时器,它们具有基本、通用定时器的所有功能,还具有三相 6 步电机的接口、刹车功能(break function)及用于 PWM 驱动电路的死区时间控制等,使得它非常适合于电机的控制。如图 4-6 所示为高级定时器结构。
相比于通用定时器,主要多出了 BRK、DTG 两个结构,因而具有了死区时间的控制功能。首先,死区时间是什么呢?在 H 桥、三相桥的 PWM 驱动电路中,上下两个桥臂的PWM 驱动信号是互补的,即上下桥臂轮流导通,但实际上为了防止出现上下两个臂同时导通(会造成短路),在上下两臂切换时留一小段时间,上下臂都施加关断信号,这个上下臂都关断的时间称为死区时间。
STM32 的高级定时器可以配置出输出互补的 PWM 信号,并且在这个 PWM 信号中加入死区时间,为电机的控制提供了极大的便利。见图 4- 7。图中的 OCxREF 为参考信号(可理解为原信号), OCx 和 OCxN 为定时器通过 GPIO 引脚输出的 PWM 互补信号。
第二部分 基础篇 - 第4章 定时器

图4-7插入死区时间

若不加入死区时间,当 OCxREF 出现下降沿,OCx 同时输出下降沿,OCxN 则同时输出相反的上升沿,即这三个信号的跳变是同时的。
加入死区时间后,当 OCxREF 出现下降沿,OCx 同时输出下降沿,但 OCxN 则过了一小段延迟再输出上升沿,OCxREF 出现上升沿后,OCx 要经过一段延时再输出上升沿。假如 OCx、 OCxN 分别控制上、下桥臂,有了延迟后,就不容易出现上、下桥臂同时导通的情况。这个延迟时间与 PWM 信号驱动的电子器件特性相关,从事工控领域的读者对此应该比较熟悉。
在保证不出现短路的情况下,死区时间越短越好。见图 4-8、图4-9。
第二部分 基础篇 - 第4章 定时器

图 4- 8 死区时间太长,OCxN 输出不正常

第二部分 基础篇 - 第4章 定时器

图4- 9 死区时间太长,OCx 输出不正常

4.2 定时器的寄存器分析

为了深入了解 STM32 的通用寄存器, 下面我们先介绍一下与我们这章的实验密切相关的几个通用定时器的寄存器。首先是控制寄存器 1( TIMx_CR1),该寄存器的各位描述如图 4-10 。

第二部分 基础篇 - 第4章 定时器第二部分 基础篇 - 第4章 定时器

图4-10控制寄存器 1(TIMx_CR1)

首先我们来看看 TIMx_CR1 的最低位,也就是计数器使能位,该位必须置 1,才能让定时器开始计数。 从第 4 位 DIR 可以看出默认的计数方式是向上计数, 同时也可以向下计数,第 5,6位是设置计数对齐方式的。 从第 8 和第 9 位可以看出,我们还可以设置定时器的时钟分频因子为 1,2,4。 接下来介绍第二个与我们这 章密切相关的寄存器: DMA/中断使能寄存器( TIMx_DIER)。该寄存器是一个 16 位的寄存器,其各位描述如图 4-11 所示:
第二部分 基础篇 - 第4章 定时器

图4-11 TIMx_ DIER 寄存器各位描述

这里我们同样仅关心它的第 0 位,该位是更新中断允许位,本章用到的是定时器的更新中断,所以该位要设置为 1,来允许由于更新事件所产生的中断。接下来我们看第三个与我们这章有关的寄存器:预分频寄存器( TIMx_PSC)。该寄存器用设置对时钟进行分频,然后提供给计数器,作为计数器的时钟。该寄存器的各位描述如图4-12。
第二部分 基础篇 - 第4章 定时器

图4-12TIMx_ PSC 寄存器各位描述

这里,定时器的时钟来源有 4 个:
1) 内部时钟( CK_INT)
2) 外部时钟模式 1:外部输入脚( TIx)
3) 外部时钟模式 2:外部触发输入( ETR)
4) 内部触发输入( ITRx):使用 A 定时器作为 B 定时器的预分频器( A 为 B 提供时钟)。
这些时钟,具体选择哪个可以通过 TIMx_SMCR 寄存器的相关位来设置。这里的 CK_INT时钟是从 APB1 倍频的来的,除非 APB1 的时钟分频数设置为 1,否则通用定时器 TIMx 的时钟是 APB1 时钟的 2 倍,当 APB1 的时钟不分频的时候,通用定时器 TIMx 的时钟就等于 APB1的时钟。这里还要注意的就是高级定时器的时钟不是来自 APB1,而是来自 APB2 的。
这里顺带介绍一下 TIMx_CNT 寄存器,该寄存器是定时器的计数器,该寄存器存储了当前定时器的计数值。
接着我们介绍自动重装载寄存器( TIMx_ARR),该寄存器在物理上实际对应着 2 个寄存器。一个是程序员可以直接操作的,另外一个是程序员看不到的,这个看不到的寄存器在《 STM32参考手册》里面被叫做影子寄存器。事实上真正起作用的是影子寄存器。 根据 TIMx_CR1 寄存器中 APRE 位的设置: APRE=0 时,预装载寄存器的内容可以随时传送到影子寄存器,此时 2者是连通的;而 APRE=1 时,在每一次更新事件( UEV)时,才把预装在寄存器的内容传送到影子寄存器。自动重装载寄存器的各位描述如图4-13。
第二部分 基础篇 - 第4章 定时器

图4-13 TIMx_ ARR 寄存器各位描述

最后,我们要介绍的寄存器是:状态寄存器( TIMx_SR)。该寄存器用来标记当前与定时器相关的各种事件/中断是否发生。该寄存器的各位描述如图 4-14。
第二部分 基础篇 - 第4章 定时器

图4-14 TIMx_ SR 寄存器各位描述

关于这些位的详细描述,请参考《 STM32 参考手册》。只要对以上几个寄存器进行简单的设置,我们就可以使用通用定时器了,并且可以产生中断。这一章,我们将使用定时器产生中断,然后在中断服务函数里面翻转 DS1 上的电平,来指示定时器中断的产生。
4.3 定时器的具体代码分析
接下来我们以通用定时器 TIM2 为实例,来说明要经过哪些步骤,才能达到这个要求,并产生中断。 这里我们就对每个步骤通过库函数的实现方式来描述。首先要提到的是,定时器相关的库函数主要集中在固件库文件 stm32f10x_tim.h 和 stm32f10x_tim.c 文件中。
1) TIM2 时钟使能。
TIM2 是挂载在 APB1 之下,所以我们通过 APB1 总线下的使能使能函数来使能 TIM2。调用的函数是:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, 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。
第三个参数是设置自动重载计数周期值,这在前面也已经讲解过。
第四个参数是用来设置时钟分频因子。
针对 TIM2初始化范例代码格式:

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

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

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

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

TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE );

4) TIM2 中断优先级设置。
在定时器中断使能之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器, 设置中断优先级。之前多次讲解到用 NVIC_Init 函数实现中断优先级的设置,这里就不重复讲解。
5) 允许 TIM2 工作,也就是使能 TIM2。
光配置好定时器还不行,没有开启定时器,照样不能用。我们在配置完后要开启定时器,通过 TIM2_CR1 的 CEN 位来设置。在固件库里面使能定时器的函数是通过 TIM_Cmd 函数来实现的:

void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)

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

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

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

ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t)

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

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

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

void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT)

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

TIM_ClearITPendingBit(TIM2, TIM_IT_Update );

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

本章参考代码

IAR版本完整代码
KEIL版本完整代码

相关标签: 定时器 STM32