先说我自己的结论:轮式编码器里程计这东西,看着简单,真正把它调明白,里面全是细节。我最早做差速机器人底盘的里程估计时,以为就是数脉冲、乘个系数、累加坐标,结果一跑起来,画出来的圆弧歪得没法看。后来硬着头皮把误差一项项拆开,重新标定轮径、轮距,甚至重新设计了一下编码器的安装方式,才算是把这个“看似人人都能做,但人人做出来精度都不一样”的活儿给摸透了。
这篇东西我就按自己做项目时踩坑的顺序来聊。从硬件选型、底层原理、算法实现,到最烦人的标定和误差排查,全部串一遍。无论你是刚入门准备给自己小车补一个定位模块,还是已经在用编码器但觉得精度始终差那么点意思,这篇文章应该都能给你提供一些可以直接落地的参考。
1. 项目概述与整体思路
1.1 轮式编码器里程计到底在解决什么问题
先说清楚它是什么。轮式里程计,英文叫Wheel Odometry,本质上是利用安装在不同驱动轮上的编码器,记录每个轮子转了多少圈、以什么速度转,然后通过一个运动学模型,在二维平面上推算出机器人相对于起始位置的位姿变化,也就是X坐标、Y坐标和朝向角。
机器人的定位方案很多,激光雷达、视觉SLAM、惯性导航,哪一个听起来都比编码器高级。但编码器里程计有一个其他传感器都替代不了的价值:它输出的频率可以很高,而且短期内非常平滑稳定。激光雷达一帧数据可能是10赫兹,IMU高频但会漂,视觉可能直接丢帧。编码器则是每毫秒就能给出一条轮速推算值,不会突然跳到离谱的位置,局部短时间内的位移测算非常可信。
这就决定了它的核心应用场景:第一,作为扩展卡尔曼滤波或粒子滤波的预测输入,在主传感器刷新间隙里补充位姿;第二,在弱纹理、无GPS、无特征的室内通道或仓库环境下做短时间的自主定位兜底;第三,给低成本的差速底盘提供闭环速度反馈。我自己的项目里,它主要就是负责“守底”,让机器人即使暂时失联环境传感器,也能靠自己的腿知道走到了哪里。
1.2 为什么轮式编码器依然是性价比很高的选择
很多人问我,既然有那么多更“聪明”的传感器,为什么还要做轮式编码器的里程估计。我一般会回答三个词:便宜、可靠、够用。
先说便宜。一个带AB相输出的增量式霍尔编码器,加在普通直流减速电机尾端,整套下来可能就几十块钱。一套精度还不错的激光雷达可能要大几千,一个能满足SLAM需求的深度相机也要一两千,而编码器的成本和它们相比几乎可以忽略。对于项目预算有限、但又需要稳定里程来源的自主移动机器人来说,这个优势非常明显。
再说可靠。编码器测量的是轮子的机械转动,不依赖外部环境。在光线变化剧烈的走廊里,视觉里程计可能就挂了;在光滑反光的地面上,激光匹配也可能出问题。但编码器只要轮子在转,它就有输出,几乎不受光照、粉尘、电磁干扰影响。我做过一次在傍晚夕阳直射的楼道里跑直线,视觉方案差点飘掉,但编码器里程计依然稳稳当当地数着脉冲。
最后说够用。如果你不是要做高精度建图,而只是让机器人在房间里大致知道自己走了多远、转了多少度,编码器里程计完全够。在短距离、平整地面的场景下,它的误差累积速度其实没那么夸张。定期检修、正确标定之后,做几十米来回往返,回到原点的精度做到厘米级是完全可以实现的。
1.3 适合什么场景,又不适合什么场景
任何技术都有边界,编码器里程计也一样,提前搞清楚它适合干什么,能避免很多后期返工。
适合的场景有三个共同点:轮子与地面接触良好、地面相对平整、行驶距离相对短。比如室内办公区巡检机器人、仓库地板上的AGV、餐厅的送餐底盘、教学用的差速小车,这些都是编码器里程计的舒适区。在这些场景下,配合一个合适的标定流程,里程计的短期精度非常可靠。
不适合的场景包括长距离越野、地面湿滑或者有大量微小碎石的环境,还有机器人经常原地疯狂打转的场景。一旦轮子出现打滑,编码器依然会计数,但实际车体没有动,误差瞬间就累积出来。有人踩过坑:在光滑的瓷砖地面让机器人高速急转弯,位置估计直接“漂移”了半米。此外,长时间行驶时,由于它的误差是积分性质的,会不断累积,所以不能单独依赖它做长时间的绝对定位,需要配合其他传感器做定期校正。这是我做一个扫地机器人底盘项目时最深刻的体会,跑一圈下来,回环闭合时发现起点位置已经偏了十几厘米。
2. 硬件选型与关键概念详解
2.1 编码器类型到底怎么选,增量式还是绝对式
做轮式里程计,第一步是选编码器,这一步直接决定了后面所有软件能达到的上限。
编码器按输出类型分,有增量式和绝对式两种。增量式编码器输出的是脉冲信号,只能告诉控制器“轮子转了多少步”,但断电后无法获知当前绝对角度。绝对式编码器则能在上电瞬间输出当前轴的绝对位置。从价格和常见的移动机器人底盘设计来看,增量式编码器是绝对的主流。原因很简单:里程计需要的是“相对变化量”,本来就不关心上电时的绝对角度,所以增量式完全够用。
增量式编码器内部又分为光电式和磁电式。光电式编码器精度高、响应快,但怕尘、怕油污;磁电式编码器结构简单、抗污性强,价格低廉,是现在很多直流减速电机尾置编码器的标配。我的经验是,用在教育机器人、室内AGV上的话,磁电式增量编码器完全够用,没必要迷信光电式的更高分辨率。反而是编码器与轮轴的装配同心度更值得关注,装偏了再高的分辨率都会被引入的周期误差毁掉。
2.2 几个必须搞清楚的参数:PPR、减速比、轮径
很多新手一上来直接看编码器“是否带AB相”,然后就下单了,这是不行的。决定里程精度的参数至少有四个:编码器每转脉冲数PPR、电机减速比、轮子的有效半径、驱动器是否支持AB相四倍频解码。
PPR全称Pulses Per Revolution,含义是电机输出轴转一圈所能产生的脉冲数。不过要注意,很多编码器是安装在电机尾端的,测量的是电机转子端的转速,不是轮子端。如果电机前端有减速箱,轮子转一圈对应的编码器脉冲数就需要乘上减速比。
举个例子:一个常见的小车电机,参数标注为减速比1:30,尾部霍尔编码器PPR是20。那么轮子转一圈,编码器会产生 20×30 = 600 个脉冲。如果用AB相四倍频模式,控制器实际可以数出 600×4 = 2400 个计数步。轮子的有效半径是0.0325米,周长就是大约0.204米,也就是说,每一个编码器计数步对应的实际位移约为0.085毫米,这个精度对小车的短距离自定位来说已经非常可观。
从这些参数关系里可以得出一个结论:不要只盯着PPR看增量,减速比和四倍频带来的“等效分辨率提升”往往比单纯提高编码器线数更明显、更划算。我见过很多项目,硬件上买了一堆高分辨率编码器,结果驱动板不支持四倍频解码,白花了不少钱。
2.3 安装与接线,这部分比你想的更影响精度
编码器装得好不好,对里程计精度的影响,甚至大过于编码器本身的分辨率。这里面最需要注意的是安装同心度。
如果编码器的码盘或磁铁安装得和轮轴不同心,轮子每转一圈,输出脉冲的间隔就会忽大忽小,呈周期性波动。这种波动很难通过标定完全消除,因为它不是恒定误差,而是一种随转角变化的正弦误差。在低分辨率下可能感知不明显,但在高分辨率下会发现车明明匀速直线走,轮速曲线却有规律地抖动。
所以我在做底盘的时候,会特别检查三样东西:
- 编码器码盘/磁铁的安装是否居中,可以使用分中测量工具辅助;
- 编码器的读头与码盘之间的间隙是否均匀,不能有倾斜;
- 电机轴和轮子的连接是否紧固,不能出现轴向窜动或径向松动。
接线方面同样不能草率。AB两相脉冲信号是高频率方波,线束太细、距离太长、没有屏蔽,都可能导致脉冲丢失或产生额外计数。控制器端要接上拉电阻,保证空闲状态下信号线为确定电平。曾经遇到过一次很诡异的现象:机器人静止时,里程计的数值仍然在变化。排查了半天,发现是差分驱动板出来的编码器线与电机大电流线绑在同一束线卡里,瞬间尖峰干扰直接灌进编码器信号里,把脉冲整出了毛刺。后来把编码器线和动力线彻底分开走,问题就消失了。
3. 核心原理解读与数学模型推导
3.1 增量式编码器计数背后的机制
咱们用一个简单例子来理解编码器计数的基本原理。增量式编码器内部通常有一圈均布的栅格,随着电机轴旋转,传感器会输出两路互相错开90度相位的方法信号,也就是A相和B相。
A、B两相之间的相位关系非常关键。正转时,A相上升沿相对于B相提前90度;反转时则反过来。控制器通过检测这两路信号的电平变化状态,既可以判断旋转方向,又可以把每个脉冲分为4个边沿事件,从而实现“四倍频”计数。这就是为什么光学或磁编码器只有几百线,最终却可以得到几千个计数步的分辨率。
如果只用单相计数,不仅无法区分正反转,分辨率还低,一旦在零速附近有抖动,计数还会乱跳。所以我强烈建议做差速底盘的里程计,至少要用带AB相输出的编码器,并且软件里要正确实现正交解码逻辑,而不是简单地对单个边沿计数。
3.2 差驱底盘的直线速度与角速度换算
双轮差速底盘是目前小型移动机器人上最常见的驱动结构,左右轮各自独立驱动。里程计推算的第一步,是把左右轮各自的转速转换成机器人的整体运动状态。
假设左轮的线速度为v_left,右轮的线速度为v_right,机器人的轮距(左右轮与地面接触点之间的有效距离)为d。那么机器人底盘中心的线速度v和角速度w分别由下面两式给出:
- v = (v_left + v_right) / 2
- w = (v_right - v_left) / d
这个公式非常基础,但它揭示了两个重要信息。第一,底盘中心线速度是两轮速度的平均值,想要准确地计算位移,就必须让左右轮转速测量值准确;如果左右轮轮径不一致,直接套公式就会导致机器人走弧线。第二,角速度与两轮速度差成正比,与轮距成反比。轮距越小,同样的差速产生的转向角越快,轮距误差对角速度的影响就越明显。
从工程角度看,逐步推导之后你会发现,轮距d并不是电机轴距,而是轮子接地点的有效横向距离。对于宽轮胎,左右轮接地点可能在轮胎中心,也可能因为轻微的侧倾发生变化。这就要求我们对“标定用轮距”和“结构设计轮距”保持区分,实际中的轮距往往需要通过实验修正,而不是直接拿尺子量。
3.3 位姿更新:把轮速积分成二维坐标
有了线速度和角速度,下一步就是将其转换成机器人在地图坐标系中的位姿。设机器人在t时刻的位姿为(x, y, θ),其中θ是朝向角。在Δt足够小的时间片内,我们可以认为机器人做匀速直线运动,那么位姿更新公式为:
- θ_new = θ + w × Δt
- x_new = x + v × Δt × cos(θ_new)
- y_new = y + v × Δt × sin(θ_new)
在工程实现上,有两种做法。一种是把Δt取为固定节拍,比如每10毫秒在定时器中断里做一次位姿更新;另一种是每当编码器读数变化时立即处理。固定节拍更稳定,也方便和IMU做融合,缺点是如果主循环偶尔卡顿,可能丢失部分区间;边沿触发式的优点是响应即时,但要小心多个轮子的编码器中断相互竞争,导致数据不同步。我的建议是:固定节拍为主,编码器数值在每个节拍统一采集一次,左右各自计数同一时刻的快照,保证两轮数据一致性。
这里还有一个容易被忽略的细节:θ应该采用更新前的角度还是更新后的角度。严格来说,在Δt内角度是连续变化的,使用积分公式中点法会更精确,但对于较小Δt,直接用θ_new的误差几乎可以忽略。如果机器人转速很快,比如高速原地转向,建议把Δt缩小,或者在角度上做一个简单迭代补偿。
3.4 轮距、轮径和速度分辨率对估算精度的数学影响
想深入理解标定为什么重要,需要先看懂误差传播关系。假设左右轮的实际有效半径存在偏差δr,机器人在Δt内计算的位移为:
- Δs = (v_left + v_right) × Δt / 2
如果轮径实际偏大,而参数里没有修正,结果是测量位移会比实际位移偏小——因为真实线速度更大,但里程计只按偏小的半径换算。同理,如果左右轮径不一致,即使两个轮子的电机转速完全一致,机器人在物理上也是走弧线的,但里程计按“直线”更新坐标,累计出来的误差会朝一个方向弯曲。这也是“跑直线不直”最经典的原因之一。
轮距误差对角速度的影响是线性放大的。假如真实轮距是200毫米,标定时用成了195毫米,那么同样的差速,计算出的转动角速度会比实际大2.5%。如果机器人要转一整圈360度,角度偏差就可能达到9度左右,这将显著影响后续的坐标计算。
从这点来看,标定是数学上绕不开的一环。编码器只是提供原始数字,真正决定定位精度的是模型参数是否贴近机械真实情况。所以下一章我重点讲怎么样一步步地把这些参数给标定出来。
4. 软件实现与关键代码流程
4.1 单片机端的脉冲计数策略
底层脉冲计数是里程计软件的第一关。常见方案有两种:外部中断计数和编码器接口定时器模式。
外部中断方案可以让任意IO口支持编码器输入,缺点是当转速高、中断频繁时,主程序会被频繁打断,影响整体实时性。STM32等芯片的定时器编码器接口模式则更推荐,它能直接在硬件层面完成AB相解码、四倍频和计数方向判断,完全不需要软件干预。我自己的项目用的就是定时器编码器模式,读取的时候只需要从定时器计数寄存器里取出当前值即可。
在纯单片机实现中,计数值需要周期性地被上层读取并清零,比如10毫秒读一次。为了防止累计溢出,处理时需要按照环形计数的思想,先减去上次读到的值,得到本周期内的脉冲增量,再把这个增量叠加到总里程表中。
4.2 里程解算的Python示例
在调试或仿真阶段,我一般会在PC上先用Python把核心算法跑通。下面这段代码,基本就是车载端定时器中断里会执行的内容,拿出来供大家做参考。
import math # 物理与编码器参数 PPR = 20 REDUCTION_RATIO = 30 PULSES_PER_REVOLUTION = PPR * REDUCTION_RATIO * 4 # 四倍频后 WHEEL_RADIUS = 0.0325 # 单位:m WHEEL_BASE = 0.200 # 轮距,单位:m TICK_TO_METER = 2 * math.pi * WHEEL_RADIUS / PULSES_PER_REVOLUTION class Odometry: def __init__(self): self.x = 0.0 self.y = 0.0 self.theta = 0.0 self.last_left_ticks = 0 self.last_right_ticks = 0 def update(self, left_ticks, right_ticks, delta_t): delta_left = left_ticks - self.last_left_ticks delta_right = right_ticks - self.last_right_ticks self.last_left_ticks = left_ticks self.last_right_ticks = right_ticks # 单位换算:脉冲数 -> 轮子前进距离 dist_left = delta_left * TICK_TO_METER dist_right = delta_right * TICK_TO_METER # 底盘中心位移与角度变化 delta_theta = (dist_right - dist_left) / WHEEL_BASE delta_s = (dist_left + dist_right) / 2.0 # 位姿更新 self.theta += delta_theta avg_theta = self.theta - delta_theta / 2.0 self.x += delta_s * math.cos(avg_theta) self.y += delta_s * math.sin(avg_theta) return self.x, self.y, self.theta各位注意,这段代码里我用了一个小小的技巧:角度用的是更新前的角度加上半个角度增量,也就是求中值。这是一种简化后的中点积分法,在Δt比较大或不均匀的时候,比直接使用θ_new更不容易引入偏差。如果你的控制节拍足够稳,也可以用常规方式,问题不大。
4.3 滤波处理与抖动抑制
电机低速时,编码器输出脉冲间隔会很不均匀,如果直接把瞬时速度作为变量去算里程,会导致位姿抖动。一个相对简单又有效的办法是对脉冲增量做滑动平均滤波。
比如维护一个长度为N的窗口,每次把当前周期脉冲增量丢进去,同时移除最旧的一个,然后取平均。N一般取5~10。这个窗口不会对稳态速度带来明显的滞后,但对抑制低速抖动很有帮助。注意一点:滤波后的增量用于计算位移,但原始脉冲增量仍然要累加到一个“总里程”变量中,用于和滤波结果做交叉校验。这样可以及时发现滤波是否过度平滑了。
另外,转向过程中,如果两轮中有一个轮子的速度接近零,编码器方向判定的死区可能会导致脉冲计数异常波动。我在做原地旋转测试时发现,零速附近的微小振动会让编码器输出一正一反的脉冲,单相计数很容易把它看成若干正向脉冲,导致角度虚增。解决方法是给速度设置一个很小的死区区间,低于某个阈值就直接按零处理,宁可牺牲极小部分灵敏度,也要避免误差累积。
5. 标定流程与参数校准实战
5.1 有效轮径标定,直线往返法
轮径是影响里程计最直接的参数,但真实有效轮径和设计值往往有不小差异。原因包括轮胎充气程度(充气轮)、橡胶受压形变、长期磨损等。我的标定方法是直线往返法。
选择一个足够长的平直走廊,长度尽量大于5米。让机器人以较低速度、恒定的PWM或速度指令从A点沿直线走到B点,用卷尺或者激光测距仪量出实际距离L_real。同时,里程计读取并记录“名义行驶距离”L_odo。那么实际有效轮径R_true可以修正为:
- R_true = R_nominal × (L_real / L_odo)
注意这里的L_odo不是靠硬算出来的,而是用当时参数算出来的累计位移。一次直线标定并不能覆盖左右轮径向不一致的问题,所以需要再做一次反向行驶,或者将左右轮单独标定。实际操作时,还可以让机器人只用左轮驱动、右轮自由,标定左轮,再对称标定右轮。这样出来的左右轮径比值才是最准的。
5.2 轮距标定,原地旋转法
轮距标定比轮径标定麻烦一点,因为很难直接用尺子量出真正的有效轮距。我常用的是原地旋转法。
具体做法是:在机器人上固定一个指向明确方向的激光笔,或者直接用量角器观测底盘朝向。让机器人执行“原地旋转”动作,即左右轮速度相反、大小相等,使底盘绕中心旋转整圈。里程计根据当前轮距参数计算出旋转角度,实际角度则通过陀螺仪或外部量角工具得到。
假设名义轮距为d_nominal,里程计测得的旋转角度为θ_odo,实际旋转角度为θ_real,那么修正后的轮距为:
- d_true = d_nominal × (θ_real / θ_odo)
为什么是这个方向?从角速度公式w = (v_right - v_left) / d来看,如果里程计角度偏大,说明分母d偏小,需要把d调大。这个公式反过来即可。标定时多转几圈取平均,效果更好。我一般会连续转三圈,记录累计角度和实际圈数,最终算出一个平均值。
5.3 标定中的常见误区
标定的时候有几个坑需要特别提示。
- 标定地面必须和实际使用地面尽量一致。在地毯上标定的轮径,拿到瓷砖直接跑,误差立刻变大。
- 标定时速度不宜过快。高速起步和刹车会有明显的滑动,容易污染标定数据。最好在机器人已经达到稳定速度后再进入测量段。
- 如果轮胎是软质橡胶,负载变化会造成明显形变。标定时要模拟实际负载,满载和空载的轮径可能差到1%以上,这部分差距在几十米距离上已经可观了。
- 不要只做一次标定就算完事。设备使用一段时间后,轮子磨损、编码器安装松动,都可能导致参数漂移。定期做一次快速校验,是维护里程精度成本最低的手段。
5.4 参数修正后的验证方案
标定做完并不是终点,必须用闭环验证来确认。我的做法是设计一个矩形路径:让机器人沿线走一个3米×4米的矩形,走完整整一圈回到起点。用最终位置与起点的偏差作为评价里程精度的综合指标。
如果标订准确,软硬件没有异常,一圈下来误差应该控制在总路程的1%以内;状态好的系统甚至可以做到0.3%左右。如果误差远大于这个范围,别急着调参数,先检查轮子是否打滑、编码器是否有失步、底座结构是否有松动。很多时候,数据“不正确”并不是模型参数的问题,而是物理条件根本没满足。
6. 常见问题与排查技巧实录
6.1 典型问题速查表
我把实际项目中遇到过的问题和解决思路整理成表格,按出现频率排序,方便各位对照排查。
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 跑直线越来越偏 | 左右轮有效半径不一致 | 分别标定左右轮轮径,检查两侧轮胎胎压 |
| 原地旋转角度明显偏多或偏少 | 轮距参数不准 | 用真正原地旋转法标定轮距 |
| 静止时里程还在变化 | 编码器信号受干扰,或读数为旧值 | 分线走线束,启用硬件滤波,检查IO悬空电平 |
| 速度较高时数据丢失 | 中断响应不及时 | 改用定时器编码器模式,或者提高中断优先级 |
| 起步刹车阶段位移异常 | 车轮空转或滑动摩擦不够 | 降低加速度,使用更软胎面或增大对地压力 |
| 左右轮行驶距离明明相等但角度漂移大 | 轮距误差或编码器安装轴线倾斜 | 检查安装同心度,重新标定轮距 |
| 长时间运行后位置越来越离谱 | 误差不断累积,缺乏外部校准 | 部署里程计+环境传感器的融合定位 |
6.2 软硬件联合排查的步骤
遇到问题时,我习惯按“后处理数据分离”的顺序来排查,不靠猜。
第一步,先把轮子架空,用手转动轮子,观察左右轮速度是否和转动完全对应。这一步能排查机械装配问题和编码器接线问题。第二步,再用一个已知速度的传送带或手推匀速跑一段短距离,重点观察左右轮各自计算的行驶距离是否接近实测。这一步能定位是轮径标定问题还是差速转弯问题。第三步,再做原地旋转测试,把角度误差单独分离出来。
这套步骤的好处是,每一步的结论都只受当前环节因素影响。举个例子,如果手推直跑时距离准确但角度漂移大,那基本可以肯定是轮距或者差速不对称问题,而不是轮径标定问题。从一次“跑歪了”的表面现象反推哪里出问题时往往会绕弯子,不如直接把误差源分开测。
6.3 我做项目时踩过的几个坑
最后分享几个我个人的实际经历,都是文档里查不到的那种。
第一坑,编码器电源不稳导致计数异常。当时为了省空间,把编码器电源直接从电机电源上取电,结果电机一加速,编码器电压就跌落,瞬间丢失一堆脉冲。后面改成了独立的稳压源供电,再也没出现过类似的丢步。
第二坑,过分追求高分辨率。有一次换了个高PPR编码器,理论上精度应该更高,但实际效果反而变差了,因为机械抖动被放大,导致计数抖动明显。后来发现,在低速运动场景下,高分辨率并不总是优势,必须搭配合理的滤波方案才好用。
第三坑,把轮距直接按电机轴间距填了。后来做旋转标定时才发现,由于轮胎接地宽度的影响,真实有效轮距其实比轴间距大了几毫米。就这么几毫米,也会在长时间转向时积累出可感知的角度误差。
6.4 升级路径:从纯里程计到多传感器融合
如果你做完以上步骤,发现单靠编码器里程计的精度还是满足不了项目需求,那就该考虑多传感器融合了。在大多数实际项目里,我不会让里程计单独扛大梁,而是把它和IMU、磁力计甚至视觉里程计一起做滤波融合。
最常见的融合方案是扩展卡尔曼滤波。编码器提供高频的位移预测,IMU提供角速度辅助,偶尔的环境观测(如激光匹配到的位姿)作为更新量来修正累积漂移。这样既能利用编码器平滑、高频的优点,又能通过绝对观测打断误差累积,是工业上成本最低的可靠方案之一。
从单纯做“轮式编码器的里程估计”到“多传感器融合定位”,是一个很自然的演进路径。如果你在这个方向上准备长期做下去,建议早点在软件架构里预留好通用接口——比如把编码器里程计算的结果定义成一个主题(Topic)或一个类,和后面的IMU、视觉一起挂到融合框架里,会省掉很多重构的麻烦。
说到底,编码器里程计是一个看着赢不了、做起来很多讲究的基础模块。把它调准了,之后做任何上层定位和导航都会轻松很多。这也是我建议每个做移动机器人的朋友,无论最后用到什么高级传感器,都先把这一课扎实补上的原因。