4. 运动学基础:差速驱动机器人运动学模型、正逆运动学解算、轮式里程计计算原理

好,咱们进入正题。这一章讲的是差速驱动机器人的运动学。说白了,就是搞清楚“轮子怎么转,车怎么走”这件事。

我刚开始做机器人那会儿,觉得运动学就是套公式。后来发现,公式背后藏着很多坑。比如轮子打滑、编码器丢步、安装误差……这些都会让理论计算和实际运动对不上。嗯,咱们今天先把理论吃透,后面再讲怎么处理这些实际问题。

4.1 差速驱动模型长什么样?

差速驱动,就是两个独立驱动的轮子,加上一个或多个万向轮。两个轮子转速不同,机器人就能转弯。你想想看,坦克也是这个原理。

常见的布局有两种:

  • 两轮差分:左右两个驱动轮,前后各一个万向轮。这是最常见的。
  • 四轮差分:四个轮子都是驱动轮,但左右两侧分别控制。这种结构更稳,但控制复杂一些。

我个人习惯用两轮差分,结构简单,调试方便。但要注意,万向轮如果选不好,转弯时会抖动。我在项目中遇到过,用了便宜的万向轮,结果机器人走直线都歪。后来换了带轴承的,问题就解决了。

4.2 正运动学解算:从轮速到车体速度

正运动学,就是已知左右轮转速,求机器人中心的速度和角速度。

先定义几个参数:

  • v:机器人中心线速度(m/s)
  • ω:机器人角速度(rad/s)
  • vL:左轮线速度(m/s)
  • vR:右轮线速度(m/s)
  • d:左右轮间距(m)

公式其实很简单:

v = (v_R + v_L) / 2
ω = (v_R - v_L) / d

为什么?你想想看,如果左右轮速度一样,机器人就走直线。如果左轮比右轮快,机器人就向右转。这个差值除以轮距,就是角速度。

这里有个坑:轮距d不是随便量的。我建议你测量两个轮子接地点的中心距离,而不是轮子边缘。我曾经量错了,结果转弯半径怎么算都不对。后来用激光测距仪重新量了一遍,才搞定。

如果轮子转速用RPM表示,需要先转换成线速度:

v = RPM * π * 轮子直径 / 60

嗯,单位别搞混了。我见过有人把直径当半径用,结果速度翻了一倍。

4.3 逆运动学解算:从目标速度到轮速

逆运动学就是反过来。已知你想要机器人以某个速度v和角速度ω运动,求左右轮应该转多快。

公式也很直接:

v_R = v + (ω * d) / 2
v_L = v - (ω * d) / 2

注意,这里算出来的是线速度。如果你要控制电机转速,还得除以轮子半径,再乘以减速比。

举个例子:假设轮距d=0.5m,轮子半径r=0.1m,减速比30:1。你想让机器人以0.5m/s的速度直线前进:

v_R = 0.5 + (0 * 0.5) / 2 = 0.5 m/s
v_L = 0.5 - (0 * 0.5) / 2 = 0.5 m/s

电机转速 = 0.5 / 0.1 * 30 = 150 RPM

如果想让机器人原地旋转(v=0, ω=1 rad/s):

v_R = 0 + (1 * 0.5) / 2 = 0.25 m/s
v_L = 0 - (1 * 0.5) / 2 = -0.25 m/s

电机转速_R = 0.25 / 0.1 * 30 = 75 RPM
电机转速_L = -75 RPM

看到没?左右轮转速相反,机器人就原地打转。这个在调试时很有用,可以用来测试里程计是否准确。

我的小技巧: 在代码里,我习惯把正逆运动学封装成两个函数。一个叫forwardKinematics(vL, vR),一个叫inverseKinematics(v, omega)。这样上层控制逻辑就干净多了。

4.4 轮式里程计计算原理

里程计,就是通过轮子转动的圈数,估算机器人走了多远、转了多少角度。说白了,就是“猜”机器人的位置。

原理很简单:

  1. 每个控制周期,读取左右轮的编码器增量。
  2. 计算左右轮移动的距离:ΔsL = 编码器增量 * 每脉冲对应的距离
  3. 计算机器人中心移动距离:Δs = (ΔsL + ΔsR) / 2
  4. 计算机器人转过的角度:Δθ = (ΔsR - ΔsL) / d
  5. 更新位置:
x += Δs * cos(θ + Δθ/2)
y += Δs * sin(θ + Δθ/2)
θ += Δθ

这里用了中点法,比直接用θ更准确。为什么?因为机器人在移动过程中角度也在变,用中间角度算位移,误差更小。

我在项目中遇到过一个问题:编码器分辨率不够,导致里程计漂移很快。后来换了高分辨率编码器(每圈1000脉冲以上),效果好了很多。但要注意,编码器分辨率太高,MCU可能处理不过来。我建议用1000-2000脉冲/圈的编码器,配合四倍频,精度就够了。

避坑指南: 我曾经犯过一个低级错误——忘记考虑轮子打滑。在光滑地面上,里程计会严重高估位移。后来我加了IMU做融合,才解决了这个问题。记住:纯轮式里程计只适用于硬质、干燥地面。

4.5 里程计误差来源与补偿

里程计误差主要来自三个方面:

误差类型 原因 补偿方法
系统误差 轮距测量不准、轮子直径不一致 标定实验,调整参数
非系统误差 打滑、地面不平 传感器融合(IMU/视觉)
量化误差 编码器分辨率有限 提高分辨率,使用插值

系统误差可以通过标定来补偿。我常用的方法是:让机器人走一段直线,然后测量实际位移和理论位移的比值,修正轮子直径。再让机器人原地旋转360度,修正轮距。

非系统误差就比较麻烦了。打滑是不可避免的,尤其是在急加速或急转弯时。我的建议是:

  • 控制加速度不要太大,避免轮子打滑
  • 使用软质轮胎,增加摩擦力
  • 如果预算允许,加一个IMU做航向角修正

嗯,说到IMU,后面会有专门章节讲。这里先提一句:IMU的偏置漂移也是个问题,需要和里程计做卡尔曼滤波融合。

4.6 实战:在ROS中实现里程计

在ROS里,里程计通常发布为nav_msgs/Odometry消息。你需要做的是:

  1. 订阅左右轮编码器数据
  2. 每个周期计算位移和角度增量
  3. 更新机器人位姿
  4. 发布Odometry消息和tf变换

下面是一个简化的代码框架:

void updateOdometry() {
    // 读取编码器增量
    double deltaLeft = leftEncoder.getDelta();
    double deltaRight = rightEncoder.getDelta();
    
    // 计算左右轮移动距离
    double dsLeft = deltaLeft * wheelRadius;
    double dsRight = deltaRight * wheelRadius;
    
    // 计算中心位移和角度变化
    double ds = (dsLeft + dsRight) / 2.0;
    double dtheta = (dsRight - dsLeft) / wheelBase;
    
    // 更新位姿(中点法)
    double thetaMid = theta + dtheta / 2.0;
    x += ds * cos(thetaMid);
    y += ds * sin(thetaMid);
    theta += dtheta;
    
    // 发布Odometry消息
    nav_msgs::Odometry odom;
    odom.pose.pose.position.x = x;
    odom.pose.pose.position.y = y;
    odom.pose.pose.orientation = tf::createQuaternionMsgFromYaw(theta);
    odom_pub.publish(odom);
    
    // 发布tf变换
    geometry_msgs::TransformStamped transform;
    transform.header.frame_id = "odom";
    transform.child_frame_id = "base_footprint";
    transform.transform.translation.x = x;
    transform.transform.translation.y = y;
    transform.transform.rotation = odom.pose.pose.orientation;
    tf_broadcaster.sendTransform(transform);
}

注意,这里我用了base_footprint作为机器人坐标系。你也可以用base_link,但base_footprint更常用,因为它把坐标系放在地面投影上,方便做导航。

核心要点: 里程计不是万能的。它只能提供短时间内的相对位置估计。长时间运行,误差会累积。所以实际应用中,里程计通常和激光雷达、视觉或IMU做融合。但作为基础,你必须先把轮式里程计做准。

好了,这一章就到这里。下一章我们会讲麦克纳姆轮的运动学,那个更复杂一些,但原理是相通的。记住:先把差速驱动搞明白,后面就轻松了。