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 轮式里程计计算原理
里程计,就是通过轮子转动的圈数,估算机器人走了多远、转了多少角度。说白了,就是“猜”机器人的位置。
原理很简单:
- 每个控制周期,读取左右轮的编码器增量。
- 计算左右轮移动的距离:ΔsL = 编码器增量 * 每脉冲对应的距离
- 计算机器人中心移动距离:Δs = (ΔsL + ΔsR) / 2
- 计算机器人转过的角度:Δθ = (ΔsR - ΔsL) / d
- 更新位置:
x += Δs * cos(θ + Δθ/2)
y += Δs * sin(θ + Δθ/2)
θ += Δθ
这里用了中点法,比直接用θ更准确。为什么?因为机器人在移动过程中角度也在变,用中间角度算位移,误差更小。
我在项目中遇到过一个问题:编码器分辨率不够,导致里程计漂移很快。后来换了高分辨率编码器(每圈1000脉冲以上),效果好了很多。但要注意,编码器分辨率太高,MCU可能处理不过来。我建议用1000-2000脉冲/圈的编码器,配合四倍频,精度就够了。
4.5 里程计误差来源与补偿
里程计误差主要来自三个方面:
| 误差类型 | 原因 | 补偿方法 |
|---|---|---|
| 系统误差 | 轮距测量不准、轮子直径不一致 | 标定实验,调整参数 |
| 非系统误差 | 打滑、地面不平 | 传感器融合(IMU/视觉) |
| 量化误差 | 编码器分辨率有限 | 提高分辨率,使用插值 |
系统误差可以通过标定来补偿。我常用的方法是:让机器人走一段直线,然后测量实际位移和理论位移的比值,修正轮子直径。再让机器人原地旋转360度,修正轮距。
非系统误差就比较麻烦了。打滑是不可避免的,尤其是在急加速或急转弯时。我的建议是:
- 控制加速度不要太大,避免轮子打滑
- 使用软质轮胎,增加摩擦力
- 如果预算允许,加一个IMU做航向角修正
嗯,说到IMU,后面会有专门章节讲。这里先提一句:IMU的偏置漂移也是个问题,需要和里程计做卡尔曼滤波融合。
4.6 实战:在ROS中实现里程计
在ROS里,里程计通常发布为nav_msgs/Odometry消息。你需要做的是:
- 订阅左右轮编码器数据
- 每个周期计算位移和角度增量
- 更新机器人位姿
- 发布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更常用,因为它把坐标系放在地面投影上,方便做导航。
好了,这一章就到这里。下一章我们会讲麦克纳姆轮的运动学,那个更复杂一些,但原理是相通的。记住:先把差速驱动搞明白,后面就轻松了。