基于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
相关产品推荐
相关产品推荐

