2. 运动学基础:底盘坐标系定义,平移与旋转的数学表示

各位同学,欢迎来到第二章。这一章是底盘控制的“地基”。

说实话,我见过太多人一上来就调PID、写运动学代码,结果车子跑起来歪歪扭扭,找半天原因——坐标系定义反了。嗯,这种坑我踩过不止一次。所以这一章,咱们把坐标系和数学基础打扎实。

2.1 底盘坐标系:世界系 vs 机器人系

做全向轮控制,脑子里必须同时装着两个坐标系:世界坐标系机器人坐标系

  • 世界坐标系:固定在地面上的参考系。通常用 \( X_W, Y_W \) 表示。你给底盘下达“向东移动1米”,这个“东”就是世界坐标系下的方向。
  • 机器人坐标系:固定在底盘上的参考系。用 \( X_R, Y_R \) 表示。原点在底盘中心,\( X_R \) 指向车头方向,\( Y_R \) 指向左侧。

我个人习惯,在ROS里把机器人坐标系命名为 base_link,世界坐标系叫 odommap。这个命名规范,后面写代码会省很多事。

核心原则: 所有传感器数据(比如IMU、里程计)先转换到机器人坐标系,再统一转换到世界坐标系。千万别混着用。

2.2 平移运动的数学表示

平移,说白了就是“从A点走到B点”。在二维平面上,我们用向量来表示位置。

假设底盘在世界坐标系下的位置是 \( (x, y) \),那么它的位置向量就是:

P = [x, y]^T

如果底盘移动了一段距离 \( \Delta x \) 和 \( \Delta y \),新位置就是:

P_new = P_old + [Δx, Δy]^T

我在项目中遇到过一个问题:用麦克纳姆轮做全向移动时,如果只给 \( \Delta x \) 不给 \( \Delta y \),车子会原地打转。后来发现是坐标系搞反了——我把机器人坐标系下的指令直接发给了世界坐标系。所以,平移向量一定要明确是在哪个坐标系下计算的

2.3 旋转运动的数学表示

旋转比平移稍微复杂一点。底盘绕自身中心旋转,我们用角度 \( \theta \) 来表示姿态。

为什么用角度?因为全向轮底盘可以原地自转,这个自由度必须单独控制。

旋转的数学表示,最常用的是旋转矩阵。二维平面下的旋转矩阵长这样:

R(θ) = [cosθ  -sinθ]
       [sinθ   cosθ]

如果你有一个点 \( (x, y) \) 在机器人坐标系下,想把它转到世界坐标系下,就乘上这个旋转矩阵:

P_world = R(θ) * P_robot

反过来,从世界坐标系转到机器人坐标系,就用逆矩阵 \( R^{-1}(\theta) \),其实就是转置矩阵:

P_robot = R(θ)^T * P_world
我的小技巧: 在代码里,我习惯用四元数代替角度。虽然四元数看着复杂,但能避免“万向锁”问题。ROS里也原生支持四元数,用 tf2 库做转换非常方便。

2.4 平移 + 旋转:齐次坐标

把平移和旋转写在一起,就引出了齐次坐标。说白了,就是把旋转矩阵和平移向量拼成一个3x3的矩阵:

T = [R(θ)  |  t]
    [0  0 1 |  1]

其中 \( t = [x, y]^T \) 是平移向量。这样,一次矩阵乘法就能同时完成旋转和平移:

P_world_homogeneous = T * P_robot_homogeneous

为什么要用齐次坐标?因为方便。你想想看,如果不用齐次坐标,你得先旋转再平移,写两行代码。用齐次坐标,一行搞定。而且ROS里的 tf 树就是基于齐次变换的。

曾经踩过的坑: 我曾经在写运动学逆解时,忘了把角度转换成弧度。C++里 cos(30) 默认是弧度,而我传了30度进去。结果底盘转得乱七八糟。所以,所有三角函数计算,务必用弧度制

2.5 知识体系结构图

下面这张图,把本章的核心逻辑串起来了。你可以把它当作一个“思维导图”来理解:

运动学基础:坐标系与数学表示 坐标系定义 世界坐标系 机器人坐标系 平移运动 位置向量 (x, y) 旋转运动 旋转矩阵 R(θ) 齐次变换矩阵 T ROS tf 树 / 运动学解算

从图中可以看到,坐标系定义是基础,平移和旋转是两大核心操作,最后通过齐次变换矩阵统一起来,应用到ROS的tf树和运动学解算中。

2.6 实战小贴士:代码里的坐标系管理

在ROS里,我推荐用 tf2 库来管理坐标系变换。下面是一个简单的C++示例,演示如何监听 odombase_link 的变换:

#include <tf2_ros/transform_listener.h>
#include <tf2_geometry_msgs/tf2_geometry_msgs.h>

tf2_ros::Buffer tf_buffer;
tf2_ros::TransformListener listener(tf_buffer);

geometry_msgs::TransformStamped transform;
try {
    transform = tf_buffer.lookupTransform("odom", "base_link", ros::Time(0));
} catch (tf2::TransformException &ex) {
    ROS_WARN("Could not get transform: %s", ex.what());
}

// 提取平移和旋转
double x = transform.transform.translation.x;
double y = transform.transform.translation.y;
double yaw = tf2::getYaw(transform.transform.rotation);

这段代码我几乎每个项目都会用。注意 ros::Time(0) 表示获取最新可用的变换,实时性要求高的场景下很实用。

总结一下: 坐标系定义清楚,平移旋转用矩阵,齐次坐标统一算,ROS里用tf2。这一章的内容,后面每一章都会用到。尤其是做运动学逆解时,坐标系一乱,整个底盘就废了。

好了,这一章就到这里。记住,数学是工具,不是负担。多用几次就熟了。


专注资料整理