1+X传感网中级备考:基本定时器的配置

1.TIM6基本定时器功能简介

2.HAL库函数开发定时器配置的步骤

3.图形化配置中定时器相关函数调用关系

1.TIM6基本定时器简介

基本定时器TIM6包含一个16位自动装载计数器,由各自的可编程预分频器驱动。它们可以作为通用定时器提供时间基准,特别地可以为数模转换器(DAC)提供时钟。实际上,它们在芯片内部直接连接到DAC并通过触发输出直接驱动DAC。
1.1主要特性
● 16位自动重装载累加计数器
● 16位可编程(可实时修改)预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频
● 触发DAC的同步电路
● 在更新事件(计数器溢出)时产生中断/DMA请求

图1 基本定时器原理框图

1.2 定时器内部原理简介

如图1所示, 内部时钟 CK_INT信号,是从 APB1 倍频的来的,其频率一般都比较高;如图2所示,由于APB1 的时钟分频数设置为 2,外接定时器的时钟是72MHz,所以图中的CK_INT=72MHz;体现在16位的定时器上的效果就是从0计数到65535上溢只需要0.9毫秒。
如图1所示,如果我们需要更长时间的定时间隔,需要预分频器对时钟进行分频处理,以降低定时器时钟(CK_CNT)的频率。
除此之外,也可以通过配置预分频器,来获取想要的定时器时钟频率。如果我们想获取一个精确的1ms中断,如果不分频,72MHz的时钟对应每周期1/72us,十分不利于计算。这时候使用预分频器将其72分频后为1MHz,每周期1us,1000个计时周期即为1ms,这样既便于计算,定时也更加精确。(预分频器的作用详解https://zhuanlan.zhihu.com/p/82590576
PSC预分频器输出CK_INT信号,作为CNT计数器的输入信号,于此同时,自动重装载寄存器将数值存入到CNT计数器中

图2:CK_INT来源于APB1 Timer Clock

如图1所示,经过定时器控制器(TIM6_CR1),输出CK_PSC=72MHz,经过预分频器PSC,如果想定时1秒;如图公式1所示,


公式1:定时1秒

如果我们想定时其他时间,单位是秒,参考公式2:


公式2:定时器实现X秒

在CubeMX图形化配置中,CKCNT的数值等同于 Prescaler的配置值;TIMxARR的数值等于Counter Preiod 的数值

图3.图形化配置对应的定时器参数

2.HAL库函数开发定时器配置的步骤:

1)TIM6 时钟使能
2)初始化定时器参数,设置自动重装值,分频系数,计数方式等:
3)使能定时器更新中断,使能定时器;
4)TIM6中断优先级设置;
5)编写中断服务函数。

在图形化配置的HAL库中,我们依然可以按照这个思路找到对应函数功能:
1)TIM6 时钟使能
HAL 中定时器使能是通过宏定义标识符来实现对相关寄存器操作的__HAL_RCC_TIM6_CLK_ENABLE()被函数HAL_TIM_Base_MspInit()调用;

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM6)
  {
    /* TIM6 clock enable */
    __HAL_RCC_TIM6_CLK_ENABLE();
    /* TIM6 interrupt Init */
    HAL_NVIC_SetPriority(TIM6_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(TIM6_IRQn);

  }
}
图4:HAL_TIM_Base_MspInit的定义

2)初始化定时器参数,设置自动重装值,分频系数,计数方式等。在TIM_Base_InitTypeDef结构体成员变量中,对相关参数都进行了申明;

typedef struct
{
  uint32_t Prescaler;    \\预分频系数
  uint32_t CounterMode;  \\计数模式
  uint32_t Period;  \\自动装载值ARR
  uint32_t ClockDivision; \\时钟分频因子
  uint32_t RepetitionCounter;
 uint32_t AutoReloadPreload; 
} TIM_Base_InitTypeDef;

参数 Prescaler 是用来设置分频系数的;
参数CounterMode 是用来设置计数方式, 常 用 的 是 向 上 计 数 模 式 TIM_CounterMode_Up 和 向 下 计 数 模 式TIM_CounterMode_Down;
参数 Period 是设置自动重载计数周期值;就是TIMx_ARR的数值
参数 ClockDivision 是用来设置时钟分频因子,也就是定时器时钟频率 CK_INT 与数字滤波器所使用的采样时钟之间的分频比。
参数 RepetitionCounter 用来设置重复计数器寄存器的值,用在高级定时器中。
在图形化配置工具中,如图3所示,我们可以找到相关参数的配置;从这里我们可以得出,任何图形化配置的参数都会最终在函数代码中找到对应关系;


图5:CubeMX定时器配置的参数

3)使能定时器更新中断,使能定时器
HAL 库 中 , 使 能 定 时 器 更 新 中 断 和 使 能 定 时 器 两 个 操 作 可 以 在 函 数HAL_TIM_Base_Start_IT()中一次完成的,该函数声明如下:
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
该 函 数只 有 一 个 入 口 参 数 。 调 用 该 定 时 器 之 后 , 会 首 先 调 用
__HAL_TIM_ENABLE_IT 宏定义使能更新中断,然后调用宏定义__HAL_TIM_ENABLE 使能相应的定时器

/**
  * @brief  Starts the TIM Base generation in interrupt mode.
  * @param  htim TIM Base handle
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
{
  uint32_t tmpsmcr;

  /* Check the parameters */
  assert_param(IS_TIM_INSTANCE(htim->Instance));

  /* Check the TIM state */
  if (htim->State != HAL_TIM_STATE_READY)
  {
    return HAL_ERROR;
  }

  /* Set the TIM state */
  htim->State = HAL_TIM_STATE_BUSY;

  /* Enable the TIM Update interrupt */
  __HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);

  /* Enable the Peripheral, except in trigger mode where enable is automatically done with trigger */
  if (IS_TIM_SLAVE_INSTANCE(htim->Instance))
  {
    tmpsmcr = htim->Instance->SMCR & TIM_SMCR_SMS;
    if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr))
    {
      __HAL_TIM_ENABLE(htim);
    }
  }
  else
  {
    __HAL_TIM_ENABLE(htim);
  }

  /* Return function status */
  return HAL_OK;
}

4)TIM6中断优先级设置
HAL_NVIC_SetPriority(TIM6_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(TIM6_IRQn);

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM6)
  {
    /* TIM6 clock enable */
    __HAL_RCC_TIM6_CLK_ENABLE();
    /* TIM6 interrupt Init */
    HAL_NVIC_SetPriority(TIM6_IRQn, 2, 0);
    HAL_NVIC_EnableIRQ(TIM6_IRQn);
}


5)编写中断服务函数
首先,定时器6的中断服务函数为:

void TIM6_IRQHandler(void)
{
  HAL_TIM_IRQHandler(&htim6);
}
图6:在stm32f1xxit.c 文档中函数定义

一般情况下我们是在中断服务函数内部编写中断控制逻辑。但是 HAL 库为我们定义了 新的定时器中断共用处理函数 HAL_TIM_IRQHandler,在每个定时器的中断服务函数内部,我们会调用该函数。该函数声明如下:

void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim);
 /* 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);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
      htim->PeriodElapsedCallback(htim);
#else
      HAL_TIM_PeriodElapsedCallback(htim);
图7:在tim.c文档中对函数的定义

而函数 HAL_TIM_IRQHandler 内部,会对相应的中断标志位进行详细判断,判断确定中断来源后,会自动清掉该中断标志位,同时调用不同类型中断的回调函数。所以我们的中断控制逻辑只用编写在中断回调函数中,并且中断回调函数中不需要清中断标志位。
比如定时器更新中断回调函数为:

 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);

我们只需要在main.c中重写该函数即可。

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  {
      
      if (TIM6 == htim ->Instance)
      {
          num=num>>1;
          if (num==0)
              num=0x80;
          HAL_GPIO_WritePin(GPIOE,0xff,GPIO_PIN_SET);
           HAL_GPIO_WritePin(GPIOE,num,GPIO_PIN_RESET);
      }
  }

在基于图形化配置生成的KEIL5工程中,我们从自动生成的main.c函数中,可以找到MX_TIM6_Init()函数,关于定时器的 时钟使能,初始参数配置,就在这个函数中完成;如图8所示;


图8:函数调用关系

如图9所示:
MX_TIM6_Init()调用了HAL_TIM_Base_Init()
HAL_TIM_Base_Init()调用了HAL_TIM_Base_MspInit()
通过这三个函数,就实现了定时器时钟使能,定时器参数配置,定时器中断优先级配置;也就是上文的第1步,第2步和第4步;

图9:开启定时器时钟和设定定时器优先级

如图10所示,在mian.c中补充使能定时器中断,就实现了上文的第3步,函数使能定时器更新中断,使能定时器;


图10:使能定时器实现定时器更新中断

如图11所示,在main.c中补充回调函数,就实现了第5步;


图11:回调函数完善中断服务程序

总结:依据定时器配置的步骤,我们找到了相关函数的定义和功能;在图形化配置中,这些功能函数都已经自动生成且帮我们封装好了,对于初学者,需要结合步骤,将每一个函数的功能进行理解,才能够有效运用;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。