本文将详细介绍如何在STM32F103微控制器上为EC11旋转编码器编写稳定可靠的驱动程序。内容涵盖从官方手册要点解读、硬件电路设计(含关键RC滤波),到基于外部中断的驱动代码实现与调试,帮助开发者快速上手。
一、EC11手册关键要点解析
在使用EC11编码器前,深入理解其数据手册是确保设计成功的第一步,尤其需要注意信号处理和抗干扰设计。

手册明确指出,编码器信号的计算方法需要综合考虑操作速度、信号采样时间以及微控制器软件逻辑。此外,为了抑制噪声,强烈建议在信号线上使用RC滤波电路。
手册推荐的RC滤波电路
下图展示了EC11官方推荐的接口电路。该电路在A、B两相输出端分别设置了由10KΩ上拉电阻和0.01μF(10nF)电容对地组成的RC低通滤波器,能有效滤除高频毛刺,C端子则直接接电源。

输出波形相位关系
EC11的A、B两相信号在旋转时存在90度的相位差。通过检测两路信号上升沿/下降沿到来的先后顺序,即可判断旋转方向(顺时针或逆时针),这是所有增量式编码器解码的基础。

二、硬件电路设计实践
在实际项目中,我们严格遵循手册建议,为EC11的CLK和DT引脚添加了RC滤波电路。
具体做法是:每个信号引脚通过一个10KΩ电阻上拉到MCU的供电电压(如3.3V),同时并联一个约100pF的电容到地(GND)。实测表明,使用100pF电容也能达到良好的滤波效果。

电路搭建完成后,强烈建议使用示波器观察波形,主要目的有二:
- 确认添加滤波电路后,信号上的噪声是否被有效抑制。
- 观察不同旋转速度下,A、B两相波形的相位关系与时间间隔,这对于后续软件中中断延迟时间的设置至关重要。
顺/逆时针旋转的实际波形
下图展示了使用示波器捕获的实际波形。可以看到,在黄色波形(CLK)的上升沿触发外部中断时,另一路DT信号的有效电平判断窗口非常窄。

如果中断服务函数中不加任何延迟,在快速旋转时,MCU可能来不及采样另一路引脚的电平状态,从而导致方向判断错误。这涉及到对嵌入式系统并发与中断响应时序的精细处理。

三、驱动程序关键实现
驱动代码的核心在于GPIO配置和外部中断处理函数。
1. GPIO配置
将连接EC11的CLK和DT的两个IO口均设置为下拉输入模式。这样,当编码器无输出时,引脚电平为稳定的低电平,便于检测高电平信号的有效跳变。
2. 中断配置
使用外部中断来检测EC11引脚的电平变化。
- 触发方式:设置为上升沿触发。这与下拉输入配置相匹配,当编码器产生脉冲时,引脚由低变高,产生中断。
- 判断逻辑:在中断服务函数中,当触发中断的引脚(例如CLK)为高电平时,立即读取另一引脚(DT)的电平状态。
- 如果DT也为高电平,则为顺时针旋转。
- 如果DT为低电平,则为逆时针旋转。
3. 关键延迟处理
这是驱动稳定的重中之重。为了防止因信号抖动或MCU响应速度导致的误判,必须在中断服务函数的起始处加入一个短暂的延时(例如delay_ms(1)),等待信号稳定后再进行采样判断。延时时间需根据实际旋转速度和硬件滤波参数进行调整。
以下是基于STM32标准外设库的核心代码示例:
knob.h 引脚定义
#define knob1_clk PAin(6) // 编码器CLK引脚连接PA6
#define knob1_dt PAin(1) // 编码器DT引脚连接PA1
knob.c GPIO初始化
void knob_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置PA6和PA1为下拉输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 输入,下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
exit.c 中断初始化与服务函数
// 外部中断初始化
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
knob_init(); // 初始化编码器IO
// 将PA6映射到EXTI Line6
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource6);
EXTI_InitStructure.EXTI_Line = EXTI_Line6;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; // 上升沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
// 中断服务函数
void EXTI9_5_IRQHandler(void)
{
delay_ms(1); // 关键延时,等待信号稳定
if(knob1_clk == 1) // 确保是CLK上升沿
{
if(knob1_dt == 1) // 判断DT电平
{
printf("knob: +1 \r\n"); // 顺时针
}
else
{
printf("knob: -1 \r\n"); // 逆时针
}
}
EXTI_ClearITPendingBit(EXTI_Line6); // 清除中断标志位
}
四、实验现象与输出验证
将上述程序下载到STM32F103开发板,并通过串口调试助手观察输出。稳定旋转EC11编码器,可以看到串口按顺序打印出“+1”或“-1”,准确反映了旋转方向和步进。

为了代码的复用和扩展,这里也提供一个更模块化、功能更完整的EC11驱动头文件设计参考,它定义了编码器类型、按键消抖、单击/双击/长按等高级功能,适用于更复杂的应用场景。
// EncoderEC11.h - 模块化EC11驱动头文件示例
#ifndef __ENCODER_EC11_H
#define __ENCODER_EC11_H
#include "stm32f10x.h"
// IO口定义
#define EC11_A_PIN GPIO_Pin_6
#define EC11_B_PIN GPIO_Pin_1
#define EC11_KEY_PIN GPIO_Pin_0
// 编码器类型
#define EC11_TYPE_SINGLE_PULSE 0 // 一定位一脉冲
#define EC11_TYPE_DOUBLE_PULSE 1 // 两定位一脉冲
// 扫描周期与按键参数
#define EC11_SCAN_MS 2
#define KEY_DEBOUNCE_CNT (20 / EC11_SCAN_MS)
// 动作标志
typedef enum {
EC11_ACTION_NONE = 0,
EC11_ACTION_CW, // 顺时针
EC11_ACTION_CCW, // 逆时针
EC11_ACTION_CLICK,
EC11_ACTION_LONG_PRESS
} EC11_Action_t;
// 函数声明
void EC11_Init(uint8_t type);
EC11_Action_t EC11_Scan(void);
#endif
该头文件框架展示了如何组织一个健壮的编码器驱动,将硬件操作与业务逻辑分离,是嵌入式软件工程化思维的体现。
通过以上步骤,我们完成了从硬件滤波到软件解调的完整流程。关键在于理解RC滤波的必要性,以及在中斷服务函数中通过合理延迟来实现信号的稳定读取,从而确保EC11旋转编码器在STM32F103上的稳定工作。