目录
定时器介绍
定时器的核心就是一个计数器模块,可以进行加一或减一计数。每出现一个计数信号,计数器的值就自动加一或减一。当计数值从0递增到最大值或者从最大值递减到0时,定时器可以向处理器发送中断请求。计数信号的来源可以选择非周期的外部输人信号或者周期性的内部时销中信号,这两种不同的计数信号决定了定时器的两种基本工作模式:计数模式和定时模式。
在衡量一个定时器的基本性能时,常常使用位宽进行描述,比如8位定时器或者16位定时器。这里的位宽代表了定时器内部的计数器器的位数,它决定了定时器的最大计数范围或者最大定时时间。16位计数器的最大计数值为65535(216-1),32 位计数器的最大值为4294967295(232-1)。
在STM32微控制器中,按照功能的不同,可以把常规定时器分为基本定时器、通用定时器和高级定时器三类:
(1)基本定时器
几乎没有任何对外的输人/输出通道,常用作时间基准(时基),实现基本的定时功能。
(2)通用定时器
具备多路独立的捕获/比较通道,可以完成定时/计数、输入捕获、输出比较等功能,还可以连接其他的传感器接口,如编码器和霍尔传感器。
(3)高级定时器
局级定时器的功能最为强大,除了具备通用定时器的功能外,还增加了重复计数器、带死区控制的互补信号输出等功能,可用于电机控制等领域。
无论哪一种定时器,最基本的功能都是定时和计数,在这两个功能的基础上又衍生出其他的功能。在实际的工程应用中,最常用的定时器功能有以下三种:
① 定时/计数功能:用于产生时间基准以以及测量外部脉冲的个数。
② 输出比较功能:包括 PWM输出、电平翻转、单脉冲输出以及强制输出等功能。
③ 输入捕获功能:用于测量输入信号的腐脉冲宽度。
在 STM32 的常规定时器中,通用定时器涵盖了定时/计数、输出比较和输入捕获等常用功能。因此,我们将以通用定时器为例来介绍定时器的应用。
HAL库外设模块设计方法
对于定时器(TIMER)串口(UART)和模数转换器(ADC)等功能较为复杂的外设, HAL 库设计了一个名为外设句柄的数据类型 PPP_HandleTypeDef (PPP表示外设名称)。外设句柄作为外设的一个标识符,一般采用结构体类型实现,结构体的成员变量对应外设的工作参数,主要由以下部分组成:
1.外设实例
表示具体的外设,它是指向对应外设寄存器组起始地址的指针。
2.初始化配置参数
定义外设初始化数据类型,例如串口通信的波特率和数据格式等。
3. I/O 缓冲区
定义数据传输时,接收缓冲区和发送缓冲区的起始地址以及数据传输的个数。I/O缓冲区主要用于数据传输类外设,如UART和ADC等。
4.外设状态
定义外设的工作状态:忙状态(busy) 就绪状态(ready)和错误状态(error)。
5. DMA通道句柄
定义外设采用 DMA 方式进行数据传输时的相关参数,包括了 DMA 控制器的寄存器定义、DMA 通信参数和 DMA 通信状态等
以定时器为例,HAL 库中用于定时器的外设句柄为 TIM_HandleTypeDef,具体定义如程序如下所示。
typedef struct
{
TIM_TypeDef *Instance; // 外设实例
TIM_Base_InitTypeDef Init; // 时基单元初始化数据类型
HAL_TIM_ActiveChannel Channel; // 捕获/比较通道
DMA_HandleTypeDef *hdma[7]; // DMA 通道句柄
HAL_LockTypeDef Lock; // 保护锁
_IO HAL_TIM_StateTypeDef State; // 定时器工作状态
}TIM_HandleTypeDef;
注意:外设实例在编程时有很重要的作用。在外设的中断回调函数中,常常使用外设实例来判断同一类外设下的具体外设。例如,在定时器的更新中断回调函数中,为了判断是哪一个定时器产生的本次更新中断,就常常使用如下的判断:
If( htmi->Instance==TIM10 );
HAL 库根据微控制器和外设的数据传输方式,设计了轮询、中断和DMA 三种编程模型,用于外设的数据传输。
以定时器的定时功能为例,三种编程模型下的接口函数分别是:
1. 轮询方式
HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim);
HAL_StatusTypeDef HAL_TIM_Base_Stop(TIM_HandleTypeDef *htim);
2. 中断方式
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM HandleTypeDef *htim);
HAL_StatusTypeDef HAL_TIM_Base_Stop_IT(TIM_HandleTypeDef *htim);
3. DMA 方式
HAL_StatusTypeDef HAL_TIM_Base_Start_DMA(TIM_HandleTypeDef *htim,
uint32_t *pData, uint16_t Length);
HAL_StatusTypeDef HAL_TIM_Base_Stop_DMA(TIM_HandleTypeDef *htim);
从接口函数的定义,我们可以总结出以下的规律:
●不同编程模型下的接口函数,通过函数后缀加以区别,如轮询方式的接口函数不加后缀,中断方式的接口函数后缀为IT,DMA 方式的接口函数后缀为 DMA。
●接口函数的第一个人口参数都是指向外设句柄的指针(地址)。
外设通用接口函数设计
为了确保 HAL库的接口函数在整个 STM32微控制器的产品线之间能够无缝移植,HAL库提供了通用的接口函数,根据接口函数的功能可以分为以下四类:
1.初始化函数
根据用户的配置参数完成外设的初始化。例如,引脚初始化函数:HAL_GPIO_Init()。
2.I/O 操作函数
这类函数主要用于外设的数据传输,按照轮询、中断和DMA 三种编程模型分类。
3.控制函数
这类函数主要用于动态配置外设参数。例如,在定时器的 PWM 输出功能中,用于配置输出通道的控制函数 HAL_TIM_PWM_ConfigChannel()。
4.状态函数
这类函数主要用于获取外设的运行状态:忙状态、就绪状态和错误状态。例如,定时器状态获取函数 HAL_TIM_Base_GetState()。
这些通用接口函数的设计不仅方便了用户快速掌握接口函数的功能,也确保了程序在STM32微控制器的各个产品线之间能够无缝移植,提高开发效率。
注意:除了通用接口函数以外,为了兼顾 STM32 微控制器产品线的一些特有功能以及同一个产品系列中不同型号芯片的特有功能,HAL库还提供了扩展接口函数,存放在后缀名为ex 的文件中。以定时器为例,通用接口函数位于 stm32f4xx_hal_tim.c 文件中,而扩展接口函数则位于 stm32f4xx_hal_tim_ex.c。
定时/计数功能
时钟源
定时器的两种工作模式:当定时器对内部时钟进行计数时,工作在定时模式下;当定时器对外部脉冲进行计数时,工作在计数模式下。两种工作模式的区别主要在于时钟源的选择。
定时器的时钟源一共有四种选择:
1.内部时钟(CK_INT)
内部时钟CK_INT来自外设总线APB1或APB2提供的定时时钟TIMx_CLK。使用内部时钟作为时钟源时,定时器工作于定时模式,并衍生出输出比较和输入捕获等功能。
2.外部时钟模式1:外部输入引脚TIX(表示引脚编号1-4,下同)
时钟信号来自外部输入引脚Tix,定时器可以在时钟信号的每个上升沿或下降沿计数注意:当使用外部时钟模式1时,时钟信号实际上是由定时器的捕获/比较通道所对应的引脚TIMx_CHn(n表示通道编号1~4,下同)输入。
3.外部时钟模式2:外部触发输入ETR
时钟信号来自外部触发输人引脚TIMX_ETR。定时器可以在时钟信号的每个上升沿或下降沿计数。
4.内部触发输入ITRx
时钟信号来自芯片内部其他定时器的触发输出,可以实现定时器的同步或级联。例如,使用一个定时器作为另一个定时器的预分频器。
通过设置相关的寄存器,选择对应的时钟源后,该时钟源将作为时基单元的预分频时钟 CK_PSC 送入时基单元。时钟源选择的示意图如图所示。
时基单元
时基单元是定时器的核心控制单元,负责时钟源的分频、计数和溢出重载等基本功能。它主要由三个模块组成:预分频模块、计数模块和自动重载模块。时基单元的功能框图如下图。
在上图中预分频时钟CK_PSC是经过时钟源选择后输出的时钟信号。如果使用内部时钟CK_INT作为时钟源,CK_PSC的频率等于定时时钟TIMx_CLK 的频率。如果使用外部时钟或者内部触发信号作为时钟源,CK_PSC的频率由外部引脚输人信号或者内部触发信号的频率决定。CK_CNT是经过预分频模块后,送入计数模块的时钟,这里称为计数时钟。
1.预分频模块
预分频模块由预分频计数器和预分频寄存器 TIMx_PSC 组成:预分频计数器通过计数的方式对预分频时钟 CK_PSC 进行分频,而预分频存器用于存放预分频系数PSC。当时钟源为内部时钟 CK_INT 时,其频率值较高。以STM32F411的通用定时器TIM2为例,定时时钟 TIM2_CLK为100 MHz体现在16位定时器上的效果就是:从0计数到65535,发生上溢的时间只需要0.65 ms左右。如果我们需要更长的定时时间,就需要对预分频时钟CK_PSC进行分频处理。
除此之外,还可以通过设置预分频系数 PSC来获得特定的时钟频率。假设内部时钟为72MHz,如果不分频,72MHz时钟对应的计数时间为 1/72 μs,不利于计算定时时间如果将内部时钟进行72分频,分频后的时钟频率为1MHz,计数时间为1μs,这个计数时间在计算定时时间时,就非常方便。
预分频模块的工作原理如下:定时器启动后,预分频计数器的初值为0。预分频时钟CK_PSC 每输人一个脉冲,预分频计数器的计数值就自动加一。当计数值等于预分频寄存器中存放的预分频系数 PSC时,计数值清零,并开始下一轮计数。
从上图可以看到:当预分频寄存器的内容为3时,预分频计数器从0开始计数,且计数值0会保持一个完整的计数脉冲。因此,实际的计数脉冲个数为 4,即最终的预分频系数为:PSC+1。例如,要对内部时钟进行 72分频,则预分频系数 PSC应设置为71。预分频时钟CK_PSC经过预分频模块后,得到计数时钟CK_CNT,它的计算公式如下:
CK_CNT=CK_PSC/(PSC+1)
2.计数模块
计数模块由核心计数器和计数器寄存器 TIMx_CNT 组成:核心计数器用来对预分频模块输出的计数时钟 CK_CNT 进行二次计数。计数时钟CK_CNT 每输入一个脉冲,核心计数器的计数值就自动加一或减一(根据用户设置的不同计数模式来决定是加一还是减一)。计数器寄存器则用来存放核心计数器运行时的计数值,便于用户读取。
3.自动重载模块
自动重载模块由自动重载寄存器 TIMx_ARR 构成,用来设置计数器的计数终值或计数初值,决定计数脉冲的多少(计数模式)或定时周期(定时模式)的长短。
我们将TIMx_ARR 寄存器的内容记为自动重值ARR。当定时器设置为递增计数模式时,ARR 作为计数器的计数终值,表示记到ARR 时发生滥出。当定时器设置为递计数模式时,ARR 作为计数器的计数初值,表示从ARR开始向下计数。
4.计数模式
国定时器的计数模块支持三种计数模式:递增计数、递减计数和中心对齐计数,并产生溢出事件,作为定时器的更新中断(定时中断)。
(1)递增计数
计数器从0开始向上计数,当计数值等于ARR 时,产生计数器上溢事件,并从0开新一轮的计数周期。
(2)递减计数
计数器从ARR开始向下计数,当计数值等于0时,产生计数器下溢事件,并从AR开始新一轮的计数周期。
(3)中心对齐计数(递增/递减计数)
计数器从0开始向上计数,当计数值等于ARR-1时,产生计数器上溢事件;然后从ARR开始向下计数,当计数值等于1时,产生计数器下溢事件。之后再从0开始新一轮的计数周期。
三种计数模式的计数过程下图所示。其中,红圈表示溢出事件发生时刻。
预装载功能和影子寄存器
自动重载寄存器TIMx_ARR和预分频寄存器 TIMx_PSC 都具有预装载功能,即每类寄存器具有双寄存器机制,分别由各自的影子寄存器和预装载寄存器组成。影子寄存器是真正起作用的寄存器,预装载寄存器为影子寄存器提供缓冲,用户只能操作预装载寄存器。
预装载功能可由软件开启或关闭。关闭预装载功能时,写入预装载寄存器的内容将立即传入影子寄存器,使修改的内容生效。开启预装载功能时,写入预装载寄存器的内容将在更新事件(计数器溢出)发生时,才传入影子寄存器并生效。
预装载功能在多个定时器同时输出信号时比较有用,可以确保多个定时器的输出,信号在同一个时刻变化,实现同步输出。单个定时器使用时,一般不开启预装载功能。
5.定时时间计算公式
当定时器工作于定时模式时,预分频时钟CK_PSC等于定时时钟TIMX_CLK。定时时间由TIMx_CLK 的频率、预分频系数 PSC和自动重载值ARR 三者决定。假设 PSC=1,ARR=36,采用递增计数模式,计数器寄存器的初值为0。定时器的时序下图所示。
从上图可以总结出以下几个特点:
当PSC=1时,预分频时钟CKLPSC进行了二分频得到计数时钟 CK_CNT。
当计数器寄存器的内容等于ARR时,产生计数器上溢事件,并申请中断。由于计数器寄存器从0开始计数,且计数值0会保持 1个完整的计数脉冲。因此,实际的计数脉冲个数为:ARR+1。
预分频时钟 CK_PSC 经过分频后,得到计数时钟 CK_CNT,送入计数器。计数器的计数时间为1/CK_CNT,代入式CK_CNT=CK_PSC/(PSC+1)可以得到计数时间为(PSC+1)/CK_PSC。由于实际的计数值为 ARR+1,将计数值和计数时间代入,可以得到定时时间的计算公式:
T=(PSC+1)X(ARR+1)/CK_PSC
同样的,可以得到定时器溢出频率的计算公式:
f=CK_PSC/[(PSC+1)X(ARR+1)]
注意:使用内部时钟 CK_INT 作为时钟源时,预分频时钟 CK_PSC就等于内部时钟而内部时钟又源自定时器的定时时钟 TIMx_CLK。因此,在定时模式下,定时器的定时时钟TIMx_CLK内部时钟CK_INT 以及预分频时钟CK_PSC三者的频率都是一样的。
寄存器名称 | 作用 |
预分频寄存器TIMx_PSC | 用于存放预分频系数 PSC,将预分频时钟(CK_PSC)进行 1~65 536之间的任意值分频,得到计数时钟(CK_CNT)。 |
计数器寄存器TIMx_CNT | 用于存放核心计数器运行时的当前计数值,便于用户读取。芯片复位后,默认值为0。 |
自动重载寄存器TIMx_ARR | 用于存放自动重载值ARR。当计数器递增计数时,ARR作为计数终值;当计数器递减计数时,ARR作为计数初值。 |
外部脉冲计数
计数的功能在现实生活中比比皆是。例如,测量一天内进出图书馆的人数或者测量单位时间内某生产线的产品数量。人数或者产品数量的测量,可以先利用传感器转化为脉冲信号,再将脉冲信号作为外部信号,输人到定时器进行计数。此时,定时器工作在计数模式下。
外部信号的输入有两个途径:
一个是通过外部触发输入引脚 ETR,经过极性选择、分频和滤波以后,再输人到时基单元进行计数。
另一个是通过外部输人引脚TIx,也就是定时器的捕获/比较通道TIMx_CHn,经过滤波和边沿检测后,再输人到时基单元进行计数。
使用外部输人引脚TIx 送入外部脉冲信号时,定时器将工作于从模式,并且占用了定时器的捕获/比较通道。因此,在实际的工程应用中一般使用ETR引脚输入外部信号。
从 ETR引脚输入的信号,需要经过极性选择、分频以及滤波等阶段后,再通过时钟源选择开关的选择,才能作为预分频时钟 CK_PSC,输入到时基单元进行计数。
外部脉冲信号的处理流程如下图所示。
①:时钟信号输入引脚
时钟信号来自于定时器的特定输入通道 TIMx_ETR,只有 1 个。
②:外部触发极性
来自 ETR 引脚输入的信号可以选择为上升沿或者下降沿有效,具体的由 TIMx_SMCR 的位 ETP 配置。
③:外部触发预分频器
由于 ETRP 的信号的频率不能超过 TIMx_CLK(72M)的 1/4,当触发信号的频率很高的情况下,就必须使用分频器来降频,具体的由 TIMx_SMCR 的位 ETPS[1:0] 配置。
④:滤波器
如果 ETRP 的信号的频率过高或者混杂有高频干扰信号的话,我们就需要使用滤波器对ETRP信号重新采样,来达到降频或者去除高频干扰的目的。具体的由 TIMx_SMCR 的位ETF[3:0]配置,其中的fDTS是由内部时钟CK_INT分频得到,具体的由TIMx_CR1 的位 CKD[1:0] 配置。
⑤:从模式选择
经过滤波器滤波的信号连接到 ETRF 引脚后,触发信号成为外部时钟模式 2 的输入,最终等于CK_PSC,然后驱动计数器 CNT 计数。具体的配置 TIMx_SMCR 的位 ECE 为 1 即可选择外部时钟模式 2。
极性选择用于选择外部脉冲信号的触发极性,可以进行反相和不反相:触发极性不反相表示在脉冲信号的上升沿出现时计数;触发极性反相表示在脉冲信号的下降沿出现时计数。
分频表示多少个有效边沿触发一次计数。当计数脉冲频率较高时,可以通过分频来降低外部脉冲信号的频率,从而扩大计数范围。
滤波的目的是防止外部脉冲信号上的噪声或边沿抖动导致误计数和误触发。
注意:外部脉冲信号的分频和滤波并不是必需的,可以根据信号的频率高低以及信号的干净程度来决定。