第四章:编码器与里程计
各位同学,大家好。今天我们来聊聊移动机器人最核心的感知部件——编码器,以及基于它构建的轮式里程计。说实话,编码器这东西看着不起眼,但它是机器人知道自己“走了多远”、“转了多少”的关键。我刚开始做机器人那会儿,总觉得GPS能搞定一切,后来在室内环境里吃尽了苦头,才老老实实回来研究编码器。
4.1 增量式编码器原理
增量式编码器,说白了就是“数脉冲”的。它内部有个码盘,上面刻着明暗相间的条纹。码盘一转,光敏元件就会输出一串脉冲信号。你数一数脉冲数量,就知道轴转了多少角度。
我个人习惯把增量式编码器比作“尺子上的刻度”。它不告诉你绝对位置,只告诉你“又往前挪了一格”。所以掉电后位置信息就丢了,得重新找零点。
核心参数:
- 分辨率(PPR): 每转一圈输出的脉冲数。常见的500、1000、2000、4096等。
- A/B相: 两路相位差90°的脉冲,用来判断正反转。
- Z相: 每转一圈输出一个脉冲,用于找零位。
为什么会需要A/B两相?你想想看,如果只有一路脉冲,你只能知道转了多少,但不知道往哪边转。A相领先B相90°是正转,B相领先A相90°就是反转。我在项目中遇到过电机抖动导致误判方向的情况,后来加了硬件滤波才解决。
避坑指南: 我曾经在高速电机上用过低分辨率的编码器(200 PPR),结果转速一高脉冲就丢失了。后来换成4096 PPR的,配合4倍频技术,相当于每转16384个计数,稳得很。
4.2 绝对式编码器原理
绝对式编码器就高级一些了。它每个位置都有唯一的编码值,掉电后位置信息不会丢失。上电就知道自己在哪里,不需要回零操作。
它的码盘上不是简单的明暗条纹,而是一圈圈格雷码或二进制码。每个角度位置对应一组唯一的码值。你读到的码值直接就是绝对角度。
| 特性 | 增量式 | 绝对式 |
|---|---|---|
| 掉电保持 | 否 | 是 |
| 分辨率 | 高(可4倍频) | 受限于位数 |
| 成本 | 低 | 高 |
| 接线 | 少(A/B/Z) | 多(并行/SSI) |
| 典型应用 | 轮式里程计 | 关节角度反馈 |
嗯,这里要注意:绝对式编码器虽然好用,但分辨率受限于位数。比如12位的绝对式编码器,一圈只有4096个位置。而增量式配合4倍频,轻松做到上万计数。所以高精度定位场景,还是得靠增量式。
4.3 编码器数据采集
数据采集这块,我踩过的坑最多。编码器信号看着简单,但实际采集时各种问题。
首先,编码器输出的是脉冲信号,需要MCU的定时器捕获。我一般用STM32的定时器编码器模式,硬件自动处理A/B相,直接读出计数值。
// STM32 定时器编码器模式配置示例
void Encoder_Init(void)
{
TIM_EncoderInterfaceConfig(TIM2, TIM_ENCODERMODE_TI12,
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
TIM_Cmd(TIM2, ENABLE);
}
// 读取编码器计数值
int16_t Encoder_GetCount(void)
{
return TIM_GetCounter(TIM2);
}
其次,信号抖动问题。电机启动瞬间,编码器信号会有毛刺。我建议在硬件上加RC滤波,软件上做去抖处理。我曾经遇到过编码器线缆太长,信号衰减导致计数不准,后来换了差分信号传输(RS422)才搞定。
重要提醒: 编码器采集频率要和电机控制频率匹配。一般建议采集频率是控制频率的2-4倍。比如控制频率1kHz,编码器采集至少2kHz。否则会丢失脉冲,里程计越跑越偏。
4.4 轮式里程计模型
有了编码器数据,怎么算出机器人的位置?这就是轮式里程计模型要干的事。
对于两轮差速底盘,模型很简单。左右轮各装一个编码器,测出左右轮的速度,然后推算机器人的线速度和角速度。
// 两轮差速里程计更新
void Odometry_Update(float dt)
{
float v_left = (left_encoder_count * wheel_circumference) / (encoder_ppr * 4 * dt);
float v_right = (right_encoder_count * wheel_circumference) / (encoder_ppr * 4 * dt);
float v = (v_left + v_right) / 2.0;
float w = (v_right - v_left) / wheel_base;
// 更新位姿
x += v * cos(theta) * dt;
y += v * sin(theta) * dt;
theta += w * dt;
}
说白了,里程计就是个积分过程。把每一小段的速度积分成位置。但积分有个问题——误差会累积。编码器本身有量化误差,轮子打滑会引入误差,地面不平也会引入误差。
我个人习惯在里程计中加入误差模型。比如给每个轮子加一个滑移系数,通过实验标定出来。这样虽然不能完全消除误差,但能延缓发散速度。
里程计误差来源:
- 系统误差: 轮径不准、轮距不准、编码器分辨率有限
- 非系统误差: 打滑、地面不平、轮胎变形
全向轮底盘的里程计模型稍微复杂一些。以麦克纳姆轮为例,每个轮子都有独立的编码器,需要根据轮子布局做运动学解算。
// 四轮麦克纳姆轮里程计
void MecanumOdometry_Update(float dt)
{
// 四个轮子的速度
float v1, v2, v3, v4;
// 从编码器读数计算轮子速度...
// 运动学逆解算
float vx = (v1 + v2 + v3 + v4) / 4.0;
float vy = (-v1 + v2 + v3 - v4) / 4.0;
float w = (-v1 + v2 - v3 + v4) / (4.0 * (L + W));
// 更新位姿
x += (vx * cos(theta) - vy * sin(theta)) * dt;
y += (vx * sin(theta) + vy * cos(theta)) * dt;
theta += w * dt;
}
下面这张图展示了编码器到里程计的完整数据流:
最后说一句,里程计这东西,单独用肯定不行。我做过一个测试,让机器人走10米直线,纯里程计误差大概在3-5%。走个来回,误差就累积到10%以上了。所以实际项目中,里程计一定要和IMU、视觉、激光雷达等传感器做融合,才能实现精准定位。
好了,编码器和里程计的内容就讲到这里。记住一句话:编码器是机器人的“脚”,里程计是机器人的“记忆”。脚要准,记忆才不会出错。
公众号:蓝海资料掘金营,微信deep3321