STM32 CubeMX TIMx编码器模式中断踩坑记录及HAL_TIM_Encoder_Start和_IT函数解析

目录

  • 问题的开始
  • 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);//当然也可以是其他什么的任何值

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 CubeMX TIMx编码器模式中断踩坑记录及HAL_TIM_Encoder_Start和_IT函数解析

    发表评论