← 返回传感器图鉴

旋转编码器(EC11 增量式)

最后更新 2026-06-20
⏱ 约 9 分钟 🟢 软件/低风险
🛒 器材清单
器材数量参考
EC11 旋转编码器模块1

价格随渠道波动,以购买页实时为准。

想给作品做一个能转、又能按下确认的菜单旋钮——左转选上一项、右转选下一项、按一下进入——第一反应往往是拿电位器顶上去。可电位器转不满一圈(机械行程通常只有约 270°),给的还是一个连续的模拟电压,得自己换算成档位,转到头就转不动了,做菜单很别扭。这时候真正顺手的是 EC11 这类增量式旋转编码器:它能无限往一个方向转下去,不分头尾,每转过一"格"都给一次脆生生的"咔哒"手感,告诉单片机"刚才往哪个方向转了一格"。绝大多数 EC11 还在转轴里集成了一个按下开关(SW),旋钮按下去就是一次确认,转和按一颗器件全包了。

它给的是相对增量——"比刚才多转了几格、朝哪转",而不是"现在指在哪个绝对角度"。这点先记牢,后面选型反复用到。

工作原理

EC11 内部其实是两个机械开关触点,习惯叫 A 相B 相(模块上常标成 CLK 和 DT)。旋钮一转,转盘上的导电片依次划过这两个触点,让 A、B 各自周期性地通断。关键在于:A 和 B 在转盘上错开了位置,所以它俩通断的时刻不重合,输出两路相位差约 90° 的方波——这叫正交编码(quadrature)。

为什么要错开 90°?因为方向就藏在这个相位差里。正转时,A 总是比 B 先变化;反转时,反过来 B 先变。换句话说,在 A 发生跳变的那一瞬间去看 B 此刻是高还是低,就能断定旋钮在往哪个方向转:

  • 往一个方向转:A 下降沿那一刻,B 是低 → 计数 +1;
  • 往另一个方向转:A 下降沿那一刻,B 是高 → 计数 −1。

每捕捉到一次 A 的跳变就计一步,把这些步累加起来,就知道"从开机到现在一共净转了多少格、当前停在哪一档"。这就是"增量"二字的含义:编码器自己不存绝对角度,是单片机在累加。一旦断电,计数清零,重新上电它并不知道旋钮原先指在哪——这是增量式和绝对式最本质的区别。

EC11 是机械触点,触点闭合的瞬间会"弹跳",一次真正的转动可能在微秒级里产生好几次假跳变。不处理的话计数会乱蹦,所以去抖是必须的,后面代码里会专门处理。另外两路触点都是开关,没接通时引脚电平是浮空的,必须配上拉把空闲电平顶高。

接线

常见的 EC11 模块(带小电路板那种)一共五个脚:CLK(A 相)、DT(B 相)、SW(按下开关)、加 VCC/GND。

EC11 模块 ESP32 说明
VCC 3.3V 模块上若有上拉电阻,供电要和 GPIO 同压,别接 5V
GND GND 共地
CLK 任意 GPIO(如 18) A 相,数字读,需上拉
DT 任意 GPIO(如 19) B 相,数字读,需上拉
SW 任意 GPIO(如 5) 按下开关,另一端到地,需上拉

三路信号脚都要上拉。模块板上通常已焊了上拉电阻,直接接即可;若用的是裸编码器(无电路板),就在代码里用 INPUT_PULLUP,或外接 10kΩ 到 3.3V。注意供电别接 5V——板载上拉会把高电平顶到 5V 灌进 ESP32 的 GPIO,3.3V 的脚扛不住。

完整代码

读编码器最稳的做法是用中断捕捉 A 相,而不是在 loop 里反复 digitalRead 轮询。原因很实在:旋钮转快时一格只持续几毫秒,loop 要是被串口打印、刷屏这类活儿卡住一会儿,轮询就会漏掉跳变、丢格。中断不管主程序在忙什么,A 相一变化立刻进中断函数把方向和计数处理掉,丢格的概率小得多。

const int PIN_CLK = 18;   // A 相
const int PIN_DT  = 19;   // B 相
const int PIN_SW  = 5;    // 按下开关

volatile long encoderCount = 0;        // 累加的格数(中断里改,加 volatile)
volatile unsigned long lastTurnUs = 0; // 上次有效跳变的时刻,用于去抖
const unsigned long TURN_DEBOUNCE = 2000; // 转动去抖 2ms(按需调)

// A 相中断:在 A 跳变时看 B 的状态来定方向
void IRAM_ATTR onEncoder() {
  unsigned long now = micros();
  if (now - lastTurnUs < TURN_DEBOUNCE) return; // 太密的跳变当抖动丢掉
  lastTurnUs = now;

  // A 跳变这一刻,B 与 A 是否一致决定方向
  if (digitalRead(PIN_DT) != digitalRead(PIN_CLK)) {
    encoderCount++;   // 一个方向
  } else {
    encoderCount--;   // 另一个方向
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(PIN_CLK, INPUT_PULLUP);
  pinMode(PIN_DT,  INPUT_PULLUP);
  pinMode(PIN_SW,  INPUT_PULLUP);
  // 捕捉 A 相的跳变(电平变化即触发)
  attachInterrupt(digitalPinToInterrupt(PIN_CLK), onEncoder, CHANGE);
}

long lastReported = 0;
bool lastBtn = HIGH;
unsigned long lastBtnMs = 0;

void loop() {
  // 计数变了才打印,避免刷屏
  noInterrupts();
  long count = encoderCount;
  interrupts();
  if (count != lastReported) {
    Serial.print("计数: ");
    Serial.println(count);
    lastReported = count;
  }

  // 读按下开关,软件去抖 30ms(按键接地,按下读到 LOW)
  bool btn = digitalRead(PIN_SW);
  if (btn != lastBtn && millis() - lastBtnMs > 30) {
    lastBtnMs = millis();
    if (btn == LOW) Serial.println("按下确认!");
    lastBtn = btn;
  }
}

想让 AI 帮你改写,把硬件说清楚即可:「ESP32 用 GPIO18/19 接 EC11 的 CLK/DT(INPUT_PULLUP),中断读 A 相判方向计数,GPIO5 接 SW 做带去抖的确认键,转动时打印累计格数」,生成的代码基本能直接跑。去抖时间(转动 2ms、按键 30ms)按手感微调即可。

你应该看到什么

烧录后打开串口监视器,慢慢顺时针拧旋钮:每过一格会有一声"咔哒"手感,串口的计数随之 +1、+2、+3……往上走。反过来逆时针拧,计数 −1、−2 往下走。来回拧到哪停就稳在哪个数,不会自己乱跳。按一下旋钮(按下转轴),串口打印一行"按下确认!"。

如果顺时针拧计数反而减小,说明 A、B 接反了,把 CLK 和 DT 两根线对调即可。如果计数偶尔一次跳两三个数、或来回轻晃就乱加,多半是去抖时间太短,把代码里的去抖值调大些再试。

读数解读

这个计数是相对增量,不是绝对角度。它告诉你的是"从程序启动(计数为 0)到现在,旋钮净转了多少格、朝哪个方向",正数一个方向、负数另一个方向。当前的菜单选项 = 起始项 + 计数对菜单条目数取模,方向就是正负号。

要把计数换算成"转了几圈",得先知道这颗编码器一圈多少格。EC11 最常见的是一圈 20 格(20 个定位点,转一圈听到 20 声咔哒),也有 15、24、30 等规格,看具体型号的 datasheet。所以"计数 40、一圈 20 格"就是净转了两圈。

再强调一遍:它不存绝对位置。断电再上电,计数从 0 重新开始,编码器并不知道旋钮物理上指在哪。需要"开机就知道当前角度"的场合,增量式做不到,得换绝对式。

选型 / 避坑

选之前先问自己要的是"无限转的相对档位"还是"确定的绝对角度":

  • 无限旋转、相对计数、还带按下——菜单旋钮、音量、参数微调——增量式 EC11 正中下怀,几块钱搞定,转一格一个数。它和电位器的根本区别是:电位器只能转约 270°、给的是绝对的模拟电压(转到头就到头),EC11 能无限转、给的是数字增量。
  • 断电也记得、要绝对角度——比如云台、机械臂关节回原、油门旋钮——得用绝对式编码器,或磁编码器 AS5600(直接给 0–360° 的绝对角度,无接触、无机械磨损)。
  • 只是想测电机/转轮的转速——那不需要编码器,贴颗磁铁用霍尔开关 A3144 数脉冲算 RPM 更省。
🚧 避坑

机械触点会弹跳,必须去抖:EC11 是机械开关,一次转动可能产生多次假跳变。不去抖(硬件加小电容,或软件按时间间隔过滤)就会乱计数、一格跳好几个数。 ② A/B 接反,方向就反:顺时针拧反而减小,把 CLK 和 DT 两根线对调即可,不是坏了。 ③ 三路信号都要上拉:CLK/DT/SW 不上拉,空闲电平浮空,读数会乱飘。用模块板自带上拉,或 INPUT_PULLUP

故障排查

现象 可能原因 排查
转动毫无反应 没上拉 / 接线松 / 没绑中断 INPUT_PULLUP;确认 CLK 接到了能触发中断的 GPIO
顺时针拧计数反而减小 A、B 相接反 对调 CLK 和 DT 两根线
轻晃一下就乱加好几个数 触点弹跳,去抖太弱 调大转动去抖时间;裸编码器可在 A/B 对地各加 0.01µF 电容
计数偶尔丢格、快转更明显 在 loop 里轮询被卡住 改用中断读 A 相;缩短中断函数、别在里面打印
按下没反应 SW 未上拉 / 判断逻辑反了 SW 用 INPUT_PULLUP,按下读 LOW;确认 SW 另一端接地
读数偶发乱跳一大片 供电接了 5V 灌进 GPIO 模块改接 3.3V,别让板载上拉把电平顶到 5V

进阶 / 变体

  • 做菜单导航 + 长按:转动改菜单高亮项、按下是"进入/确认",再加一个"长按 SW 超过 1 秒 = 返回上一级",转、点、退三种操作一颗旋钮全包,配一块 OLED 就是完整的旋钮式 UI。
  • 加速旋转(动态步进):检测两次跳变的时间间隔,转得快时一格多加几个数(比如快转一格 +5、慢转 +1),调大范围数值时不用拧到手酸,这是相机、示波器旋钮的常见手感。
  • 断电记住档位:增量式本身不记位置,但可以把当前计数存进 ESP32 的 NVS/Flash,下次上电读回来,自己"补"出一个伪绝对位置。
  • 接入逻辑:编码器是数字信号输入,读法和按键同源,可把当前档位接到 OLED 显示,或喂给上层应用做参数调节面板。

典型应用

  • 菜单导航旋钮:转选项、按确认、长按返回,小屏设备最顺手的输入方式。
  • 音量 / 亮度控制:无限旋转调大小,比触摸滑条更有手感、更精准。
  • 参数微调:调温度、频率、PID 系数这类需要细调又要大范围扫的数值。
  • 3D 打印机控制面板:选菜单、调进给倍率、微调 Z 轴偏移,几乎是标配。
  • 步进微调:给数控设备、云台做手动点动微调,转一格走一步。

小结

EC11 增量式旋转编码器,本质是两个错开 90° 的机械触点 A、B:旋钮一转,两路输出相位差 90° 的正交方波;在 A 跳变那一刻看 B 的状态,就能判定转向,每跳一次累加一步,于是知道"净转了几格、朝哪转"。它给的是相对增量不是绝对角度,断电清零。用好它记住三件事:机械触点会弹跳、必须去抖A/B 接反方向就反,对调两线即可;三路信号都要上拉。读编码器优先用中断读 A 相,比轮询少丢格。EC11 常见一圈 20 格、多数自带按下开关,转和按一颗全包。需要无限转的相对档位、带按下,选它;要绝对角度、断电也记得,换磁编码器 AS5600 或绝对式;只想测转速,用霍尔 A3144 数脉冲。

相关阅读:搞清它和电位器分压的区别、数字与模拟信号上拉/下拉电阻(触点空闲必备),以及读法同源的按键输入与配屏的 OLED 显示。更多模块见传感器总览原理篇

参数以 datasheet 为准;本页公开资料整理,接线与代码请结合实物验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为公开资料的学习整理,非亲测。涉接线/花钱/合规的步骤请结合实物与官方最新资料验证,风险自负。见免责声明