共计 1536 个字符,预计需要花费 4 分钟才能阅读完成。
当示波器遇上 EC11:那些令人头疼的毛刺信号
第一次用示波器观察 EC11 编码器输出时,我被眼前的波形吓了一跳——理论上应该干净的方波信号上布满了高频抖动(实测抖动宽度约 50-200μs)。这种噪声直接导致 Arduino 的 digitalRead() 误判,表现为旋转时计数跳变、方向随机错误。

实测 EC11 输出波形(抖动幅度 3.3V,频率约 8kHz)
硬件消抖 vs 软件消抖:性价比之战
方案 A:RC 硬件滤波
- 电路设计:在 A / B 信号线对地加 104 电容 +10k 电阻
- 成本:每通道约 0.2 元(批量价)
- 优点:
- 减少 CPU 处理负担
- 响应速度稳定(τ=RC=1ms)
- 缺点:
- 高速旋转时信号延迟明显
- 无法动态适应不同转速
方案 B:软件消抖(Debouncing)
- 实现方式:状态机 + 时间阈值判断
- 成本:仅需代码实现
- 优点:
- 可动态调整消抖时间(后文给出公式)
- 兼容不同型号编码器
- 缺点:
- 消耗 CPU 周期
- 需要精细调参
推荐选择:对成本敏感且转速 <100RPM 选硬件方案;需要高精度控制时用软件方案。
核心实现:状态机解码的艺术
状态转换图
+---------+ A↑ +---------+
| Wait |----->| A_High |
+---------+ +---------+
^ |
| B↑ | B↑
| v
+---------+ +---------+
| B_High |<-----| Both_H |
+---------+ A↓ +---------+
动态消抖算法
消抖时间阈值(ms) = 最大旋转周期 × 安全系数(建议 0.2)
例如:
– 300RPM → 周期 =200ms → 阈值 =40ms
– 30RPM → 周期 =2000ms → 阈值 =400ms
完整代码实现
// 引脚定义
#define ENC_A 2
#define ENC_B 3
volatile int count = 0;
unsigned long lastTime = 0;
void setup() {pinMode(ENC_A, INPUT_PULLUP);
pinMode(ENC_B, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENC_A), encoderISR, CHANGE);
}
void encoderISR() {
static uint8_t state = 0;
uint8_t aVal = digitalRead(ENC_A);
uint8_t bVal = digitalRead(ENC_B);
// 状态机实现
state = (state << 2) | (aVal << 1) | bVal;
if(millis() - lastTime < 40) return; // 动态消抖
switch(state & 0x0F) {
case 0x07: count++; break; // 正向
case 0x0B: count--; break; // 反向
}
lastTime = millis();}
性能测试数据
| 转速(RPM) | 硬件滤波准确率 | 软件消抖准确率 |
|---|---|---|
| 30 | 98% | 99.5% |
| 150 | 85% | 97% |
| 300 | 72% | 91% |
测试条件:5V 供电,1 米非屏蔽线,环境温度 25℃
必看的避坑指南
- 接地干扰:
- 使用星型接地布局
- 编码器金属外壳接 GND
-
信号线加磁环(尤其长距离传输时)
-
机械安装:
- 轴心偏移需 <0.5mm
- 避免轴向受力(会加速触点磨损)
-
推荐使用弹性联轴器
-
代码陷阱:
- 中断服务中不要用
Serial.print() - 全局变量必须加
volatile - 消抖时间不宜超过最小脉冲间隔的 1 /3
进阶思考:多编码器系统设计
当需要同时处理多个 EC11 时(比如机械臂关节控制),可以考虑:
1. 采用 IO 扩展芯片(如 PCA9555)
2. 为每个编码器分配独立定时器
3. 使用 RTOS 的任务优先级管理
下次可以聊聊我是怎么用 3 个 EC11+Arduino Due 实现六自由度机械手控制的——那又是另一个充满中断冲突和资源竞争的故事了。
正文完
