STM32-CubeMX学习使用记录5-基础定时器的使用

1.STM32定时器介绍

        1.stm32的定时器分为高级定时器通用定时器基础定时器三种类型

        2.基础定时器:TIM6、TIM7

                

                基础定时器的功能包括:

                        触发DAC的同步电路、在更新事件(计数器溢出)时产生中断/DMA请求 

                2.1 基本定时器是连接在APB1总线上的,CK_INT为内部时钟源,CK_INT = CK_PSC

                2.2 基本定时器的时基单元有:

                计数器寄存器(TIMx_CNT) 、预分频寄存器(TIMx_PSC)、  自动重装载寄存器(TIMx_ARR)且都是16位寄存器。

                工作流程:内部时钟CK_INT 经过控制器(主要控制计数器的复位、使能、计数)后名字改为CK_PSC(频率与CK_INT 相同)通过PSC预分频器进行分频,名字改为CK_CNT到达计数器,为计数器提供脉冲开始计数,当计数值到达自动重装载寄存器的数值后,触发一次更新中断,根据工作模式的不同,停止或者不停循环计数触发更新中断。

                2.3 软件可以读写计数器自动重装载寄存器预分频寄存器,即使计数器运行时也可以操作

 在计时器运行过程中修改寄存器: 

                    1.预分频器

                    预分频可以以系数介于1至65536之间的任意数值对计数器时钟分频,因为TIMx_PSC控制寄存器具有缓冲,可以在运行过程中改变它 的数值,新的预分频数值将在下一个更新事件时起作用.                  

                    2.自动重装载寄存器:

                    在计数器运行过程中修改自动重装载寄存器的值分为两种情况,这两种情况受到TIMx_CR1 控制寄存器中的ARPE位决定,如果ARPE = 0,则会直接修改自动重装载寄存器。如果ARPE = 1 ,则会将修改后的值存入影子寄存器中,等待更新事件触发后影子寄存器的数据更新到自动重装载寄存器,所以自动重装载寄存器是有预加载的。

   

                         3. 计数器寄存器:

                        计数器寄存器也可以修改其数据,不过一般是在计数器停止或者重新计数时修改。

                2.4  定时器的工作模式有两种,一种是触发一次更新中断后就停止计数。另一种是在触发一次更新中断后,触发控制器重新进行新一轮的计数。两种模式的选择由TIMx_CR1寄存器中的OPM位来控制。


2.STM_HAL库基础定时器函数

定时器初始化的函数(cubemx自动生成好了):

  Ctrl + F 搜索 HAL_TIM_Base找到有关基础定时器的函数

 上述函数中的结构体有:

  TIM_HandleTypeDef

  TIM_TypeDef

  TIM_Base_InitTypeDef

  HAL_TIM_StateTypeDef

                                        

 1. TIM_HandleTypeDef :定时器句柄,包含的东西有点多,基础定时器看前两个

2. TIM_TypeDef :   就是有关定时器寄存器的结构体,这样就可以操作寄存器了。

比如:

TIM_TypeDef *TIM6 = (TIM_TypeDef *) TIM6_BASE;  
TIM6->CR1 |= TIM_CR1_CEN; // 启用定时器6

3. TIM_Base_InitTypeDef :对定时器进行参数配置

 4. HAL_TIM_StateTypeDef    :返回定时器的状态

简单看一下HAL_TIM_Base_Init函数,可以直接将这段代码复制到ChatGpt中查看讲解。其他的函数一样的做法。

HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)
{
  /* Check the TIM handle allocation */
  if (htim == NULL)
  {
    return HAL_ERROR;
  }

  /* Check the parameters */
  assert_param(IS_TIM_INSTANCE(htim->Instance));
  assert_param(IS_TIM_COUNTER_MODE(htim->Init.CounterMode));
  assert_param(IS_TIM_CLOCKDIVISION_DIV(htim->Init.ClockDivision));
  assert_param(IS_TIM_PERIOD(htim, htim->Init.Period));
  assert_param(IS_TIM_AUTORELOAD_PRELOAD(htim->Init.AutoReloadPreload));

  if (htim->State == HAL_TIM_STATE_RESET)
  {
    /* Allocate lock resource and initialize it */
    htim->Lock = HAL_UNLOCKED;

#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
    /* Reset interrupt callbacks to legacy weak callbacks */
    TIM_ResetCallback(htim);

    if (htim->Base_MspInitCallback == NULL)
    {
      htim->Base_MspInitCallback = HAL_TIM_Base_MspInit;
    }
    /* Init the low level hardware : GPIO, CLOCK, NVIC */
    htim->Base_MspInitCallback(htim);
#else
    /* Init the low level hardware : GPIO, CLOCK, NVIC */
    HAL_TIM_Base_MspInit(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
  }

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

  /* Set the Time Base configuration */
  TIM_Base_SetConfig(htim->Instance, &htim->Init);

  /* Initialize the DMA burst operation state */
  htim->DMABurstState = HAL_DMA_BURST_STATE_READY;

  /* Initialize the TIM channels state */
  TIM_CHANNEL_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);
  TIM_CHANNEL_N_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);

  /* Initialize the TIM state*/
  htim->State = HAL_TIM_STATE_READY;

  return HAL_OK;
}

可以知道 USE_HAL_TIM_REGISTER_CALLBACKS != 1 所以调用HAL_TIM_Base_MspInit(htim) 函数来配置时钟、NVIC优先级、中断开启,也就是CubeMX生成的函数。然后TIM_Base_SetConfig(htim->Instance, &htim->Init)这个函数会将配置好的参数赋值给对应的寄存器。HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)这个函数会在main中的MX_TIM6_Init()中调用,MX_TIM6_Init()这个函数也是CubeMX自动生成的所以初始化只要知道流程就好了。初始化流程和标准库是一样的。

打开main中的MX_TIM6_Init()函数:这里我是使用的168Mhz的STM32F407VET6单片机,主要是记录学习过程,这段代码是cubemx生成的。

/* TIM6 init function */
void MX_TIM6_Init(void)
{

  /* USER CODE BEGIN TIM6_Init 0 */

  /* USER CODE END TIM6_Init 0 */

  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM6_Init 1 */

  /* USER CODE END TIM6_Init 1 */
  htim6.Instance = TIM6;
  htim6.Init.Prescaler = 83;
  htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim6.Init.Period = 999;
  htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM6_Init 2 */

  /* USER CODE END TIM6_Init 2 */

}

   可以看到在这个函数中调用了  HAL_TIM_Base_Init 函数来进行初始化基础定时器。   

打开生成的HAL_TIM_Base_MspInit(htim)函数

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM6)
  {
  /* USER CODE BEGIN TIM6_MspInit 0 */

  /* USER CODE END TIM6_MspInit 0 */
    /* TIM6 clock enable */
    __HAL_RCC_TIM6_CLK_ENABLE();

    /* TIM6 interrupt Init */
    HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
  /* USER CODE BEGIN TIM6_MspInit 1 */

  /* USER CODE END TIM6_MspInit 1 */
  }
}

可以看到配置了定时器的时钟以及中断优先级和打开中断。 


初始化配置好后,再就是看中断的开启与中断回调流程:      

使用cubemx生成的代码中,定时器中断放到了it . c文件中。打开stm32f4xx_it.c文件。可以看到

/**
  * @brief This function handles TIM6 global interrupt, DAC1 and DAC2 underrun error interrupts.
  */
void TIM6_DAC_IRQHandler(void)
{
  /* USER CODE BEGIN TIM6_DAC_IRQn 0 */

  /* USER CODE END TIM6_DAC_IRQn 0 */
  HAL_TIM_IRQHandler(&htim6);
  /* USER CODE BEGIN TIM6_DAC_IRQn 1 */

  /* USER CODE END TIM6_DAC_IRQn 1 */
}

打开HAL_TIM_IRQHandler(&htim6):

void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)
{
  uint32_t itsource = htim->Instance->DIER;
  uint32_t itflag = htim->Instance->SR;

  /* Capture compare 1 event */
  if ((itflag & (TIM_FLAG_CC1)) == (TIM_FLAG_CC1))
  {
    if ((itsource & (TIM_IT_CC1)) == (TIM_IT_CC1))
    {
      {
        __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_CC1);
        htim->Channel = HAL_TIM_ACTIVE_CHANNEL_1;

        /* Input capture event */
        if ((htim->Instance->CCMR1 & TIM_CCMR1_CC1S) != 0x00U)
        {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
          htim->IC_CaptureCallback(htim);
#else
          HAL_TIM_IC_CaptureCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
        }
        /* Output compare event */
        else
        {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
          htim->OC_DelayElapsedCallback(htim);
          htim->PWM_PulseFinishedCallback(htim);
#else
          HAL_TIM_OC_DelayElapsedCallback(htim);
          HAL_TIM_PWM_PulseFinishedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
        }
        htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
      }
    }
  }
  /* Capture compare 2 event */
  if ((itflag & (TIM_FLAG_CC2)) == (TIM_FLAG_CC2))
  {
    if ((itsource & (TIM_IT_CC2)) == (TIM_IT_CC2))
    {
      __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_CC2);
      htim->Channel = HAL_TIM_ACTIVE_CHANNEL_2;
      /* Input capture event */
      if ((htim->Instance->CCMR1 & TIM_CCMR1_CC2S) != 0x00U)
      {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
        htim->IC_CaptureCallback(htim);
#else
        HAL_TIM_IC_CaptureCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
      }
      /* Output compare event */
      else
      {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
        htim->OC_DelayElapsedCallback(htim);
        htim->PWM_PulseFinishedCallback(htim);
#else
        HAL_TIM_OC_DelayElapsedCallback(htim);
        HAL_TIM_PWM_PulseFinishedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
      }
      htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
    }
  }
  /* Capture compare 3 event */
  if ((itflag & (TIM_FLAG_CC3)) == (TIM_FLAG_CC3))
  {
    if ((itsource & (TIM_IT_CC3)) == (TIM_IT_CC3))
    {
      __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_CC3);
      htim->Channel = HAL_TIM_ACTIVE_CHANNEL_3;
      /* Input capture event */
      if ((htim->Instance->CCMR2 & TIM_CCMR2_CC3S) != 0x00U)
      {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
        htim->IC_CaptureCallback(htim);
#else
        HAL_TIM_IC_CaptureCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
      }
      /* Output compare event */
      else
      {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
        htim->OC_DelayElapsedCallback(htim);
        htim->PWM_PulseFinishedCallback(htim);
#else
        HAL_TIM_OC_DelayElapsedCallback(htim);
        HAL_TIM_PWM_PulseFinishedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
      }
      htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
    }
  }
  /* Capture compare 4 event */
  if ((itflag & (TIM_FLAG_CC4)) == (TIM_FLAG_CC4))
  {
    if ((itsource & (TIM_IT_CC4)) == (TIM_IT_CC4))
    {
      __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_CC4);
      htim->Channel = HAL_TIM_ACTIVE_CHANNEL_4;
      /* Input capture event */
      if ((htim->Instance->CCMR2 & TIM_CCMR2_CC4S) != 0x00U)
      {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
        htim->IC_CaptureCallback(htim);
#else
        HAL_TIM_IC_CaptureCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
      }
      /* Output compare event */
      else
      {
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
        htim->OC_DelayElapsedCallback(htim);
        htim->PWM_PulseFinishedCallback(htim);
#else
        HAL_TIM_OC_DelayElapsedCallback(htim);
        HAL_TIM_PWM_PulseFinishedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
      }
      htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
    }
  }
  /* TIM Update event */
  if ((itflag & (TIM_FLAG_UPDATE)) == (TIM_FLAG_UPDATE))
  {
    if ((itsource & (TIM_IT_UPDATE)) == (TIM_IT_UPDATE))
    {
      __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
      htim->PeriodElapsedCallback(htim);
#else
      HAL_TIM_PeriodElapsedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
    }
  }
  /* TIM Break input event */
  if ((itflag & (TIM_FLAG_BREAK)) == (TIM_FLAG_BREAK))
  {
    if ((itsource & (TIM_IT_BREAK)) == (TIM_IT_BREAK))
    {
      __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_BREAK);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
      htim->BreakCallback(htim);
#else
      HAL_TIMEx_BreakCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
    }
  }
  /* TIM Trigger detection event */
  if ((itflag & (TIM_FLAG_TRIGGER)) == (TIM_FLAG_TRIGGER))
  {
    if ((itsource & (TIM_IT_TRIGGER)) == (TIM_IT_TRIGGER))
    {
      __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_TRIGGER);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
      htim->TriggerCallback(htim);
#else
      HAL_TIM_TriggerCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
    }
  }
  /* TIM commutation event */
  if ((itflag & (TIM_FLAG_COM)) == (TIM_FLAG_COM))
  {
    if ((itsource & (TIM_IT_COM)) == (TIM_IT_COM))
    {
      __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_COM);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
      htim->CommutationCallback(htim);
#else
      HAL_TIMEx_CommutCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
    }
  }
}

看到代码很多,但是基础定时器一般只用中断更新的功能,所以找到更新相关的函数:

 /* TIM Update event */
  if ((itflag & (TIM_FLAG_UPDATE)) == (TIM_FLAG_UPDATE))
  {
    if ((itsource & (TIM_IT_UPDATE)) == (TIM_IT_UPDATE))
    {
      __HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
      htim->PeriodElapsedCallback(htim);
#else
      HAL_TIM_PeriodElapsedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
    }
  }

和外部中断一样都是先检查再清楚标志位,然后进入一个函数HAL_TIM_PeriodElapsedCallback(htim);

打开此函数:

__weak void HAL_TIM_PeriodElapsedCallback(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_PeriodElapsedCallback could be implemented in the user file
   */
}

发现是一个弱函数,所以和外部中断一样,进行复写。

我选择在tim.c中进行复写:

/* USER CODE BEGIN 1 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  static uint16_t num;
  if (htim->Instance == TIM6)
  {
    num++;
    if (num == 500)
    {
      HAL_GPIO_TogglePin(LED_USER_GPIO_Port, LED_USER_Pin); // 1s翻转一次电平
      num = 0;
    }
  }
}
/* USER CODE END 1 */

在main中加入中断启动定时器的函数:

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_TIM6_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim6);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

这样就可以实现1ms进入一次更新中断,500ms翻转一次电平。

一般基础定时器用来作为中断使用。

除此之外,如果想要改变正在运行时定时器的配置,则可以使用宏定义函数来修改:

在stm32f4xx_hal_tim.h中有关于定时器的宏定义函数:

1. __HAL_TIM_ENABLE(__HANDLE__)  启动定时器

2.__HAL_TIM_DISABLE(__HANDLE__) 关闭定时器

3.__HAL_TIM_ENABLE_IT(__HANDLE__, __INTERRUPT__) 使能定时器中断

4.__HAL_TIM_DISABLE_IT(__HANDLE__, __INTERRUPT__) 失能定时器中断

5. __HAL_TIM_ENABLE_DMA(__HANDLE__, __DMA__) 使能更新触发DMA

6.__HAL_TIM_DISABLE_DMA(__HANDLE__, __DMA__) 失能更新触发DMA

7. __HAL_TIM_GET_FLAG(__HANDLE__, __FLAG__)获取当前定时器的中断状态

8. __HAL_TIM_SET_PRESCALER(__HANDLE__, __PRESC__) 设置定时器的预分频值

9. __HAL_TIM_SET_COUNTER(__HANDLE__, __COUNTER__) 设置计数器的计数值 

10. __HAL_TIM_SET_COUNTER(__HANDLE__, __COUNTER__) 获取当前计数器的计数值

11. __HAL_TIM_SET_AUTORELOAD(__HANDLE__, __AUTORELOAD__)  设置重装载寄存器的值

12. __HAL_TIM_GET_AUTORELOAD(__HANDLE__) 获取重装载寄存器的值

除了以上宏定义函数外还有很多。 


3.STM_HAL库 基础定时器cubemx 的配置

    

总结:

在配置基础定时器后,主要做的就两件事:1.在main.c中开启定时器(不要忘)

                                                                      2.复写回调函数。 

  • 7
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值