STM32 CubeMX TIMx编码器模式无法进入中断踩坑记录(顺带解析一下HAL_TIM_Encoder_Start及其_IT函数)

目录


问题的开始

        今天在用STM32F103VET6实现定时器TIM2编码器功能的时候,预采用中断查询方式对于编码器计数值进行查询,发现程序不能进入中断。工程使用CubeMX生成初始化代码。Main函数中是这样写的:

  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
    printf("ready!");
    HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_3); 

  /* USER CODE END 2 */

        在此,已经确认开启了TIM2中断的NVIC通道,但函数执行没法进入TIM2中断。原因是应该使用:

HAL_TIM_Encoder_Start_IT(&htim2,TIM_CHANNEL_ALL);

        函数来作为定时器编码器功能开启函数。一般来说,HAL库函数中,函数末尾加入_IT的函数才会触发中断,没有加_IT的函数往往没有中断功能,而常常是使用轮询方式使用。像类似的函数组还有:HAL_UART_Transmit();HAL_UART_Transmit_IT();等等。

        至于为什么HAL_TIM_Encoder_Start_IT函数可以触发中断而HAL_TIM_Encoder_Start函数没有触发中断的功能,还是要看一下HAL库中关于这两个函数的定义有什么不同。其实这两个函数前半部分区别不大,有区别的主要是以下部分(请注意红色部分):

        首先是IT函数:

 ………………

  /* Enable the encoder interface channels */
  /* Enable the capture compare Interrupts 1 and/or 2 */

  switch (Channel)
  {
    case TIM_CHANNEL_1:
    {
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
      __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1);
      break;
    }

    case TIM_CHANNEL_2:
    {
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
      __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC2);
      break;
    }

    default :
    {
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
      __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1);
      __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC2);

      break;
    }
  }

  /* Enable the Peripheral */
  __HAL_TIM_ENABLE(htim);

  /* Return function status */
  return HAL_OK;

        之后是轮询函数:

  /* Enable the encoder interface channels */
  switch (Channel)
  {
    case TIM_CHANNEL_1:
    {
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
      break;
    }

    case TIM_CHANNEL_2:
    {
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
      break;
    }

    default :
    {
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
      break;
    }
  }
  /* Enable the Peripheral */
  __HAL_TIM_ENABLE(htim);

  /* Return function status */
  return HAL_OK;

        很明显_IT函数比轮询函数多了个开启中断的功能。如果在调用轮询之后将中断打开,也可以得到正确的结果,所以以下上下两段代码的功能其实是一样的。

    //  开启TIM2编码器模式(注意,这里用的是中断方式)

    HAL_TIM_Encoder_Start_IT(&htim2,TIM_CHANNEL_ALL);


    //  开启TIM2编码器模式(注意,这里用的是轮询方式)
    HAL_TIM_Encoder_Start(&htim2,TIM_CHANNEL_ALL);
     __HAL_TIM_ENABLE_IT(&htim2,TIM_IT_CC1);
     __HAL_TIM_ENABLE_IT(&htim2,TIM_IT_CC2);

        额……既然都看到这里了,不如把这个函数的底层看了吧……

HAL_TIM_Encoder_Start及其_IT函数解析

        这里以HAL_TIM_Encoder_Start_IT为例(其实前面都差不多看懂一个就能看懂另一个)

HAL_StatusTypeDef HAL_TIM_Encoder_Start_IT(TIM_HandleTypeDef *htim, uint32_t Channel)
{
  HAL_TIM_ChannelStateTypeDef channel_1_state = TIM_CHANNEL_STATE_GET(htim, TIM_CHANNEL_1);//  拿到htim定时器的Channel1通道状态
  HAL_TIM_ChannelStateTypeDef channel_2_state = TIM_CHANNEL_STATE_GET(htim, TIM_CHANNEL_2);//  拿到htim定时器的Channel2通道状态
  HAL_TIM_ChannelStateTypeDef complementary_channel_1_state = TIM_CHANNEL_N_STATE_GET(htim, TIM_CHANNEL_1);//  拿到htim定时器的Channel1互补通道状态
  HAL_TIM_ChannelStateTypeDef complementary_channel_2_state = TIM_CHANNEL_N_STATE_GET(htim, TIM_CHANNEL_2);//  拿到htim定时器的Channel2互补通道状态
…………

        这里出现了TIM_CHANNEL_STATE_GET()和TIM_CHANNEL_N_STATE_GET(),这两个什么意思呢,跳转一下看到:

#define TIM_CHANNEL_STATE_GET(__HANDLE__, __CHANNEL__)\
  (((__CHANNEL__) == TIM_CHANNEL_1) ? (__HANDLE__)->ChannelState[0] :\
   ((__CHANNEL__) == TIM_CHANNEL_2) ? (__HANDLE__)->ChannelState[1] :\
   ((__CHANNEL__) == TIM_CHANNEL_3) ? (__HANDLE__)->ChannelState[2] :\
   (__HANDLE__)->ChannelState[3])

#define TIM_CHANNEL_N_STATE_GET(__HANDLE__, __CHANNEL__)\
  (((__CHANNEL__) == TIM_CHANNEL_1) ? (__HANDLE__)->ChannelNState[0] :\
   ((__CHANNEL__) == TIM_CHANNEL_2) ? (__HANDLE__)->ChannelNState[1] :\
   ((__CHANNEL__) == TIM_CHANNEL_3) ? (__HANDLE__)->ChannelNState[2] :\
   (__HANDLE__)->ChannelNState[3])

        这里出现__HANDLE__里的成员ChannelState[]数组,跳转看一下TIM总控句柄里面都有什么,然后看到这一段代码:

  TIM_TypeDef     *Instance;         /*!< Register base address*/
  TIM_Base_InitTypeDef     Init;    /*!< TIM Time Base required parameters*/
  HAL_TIM_ActiveChannel   Channel;  /*!< Active channel*/
  DMA_HandleTypeDef   *hdma[7];   /*!< DMA Handlers array */
  HAL_LockTypeDef    Lock;              /*!< Locking object*/
  __IO HAL_TIM_StateTypeDef   State;  /*!< TIM operation state*/
  __IO HAL_TIM_ChannelStateTypeDef   ChannelState[4];  

/*!< TIM channel operation state*/
  __IO HAL_TIM_ChannelStateTypeDef   ChannelNState[4];  

/*!< TIM complementary channel operation state*/
  __IO HAL_TIM_DMABurstStateTypeDef  DMABurstState;

    /*!< DMA burst operation state */

…………

        可以看见里面有个__IO HAL_TIM_ChannelStateTypeDef   ChannelState[4];  以及  __IO HAL_TIM_ChannelStateTypeDef   ChannelNState[4];  一个通用或者高级定时器有四个捕获比较通道及其互补通道,这个存储的其实是这四个通道及其互补通道的占用状态,这个状态的取值可以跳转到HAL_TIM_DMABurstStateTypeDef枚举中查看,可以看到有释放、就绪、占用三种状态:

        所以这两个很长的宏定义这里想表达的意思其实是,传入句柄__HANDLE__,待查询 __CHANNEL__,然后类似一问一答的模式:

  • 你想查询TIM_CHANNEL_1吗?是的,返回句柄__HANDLE__中的ChannelState[0](也就是通道1的占用情况):不是
  • 你想查询TIM_CHANNEL_2吗?是的,返回句柄__HANDLE__中的ChannelState[1](也就是通道2的占用情况):不是
  • 你想查询TIM_CHANNEL_3吗?是的,返回句柄__HANDLE__中的ChannelState[2](也就是通道3的占用情况):不是
  • 返回句柄__HANDLE__中的ChannelState[3](也就是通道4的占用情况)

         这部分就比较好看了,校验总控结构体实例正确性后,就是判断用于编码器模式的通道Channel1以及Channel2占用情况,如果被占用,就返回设置错误的信息;如果通道空闲,那么编码器模式将占用他们,函数其占用情况设置为被占用。

/* Check the parameters */
  assert_param(IS_TIM_ENCODER_INTERFACE_INSTANCE(htim->Instance));//校验总控结构体实例正确性

  /* Set the TIM channel(s) state */
  if (Channel == TIM_CHANNEL_1)
  {
    if ((channel_1_state != HAL_TIM_CHANNEL_STATE_READY)
        || (complementary_channel_1_state != HAL_TIM_CHANNEL_STATE_READY))
    {
      return HAL_ERROR;
    }
    else
    {
      TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_1, HAL_TIM_CHANNEL_STATE_BUSY);
      TIM_CHANNEL_N_STATE_SET(htim, TIM_CHANNEL_1, HAL_TIM_CHANNEL_STATE_BUSY);
    }
  }
  else if (Channel == TIM_CHANNEL_2)
  {
    if ((channel_2_state != HAL_TIM_CHANNEL_STATE_READY)
        || (complementary_channel_2_state != HAL_TIM_CHANNEL_STATE_READY))
    {
      return HAL_ERROR;
    }
    else
    {
      TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_2, HAL_TIM_CHANNEL_STATE_BUSY);
      TIM_CHANNEL_N_STATE_SET(htim, TIM_CHANNEL_2, HAL_TIM_CHANNEL_STATE_BUSY);
    }
  }
  else
  {
    if ((channel_1_state != HAL_TIM_CHANNEL_STATE_READY)
        || (channel_2_state != HAL_TIM_CHANNEL_STATE_READY)
        || (complementary_channel_1_state != HAL_TIM_CHANNEL_STATE_READY)
        || (complementary_channel_2_state != HAL_TIM_CHANNEL_STATE_READY))
    {
      return HAL_ERROR;
    }
    else
    {
      TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_1, HAL_TIM_CHANNEL_STATE_BUSY);
      TIM_CHANNEL_STATE_SET(htim, TIM_CHANNEL_2, HAL_TIM_CHANNEL_STATE_BUSY);
      TIM_CHANNEL_N_STATE_SET(htim, TIM_CHANNEL_1, HAL_TIM_CHANNEL_STATE_BUSY);
      TIM_CHANNEL_N_STATE_SET(htim, TIM_CHANNEL_2, HAL_TIM_CHANNEL_STATE_BUSY);
    }
  }

         最后这一部分就是到底层寄存器的设置操作了。这个 TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel, uint32_t ChannelState)函数可以跳转一下,根据手册寄存器验证其正确性。最后就是因为这个函数是使用中断方式打开的编码器,所以在最后使用宏__HAL_TIM_ENABLE_IT开启了中断,再到最后用宏__HAL_TIM_ENABLE使能了定时器,返回设置成功的状态信息。

 /* Enable the encoder interface channels */
  /* Enable the capture compare Interrupts 1 and/or 2 */
  switch (Channel)
  {
    case TIM_CHANNEL_1:
    {
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
      __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1);
      break;
    }

    case TIM_CHANNEL_2:
    {
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
      __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC2);
      break;
    }

    default :
    {
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
      __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC1);
      __HAL_TIM_ENABLE_IT(htim, TIM_IT_CC2);
      break;
    }
  }

  /* Enable the Peripheral */
  __HAL_TIM_ENABLE(htim);

  /* Return function status */
  return HAL_OK;
}

一个比较好玩的事情

       一个比较好玩的事情是我发现虽然ST对这两个个函数的简介说Channel这个参数取值有三:

但是这两个函数里面都有这样的一段代码:

  switch (Channel)
  {
    case TIM_CHANNEL_1:
    {
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
      break;
    }

    case TIM_CHANNEL_2:
    {
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
      break;
    }

    default :
    {
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_1, TIM_CCx_ENABLE);
      TIM_CCxChannelCmd(htim->Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
      break;
    }
  }

        这个switch结构没有对Channel这个参数进行参数验证,这就是说只要我不传入Channel1以及Channel2,其他传进去任何参数都可以设置编码器双通道边沿检测模式,即如下代码段可以使得程序逻辑成立。

HAL_TIM_Encoder_Start_IT(&htim2,1651);//当然也可以是其他什么的任何值

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
Updated Pack to STM32Cube_FW_F4 Firmware Package version V1.25.1 using HAL Drivers V1.7.9. STM32CubeMX integration (Version 6.0.1): Added support for Timebase Source TIMx (FrameworkCubeMX_gpdsc.ftl). Removed non-existent include path. CMSIS Flash Algorithm: Corrected STM32F42xxx_43xxx_OPT Algorithm. CMSIS SVD: Updated STM32F42*.svd, STM32F43*.svd files. CMSIS-Driver: I2C: Corrected 2 byte reception in master mode. MCI: Replaced empty delay loops with _NOP(). SPI: Corrected PowerControl function (to return error if Initialize was not called, to abort active transfer if power off was requested). Updated GetDataCount function to give accurate count in DMA mode. Corrected Control function (abort in DMA mode, software controlled slave select in slave mode, TI Frame Format selection, ignore bus speed for slave mode). Corrected Uninitialize function (to power off the peripheral if it is powered). Corrected SPI3_SCK pin configuration. Corrected DMA MemDataAlignment configuration. USART: Corrected DMA MemDataAlignment configuration. USBD_HS/USBH_HS: OTG_HS ULPI clock disabled in low power if internal PHY is used to enable proper operation of OTG_HS port in FS mode during CPU sleep. CAN/EMAC/USBD/USBH: Removed macros already provided by cmsis_compiler.h. Updated Boards Examples: Migrated CubeMX projects to V6.0.1 and updated config files. Changed variant selection to "MDK-Plus" where possible. Updated all USB Host/Device examples with user templates from MDK-Middleware v7.11.1. Terminating app_main thread with osThreadExit() to avoid endless loop Updated MS Windows UBS driver files.

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

诗和远方曾来过

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值