You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于STM32定时器的Rotary Encoder编程问题求助

STM32F103CBT6 旋转编码器定时器模式编程问题解决

我设计了一块搭载STM32F103CBT6微控制器与16MHz晶振的定制PCB,板载16键矩阵按键、2个独立按键及4个旋转编码器。已通过轮询方式实现按键矩阵与独立按键功能,现在需要解决旋转编码器的编程问题。

尝试过两种方案:

  • 方案一:将编码器引脚设为GPIO输入监测状态变化,但存在引脚抖动导致输入错误或重复的问题;
  • 方案二:采用定时器编码器模式,每个编码器连接至定时器的Channel 1与Channel 2:
    • Encoder 1 → TIM1的PA8(Channel 1)与PA9(Channel 2)
    • Encoder 2 → TIM2的PA0(Channel 1)与PA1(Channel 2)
    • Encoder 3 → TIM3的PA6(Channel 1)与PA7(Channel 2)
    • Encoder 4 → TIM4的PB6(Channel 1)与PB7(Channel 2)

目前仅能让Encoder 1与TIM1配合工作,但输入结果异常:随机触发button 1和button 2的输入,且无法响应每一次脉冲。

目标需求:为每个编码器的顺时针、逆时针旋转生成唯一值,以HID报告形式发送至PC。4个编码器对应8个不同输入值(每个编码器占2个比特位:Encoder 1顺时针对应bit 0、逆时针对应bit 1;Encoder 2对应bit 2、bit 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 */

}

问题分析与修正方案

1. 定时器配置错误

当前TIM1的Period设为1,计数器会频繁溢出触发更新中断,导致误判旋转方向;编码器模式下定时器由输入引脚驱动,Prescaler应设为0,无需额外分频。

2. 编码器极性与模式匹配

编码器的IC极性需根据硬件接线调整,建议先设为TIM_ICPOLARITY_RISING,配合TIM_ENCODERMODE_TI12模式,确保计数器随编码器旋转正确增减。

3. 中断处理逻辑优化

无需依赖更新中断读取计数器值,直接在主循环中定期读取即可,避免溢出导致的差值计算错误;同时为每个编码器维护独立的前值变量,防止相互干扰。

4. HID报告处理优化

每次发送HID报告后需清除对应比特位,避免同一旋转动作被重复识别;缩短主循环延时,提升编码器响应速度。

修改后的代码示例

全局变量定义

typedef struct {
    uint8_t report_id;
    uint16_t buttons; // bit0-7对应4个编码器的8种旋转状态
} ButtonReport_t;

ButtonReport_t buttonReport = {0};
int16_t prevEncoderValue[4] = {0}; // 分别对应TIM1-TIM4的计数器前值

定时器初始化调整(以TIM1为例)

static void MX_TIM1_Init(void)
{
  TIM_Encoder_InitTypeDef sConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 0; // 编码器模式下预分频器设为0
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 0xFFFF; // 设置最大周期,减少溢出频率
  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_RISING; // 改为上升沿触发,可根据硬件调整
  sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC1Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC1Filter = 0x0F; // 增强滤波,消除引脚抖动
  sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
  sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
  sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
  sConfig.IC2Filter = 0x0F;
  
  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();
  }
}

主循环与编码器读取逻辑

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

  // 初始化编码器计数器前值
  prevEncoderValue[0] = TIM1->CNT;
  prevEncoderValue[1] = TIM2->CNT;
  prevEncoderValue[2] = TIM3->CNT;
  prevEncoderValue[3] = TIM4->CNT;

  while (1) {
      SCANALL();
      ReadAllEncoders(); // 批量读取所有编码器状态
      HAL_Delay(10); // 缩短延时,提升响应速度
  }
}

void ReadAllEncoders(void)
{
  int16_t currentValue;
  int16_t diff;

  // 处理Encoder 1(TIM1)
  currentValue = TIM1->CNT;
  diff = currentValue - prevEncoderValue[0];
  if (diff != 0)
  {
    buttonReport.buttons |= (diff > 0) ? (1 << 0) : (1 << 1);
    buttonReport.report_id = 1;
    USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&buttonReport, sizeof(buttonReport));
    buttonReport.buttons &= ~((1 << 0) | (1 << 1)); // 清除对应比特位
    prevEncoderValue[0] = currentValue;
  }

  // 处理Encoder 2(TIM2)
  currentValue = TIM2->CNT;
  diff = currentValue - prevEncoderValue[1];
  if (diff != 0)
  {
    buttonReport.buttons |= (diff > 0) ? (1 << 2) : (1 << 3);
    buttonReport.report_id = 1;
    USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&buttonReport, sizeof(buttonReport));
    buttonReport.buttons &= ~((1 << 2) | (1 << 3));
    prevEncoderValue[1] = currentValue;
  }

  // 处理Encoder 3(TIM3)
  currentValue = TIM3->CNT;
  diff = currentValue - prevEncoderValue[2];
  if (diff != 0)
  {
    buttonReport.buttons |= (diff > 0) ? (1 << 4) : (1 << 5);
    buttonReport.report_id = 1;
    USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&buttonReport, sizeof(buttonReport));
    buttonReport.buttons &= ~((1 << 4) | (1 << 5));
    prevEncoderValue[2] = currentValue;
  }

  // 处理Encoder 4(TIM4)
  currentValue = TIM4->CNT;
  diff = currentValue - prevEncoderValue[3];
  if (diff != 0)
  {
    buttonReport.buttons |= (diff > 0) ? (1 << 6) : (1 << 7);
    buttonReport.report_id = 1;
    USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, (uint8_t*)&buttonReport, sizeof(buttonReport));
    buttonReport.buttons &= ~((1 << 6) | (1 << 7));
    prevEncoderValue[3] = currentValue;
  }
}

补充说明

  • 若编码器旋转方向判断相反,只需将对应IC的极性改为TIM_ICPOLARITY_FALLING;
  • 滤波值IC1Filter和IC2Filter可根据实际抖动情况调整,范围0-15;
  • 若计数器溢出/下溢导致差值异常,可添加溢出判断逻辑,或使用32位变量累加计数器变化量。

内容的提问来源于stack exchange,提问作者Djowwie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 19:42:02