简明易懂解析STM32定时器的工作原理

前言

定时器是嵌入式开发中极其重要的一员,它可以分为软件定时器和硬件定时器。

软件定时器很不精准,通过循环语句粗略的去计算延时的时间,对时序要求较高的场景是完全不适用的;

硬件定时器在stm32中种类也是比较多的,基本定时器、通用定时器、高级定时器、低功耗定时器等,它们的基本特性相差无几,更多的是应用场景的不一样,例如高级定时器特性会更好一点(带可编程死区的互补输出、输出通道数更多等),意味着它的应用场景更高级,低功耗定时器功耗更低,可以在除待机模式以外的所有电源模式下保持运行,即使没有内部时钟源也可以运行。根据自己的需求去选择合适的定时器,这里仅对通用定时器进行讲解。

1. 通用定时器特性

16 /32位递增、递减和递增/递减自动重载计数器(递增、递减只是计数方式而已,往ARR寄存器中写入计数值,为空时计数器不工作);

② 16 位可编程预分频器,用于对计数器时钟频率进行分频(可在运行时修改),分频系数介于 1 到 65535 之间

③ 独立通道可用于:输入捕获/PWM 生成/输出比较/单脉冲模式输出等

④ 发生如下事件时生成中断/DMA 请求:(有些定时器不具备DMA请求

        更新:计数器上溢/下溢、计数器初始化;

        触发事件(计数器启动、停止、初始化或通过内部/外部触发计数);

        输入捕获;

        输出比较;

⑤ 触发输入作为外部时钟(可以作为其它定时器的外部时钟源);

2. 定时器框图

查看手册可以知道各个定时器挂载在哪条总线下(APB1/APB2)。

需要注意的是,定时器挂载在APB1总线上时,当 APB1的时钟不分频的时候,通用定时器 TIMx的时钟就等于 APB1的时钟,否则定时器 TIMx的时钟是 APB1时钟的 2倍;挂载在APB2的定时器时钟就等同于APB2时钟。

图1

 通过TIMx_SMCR寄存器的相关位来设置具体选择哪个时钟源。通常不需要去配置,直接使用内部时钟源即可。 

位 2:0 SMS:从模式选择 (Slave mode selection)
选择外部信号时,触发信号 (TRGI) 的有效边沿与外部输入上所选的极性相关(请参见输入控
制寄存器和控制寄存器说明)。
0000:禁止从模式——如果 CEN =“1”,预分频器时钟直接由内部时钟提供。
0001:编码器模式 1——计数器根据 TI1FP2 电平在 TI2FP1 边沿递增/递减计数。
0010:编码器模式 2——计数器根据 TI2FP1 电平在 TI1FP2 边沿递增/递减计数。 

0111:外部时钟模式 1——由所选触发信号 (TRGI) 的上升沿提供计数器时钟……

图2

3. 计数模式

向上、向下计数也叫递增、递减计数;

递增计数:从0计数到设定的ARR值,然后产生溢出事件;

递减计数:与递增计数相反;

中心对齐计数:也就是结合递增和递减两种模式,需要注意的是它的溢出值。

4. 时基单元

4.1  计数器寄存器 (TIMx_CNT)

存储当前计数器计数值,由上图2可知,计数器时钟由PSC预分频器输出的 CK_CNT提供,仅当 TIMx_CR1 寄存器中的计数器启动位(CEN) 置 1 时,才会启动计数器。

位 0 CEN:计数器使能 (Counter enable)
0:禁止计数器
1:使能计数器

图3

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

 

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

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);//触发中断

这些都是弱函数,需要我们去重写,就是说把自己的中断处理逻辑写到这里。

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))
    {
        //中断控制逻辑编写
    }
}
物联沃分享整理
物联沃-IOTWORD物联网 » 简明易懂解析STM32定时器的工作原理

发表评论