共计 1255 个字符,预计需要花费 4 分钟才能阅读完成。
背景痛点
作为刚接触电机控制的开发者,第一次用 51 编码器测速时经常会遇到这些问题:

- 信号抖动 :机械抖动导致 AB 相产生毛刺,计数器误触发
- 计数丢失 :电机高速旋转时脉冲密集,普通轮询方式漏计数
- 转速误差 :未考虑测量周期与脉冲数的匹配关系,计算结果波动大
这些问题本质上是硬件信号处理和软件算法设计未协同优化的结果。下面我们通过完整的实现方案来逐个击破。
技术原理
增量式编码器工作方式
常见的 200 线增量编码器(旋转一周产生 200 个脉冲)通过 AB 两相输出相位差 90°的方波:
A 相: ___|‾‾‾|___|‾‾‾|___
B 相: _|‾‾‾|___|‾‾‾|___|‾
↑ A 相上升沿触发计数
→ B 相电平决定方向
M 法与 T 法测速对比
- M 法(频率法):固定时间内统计脉冲数,适合中高速测量
转速 (rpm) = (脉冲数 × 60) / (编码器线数 × 测速周期 (s)) - T 法(周期法):测量单个脉冲周期,适合低速高精度场景
硬件设计
典型接口电路需注意:
- 上拉电阻 :10kΩ 电阻避免引脚悬空
- 滤波电路 :0.1μF 电容并联在 AB 相到地
- ESD 保护 :TVS 二极管防止静电损坏
编码器 51 单片机
A 相 ——电阻——> P3.2(INT0)
B 相 ——电阻——> P3.3(INT1)
↑
电容
↓
GND
代码实现
核心宏定义
#define ENCODER_LINES 200 // 编码器线数
#define SAMPLE_MS 50 // 采样周期 (ms)
#define WHEEL_DIAMETER 6.5 // 轮子直径 (cm)
中断服务函数
volatile int pulseCount = 0; // 必须声明为 volatile
void INT0_ISR() interrupt 0 {if(digitalRead(PIN_B)) pulseCount++;
else pulseCount--;
// 软件消抖延时 2us
__asm__ __volatile__("nop");
__asm__ __volatile__("nop");
}
定时器测速
float getSpeedRPM() {
static int lastCount = 0;
int delta = pulseCount - lastCount;
lastCount = pulseCount;
return (delta * 60.0 * 1000) /
(ENCODER_LINES * SAMPLE_MS);
}
避坑指南
- 中断触发方式选择
- 低速场景:用双边沿触发提高分辨率
-
高速场景:单边沿触发降低 CPU 负载
-
脉冲丢失应对
- 启用输入捕获功能
-
使用硬件计数器 (如 PCA 模块)
-
软件消抖优化
- 两次采样法:间隔 10us 重复检测
- 状态机滤波:连续 3 次相同状态才确认
性能验证
| 设定转速 (rpm) | 测量值 (rpm) | 误差率 |
|---|---|---|
| 300 | 297 | 1% |
| 800 | 812 | 1.5% |
| 1500 | 1533 | 2.2% |
进阶思考
- 如何通过 Z 相信号实现位置归零?
- 当电机转速超过编码器最大响应频率时该怎么办?
- 怎样用最小二乘法对转速数据进行平滑处理?
通过这套方案,我在智能小车项目中将测速误差控制在 3% 以内。关键是要理解:硬件消抖是基础,中断优化是核心,算法匹配是灵魂。建议先用示波器观察原始信号质量,再逐步调试软件参数。
正文完
