标准库3.5实现:
《嵌入式-STM32开发指南》第二部分 基础篇 - 第4章 定时器
4.1定时器的工作原理
STM32有三类定时器,基本定时器就是单纯的定时计数器,通用定时器多了四个通道,相对应的增加了功能,高级定时器具有基本,通用定时器的所有的功能,并且添加了其他功能。定时器的对比特性如下表所示。
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.1.2通用定时器
通用TIMx (TIM2、 TIM3、 TIM4和TIM5)定时器功能包括:
● 16位向上、向下、向上/向下自动装载计数器;
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意数值;
● 4个独立通道:输入捕获,输出比较,PWM生成(边缘或中间对齐模式),单脉冲模式输出;
● 使用外部信号控制定时器和定时器互连的同步电路;
● 如下事件发生时产生中断/DMA:更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发), 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数),输入捕获,输出比较;
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
相比之下,通用定时器 TIM2 ~ TIM5 就比基本定时器复杂得多了。除了基本的定时,它主要用在测量输入脉冲的频率、脉冲宽与输出 PWM 脉冲的场合,还具有编码器的接口。
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) 。见图3 PWM 输出模式,图中为重载寄存器 TIMx_ARR 被配置为 N=8,向上计数 ;比较寄存器 TIMx_CCR 的值被设置为 4、8、大于 8、等于 0 时的输出时序图。图中OCxREF 即为 GPIO 引脚的输出时序、CCxIF 为触发中断的时序。
3.PWM 输入过程分析
而当定时器被配置为输入功能时,可以用于检测输入到 GPIO 引脚的信号(频率检测、输入 PWM 检测),此时捕获 / 比较寄存器 TIMx_CCR 被用作捕获功能,下面把它简称为捕获寄存器。见图4,为 PWM 输入时的脉冲宽检测时序图。
按照图 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 的时钟来源并不是完全一样的,见图5。
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:更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发),触发事件(计数器启动、停止、初始化或者由内部/外部触发计数),输入捕获,输出比较,刹车信号输入;
● 支持针对定位的增量(正交)编码器和霍尔传感器电路;
● 触发输入作为外部时钟或者按周期的电流管理。
总的来说,TIM1 和 TIM8 是两个高级定时器,它们具有基本、通用定时器的所有功能,还具有三相 6 步电机的接口、刹车功能(break function)及用于 PWM 驱动电路的死区时间控制等,使得它非常适合于电机的控制。如图6 所示为高级定时器结构。
相比于通用定时器,主要多出了 BRK、DTG 两个结构,因而具有了死区时间的控制功能。首先,死区时间是什么呢?在 H 桥、三相桥的 PWM 驱动电路中,上下两个桥臂的PWM 驱动信号是互补的,即上下桥臂轮流导通,但实际上为了防止出现上下两个臂同时导通(会造成短路),在上下两臂切换时留一小段时间,上下臂都施加关断信号,这个上下臂都关断的时间称为死区时间。
STM32 的高级定时器可以配置出输出互补的 PWM 信号,并且在这个 PWM 信号中加入死区时间,为电机的控制提供了极大的便利。见图7。图中的 OCxREF 为参考信号(可理解为原信号), OCx 和 OCxN 为定时器通过 GPIO 引脚输出的 PWM 互补信号。
若不加入死区时间,当 OCxREF 出现下降沿,OCx 同时输出下降沿,OCxN 则同时输出相反的上升沿,即这三个信号的跳变是同时的。
加入死区时间后,当 OCxREF 出现下降沿,OCx 同时输出下降沿,但 OCxN 则过了一小段延迟再输出上升沿,OCxREF 出现上升沿后,OCx 要经过一段延时再输出上升沿。假如 OCx、 OCxN 分别控制上、下桥臂,有了延迟后,就不容易出现上、下桥臂同时导通的情况。这个延迟时间与 PWM 信号驱动的电子器件特性相关,从事工控领域的读者对此应该比较熟悉。
在保证不出现短路的情况下,死区时间越短越好。见图8、9。
4.2定时器计数模式
定时器可以向上计数、向下计数、向上向下双向计数模式。
- 向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件。
- 向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件。
- 中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。
简单地理解三种计数模式,可以通过下面的图形:
计数器时钟可由下列时钟源提供:
内部时钟(TIMx_CLK)
外部时钟模式1:外部捕捉比较引脚(TIx)
外部时钟模式2:外部引脚输入(TIMx_ETR) 仅适用TIM2,3,4
内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。
在前文对三类定时器做了讲解,下面列出定时器功能引脚分布。
4.2 STM32Cube生成工程
我门在流水灯程序的基础上进行修改即可,不必每次都新建工程。本文介绍在STM32CubeMX进行定时器的配置,产生固定时间中断的方法。这里以TIM2为例。
1.GPIO配置
2.设置RCC
设置高速外部时钟HSE,选择外部时钟源。
3.时钟配置
笔者的板子使用的外部晶振为8MHz,选择外部时钟HSE 8MHz ,PLL锁相环9倍频后为72MHz,系统时钟来源选择为PLL,设置APB1分频器为 /2,这时候定时器的时钟频率为72Mhz。本文笔者使用的定时器是TIM2,TIM2挂在APB1上,不同的定时器挂在不同总线上的。
4.Times配置
选择TIM,使能TIM2,指定时钟源。
【注】TIM2的时钟源有两个选项
选项1 :Internal Clock 内部时钟
选项2 : ETR2 外部触发输入(ETR)(仅适用TIM2,3,4)
定时器参数配置如下:
Prtscaler (定时器分频系数) : 71
Counter Mode(计数模式) :Up(向上计数模式)
Counter Period(自动重装载值) : 999
CKD(时钟分频因子) : No Division 不分频
选项: 可以选择二分频和四分频
auto-reload-preload(自动重装载) : Enable 使能
TRGO Parameters 触发输出 (TRGO) :不使能
TRGO:定时器的触发信号输出 在定时器的定时时间到达的时候输出一个信号(如:定时器更新产生TRGO信号来触发ADC的同步转换,)
TIM2配置选项的前两个为定时器主从模式配置,很少用到,我们用不到,所以全部关闭。
使能定时器中断:
定时器溢出时间:
这里我们 ,
好了,配置就完成了,生成工程就行了。
4.3 定时器的具体代码分析
我们先看看主函数,其代码如下:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM2_Init();
/* USER CODE BEGIN 2 */
/*使能定时2*/
HAL_TIM_Base_Start_IT(&htim2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
if(timer_count == 1000)
{
timer_count = 0;
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
}
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
在主循环前面,需要对TIM2进行初始化配置:
HAL_TIM_Base_Start_IT(&htim2);
在主循环内,通过全局变量timer_count来计算延时时间,TIM2的中断时间是1ms,我们计算1000表示1s,再让LED反转。
在主函数后面还需添加以下函数:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
timer_count++;
}
4.3.1定时器外设结构体
前文提到了初始化定时器TIM2,这里介绍定时器的句柄。
typedef struct
{
TIM_TypeDef *Instance; /*!< Register base address */
TIM_Base_InitTypeDef Init; /*!< TIM Time Base required parameters */
HAL_TIM_ActiveChannel Channel; /*!< Active channel */
DMA_HandleTypeDef *hdma[7]; /*!< DMA Handlers array
This array is accessed by a @ref DMA_Handle_index */
HAL_LockTypeDef Lock; /*!< Locking object */
__IO HAL_TIM_StateTypeDef State; /*!< TIM operation state */
…/*还有其他未使用的函数*/
} TIM_HandleTypeDef;
- Instance:TIM寄存器地址。
- Init:基本定时器的结伴参数,后文会详细介绍。
- Channel:定时器的通道选择。
- hdma:定时器DMA相关的结构体,在后面会有专门讲DMA,先不管他。
- Lock/ State:锁定机制和定时器操作的状态。
接下来说说配置定时器基本参数的结构体。
typedef struct
{
uint32_t Prescaler; /*!< Specifies the prescaler value used to divide the TIM clock.
This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF */
uint32_t CounterMode; /*!< Specifies the counter mode.
This parameter can be a value of @ref TIM_Counter_Mode */
uint32_t Period; /*!< Specifies the period value to be loaded into the active
Auto-Reload Register at the next update event.
This parameter can be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */
uint32_t ClockDivision; /*!< Specifies the clock division.
This parameter can be a value of @ref TIM_ClockDivision */
uint32_t RepetitionCounter; /*!< Specifies the repetition counter value. Each time the RCR downcounter
reaches zero, an update event is generated and counting restarts
from the RCR value (N).
This means in PWM mode that (N+1) corresponds to:
- the number of PWM periods in edge-aligned mode
- the number of half PWM period in center-aligned mode
GP timers: this parameter must be a number between Min_Data = 0x00 and Max_Data = 0xFF.
Advanced timers: this parameter must be a number between Min_Data = 0x0000 and Max_Data = 0xFFFF. */
uint32_t AutoReloadPreload; /*!< Specifies the auto-reload preload.
This parameter can be a value of @ref TIM_AutoReloadPreload */
} TIM_Base_InitTypeDef;
- Prescaler:定时器预分频系数,时钟源进过该分频器到大定时器时钟,可以设置的范围为0~65535,我们设置的是71,这也是在STM32Cube设置的分频系数,通过分频后得到的时钟是1MHz。
- CounterMode:定时器计数方式,选择的是向上技术,值得注意,基本定时器只能向上计数。
- Period:定时器周期,本文设置的是999,可设置范围是0~65535,因此产生中断的频率为:1MHz/1000=1KHz,即1ms的定时周期。
- ClockDivision:时钟分频,主要是设置定时器时钟频率与数字滤波器采样时中频率比,基本定时器没有这个功能。
- RepetitionCounter:重复计数器,属于高级控制寄存器,利用它可控制PWM,后面的文章会具体讲解。
- AutoReloadPreload:自动重装装载。这里使能就行。
4.3.2定时器编程流程分析
1.初始化GPIO
MX_GPIO_Init();
这个就不用多讲了吧。
2.初始化定时器
MX_TIM2_Init();
函数原型如下:
static void MX_TIM2_Init(void)
{
/* USER CODE BEGIN TIM2_Init 0 */
/* USER CODE END TIM2_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM2_Init 1 */
/* USER CODE END TIM2_Init 1 */
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 999;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM2_Init 2 */
/* USER CODE END TIM2_Init 2 */
}
该函数主要初始化定时器参数,比如分频系数等,没啥好讲的。
3.使能定时器
HAL_TIM_Base_Start_IT(&htim2);
4.使用定时器
前面讲了定时器的初始化,使能,那么定时是如何使用的呢?它又是如何进行中断计时的呢?其实和滴答定时器一样,在stm32f1xx_it.c中定义了TIM2_IRQHandler中断服务函数,中断服务函数组中调用了我们自己写的回调函数。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
timer_count++;
}
当然,我们也可以将主函数的循环体中代码放在上面的代码中。值得注意的是,HAL库提供了一个HAL_TIM_PeriodElapsedCallback函数。其原型如下:
__weak void HAL_TIM_PeriodElapsedHalfCpltCallback(TIM_HandleTypeDef *htim)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(htim);
/* NOTE : This function should not be modified, when the callback is needed,
the HAL_TIM_PeriodElapsedHalfCpltCallback could be implemented in the user file
*/
}
当用户没有写回调函数时,这个函数就会调用,当用户写了回调函数,这个函数就不会调用,也可在这里加代码,但是不推荐,最好自己重新写一个函数。
4.4 实现现象
将编译好的程序下载到看板子中,可以看到LED不停闪烁。
代码获取方式
1.关注公众号[嵌入式实验楼]
2.在公众号回复关键词[STM32F1]获取资料
欢迎访问我的网站:
BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
CSDN博客
简书