共计 1604 个字符,预计需要花费 5 分钟才能阅读完成。
背景痛点
旋转编码器在 Arduino 项目中常被用于精确的位置检测,但实际应用中经常会遇到以下问题:

- 信号抖动:机械接触式编码器在旋转时会产生瞬态抖动,导致误计数。
- 方向误判:A/ B 相信号的相位差解析不准确,导致旋转方向识别错误。
- 计数丢失:轮询方式读取时,高速旋转可能导致信号跳变被遗漏。
这些问题直接影响编码器的精度和可靠性,尤其在工业控制或机器人应用中更为明显。
硬件原理
增量式旋转编码器通常输出两路正交信号(A 相和 B 相),其波形关系如下:
- 顺时针旋转:A 相领先 B 相 90 度。
- 逆时针旋转:B 相领先 A 相 90 度。
通过检测 A / B 相的边沿变化和相位差,可以准确判断旋转方向和步数。
技术方案
轮询法 vs 中断法
- 轮询法:简单易实现,但效率低,可能丢失高速信号。
- 中断法:响应速度快,适合实时性要求高的场景,但需要合理设计中断服务程序。
状态机解码算法
- 使用 PORT 寄存器直接读取 :避免
digitalRead函数的开销,提升读取速度。 - 4 倍频计数:在 A / B 相的上升沿和下降沿均触发计数,实现更高分辨率。
- 时间窗口消抖:设置合理的时间阈值(如 5ms),忽略短时间内多次触发。
代码实现
以下是基于 PlatformIO 的 Arduino 代码示例:
#include <Arduino.h>
// 定义编码器引脚
#define ENC_A 2
#define ENC_B 3
volatile int32_t count = 0;
volatile uint8_t state = 0;
void setup() {
// 配置引脚为输入,启用内部上拉
pinMode(ENC_A, INPUT_PULLUP);
pinMode(ENC_B, INPUT_PULLUP);
// 配置中断,在 A 相边沿触发
attachInterrupt(digitalPinToInterrupt(ENC_A), encoderISR, CHANGE);
}
void encoderISR() {
static uint32_t lastTime = 0;
uint32_t currentTime = millis();
// 消抖处理
if (currentTime - lastTime < 5) return;
lastTime = currentTime;
// 读取当前 A / B 相状态
uint8_t a = digitalRead(ENC_A);
uint8_t b = digitalRead(ENC_B);
// 更新状态机
uint8_t newState = (a << 1) | b;
if (state == 0x00 && newState == 0x02) count++;
if (state == 0x02 && newState == 0x03) count++;
if (state == 0x03 && newState == 0x01) count++;
if (state == 0x01 && newState == 0x00) count++;
state = newState;
}
void loop() {
// 主循环中可以安全使用 count 变量
Serial.println(count);
delay(100);
}
避坑指南
- 上拉电阻选择:10kΩ 是常见选择,但高速应用可能需要更低阻值(如 4.7kΩ)。
- 中断优先级:多编码器系统中,需确保高优先级编码器的中断能及时响应。
- 机械安装:编码器轴与旋转部件需严格同轴,避免信号不同步。
性能验证
使用示波器观察 A / B 相信号,确保:
- 信号边沿清晰,无过多抖动。
- 旋转方向变化时,相位差正确。
- 计数结果与实际旋转步数一致。
扩展应用
将编码器计数用于 PID 控制时,位置差计算公式为:
error = target_position - current_position;
通过 PID 算法调整输出,实现精准的位置控制。
动手实验
尝试修改代码,实现以下功能:
- 将 4 倍频计数改为 2 倍频或单边沿计数,观察分辨率变化。
- 添加方向输出,实时显示旋转方向。
- 测试不同消抖时间阈值(如 2ms、10ms)对计数稳定性的影响。
通过以上实践,可以更深入理解旋转编码器的工作原理和优化方法。
正文完
