传感器 · EC11 旋转编码器(带按键 SW)
EC11 旋转编码器 STM32F103 接线与代码 实测通过
旋转编码器就是一个「带卡点的手摇旋钮」——转一格咔哒一下,单片机就 +1 或 -1,正转加、反转减,按下去还能当按钮用。音量旋钮、菜单滚轮、机床手轮都是这类东西。 本页是 STM32F103C8T6 + EC11 + 0.96 OLED 的完整实机验证记录:接线表、完整代码、工程 ZIP 全部实测通过。 最值得看的是「一个卡点为什么不能靠每个相位沿计数」和「快转为什么会漏计」两段——这两个坑几乎人人会踩。
Quick Facts
- 型号:EC11 旋转编码器(带按键 SW)
- 平台:STM32F103C8T6 + VS Code + PlatformIO + Arduino(STM32duino)
- SKU:待补充
- 更新:2026-08-16
Verified
实机验证结果
编译与烧录:PlatformIO 输出 Programming Finished → Verified OK → [SUCCESS],烧录成功。
| 测试动作 | 预期 | 实测结果 |
|---|---|---|
| 上电不动 | COUNT 保持 0 | COUNT 一直 0 |
| 顺时针转一格 | COUNT +1,DIR 显示 CW | 每格 +1 |
| 逆时针转一格 | COUNT -1,DIR 显示 CCW | 每格 -1 |
| 快速连续甩旋钮 | 不漏计 | 慢转快转都不漏 |
| 按 SW | 显示 PRESSED | 按下 PRESSED,松开 UP |
| 停止 500 ms | DIR 恢复 --- | 自动恢复 |
串口输出示例(115200):
Rotary encoder demo v1.5 starting...
Count: 1 | Dir: CW | SW: UP
Count: 2 | Dir: CW | SW: UP
Count: 3 | Dir: CW | SW: UP
Count: 2 | Dir: CCW | SW: UP
Count: 2 | Dir: --- | SW: PRESSED
Count: 2 | Dir: --- | SW: UP
实机验证环境(每一项都是真正用过的):
| 项目 | 本项目使用 |
|---|---|
| 主控 | STM32F103C8T6(BluePill) |
| 模块 | EC11 旋转编码器(带按键 SW) |
| 显示 | 0.96 寸 OLED,SSD1306,128×64,0x3C |
| 开发工具 | VS Code + PlatformIO |
| 框架 | Arduino / STM32duino |
| 烧录 | ST-Link |
| 串口 | 115200 |
| 依赖库 | Adafruit SSD1306、Adafruit GFX Library |
未测试的环境不当成支持:本项目没有在 Arduino IDE、Keil、STM32CubeIDE 下编译或验证过,不提供这些环境的支持说明。
About
这个模块干什么
转动旋钮时,内部两个触点 A、B 一前一后地通断,形成一组「正交信号」。STM32 靠 A、B 谁先谁后判断方向,靠转到定位点才计一格,再显示到 OLED 和串口。按键 SW 是常开型,按下接通。
三个术语:
- 卡点(detent):转一下「咔哒」一下的那个位置,一格就是一个卡点。
- 正交(quadrature):A、B 两根信号错开四分之一周期,靠先后顺序判断方向。
- 定位态:旋钮停下时 A、B 同为高或同为低的状态,卡点计数就靠它。
和电位器怎么选:
| 旋转编码器 EC11 | 电位器 | |
|---|---|---|
| 输出 | 数字脉冲,可无限旋转、可计数 | 模拟电压,只能转到头 |
| 用途 | 计数、菜单滚轮、音量档位 | 连续调节(如调亮度) |
| 要不要 ADC | 不要,走 GPIO | 要接 ADC 读电压 |
Reference
实用参数与资料
模块参数(本项目实测 / 厂家资料):
| 项目 | 说明 | 来源 |
|---|---|---|
| 输出 | A、B 两相正交方波,相位差约 90° | 资料参考 |
| 卡点数 | 常见 20 或 30 卡点 / 圈,以手上模块实际为准 | 资料参考 |
| 方向判断 | A 领先 B 是一种方向,B 领先 A 是另一种方向 | 资料参考 |
| 按键 SW | 旋钮按下时接通,常开型 | 资料参考 |
| 上拉 | 多数模块板已带上拉电阻;本项目用 STM32 内部上拉 | 本项目实测 |
| 供电 | 3.3 V,与主控共地 | 本项目实测 |
| 计数精度 | 一个机械卡点稳定 ±1 | 本项目实测 |
| 具体卡点数 / 引脚顺序 / 丝印 | 待确认(按手上模块批次为准) | 待确认 |
配套 OLED:0.96 寸 SSD1306,128×64,I2C 地址 0x3C。
Wiring
接线
编码器和 OLED 分开接,一共 9 根线。
编码器:
| 信号 | 编码器丝印 | STM32F103C8T6 |
|---|---|---|
| A 相 | CLK / A | PB0 |
| B 相 | DT / B | PB1 |
| 按键 | SW | PA0 |
| 电源 | + / VCC | 3.3V |
| 地 | GND | GND |
OLED:
| 信号 | OLED | STM32F103C8T6 |
|---|---|---|
| 电源 | VCC / VDD | 3.3V |
| 地 | GND | GND |
| 时钟 | SCL / SCK | PB8 |
| 数据 | SDA | PB9 |
最容易接错的三处(必须单独强调):
- OLED 的 I2C 用的是重映射引脚 PB8/PB9,不是默认的 PB7/PB6。 代码里要先
Wire.setSDA(PB9)、Wire.setSCL(PB8)再Wire.begin(),接错就读不到屏幕。 - 按键不要接 PB2。 PB2 在 STM32F103 上兼作 BOOT1,接东西可能影响启动,所以本项目 SW 改接 PA0。
- 必须共地。 编码器、OLED、STM32 的 GND 要接在一起,否则信号对不上。
- PA13 / PA14 留给 ST-Link 调试,不要占用。
How it works
工作原理
一个卡点为什么不能靠每个相位沿计数:一个完整的正交周期里有 4 次电平变化。如果每个沿都计一次,转一个卡点就会 +4 或 +2,而不是 +1。要一个卡点只 +1,必须只挑「到达定位点」的那一下来计。本项目用的是「到达定位态(A 和 B 同为高或同为低)才计一次」,配合 2 ms 去抖,实测一个卡点稳定 ±1。
快速旋转为什么会漏计:如果编码器靠主循环轮询,OLED 整屏刷新那几十毫秒里 CPU 读不到 A/B,快转时跳变就会漏掉。解决办法是改用引脚中断——A/B 一跳变就进中断计数,刷屏也挡不住。
为什么读数不会被显示拖慢:中断里只改数字、置一个「脏」标记;主循环里 OLED 至少隔 40 ms 才整屏刷一次,串口至少隔 100 ms 且有变化才打一行,把时间都让给编码器。
Code notes
关键代码说明
1. 把 I2C 指到 PB8 / PB9。 STM32duino 默认 I2C 是 PB7/PB6,必须手动指到重映射引脚,并升到 400 kHz 缩短整屏刷新时间。
Wire.setSDA(PB9);
Wire.setSCL(PB8);
Wire.begin();
Wire.setClock(400000); // 400kHz
2. 编码器走中断,不在主循环轮询。 A、B 任一引脚跳变就进中断,OLED 刷屏占了几十毫秒也不会漏掉旋钮跳变——这是解决「快转漏计」的关键。
attachInterrupt(ENCODER_A_PIN, encoderISR, CHANGE);
attachInterrupt(ENCODER_B_PIN, encoderISR, CHANGE);
3. 只挑「到达定位态」计一次。 中断里读 A/B 拼成状态,只在到达定位态(A==B)的跳变上计数,并用 2 ms 去抖。转一个卡点正好 ±1。
switch (transition) {
case 0b0100: // 01 -> 00 顺时针
case 0b1011: // 10 -> 11 顺时针
if (now - g_lastCountMs < 2UL) break;
g_state.count += 1;
g_state.direction = Direction::CW;
break;
case 0b1000: // 10 -> 00 逆时针
case 0b0111: // 01 -> 11 逆时针
if (now - g_lastCountMs < 2UL) break;
g_state.count -= 1;
g_state.direction = Direction::CCW;
break;
}
4. 中断里写、主循环里读的字段全部标 volatile,否则编译器优化后主循环可能一直读到旧值。
5. 按键消抖用轮询 + 20 ms 稳定判定,机械按键抖动期内不改状态。
Code
完整代码
完整代码见模块工程 src/main.cpp(v1.5,实测通过 2026-08-16)。网站页面上贴的就是这一份,一字未改。 依赖库 Adafruit SSD1306 和 Adafruit GFX Library 由 PlatformIO 按 platformio.ini 自动下载,不用手动装。
Troubleshooting
常见问题排查
OLED 完全不亮 / 串口打印 SSD1306 allocation failed → 十有八九是 I2C 引脚或地址。确认 SDA 接 PB9、SCL 接 PB8(不是默认的 PB7/PB6),代码里 setSDA/setSCL 必须在 Wire.begin() 之前调用。OLED 地址常见 0x3C,少数是 0x3D。
转一格 COUNT 跳 2 下或 4 下 → 计数写法不对。用了「每个相位沿都计数」,一个卡点里有 4 次电平变化。改成只在到达定位态(A==B)的跳变上计数。
慢转正常,快速甩旋钮会少计几格 → 编码器在主循环里轮询,被 OLED 刷屏阻塞了。改用 attachInterrupt(..., CHANGE) 把 A/B 都挂中断。
方向反了,顺时针在减 → A、B 两根线对调,或者代码里把 CW/CCW 两组 case 对调。本项目已验证:顺时针 = CW = 加。
按键按下没反应 / 一按就乱跳 → 先确认 SW 接的是 PA0 不是 PB2(PB2 兼作 BOOT1)。乱跳是没消抖,本项目用 20 ms 稳定判定。
串口看不到数据 → 先查当前串口:pio device list,找 CP2102 / CP210x / CH340 对应的那个 COM。需要看串口时在自己电脑的 platformio.ini 里加 monitor_port = COMx 和 monitor_speed = 115200,COMx 换成自己电脑实际识别到的端口,不要照抄我们截图里的 COM 号。正常用 ST-Link 烧录不需要加这两行。
Photos
实机图片
- 实机图:ec11-oled-overview.jpg — 实机接线全景,OLED 显示 ROTARY ENCODER v1.5 与 COUNT 数字(1000×1334,由实机原片降采样得到,未上采样)
Download
下载
Video
视频演示
成片制作中。B 站原片 素材\旋转编码器_实机演示.MOV(274 MB)不上站,做好网页版(<5 MB,带 poster)后再补。
Sources
资料来源
- 接线、代码、实测结果、编译烧录记录:本项目实测,2026-08-16 实机测试
- EC11 正交输出、卡点数、方向判断、按键类型、模块上拉:资料参考,来自公开资料与常规工程实践,由我们重新整理表述
- 具体卡点数、引脚顺序:待确认,以手上模块实际批次和丝印为准
- SKU:待补充