51单片机EC11编码器编程实战:从原理到避坑指南

1次阅读
没有评论

共计 2087 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

背景痛点:为什么 EC11 编码器这么难搞?

第一次用 51 单片机接 EC11 旋转编码器时,我天真地以为就是简单的电平检测。结果现实给了我一记耳光——明明只转了一格,计数器却跳了三四个数。后来用逻辑分析仪抓波形才发现,机械触点闭合时会产生密集的抖动(就像手机按键老化的感觉):

51 单片机 EC11 编码器编程实战:从原理到避坑指南

  • 触点抖动:实测 EC11 的抖动持续时间可达 5 -10ms,远超 51 单片机指令周期
  • 方向误判:AB 相时序交叉时容易产生误判(特别是快速旋转时)
  • 资源占用:中断法处理高频旋转会严重占用 CPU 资源

查询法 vs 中断法实测对比

  1. 查询法:在主循环周期性扫描 GPIO
  2. 优点:不占用中断资源,代码结构简单
  3. 缺点:响应延迟大,高速旋转时丢脉冲

  4. 中断法:配置双边沿触发中断

  5. 优点:实时性极高,零延迟响应
  6. 缺点:抖动期间会频繁进中断,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++;
}

验证:用逻辑分析仪说话

测试步骤:

  1. 以 60RPM 速度匀速旋转编码器
  2. 抓取 AB 相原始波形和消抖后波形
  3. 检查计数器增量与实际旋转步数是否一致

关键指标:

  • 抖动滤除率应 >95%
  • 最大跟踪转速≥120RPM
  • 反向旋转时计数必须递减

扩展思考

多编码器并联方案

  1. 矩阵扫描法:将 N 个编码器的 A 相连在一起,B 相分别接 IO
  2. 分时复用:用模拟开关切换不同编码器

中断模式优化

// 在中断中仅记录时间戳
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;
}

通过这个项目,我深刻体会到:硬件设计是数学,软件实现是哲学。希望这篇笔记能帮你少走弯路。如果有更好的实现方案,欢迎在评论区交流!

正文完
 0
评论(没有评论)