共计 2087 个字符,预计需要花费 6 分钟才能阅读完成。
背景痛点:为什么 EC11 编码器这么难搞?
第一次用 51 单片机接 EC11 旋转编码器时,我天真地以为就是简单的电平检测。结果现实给了我一记耳光——明明只转了一格,计数器却跳了三四个数。后来用逻辑分析仪抓波形才发现,机械触点闭合时会产生密集的抖动(就像手机按键老化的感觉):

- 触点抖动:实测 EC11 的抖动持续时间可达 5 -10ms,远超 51 单片机指令周期
- 方向误判:AB 相时序交叉时容易产生误判(特别是快速旋转时)
- 资源占用:中断法处理高频旋转会严重占用 CPU 资源
查询法 vs 中断法实测对比
- 查询法:在主循环周期性扫描 GPIO
- 优点:不占用中断资源,代码结构简单
-
缺点:响应延迟大,高速旋转时丢脉冲
-
中断法:配置双边沿触发中断
- 优点:实时性极高,零延迟响应
- 缺点:抖动期间会频繁进中断,CPU 负载飙升 50% 以上
经过实测,我的选择是:状态机 + 定时器查询 的混合方案。下面具体展开 …
状态机消抖:把玄学变成科学
有限状态机 (FSM) 设计
EC11 编码器的 AB 相有 4 种稳定状态,我们定义状态机如下:
// 状态枚举定义
typedef enum {
STATE_00, // A=0 B=0
STATE_01, // A=0 B=1
STATE_10, // A=1 B=0
STATE_11 // A=1 B=1
} EncoderState;
消抖核心逻辑是:只有持续检测到同一状态超过阈值时间,才认为有效。这里我用的 5ms 定时器中断作为时间基准:
// 在定时器中断服务函数中
if(current_state == last_state) {if(++stable_counter > DEBOUNCE_TICKS) {
stable_counter = DEBOUNCE_TICKS;
state_changed = true;
}
} else {
stable_counter = 0;
last_state = current_state;
}
方向判定神操作
通过观察状态转移规律,发现可以用位运算快速判断方向:
// AB 相旧状态: A_old, B_old
// AB 相新状态: A_new, B_new
uint8_t change = (A_old << 3) | (B_old << 2) | (A_new << 1) | B_new;
// 状态转移真值表判向
switch(change) {
case 0b0001: case 0b0111: case 0b1110: case 0b1000:
return DIR_CW; // 顺时针
case 0b0010: case 0b1011: case 0b1101: case 0b0100:
return DIR_CCW; // 逆时针
}
生产级代码实现
GPIO 初始化(Keil C51)
sbit ENC_A = P1^0; // 编码器 A 相
sbit ENC_B = P1^1; // 编码器 B 相
void Encoder_Init() {
// 配置为准双向模式,内部上拉
P1M0 &= ~0x03; // P1.0/P1.1
P1M1 &= ~0x03;
// 定时器 0 配置为 5ms 中断
TMOD &= 0xF0;
TMOD |= 0x01;
TH0 = 0xEE;
TL0 = 0x00;
ET0 = 1;
EA = 1;
TR0 = 1;
}
核心状态机处理函数
__bit Encoder_Process() {
static uint8_t last_val = 0;
uint8_t current_val = (ENC_A << 1) | ENC_B;
if(current_val == last_val) {if(++stable_ticks > DEBOUNCE_THRESHOLD) {
stable_ticks = DEBOUNCE_THRESHOLD;
return true; // 状态稳定
}
} else {
stable_ticks = 0;
last_val = current_val;
}
return false;
}
来自产线的血泪经验
不同型号参数调整
- EC11K:15ms 消抖时间(簧片较软)
- EC11E:8ms 即可(金属触点更硬)
- 带按键款:需要额外处理按键抖动(通常 20ms)
防丢脉冲黑科技
在状态转移时加入异常检测:
if(abs(encoder_count - last_count) > 2) {
// 超过 2 步的跳变视为异常
encoder_count = last_count; // 保持原值
error_counter++;
}
验证:用逻辑分析仪说话
测试步骤:
- 以 60RPM 速度匀速旋转编码器
- 抓取 AB 相原始波形和消抖后波形
- 检查计数器增量与实际旋转步数是否一致
关键指标:
- 抖动滤除率应 >95%
- 最大跟踪转速≥120RPM
- 反向旋转时计数必须递减
扩展思考
多编码器并联方案
- 矩阵扫描法:将 N 个编码器的 A 相连在一起,B 相分别接 IO
- 分时复用:用模拟开关切换不同编码器
中断模式优化
// 在中断中仅记录时间戳
void EXTI_Handler() __interrupt 0 {
static uint32_t last_time;
uint32_t now = sys_tick;
if(now - last_time > DEBOUNCE_TIME) {process_encoder();
}
last_time = now;
}
通过这个项目,我深刻体会到:硬件设计是数学,软件实现是哲学。希望这篇笔记能帮你少走弯路。如果有更好的实现方案,欢迎在评论区交流!
正文完
