从光栅盘到数字信号:手把手拆解增量式编码器,并用Arduino做个转速计
从光栅盘到数字信号:手把手拆解增量式编码器,并用Arduino做个转速计
在工业自动化和机器人控制领域,编码器如同设备的"眼睛",将机械运动转化为电子系统能理解的数字信号。增量式编码器因其结构简单、成本低廉且性能可靠,成为DIY爱好者和工程师最常接触的传感器类型之一。本文将带您深入理解这种精巧装置的工作原理,并完成一个实用的Arduino转速测量项目。
1. 增量式编码器的机械解剖
拆开一个典型的欧姆龙E6B2系列编码器外壳,内部结构主要由三部分组成:光栅盘、光电耦合器组和信号处理电路。光栅盘通常由玻璃或聚碳酸酯制成,上面刻有精确等距的透光条纹。以E6B2-CWZ6C为例,其光栅盘刻有360个条纹,意味着每转可产生360个脉冲。
光电检测装置包含至少两组红外LED和光敏晶体管,它们以特定角度排列:
[LED] → [光栅盘] → [光敏晶体管A] ↘ [光敏晶体管B]当光栅盘旋转时,光线被周期性遮挡,产生两路相位差90°的方波信号。这种设计巧妙之处在于:
- A相和B相的相位关系决定了旋转方向
- 脉冲频率直接对应转速信息
- Z相(零位信号)提供绝对位置参考
2. 电气接口与信号解读
常见增量式编码器采用5V或24V供电,输出信号通常有三种接口形式:
| 接口类型 | 信号特点 | 传输距离 | 典型应用场景 |
|---|---|---|---|
| TTL | 5V电平,A/B/Z三路 | ≤5米 | 实验室环境 |
| HTL | 24V电平,抗干扰更强 | ≤100米 | 工业现场 |
| 差分驱动 | RS422标准,A+/A-等对称 | ≤300米 | 高噪声环境 |
使用示波器观察旋转时的输出信号,可以看到:
A相: _|‾|_|‾|_|‾|_|‾ B相: _|‾|_|‾|_|‾|_|‾ ↑ A相领先表示顺时针旋转 ↓ B相领先表示逆时针旋转方向判断逻辑:
- 当A相上升沿时检测B相电平:高→正转,低→反转
- 或使用XOR门电路:A⊕B=正转脉冲,A⊕B'=反转脉冲
3. Arduino硬件连接实战
准备材料清单:
- Arduino Uno开发板
- E6B2-CWZ6C编码器(5V供电)
- 10kΩ上拉电阻×3
- 0.1μF去耦电容
- 面包板和跳线
接线示意图:
编码器 Arduino ----------------- V+ → 5V GND → GND A → D2(外部中断0) B → D3(外部中断1) Z → D4(可选)注意:工业编码器输出通常是开集电极形式,需加上拉电阻。若使用带推挽输出的编码器模块,则可省略上拉电阻。
消抖电路设计建议:
A相 → 10kΩ → Arduino ↑ 100nF → GND4. 转速测量算法实现
转速计算的核心公式:
RPM = (脉冲数 × 60) / (编码器线数 × 采样周期)Arduino代码框架:
volatile long pulseCount = 0; unsigned long lastTime = 0; const int PPR = 360; // 每转脉冲数 void setup() { Serial.begin(115200); attachInterrupt(0, countPulse, RISING); // D2接A相 } void loop() { if(millis() - lastTime > 100) { // 每100ms计算一次 noInterrupts(); long currentCount = pulseCount; pulseCount = 0; interrupts(); float rpm = (currentCount * 60.0) / (PPR * 0.1); Serial.print("RPM: "); Serial.println(rpm); lastTime = millis(); } } void countPulse() { if(digitalRead(3)) pulseCount++; // B相高电平 else pulseCount--; // B相低电平 }性能优化技巧:
- 使用硬件中断而非轮询,确保不丢失脉冲
- 采用环形缓冲区存储多次测量结果进行滑动平均
- 对于高速测量,启用输入捕获功能精确记录边沿时间
- 启用Arduino的输入去抖功能:
EICRA |= (1 << ISC00)
5. 进阶功能扩展
5.1 零位校准系统
利用Z相实现自动校准:
void setup() { pinMode(4, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(4), zeroCalibrate, FALLING); } void zeroCalibrate() { pulseCount = 0; // 重置计数器 Serial.println("Zero position detected"); }5.2 四倍频技术提升分辨率
通过检测A、B相的上升沿和下降沿,将分辨率提高4倍:
void countPulse() { static byte lastAB = 0; byte currentAB = (digitalRead(2) << 1) | digitalRead(3); switch(lastAB << 2 | currentAB) { case 0b0001: case 0b0111: case 0b1110: case 0b1000: pulseCount++; break; case 0b0010: case 0b1011: case 0b1101: case 0b0100: pulseCount--; break; } lastAB = currentAB; }5.3 通过I2C LCD显示实时数据
连接1602 LCD显示屏显示测量结果:
#include <Wire.h> #include <LiquidCrystal_I2C.h> LiquidCrystal_I2C lcd(0x27, 16, 2); void setup() { lcd.init(); lcd.backlight(); } void loop() { lcd.setCursor(0,0); lcd.print("Speed:"); lcd.setCursor(7,0); lcd.print(rpm); lcd.setCursor(12,0); lcd.print("RPM"); }6. 常见问题排查指南
当遇到信号不稳定或计数不准时,可按以下步骤诊断:
电源检查
- 测量编码器供电电压应在4.75-5.25V范围
- 示波器观察电源纹波应<50mV
信号完整性测试
# 使用Arduino作为简易逻辑分析仪 void setup() { Serial.begin(250000); } void loop() { Serial.write(PIND); // 直接读取D0-D7状态 delayMicroseconds(10); }将输出数据导入串口绘图工具观察波形
机械安装问题
- 检查联轴器是否同心,径向偏差应<0.1mm
- 确保轴系无轴向窜动
- 过大的振动会导致脉冲抖动
软件滤波方案
// 移动平均滤波实现 #define FILTER_SIZE 5 float rpmBuffer[FILTER_SIZE]; byte filterIndex = 0; float filteredRPM(float newRPM) { rpmBuffer[filterIndex] = newRPM; filterIndex = (filterIndex + 1) % FILTER_SIZE; float sum = 0; for(byte i=0; i<FILTER_SIZE; i++) { sum += rpmBuffer[i]; } return sum / FILTER_SIZE; }
在实际项目中,我曾遇到一个棘手案例:当电机启动瞬间,转速显示会出现剧烈波动。后来发现是电源地线环路导致,通过在编码器电源端增加磁珠滤波,并将信号地单点接入系统,问题得到完美解决。
