STM32F7普通定时器的使用(定时+中断+PWM)

此篇博客记录的是自己通过CubeMX学习F7系列定时器功能的过程,献给有过标准库开发经验的同学。

基本计时功能

最简单的,定时器嘛,基本的定时器就是定时功能,简单来说就是TIMx->CNT会跟随着输入时钟的脉冲而计数。
初始化定时器的参数,大家都好理解,因为TIM2的输入时钟是108Mhz,这里进行10800分频,输入频率为10K,重装载值设置为20K,每2秒溢出一次。
HAL_TIM_Base_Init的执行过程中,会先调用HAL_TIM_Base_MspInit再进行其他参数的配置,即先开时钟。

TIM_HandleTypeDef TIM2_Handler;
static void MX_TIM2_Init(void)
{
    TIM2_Handler.Instance = TIM2;
    TIM2_Handler.Init.Prescaler = 10800;
    TIM2_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
    TIM2_Handler.Init.Period = 20000;
    TIM2_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

    HAL_TIM_Base_Init(&TIM2_Handler);
	HAL_TIM_Base_Start(&TIM2_Handler);
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
		__HAL_RCC_TIM2_CLK_ENABLE();            //使能TIM3时钟  
}

主函数中每秒打印一次定时器的值:

   while (1)
    {
		printf("cnt:%d\r\n",TIM2->CNT);
        delay_ms(1000);
    }

显示效果如下:
在这里插入图片描述

定时器中断

通过HAL_TIM_Base_Start可以开启基本计时功能,但要实现定时器中断功能,就需要开启相应的标志位,即使用HAL_TIM_Base_Start_IT进行定时的开启;
在配置定时器之前,除了要开启时钟,还需要先设置中断优先级,和使能中断向量;
在定时器中断TIM2_IRQHandler服务函数中调用HAL库提供的定时器中断处理函数HAL_TIM_IRQHandler,解析到的定时器超时中断会自动跳转到HAL_TIM_PeriodElapsedCallback

TIM_HandleTypeDef TIM2_Handler;
static void MX_TIM2_Init(void)
{
    TIM2_Handler.Instance = TIM2;
    TIM2_Handler.Init.Prescaler = 10800;
    TIM2_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
    TIM2_Handler.Init.Period = 20000;
    TIM2_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

    HAL_TIM_Base_Init(&TIM2_Handler);
    //HAL_TIM_Base_Start(&TIM2_Handler);
	HAL_TIM_Base_Start_IT(&TIM2_Handler);
}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
    __HAL_RCC_TIM2_CLK_ENABLE();            //使能TIM2时钟
    HAL_NVIC_SetPriority(TIM2_IRQn, 1, 3);  //设置中断优先级,抢占优先级1,子优先级3
    HAL_NVIC_EnableIRQ(TIM2_IRQn);          //开启ITM2中断
}

void TIM2_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&TIM2_Handler);
}


void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == (&TIM2_Handler))
    {
        printf("enter irq\r\n");
    }
}

显示效果如下:
在这里插入图片描述

PWM输出

硬件PWM输出是不需要使用定时器中断的,但同样需要基本的定时参数配置,初始化也不再是使用HAL_TIM_Base_Init而是使用HAL_TIM_PWM_Init进行初始化了;
配置完定时器为PWM模式,那么相应的输出通道也需要通过HAL_TIM_PWM_ConfigChannel进行配置;
同样的启动也不是HAL_TIM_Base_Start或者HAL_TIM_Base_Start_IT了,而是HAL_TIM_PWM_Start了;

TIM_HandleTypeDef TIM2_Handler;
TIM_OC_InitTypeDef TIM2_CH2Handler;
static void MX_TIM2_Init(void)
{
    TIM2_Handler.Instance = TIM2;
    TIM2_Handler.Init.Prescaler = 10800;
    TIM2_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
    TIM2_Handler.Init.Period = 20000;
    TIM2_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_PWM_Init(&TIM2_Handler);

    TIM2_CH2Handler.OCMode = TIM_OCMODE_PWM1;
    TIM2_CH2Handler.Pulse = 10000;
    TIM2_CH2Handler.OCPolarity = TIM_OCPOLARITY_HIGH;
    TIM2_CH2Handler.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&TIM2_Handler, &TIM2_CH2Handler, TIM_CHANNEL_2) ;

    HAL_TIM_PWM_Start(&TIM2_Handler, TIM_CHANNEL_2); //开启PWM通道2
}

void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
		
	__HAL_RCC_TIM2_CLK_ENABLE();           
    __HAL_RCC_GPIOA_CLK_ENABLE();
    GPIO_InitStruct.Pin = GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}

通过万用表可以发现PA1口的电压在0和3.3V中大概每秒变动一次(疫情期间在家学习,没有示波器真是太惨了……为了确定确实有PWM波形,只能出此下策了T_T),串口打印数据如下:
在这里插入图片描述

PWM + 定时器溢出中断

我们都知道,PWM模式是在比较值处翻转,在溢出的时候再次翻转,而溢出的时候,我们也是可以产生中断的;所以同一个定时器,在这种定时时长和周期相同的时候,是可以即做硬件PWM输出,又做溢出中断的。
这里我们观察一下两种初始化函数:

/**
  * @brief  Initializes the TIM Time base Unit according to the specified
  *         parameters in the TIM_HandleTypeDef and create the associated handle.
  * @param  htim: pointer to a TIM_HandleTypeDef structure that contains
  *                the configuration information for TIM module.
  * @retval HAL status
  */
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));
  
  if(htim->State == HAL_TIM_STATE_RESET)
  {  
    /* Init the low level hardware : GPIO, CLOCK, NVIC */
    HAL_TIM_Base_MspInit(htim);
  }
  
  /* Set the TIM state */
  htim->State= HAL_TIM_STATE_BUSY;
  
  /* Set the Time Base configuration */
  TIM_Base_SetConfig(htim->Instance, &htim->Init); 
  
  /* Initialize the TIM state*/
  htim->State= HAL_TIM_STATE_READY;
  
  return HAL_OK;
}


HAL_StatusTypeDef HAL_TIM_PWM_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));

  if(htim->State == HAL_TIM_STATE_RESET)
  {
    /* Allocate lock resource and initialize it */
    htim->Lock = HAL_UNLOCKED;  
    /* Init the low level hardware : GPIO, CLOCK, NVIC and DMA */
    HAL_TIM_PWM_MspInit(htim);
  }

  /* Set the TIM state */
  htim->State= HAL_TIM_STATE_BUSY;  
  
  /* Init the base time for the PWM */  
  TIM_Base_SetConfig(htim->Instance, &htim->Init); 
   
  /* Initialize the TIM state*/
  htim->State= HAL_TIM_STATE_READY;
  
  return HAL_OK;
}  

两者除了底层的HAL_TIM_Base_MspInitHAL_TIM_PWM_MspInit,其他地方一模一样,所以我们随便调用一个初始化就可以了,但是要注意,我们在HAL_TIM_Base_MspInit里配置了NVIC,在HAL_TIM_PWM_MspInit里配置了GPIO,所以只调用一个的话,必须手动把底层的粘贴到另一个函数里去,如果是想两个都调用一次,这里就需要考虑htim->State这个变量了,因为在调用了一次之后,其值就从HAL_TIM_STATE_RESET变成了HAL_TIM_STATE_READY,后面的一个就得不到执行了。
再来看启动代码:

/**
  * @brief  Starts the TIM Base generation.
  * @param  htim: pointer to a TIM_HandleTypeDef structure that contains
  *                the configuration information for TIM module.
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)
{
  /* Check the parameters */
  assert_param(IS_TIM_INSTANCE(htim->Instance));
  
  /* Set the TIM state */
  htim->State= HAL_TIM_STATE_BUSY;
  
  /* Enable the Peripheral */
  __HAL_TIM_ENABLE(htim);
  
  /* Change the TIM state*/
  htim->State= HAL_TIM_STATE_READY;
  
  /* Return function status */
  return HAL_OK;
}


/**
  * @brief  Starts the TIM Base generation in interrupt mode.
  * @param  htim: pointer to a TIM_HandleTypeDef structure that contains
  *                the configuration information for TIM module.
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
{
  /* Check the parameters */
  assert_param(IS_TIM_INSTANCE(htim->Instance));
  
  /* Enable the TIM Update interrupt */
  __HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
      
  /* Enable the Peripheral */
  __HAL_TIM_ENABLE(htim);
      
  /* Return function status */
  return HAL_OK;
}


/**
  * @brief  Starts the PWM signal generation.
  * @param  htim: pointer to a TIM_HandleTypeDef structure that contains
  *                the configuration information for TIM module.
  * @param  Channel: TIM Channels to be enabled.
  *          This parameter can be one of the following values:
  *            @arg TIM_CHANNEL_1: TIM Channel 1 selected
  *            @arg TIM_CHANNEL_2: TIM Channel 2 selected
  *            @arg TIM_CHANNEL_3: TIM Channel 3 selected
  *            @arg TIM_CHANNEL_4: TIM Channel 4 selected
  * @retval HAL status
  */
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
{
  /* Check the parameters */
  assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));

  /* Enable the Capture compare channel */
  TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
  
  if(IS_TIM_ADVANCED_INSTANCE(htim->Instance) != RESET)  
  {
    /* Enable the main output */
    __HAL_TIM_MOE_ENABLE(htim);
  }
    
  /* Enable the Peripheral */
  __HAL_TIM_ENABLE(htim);
  
  /* Return function status */
  return HAL_OK;
} 

其中的核心代码分别是:

 __HAL_TIM_ENABLE(htim);
  __HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
  __HAL_TIM_ENABLE(htim);
  TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
  __HAL_TIM_ENABLE(htim);

那么我们可以不用hal库提供给我们的启动函数,直接写三句话,就实现了定时器中断+PWM功能了。

  __HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
  TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
  __HAL_TIM_ENABLE(htim);

完整代码如下:

TIM_HandleTypeDef TIM2_Handler;
TIM_OC_InitTypeDef TIM2_CH2Handler;


static void MX_TIM2_Init(void)
{
    __HAL_RCC_TIM2_CLK_ENABLE();
    HAL_NVIC_SetPriority(TIM2_IRQn, 1, 3);
    HAL_NVIC_EnableIRQ(TIM2_IRQn);

    TIM2_Handler.Instance = TIM2;
    TIM2_Handler.Init.Prescaler = 10800;
    TIM2_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
    TIM2_Handler.Init.Period = 20000;
    TIM2_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    
    HAL_TIM_PWM_Init(&TIM2_Handler);

    TIM2_CH2Handler.OCMode = TIM_OCMODE_PWM1;
    TIM2_CH2Handler.Pulse = 10000;
    TIM2_CH2Handler.OCPolarity = TIM_OCPOLARITY_HIGH;
    TIM2_CH2Handler.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&TIM2_Handler, &TIM2_CH2Handler, TIM_CHANNEL_2) ;

    __HAL_TIM_ENABLE_IT(&TIM2_Handler, TIM_IT_UPDATE);
    TIM_CCxChannelCmd(TIM2_Handler.Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
    __HAL_TIM_ENABLE(&TIM2_Handler);
}


void TIM2_IRQHandler(void)
{
    HAL_TIM_IRQHandler(&TIM2_Handler);
}


void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    if (htim == (&TIM2_Handler))
    {
        printf("enter irq\r\n");
    }
}


void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    __HAL_RCC_TIM2_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    GPIO_InitStruct.Pin = GPIO_PIN_1;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值