带定时器的旋转编码器编程

Programming rotary encoder with timers

提问人:Djowwie 提问时间:11/10/2023 最后编辑:Djowwie 更新时间:11/20/2023 访问量:105

问:

我设计了一个带有STM32F103CBT6微控制器和 16 MHz 晶体振荡器的定制 PCB。该板具有一个按钮矩阵,其中包含 16 个按钮、2 个独立按钮和 4 个旋转编码器。

虽然我已经使用轮询方法成功地对按钮矩阵和独立按钮进行了编程,以检查按钮状态,但我现在正在解决对旋转编码器进行编程的挑战。我正在探索两种选择。

第一种选择是将所有编码器引脚设置为gpio_input并监控它们的状态变化,类似于按钮矩阵(例如,当引脚变为低电平时注册输入)。但是,这会在引脚上引入弹跳/跳跃,从而导致不正确或重复的输入。

向我推荐的第二种选择是使用计时器。每个编码器都连接到定时器的通道 1 和通道 2。例如,编码器 1 连接到定时器 1(通道 1 = PA8 和通道 2 = PA9),编码器 2 连接到定时器 2(通道 1 = PA0 和通道 2 = PA1),编码器 3 连接到定时器 3(通道 1 = PA6,通道 2 = PA7),编码器 4 连接到定时器 4(通道 1 = PB6,通道 2 = PB7)。

由于我不太熟悉在STM32上使用定时器,因此我在网上查找了信息,并尝试了各种方法,但收效甚微。目前,我有编码器 1 与定时器 1 一起工作,但输入不正确,提供随机按钮 1 和按钮 2 输入,甚至不是每个脉冲。

目标是为每个编码器的顺时针和逆时针移动生成唯一值,这些值将作为 HID 报告发送到 PC。这意味着我需要 8 个不同的输入值(每个编码器 2 个,x4),例如编码器 1 的顺时针位 0 和逆时针位 1,以及编码器 2 的位 2 和位 3,依此类推。

我目前拥有的计时器配置代码:

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM1) {
        int16_t currentEncoderValue = TIM1->CNT;
        int16_t encoderDiff = currentEncoderValue - prevEncoderValue;

        if (encoderDiff > 0) {
            // Clockwise rotation detected
            buttonReport.buttons |= (1 << 0);
        } else if (encoderDiff < 0) {
            // Counterclockwise rotation detected
            buttonReport.buttons |= (1 << 1);
        }

        if (encoderDiff != 0) {
            buttonReport.report_id = 1;
            USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&buttonReport, sizeof(buttonReport));
        }

        prevEncoderValue = currentEncoderValue;
        __HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
    }
}

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_TIM2_Init();
  MX_TIM3_Init();
  MX_TIM4_Init();
  MX_USB_DEVICE_Init();

  HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);
  HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
  HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
  HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);

  HAL_TIM_Base_Start_IT(&htim1);
  HAL_TIM_Base_Start_IT(&htim2);
  HAL_TIM_Base_Start_IT(&htim3);
  HAL_TIM_Base_Start_IT(&htim4);

  while (1) {
      SCANALL();
       HAL_Delay(50);
  }
}

static void MX_TIM1_Init(void)
{

  /* USER CODE BEGIN TIM1_Init 0 */

  /* USER CODE END TIM1_Init 0 */

  TIM_Encoder_InitTypeDef sConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM1_Init 1 */

  /* USER CODE END TIM1_Init 1 */
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 1;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 1;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
  sConfig.IC1Polarity = TIM_ICPOLARITY_FALLING;
  sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC1Filter = 10;
  sConfig.IC2Polarity = TIM_ICPOLARITY_FALLING;
  sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC2Filter = 10;
  if (HAL_TIM_Encoder_Init(&htim1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM1_Init 2 */

  /* USER CODE END TIM1_Init 2 */

}
C 定时器 STM32 游戏控制器

评论

0赞 Lundin 11/10/2023
看起来像一个经典的缺失错误等。volatileencoder1_state
0赞 Djowwie 11/15/2023
我已经用更新的代码编辑了这篇文章。它现在正在工作,但仍然存在问题。

答:

1赞 wek 11/10/2023 #1

Cube/HAL 在 Update 事件(即计数器翻转)时调用 HAL_TIM_PeriodElapsedCallback(),因此在您的设置中,当编码器向一个方向转动时,需要 65535 个脉冲才能实现此目的。

通常/推荐的过程是定期对计数器进行采样,例如在 main() 的定时循环中。中断对于编码器来说没有多大用处。

顺便说一句,TIM1 专门为其各种中断源提供了单独的中断向量,请参阅启动文件中的向量表(有关更新,它TIM1_UP_IRQHandler)。

评论

0赞 Djowwie 11/17/2023
你是最棒的,我的问题已经解决了!编码器现在可以完美地工作!我现在在 while 循环中编程了计时器,我不再有弹跳/跳跃了!我没有足够的声誉来标记你的答案或给分数。但谢谢你!
0赞 Djowwie 11/20/2023 #2

下面的代码是我现在如何编程的。它确实有效,但是需要一些调整,因为我有时仍然会弹跳/跳跃,但不像以前那么多了。

    while (1) {
        // Poll TIM1 encoder
        int16_t currentEncoderValue1 = TIM1->CNT;
        int16_t encoderDiff1 = currentEncoderValue1 - prevEncoderValue1;

        if (encoderDiff1 > 0) {
            // Clockwise rotation detected
            buttonReport.buttons |= (1 << 0);
        } else if (encoderDiff1 < 0) {
            // Counterclockwise rotation detected
            buttonReport.buttons |= (1 << 1);
        }

        if (encoderDiff1 != 0) {
            buttonReport.report_id = 1;
            USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&buttonReport, sizeof(buttonReport));
        }

        prevEncoderValue1 = currentEncoderValue1;

static void MX_TIM1_Init(void)
{

  /* USER CODE BEGIN TIM1_Init 0 */

  /* USER CODE END TIM1_Init 0 */

  TIM_Encoder_InitTypeDef sConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM1_Init 1 */

  /* USER CODE END TIM1_Init 1 */
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 0;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 20;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
  sConfig.EncoderMode = TIM_ENCODERMODE_TI12;
  sConfig.IC1Polarity = TIM_ICPOLARITY_FALLING;
  sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC1Filter = 5;
  sConfig.IC2Polarity = TIM_ICPOLARITY_FALLING;
  sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC2Filter = 5;
  if (HAL_TIM_Encoder_Init(&htim1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM1_Init 2 */

  /* USER CODE END TIM1_Init 2 */

}

评论

0赞 wek 11/20/2023
您的问题是翻转 - 从 3 到 4 的增量与从 20 到 0 的方向相同,但您的程序将后者评估为相反方向的运动。通常,您希望周期尽可能大,如果变化的绝对值大于周期的一半,则变化的方向与差异符号所指示的方向相反。
0赞 Djowwie 11/20/2023
谢谢你,先生!我已将计数器周期设置为 65535,并在下面添加了代码,我现在的弹跳为零!if (encoderDiff1 > 32767) { encoderDiff1 -= 65536; else if (encoderDiff1 < -32768) { encoderDiff1 += 65536; }