EC11编码器实战:从轮询到定时器Encoder模式详解 1. EC11编码器基础与工作原理EC11编码器是嵌入式开发中常见的人机交互元件本质上是一个增量式旋转编码器。我第一次接触EC11是在设计智能家居控制面板时需要用它来调节灯光亮度。这种编码器最大的特点就是结构简单、成本低廉但实现稳定读取却需要不少技巧。EC11的核心在于两个输出引脚通常标记为A相和B相产生的正交信号。当旋钮旋转时两个引脚会输出相位差90度的方波。具体来说顺时针旋转时A相领先B相90度逆时针旋转时B相领先A相90度市场上常见的EC11有两种规格15脉冲/30定位型每旋转一格产生半个脉冲周期20脉冲/20定位型每旋转一格产生完整脉冲周期实际项目中我遇到过最头疼的问题是信号抖动。有一次产品在工厂测试时发现编码器偶尔会误触发后来用示波器抓取信号才发现是机械触点抖动导致的。这就引出了编码器应用中的两个关键技术点信号防抖处理和方向判断算法。2. 轮询模式实现详解2.1 硬件连接与初始化轮询模式是最直接的实现方式适合资源有限的MCU。在我的一个STM32F103项目中硬件连接是这样的EC11的A相接PB6B相接PB7两个IO都配置为内部上拉输入初始化代码很简单void Encoder_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOB_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOB, GPIO_InitStruct); }2.2 核心算法与防抖处理轮询模式的关键在于状态机设计。经过多次实践我总结出一个稳定的判断逻辑uint8_t last_A 1; int32_t encoder_count 0; void Polling_Encoder_Update(void) { uint8_t current_A HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6); uint8_t current_B HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7); if(current_A ! last_A) { // 防抖延时2ms HAL_Delay(2); current_A HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_6); current_B HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7); if(current_A 1) { encoder_count (current_B 0) ? 1 : -1; } else { encoder_count (current_B 1) ? 1 : -1; } last_A current_A; } }这个实现有几个关键点只在A相变化时进行判断减少误触发加入2ms硬件防抖延时根据A、B相的相对状态确定方向在实际应用中我发现如果主循环执行太快可能会漏掉快速旋转时的脉冲。解决方法是可以增加一个定时器中断每1ms调用一次Polling_Encoder_Update()。3. 定时器Encoder模式进阶实现3.1 硬件定时器配置STM32的定时器Encoder模式简直是EC11的绝配。以TIM4为例配置步骤如下将PB6(PIN1)和PB7(PIN2)配置为TIM4_CH1和TIM4_CH2定时器工作在Encoder Mode TI1模式设置合适的输入滤波我通常用0x3具体初始化代码void MX_TIM4_Init(void) { TIM_Encoder_InitTypeDef sConfig {0}; TIM_MasterConfigTypeDef sMasterConfig {0}; htim4.Instance TIM4; htim4.Init.Prescaler 0; htim4.Init.CounterMode TIM_COUNTERMODE_UP; htim4.Init.Period 65535; htim4.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim4.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_ENABLE; sConfig.EncoderMode TIM_ENCODERMODE_TI1; sConfig.IC1Polarity TIM_ICPOLARITY_RISING; sConfig.IC1Selection TIM_ICSELECTION_DIRECTTI; sConfig.IC1Prescaler TIM_ICPSC_DIV1; sConfig.IC1Filter 3; sConfig.IC2Polarity TIM_ICPOLARITY_RISING; sConfig.IC2Selection TIM_ICSELECTION_DIRECTTI; sConfig.IC2Prescaler TIM_ICPSC_DIV1; sConfig.IC2Filter 3; HAL_TIM_Encoder_Init(htim4, sConfig); sMasterConfig.MasterOutputTrigger TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(htim4, sMasterConfig); HAL_TIM_Encoder_Start(htim4, TIM_CHANNEL_ALL); }3.2 计数处理与边界条件使用定时器Encoder模式时需要特别注意计数器溢出问题。我的解决方案是int32_t encoder_total 0; uint16_t last_count 0; void Encoder_Update(void) { uint16_t current_count __HAL_TIM_GET_COUNTER(htim4); int16_t diff (int16_t)(current_count - last_count); // 处理计数器溢出 if(diff 32767) diff - 65536; else if(diff -32768) diff 65536; encoder_total diff; last_count current_count; }这种方法可以正确处理计数器从65535到0或者从0到65535的跳变。在我的测试中即使以最快速度旋转编码器计数也不会丢失。4. 两种模式对比与选型建议4.1 性能实测数据我在STM32F407上对两种实现进行了对比测试指标轮询模式定时器模式CPU占用率~15%1%最高响应速度200RPM1000RPM代码复杂度简单中等需要硬件资源任意IO特定定时器防抖效果软件实现硬件滤波4.2 实际项目选型经验根据我的项目经验选型建议如下轮询模式适合低端MCU如STM32F0系列旋转速度较慢的场景如音量调节需要节省定时器资源的应用定时器模式适合高性能应用如数控旋钮需要精确计数的场合系统实时性要求高的场景有个实际案例在工业HMI项目中同时有多个EC11需要处理我采用了混合方案 - 主要旋钮用定时器模式辅助按钮用轮询模式这样既保证了主要操作的流畅性又节省了硬件资源。最后提醒一点无论哪种模式硬件设计都很关键。我的经验是一定要在EC11引脚加104电容滤波PCB走线尽量短避免与其他高频信号平行走线有条件的话可以用光耦隔离这些细节处理好了编码器的稳定性会有质的提升。