4. 构建你的第一个机器人
好,咱们正式开始动手了。
前面几章我们把Gazebo的界面、传感器、世界文件都过了一遍。说实话,光看不练容易犯困。今天咱们来点硬核的——用SDF搭一个两轮小车底盘。
你可能会问:为什么非要从底盘开始?
嗯,我个人习惯是,学任何仿真工具,先搞定一个能动的、看得见的东西。底盘就是机器人的骨架,你把它搭明白了,后面加传感器、加控制器,都是往上添砖加瓦的事。
4.1 机器人的基本构成:link和joint
在Gazebo里,一个机器人模型由两样东西组成:link(连杆)和joint(关节)。
link 就是机器人的一个刚体部件。比如一个轮子、一块底盘、一个机械臂的臂杆。每个link都有自己的物理属性:质量、惯性、碰撞形状、视觉外观。
joint 是连接两个link的约束。它决定了两个link之间怎么相对运动。可以固定不动,也可以旋转、平移。
说白了,link是骨头,joint是关节。骨头不会动,关节让骨头动起来。
核心概念:一个机器人模型必须至少有一个link(叫"base_link"),否则Gazebo会报错。joint可以没有,但link必须有。
我在项目中遇到过新手把link和joint搞混。有个同事把轮子直接写成了joint,结果Gazebo里显示一个旋转的圆环,根本没法用。记住:能摸到的是link,能转动的是joint。
4.2 SDF模型的结构骨架
一个完整的SDF机器人模型,长这样:
<model name="my_robot">
<link name="base_link">
<!-- 视觉、碰撞、惯性 -->
</link>
<link name="left_wheel">
<!-- 视觉、碰撞、惯性 -->
</link>
<link name="right_wheel">
<!-- 视觉、碰撞、惯性 -->
</link>
<joint name="left_wheel_joint" type="revolute">
<parent>base_link</parent>
<child>left_wheel</child>
<!-- 关节位置、轴方向 -->
</joint>
<joint name="right_wheel_joint" type="revolute">
<parent>base_link</parent>
<child>right_wheel</child>
<!-- 关节位置、轴方向 -->
</joint>
</model>
你看,结构非常清晰。一个model下面,先定义所有link,再定义所有joint。joint里用<parent>和<child>指明谁连谁。
这里有个坑:parent和child的顺序不能反。parent是靠近机器人基座的link,child是远离基座的link。我曾经把轮子当parent、底盘当child,结果轮子拖着底盘跑,画面相当诡异。
4.3 两轮小车底盘的SDF实现
好,咱们直接写代码。下面是一个完整的两轮小车底盘SDF文件:
<?xml version="1.0" ?>
<sdf version="1.6">
<model name="two_wheel_robot">
<!-- 底盘 -->
<link name="base_link">
<pose>0 0 0.05 0 0 0</pose>
<inertial>
<mass>1.0</mass>
<inertia>
<ixx>0.01</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>0.01</iyy>
<iyz>0</iyz>
<izz>0.01</izz>
</inertia>
</inertial>
<collision name="base_collision">
<geometry>
<box>
<size>0.4 0.3 0.1</size>
</box>
</geometry>
</collision>
<visual name="base_visual">
<geometry>
<box>
<size>0.4 0.3 0.1</size>
</box>
</geometry>
<material>
<ambient>0.2 0.5 0.8 1</ambient>
</material>
</visual>
</link>
<!-- 左轮 -->
<link name="left_wheel">
<pose>-0.15 0.2 0.05 0 1.5708 0</pose>
<inertial>
<mass>0.2</mass>
<inertia>
<ixx>0.0001</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>0.0001</iyy>
<iyz>0</iyz>
<izz>0.0001</izz>
</inertia>
</inertial>
<collision name="left_wheel_collision">
<geometry>
<cylinder>
<radius>0.05</radius>
<length>0.04</length>
</cylinder>
</geometry>
</collision>
<visual name="left_wheel_visual">
<geometry>
<cylinder>
<radius>0.05</radius>
<length>0.04</length>
</cylinder>
</geometry>
<material>
<ambient>0.8 0.2 0.2 1</ambient>
</material>
</visual>
</link>
<!-- 右轮 -->
<link name="right_wheel">
<pose>-0.15 -0.2 0.05 0 1.5708 0</pose>
<inertial>
<mass>0.2</mass>
<inertia>
<ixx>0.0001</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>0.0001</iyy>
<iyz>0</iyz>
<izz>0.0001</izz>
</inertia>
</inertial>
<collision name="right_wheel_collision">
<geometry>
<cylinder>
<radius>0.05</radius>
<length>0.04</length>
</cylinder>
</geometry>
</collision>
<visual name="right_wheel_visual">
<geometry>
<cylinder>
<radius>0.05</radius>
<length>0.04</length>
</cylinder>
</geometry>
<material>
<ambient>0.2 0.8 0.2 1</ambient>
</material>
</visual>
</link>
<!-- 左轮关节 -->
<joint name="left_wheel_joint" type="revolute">
<parent>base_link</parent>
<child>left_wheel</child>
<pose>-0.15 0.2 0.05 0 0 0</pose>
<axis>
<xyz>0 0 1</xyz>
</axis>
</joint>
<!-- 右轮关节 -->
<joint name="right_wheel_joint" type="revolute">
<parent>base_link</parent>
<child>right_wheel</child>
<pose>-0.15 -0.2 0.05 0 0 0</pose>
<axis>
<xyz>0 0 1</xyz>
</axis>
</joint>
</model>
</sdf>
这段代码里,我做了几件事:
- 底盘是一个长方体,长0.4米、宽0.3米、高0.1米,颜色是蓝色
- 两个轮子是圆柱体,半径0.05米、厚度0.04米,左红右绿
- 轮子通过
revolute关节(旋转关节)连接到底盘 - 关节的旋转轴是Z轴(
0 0 1),这样轮子可以前后滚动
你可能会注意到,轮子的pose里有个1.5708。那是90度的弧度值。因为圆柱体默认是竖着的,我需要把它横过来当轮子用。
小技巧:SDF里的角度单位是弧度,不是度。90° = 1.5708 rad,180° = 3.1416 rad。记不住没关系,我每次都是现算的。
4.4 在Gazebo中查看模型结构
文件写好了,怎么验证?
把上面的代码保存为two_wheel_robot.sdf,然后启动Gazebo:
gazebo --verbose two_wheel_robot.sdf
如果一切正常,你会看到一个小车底盘出现在世界里。用鼠标滚轮拉近看看,底盘是蓝色的长方体,两个轮子一红一绿。
Gazebo左侧的Model Tree面板里,你能看到模型的层级结构:
two_wheel_robotbase_linkleft_wheelright_wheelleft_wheel_jointright_wheel_joint
点开每个link,还能看到它的碰撞形状、视觉形状、惯性参数。这就是Gazebo的模型浏览器,非常直观。
注意:如果模型加载后轮子掉在地上或者位置不对,八成是pose写错了。检查一下轮子的位置是不是相对于底盘,而不是相对于世界坐标系。
4.5 本章知识结构图
下面这张图帮你理清link和joint的关系:
这张图把模型、link、joint的关系画得很清楚。你写SDF的时候,脑子里要有这个结构树。
4.6 常见问题与避坑
最后分享几个我踩过的坑:
- 惯性参数不能全零:Gazebo要求每个link的惯性矩阵必须合理。如果你偷懒全写0,仿真会直接崩掉。至少给个很小的正数。
- 碰撞形状和视觉形状可以不同:为了性能,碰撞形状可以简化。比如视觉用精细的网格模型,碰撞用一个长方体代替。
- 关节类型选错:轮子用
revolute,机械臂关节用revolute或prismatic,固定连接用fixed。别把轮子写成fixed,那样它转不了。
嗯,今天就到这。把上面那个SDF文件跑起来,在Gazebo里转转视角,看看模型树。动手试一次,比看十遍教程都管用。
可可精品资料库