教程 · 中级 · 阅读约 35 分钟

麦克纳姆轮运动学:四个轮子,三个运动轴

从一个 45° 滚子出发,推导出麦克纳姆底盘的四行逆运动学方程,把它反过来用作里程计,再用多出来的第四个方程检测打滑。

一个滚子决定一切

把一个麦克纳姆轮从机器人上拆下来拿在手里,它像普通轮子一样绕轴转。轮缘上装着八九个桶形小滚子,每个有自己的小轴、都能自由转动,且都与轮轴成 45°。

现在问这个轮子能给地面施加什么力。

它不能沿滚子的滚动方向推——滚子一转,什么都传不出去。它只能沿滚子轴线推,也就是滚子拒绝滚动的那个方向。所以轮子的地面作用力被锁在地板上一条 45° 线上,加再大电机扭矩也换不了这条线。

机制的全部就是这个,下面全是记账。

单个麦克纳姆轮侧视图,下方是地面线。轮胎上画着 45 度滚子条纹,标注滚子自由转动、轴线 45 度。从接地点发出红色箭头,沿 45 度指向右下方,标注地面作用力 F√2,受 μN 限制。青色箭头沿地面水平,标注有效推力 F,是红色箭头的水平分量,虚线补成直角三角形并标出 45 度角。注释说明摩擦限制整个 45 度向量,但只有水平分量驱动机器人,所以每轮有效推力为 μN/√2,约为普通轮的 71%。
一个三角形讲清全部牵引代价。滚子只沿自身轴线传力,轮子的地面作用力被卡在 45° 上——摩擦限制整个向量,而只有水平分量推动机器人。 下载 SVG

约束,一次一个轮子

用常用机器人坐标系:x 朝前,y 朝机器人左方,ω 逆时针。这是 ROS 约定,用别的以后会和每个库打架。

轮子 i 在该坐标系中的位置为 (xᵢ, yᵢ),滚子旋向 sᵢ = ±1 表示它的滚子沿两条 45° 线中的哪一条。轮缘速度 vᵢ(轮胎处线速度,单位 m/s,不是弧度)与底盘速度的关系为:

vᵢ = vx + sᵢ·vy + (sᵢ·xᵢ − yᵢ)·ω

三项,每项一句话:

  • vx——无论旋向如何,每个轮子都全额贡献前进运动,这就是麦克纳姆底盘前进开起来完全正常的原因。
  • sᵢ·vy——侧移让一半轮子正转、一半反转,旋向决定哪一半。
  • (sᵢ·xᵢ − yᵢ)·ω——力臂。注意它不只是轮距,滚子旋向在里面,所以装错角的轮子连旋转带侧移一起毁。

从轮缘速度到真正发给电机的指令,除以轮半径:ωwheel = vᵢ / r,单位 rad/s。

四个轮子:矩阵

四个轮子的行叠起来,就是 4×3 矩阵 A,底盘速度 ξ = (vx, vy, ω):

v = A · ξ

标准布局——左前配右后一种旋向,右前配左后另一种——记 k = lx + ly,其中 lx 为半轴距(前后)、ly 为半轮距(左右),则:

轮子 vx vy ω
左前 1 −1 −k
右前 1 +1 +k
左后 1 +1 −k
右后 1 −1 +k

也就是人人都在写的四行:

float fl = vx - vy - K * omega;
float fr = vx + vy + K * omega;
float rl = vx + vy - K * omega;
float rr = vx - vy + K * omega;

用你已知的麦克纳姆机器人四种行为验算一下:

指令 轮速(左前,右前,左后,右后) 现象
前进,vx = 1 1, 1, 1, 1 四轮同向,侧向分量抵消。
左平移,vy = 1 −1, 1, 1, −1 对角两对反向,这就是绝活。
逆时针转,ω = 1 −k, k, −k, k 左侧后退、右侧前进——普通坦克转向。
对角,vx = vy = 1 0, 2, 2, 0 **两个轮子完全停转。**记住这一点。
同一台车头朝上麦克纳姆机器人的四个俯视图,每幅显示一种指令下四个轮子的转向,轮速按左前、右前、左后、右后列出并归一到最快轮。前进:四个全 +1,箭头同向,注释侧向分量抵消。左平移:−1、+1、+1、−1,对角两对反向,琥珀色箭头指左。逆时针转:−1、+1、−1、+1,左侧后退右侧前进,琥珀色圆弧。斜行:0、+1、+1、0,琥珀色箭头指左前,注释两个轮子干全部活、两个轮子闲置。
四个指令,直接来自矩阵。注意最后一个:斜行让两个轮子干全部活、两个轮子闲置,71% 的斜行上限就从这里来。 下载 SVG

算例

底盘 lx = 75 mm、ly = 85 mm,97 mm 轮(r = 48.5 mm),指令 vx = 0.30 m/s、vy = 0.20 m/s、ω = 0.50 rad/s。则 k = 0.160 m。

轮子 算式 轮缘速度 电机转速
左前 0.30 − 0.20 − 0.160 × 0.50 0.020 m/s 0.41 rad/s(3.9 rpm)
右前 0.30 + 0.20 + 0.160 × 0.50 0.580 m/s 11.96 rad/s(114 rpm)
左后 0.30 + 0.20 − 0.160 × 0.50 0.420 m/s 8.66 rad/s(83 rpm)
右后 0.30 − 0.20 + 0.160 × 0.50 0.180 m/s 3.71 rad/s(35 rpm)

看起来不极端的指令,让一个轮子比另一个快 29 倍。麦克纳姆底盘一辈子都在让四个电机干差别巨大的活,所以“四个给一样 PWM 还能走直线”在这里不是有效测试。

饱和:缩放,绝不截幅

指令超出电机能力,总要放弃点什么,只有两种选择,而且不等价。

float peak = max(max(fabs(fl), fabs(fr)), max(fabs(rl), fabs(rr)));
if (peak > V_MAX) {            // SCALE: keep the direction, lose the speed
  float s = V_MAX / peak;
  fl *= s; fr *= s; rl *= s; rr *= s;
}

对比直接给每个电机写 constrain(fl, -255, 255) 的做法:截幅。截幅保住速度、扔掉方向。假设轮子极限 0.50 m/s,指令 0.60 m/s 前进、0.15 侧移、0.8 rad/s 旋转:

航向 前进 旋转 结论
指令 14.0° 0.60 m/s 0.80 rad/s —
缩放 14.0° 0.34 m/s 0.46 rad/s 每个轴都变慢,指向分毫不差
截幅 5.6° 0.46 m/s 0.28 rad/s 前进更快,偏航 8.4°,旋转少 40%

最阴险的是截幅在人们常测的指令上看起来没事。纯斜行让两个轮子跑 2、两个跑 0,零本来就在限内,截幅和缩放结果一样,缺陷就藏起来了。它之后会在路径跟随器底下现形:机器人莫名其妙向弯道外偏。在麦克纳姆模拟器同一趟跑里对比两种限幅,差别是一条看得见的曲线。

反过来:里程计

正运动学是同一个矩阵反着读。但 A 是 4×3——四个测量、三个未知数——没有逆,只有最小二乘解:

ξ = (Aᵀ A)⁻¹ Aᵀ v

标准布局下 AᵀA 是对角阵,整体坍缩成三行,Arduino 都放得下:

vx    = (fl + fr + rl + rr) / 4.0;
vy    = (-fl + fr + rl - rr) / 4.0;
omega = (-fl + fr - rl + rr) / (4.0 * K);

每个节拍结合航向积分,就是航位推算:

float dt = (now - last) * 1e-6f;
theta += omega * dt;
x += (vx * cosf(theta) - vy * sinf(theta)) * dt;
y += (vx * sinf(theta) + vy * cosf(theta)) * dt;

效果和底下的假设一样好——轮子不打滑。而麦克纳姆轮打滑是设计使然,所以侧向里程计是全车最不可信的数。

没人用的第四方程

四个方程,三个未知数,多出来的方程不是浪费——是整个传动链上唯一的免费诊断。

机器人是刚体且每轮都抓地时,四个轮缘速度不能任意。矩阵前两行相加与后两行相加都是 2·vx,所以:

FL + FR = RL + RR

恒成立,精确成立,刚体麦克纳姆底盘的任何运动都满足。所以每循环算一次残差:

float residual = (fl + fr) - (rl + rr);   // should be ~0 forever

非零说明四个编码器描述的运动是刚体做不出来的。原因按可能性排序:轮子离地、轮子压到湿滑处、滚子卡死、联轴器打滑、编码器某通道坏——或轮子装错角,此时残差大而恒定,而非阵发。

诚实说出它看不见的:如果四个轮子同时同向打滑,残差保持为零,机器人悄无声息地斜飘出航线。这正是麦克纳姆里程计悄无声息失效而非大声报错的原因,认真的做法是加 IMU 或无动力随动轮,而不是信驱动编码器。

45° 的两个后果

都从矩阵里掉出来,谁也买不掉。

斜行限于 1/√2。 45° 指令把 vx + vy 压在两个轮子上,另两个为 0,忙的那对到限时机器人速度只有直线极速的 V_MAX / √2 ≈ 71%。更大的电机抬高天花板,但去不掉它。

牵引也限于 1/√2,原因不同。 电机有效驱动力 F 沿滚动方向,而实际地面作用力沿滚子轴线为 F·√2,摩擦限制的是那个。所以每轮有效推力上限 μN/√2,整车加速度上限为:

a_max = μ·g / √2

μ = 0.85 时是 5.9 m/s²,同样机器人用普通轮是 8.3。质量在两边都约掉了,所以压重没用——和相扑机器人重量即一切的教训正好相反。

侧向速度对前进速度曲线(m/s),展示轮速 0.60 m/s 的麦克纳姆机器人能达到的所有速度。可达集为青色实心菱形,顶点在两轴 ±0.60 处。灰色虚线圆半径 0.60 作对比,标注无方向惩罚。琥珀色点标出菱形顶点处 0.60 m/s 前进与侧移,红色点标在斜边上,标注斜向 0.424 m/s。
机器人能达到的所有速度,逐方向解出。直前直侧免费;菱形斜边就是让你付出 1/√2 的地方。只有不带 45° 的传动才配拥有那个圆。 下载 SVG

在固件里

几何放一处,绝不手写两遍。

struct Mecanum {
  float lx, ly, r;                 // half wheelbase, half track, wheel radius (m)
  float k() const { return lx + ly; }

  // twist -> four wheel angular velocities, rad/s
  void inverse(float vx, float vy, float w, float out[4]) const {
    out[0] = (vx - vy - k() * w) / r;   // FL
    out[1] = (vx + vy + k() * w) / r;   // FR
    out[2] = (vx + vy - k() * w) / r;   // RL
    out[3] = (vx - vy + k() * w) / r;   // RR
  }

  // four measured wheel angular velocities -> twist, plus the residual
  float forward(const float in[4], float &vx, float &vy, float &w) const {
    float fl = in[0] * r, fr = in[1] * r, rl = in[2] * r, rr = in[3] * r;
    vx = (fl + fr + rl + rr) * 0.25f;
    vy = (-fl + fr + rl - rr) * 0.25f;
    w  = (-fl + fr - rl + rr) * 0.25f / k();
    return (fl + fr) - (rl + rr);       // the free diagnostic
  }
};

lx 和 ly 量轮心到轮心再对半——不是车体板,车体板通常更宽。k 错了机器人照样走,但旋转和平移互相串:原地转会漂移,纯侧移会画弧。原地转还漂移,先怀疑 k 再怀疑代码。

故障排查

现象 可能原因 查什么
前进正常,侧移画弧还带转 轮子装错角 上面的 X 检查;残差大而稳定
侧移方向和指令相反 四轮镜像 左右两对轮子对调
原地转还带平移 k 错,或按车体而非轮心量的 lx/ly 重量轮心到轮心
斜行比前进慢得多 1/√2 上限——不是故障 不用修,设计时留出来
路径跟随器下过弯外飘 逐电机截幅而非缩放 按最忙的轮子整体缩放指令
里程计侧向漂得快、前向还好 滚子打滑,符合设计 航向用 IMU,位置用随动轮
只在某种地面残差跳变 那块地滑,或滚子卡死 每个滚子用手转一遍

下一步

用会打滑的机器人验算这些数:麦克纳姆模拟器把这些方程开进摩擦模型,看里程计和地面怎么吵架。然后让轮子真正转出你算的速度——车轮速度闭环——再用场定向驱动让“前进”指地面前进而非车体前进。

探索关联图谱

本教程所属的搭建

包含本教程的项目和学习路线。

延伸阅读

参考资料