前言
定时器是嵌入式开发中极其重要的一员,它可以分为软件定时器和硬件定时器。
软件定时器很不精准,通过循环语句粗略的去计算延时的时间,对时序要求较高的场景是完全不适用的;
硬件定时器在stm32中种类也是比较多的,基本定时器、通用定时器、高级定时器、低功耗定时器等,它们的基本特性相差无几,更多的是应用场景的不一样,例如高级定时器特性会更好一点(带可编程死区的互补输出、输出通道数更多等),意味着它的应用场景更高级,低功耗定时器功耗更低,可以在除待机模式以外的所有电源模式下保持运行,即使没有内部时钟源也可以运行。根据自己的需求去选择合适的定时器,这里仅对通用定时器进行讲解。
1. 通用定时器特性
① 16 /32位递增、递减和递增/递减自动重载计数器(递增、递减只是计数方式而已,往ARR寄存器中写入计数值,为空时计数器不工作);
② 16 位可编程预分频器,用于对计数器时钟频率进行分频(可在运行时修改),分频系数介于 1 到 65535 之间;
③ 独立通道可用于:输入捕获/PWM 生成/输出比较/单脉冲模式输出等;
④ 发生如下事件时生成中断/DMA 请求:(有些定时器不具备DMA请求)
更新:计数器上溢/下溢、计数器初始化;
触发事件(计数器启动、停止、初始化或通过内部/外部触发计数);
输入捕获;
输出比较;
⑤ 触发输入作为外部时钟(可以作为其它定时器的外部时钟源);
2. 定时器框图
查看手册可以知道各个定时器挂载在哪条总线下(APB1/APB2)。
需要注意的是,定时器挂载在APB1总线上时,当 APB1的时钟不分频的时候,通用定时器 TIMx的时钟就等于 APB1的时钟,否则定时器 TIMx的时钟是 APB1时钟的 2倍;挂载在APB2的定时器时钟就等同于APB2时钟。
通过TIMx_SMCR寄存器的相关位来设置具体选择哪个时钟源。通常不需要去配置,直接使用内部时钟源即可。
位 2:0 SMS:从模式选择 (Slave mode selection)
选择外部信号时,触发信号 (TRGI) 的有效边沿与外部输入上所选的极性相关(请参见输入控
制寄存器和控制寄存器说明)。
0000:禁止从模式——如果 CEN =“1”,预分频器时钟直接由内部时钟提供。
0001:编码器模式 1——计数器根据 TI1FP2 电平在 TI2FP1 边沿递增/递减计数。
0010:编码器模式 2——计数器根据 TI2FP1 电平在 TI1FP2 边沿递增/递减计数。
0111:外部时钟模式 1——由所选触发信号 (TRGI) 的上升沿提供计数器时钟......
3. 计数模式
向上、向下计数也叫递增、递减计数;
递增计数:从0计数到设定的ARR值,然后产生溢出事件;
递减计数:与递增计数相反;
中心对齐计数:也就是结合递增和递减两种模式,需要注意的是它的溢出值。
4. 时基单元
4.1 计数器寄存器 (TIMx_CNT)
存储当前计数器计数值,由上图2可知,计数器时钟由PSC预分频器输出的 CK_CNT提供,仅当 TIMx_CR1 寄存器中的计数器启动位(CEN) 置 1 时,才会启动计数器。
位 0 CEN:计数器使能 (Counter enable)
0:禁止计数器
1:使能计数器
4.2 自动重载寄存器 (TIMx_ARR)
预装载寄存器的内容既可以直接传送到影子寄存器,也可以在每次发生更新事件 (UEV)时传送到影子寄存器,这取决于 TIMx_CR1 寄存器中的自动重载预装载使能位 (ARPE),当需要动态去修改ARR寄存器值的时候就设置ARPE为1,具有缓冲功能,可以减少误差。
当定时器触发事件的时候,且当TIMx_CR1 寄存器中的 UDIS 位为0 时,将发送更新事件。
位 7- ARPE:自动重载预装载使能 (Auto-reload preload enable)
0:TIMx_ARR 寄存器不进行缓冲
1:TIMx_ARR 寄存器进行缓冲
位 1 UDIS:更新禁止 (Update disable)(此位由软件置 1 和清零,用以使能/禁止 UEV 事件生成)
0:使能 UEV。更新 (UEV) 事件可通过以下事件之一产生:
– 计数器上溢/下溢
– 将 UG 位置 1
– 通过从模式控制器生成的更新事件
1:禁止 UEV。
4.3 预分频器寄存器 (TIMx_PSC)
预分频器可对计数器时钟频率进行分频,分频系数介于 1 和 65536 之间。该预分频器基于16 位/32 位寄存器(TIMx_PSC 寄存器)所控制的 16 位计数器。由于该控制寄存器具有缓冲功能,因此预分频器可实现实时更改。而新的预分频比将在下一更新事件发生时被采用。
举个例子:假如定时器挂载在APB2(108Mhz)总线下,定时器的预分频寄存器设置为107(真实的预分频数为PSC寄存器的值加1),则定时器间隔1us就计一个数。
溢出时间计算公式为:T = (ARR+1)*(PSC+1) / Ft时钟源频率。
4.4 时序图分析
下图5时序图为预分频器分频由1变为2:
使能CEN开始计数,从计数器寄存器数值可以看出当前为递增式计数;
分频为1,且未产生溢出事件时,每间隔一个时钟周期计数器寄存器就计一次数;
在发生溢出事件前,动态更改预分频器分频为2,在更新事件后,预分频器的缓冲区中将重新装载预装载值,每间隔2个时钟周期(预分频器计数器计两个数)计数器计时器计一次数。
5. 定时器示例
示例使用定时器3:挂载在APB1下,其定时器时钟频率为108MHz;
目的是将它配置为间隔1000ms触发溢出事件。
5.1 重点寄存器
首先,需要重点关注如下几个寄存器:
控制寄存器1 (TIMx_CR1):第0位置 1 表示使能计数器;
DMA/中断使能寄存器( TIMx_DIER):第0位 置 1,来允许由于更新事件所产生的中断;
预分频寄存器( TIMx_PSC):设置对时钟进行分频,然后提供给计数器,作为计数器的时钟;
自动重装载寄存器(TIMx_ARR):设置计数值,达到后触发溢出事件。
5.2 软件配置流程
定时器结构体如下:
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;
预分频设为10800分频,计数10000次,即为溢出时间为1000ms;
配置好结构体变量后,调用hal库函数HAL_TIM_Base_Init()进行初始化;
HAL_TIM_Base_Start_IT()函数开启定时器并开启更新中断,对相应寄存器操作,该函数已经封装好的了,直接调用就很省事。
TIM_HandleTypeDef TIME3_Handler; //定时器句柄
void TIM3_Init()
{
TIME3_Handler.Instance=TIM3; //通用定时器3
TIME3_Handler.Init.Prescaler=10800-1; //分频
TIME3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP; //向上计数器
TIME3_Handler.Init.Period=10000-1; //自动装载值
TIME3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;//时钟分频因子
HAL_TIM_Base_Init(&TIME3_Handler);
HAL_TIM_Base_Start_IT(&TIME3_Handler); //使能定时器3和定时器3更新中断:TIM_IT_UPDATE
}
系统运行中会产生很多中断,所以需要去设置定时器中断的优先级;
HAL_NVIC_SetPriority(TIM3_IRQn,2,1); //设置中断优先级,抢占优先级2,子优先级1
HAL_NVIC_EnableIRQ(TIM3_IRQn); //开启ITM3中断
定时器产生中断,意味着就需要中断处理函数,中断处理函数中编写控制逻辑,但别去做耗时的操作,也尽量别添加打印输出,不然会影响实时性。
定时器3的中断服务函数TIM3_IRQHandler(),在startup_xxx.s的汇编文件里可以看到,知道就好了,不需要格外关注;
调用hal库函数HAL_TIM_IRQHandler()处理更新中断等,
常用的回调函数:
__weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);//更新中断
__weak void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim);//输出比较
__weak void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);//输入捕获
__weak void HAL_TIM_TriggerCallback(TIM_HandleTypeDef *htim);//触发中断文章来源:https://www.toymoban.com/news/detail-758383.html这些都是弱函数,需要我们去重写,就是说把自己的中断处理逻辑写到这里。文章来源地址https://www.toymoban.com/news/detail-758383.html
void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)
{
/* Capture compare 1 event */
if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC1) != RESET)
{
if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC1) !=RESET)
{
{
__HAL_TIM_CLEAR_IT(htim, TIM_IT_CC1);
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_1;
/* Input capture event */
if((htim->Instance->CCMR1 & TIM_CCMR1_CC1S) != 0x00)
{
HAL_TIM_IC_CaptureCallback(htim);
}
/* Output compare event */
else
{
HAL_TIM_OC_DelayElapsedCallback(htim);
HAL_TIM_PWM_PulseFinishedCallback(htim);
}
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
}
}
}
/* Capture compare 2 event */
if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC2) != RESET)
{
if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC2) !=RESET)
{
__HAL_TIM_CLEAR_IT(htim, TIM_IT_CC2);
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_2;
/* Input capture event */
if((htim->Instance->CCMR1 & TIM_CCMR1_CC2S) != 0x00)
{
HAL_TIM_IC_CaptureCallback(htim);
}
/* Output compare event */
else
{
HAL_TIM_OC_DelayElapsedCallback(htim);
HAL_TIM_PWM_PulseFinishedCallback(htim);
}
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
}
}
/* Capture compare 3 event */
if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC3) != RESET)
{
if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC3) !=RESET)
{
__HAL_TIM_CLEAR_IT(htim, TIM_IT_CC3);
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_3;
/* Input capture event */
if((htim->Instance->CCMR2 & TIM_CCMR2_CC3S) != 0x00)
{
HAL_TIM_IC_CaptureCallback(htim);
}
/* Output compare event */
else
{
HAL_TIM_OC_DelayElapsedCallback(htim);
HAL_TIM_PWM_PulseFinishedCallback(htim);
}
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
}
}
/* Capture compare 4 event */
if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_CC4) != RESET)
{
if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_CC4) !=RESET)
{
__HAL_TIM_CLEAR_IT(htim, TIM_IT_CC4);
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_4;
/* Input capture event */
if((htim->Instance->CCMR2 & TIM_CCMR2_CC4S) != 0x00)
{
HAL_TIM_IC_CaptureCallback(htim);
}
/* Output compare event */
else
{
HAL_TIM_OC_DelayElapsedCallback(htim);
HAL_TIM_PWM_PulseFinishedCallback(htim);
}
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
}
}
/* TIM Update event */
if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET)
{
if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_UPDATE) !=RESET)
{
__HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE);
HAL_TIM_PeriodElapsedCallback(htim);
}
}
/* TIM Break input event */
if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_BREAK) != RESET)
{
if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_BREAK) !=RESET)
{
__HAL_TIM_CLEAR_IT(htim, TIM_IT_BREAK);
HAL_TIMEx_BreakCallback(htim);
}
}
/* TIM Break input event */
if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_BREAK2) != RESET)
{
if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_BREAK) !=RESET)
{
__HAL_TIM_CLEAR_IT(htim, TIM_IT_BREAK);
HAL_TIMEx_BreakCallback(htim);
}
}
/* TIM Trigger detection event */
if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_TRIGGER) != RESET)
{
if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_TRIGGER) !=RESET)
{
__HAL_TIM_CLEAR_IT(htim, TIM_IT_TRIGGER);
HAL_TIM_TriggerCallback(htim);
}
}
/* TIM commutation event */
if(__HAL_TIM_GET_FLAG(htim, TIM_FLAG_COM) != RESET)
{
if(__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_COM) !=RESET)
{
__HAL_TIM_CLEAR_IT(htim, TIM_FLAG_COM);
HAL_TIMEx_CommutationCallback(htim);
}
}
}
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
HAL_TIM_IRQHandler(&TIME3_Handler);
}
//定时器3中断更新函数调用
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim==(&TIM3E_Handler))
{
//中断控制逻辑编写
}
}
到了这里,关于通俗易懂讲明白定时器(stm32)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!