第二章:SDFormat模型语言精讲:从零手写一个差速轮机器人
好,咱们进入正题。上一章我们把Gazebo的生态摸了个大概,这一章,我带你亲手写一个能动的机器人模型。不是用URDF,而是用SDFormat——Gazebo的亲儿子语言。
说实话,我刚开始接触Gazebo时也纠结过:URDF不是挺好的吗?为什么还要学SDF?后来踩了几个坑才明白——URDF是ROS时代的产物,而SDFormat才是为仿真而生的。它更灵活,更贴近物理引擎的思维方式。
2.1 SDFormat vs URDF:为什么我选SDF
先别急着写代码,咱们理清一个概念。URDF和SDF都能描述机器人,但定位完全不同。
| 对比项 | URDF | SDFormat |
|---|---|---|
| 设计初衷 | ROS机器人描述 | Gazebo仿真描述 |
| 物理属性 | 需要额外插件 | 原生支持 |
| 多机器人 | 不支持 | 原生支持 |
| 世界环境 | 不支持 | 原生支持 |
| 闭链机构 | 不支持 | 支持 |
你看,URDF更像一个「零件清单」,而SDF是「完整的世界说明书」。我个人习惯:只要用Gazebo,一律用SDF。省心。
核心观点:SDFormat是Gazebo的「母语」。你用URDF进Gazebo,中间还得过一层解析器,多一道转换就多一个坑。直接写SDF,一步到位。
2.2 从零开始:差速轮机器人的骨架
咱们要做的机器人很简单:一个底盘,两个驱动轮,一个万向轮。差速驱动,靠左右轮速度差实现转向。
先搭骨架。SDF文件最外层是<sdf>标签,里面包着<model>。每个模型由<link>和<joint>组成。
<?xml version="1.0" ?>
<sdf version="1.7">
<model name="my_diff_drive">
<!-- 这里放link和joint -->
</model>
</sdf>
嗯,这里要注意:version我建议用1.7,这是目前最稳定的版本。1.8虽然出了,但有些插件还没跟上。
2.3 底盘:<link>标签深度解析
底盘是机器人的身体。我们用<link>定义它。一个link包含三部分:惯性、碰撞、视觉。
<link name="chassis">
<pose>0 0 0.05 0 0 0</pose>
<inertial>
<mass>2.0</mass>
<inertia>
<ixx>0.02</ixx>
<ixy>0</ixy>
<ixz>0</ixz>
<iyy>0.02</iyy>
<iyz>0</iyz>
<izz>0.04</izz>
</inertia>
</inertial>
<collision name="collision">
<geometry>
<box>
<size>0.4 0.3 0.1</size>
</box>
</geometry>
</collision>
<visual name="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>
这里我多说一句惯性矩阵。很多新手直接复制粘贴,结果机器人飘得像气球。为什么?因为惯性参数不对。我有个经验公式:对于实心长方体,ixx = (1/12)*m*(h² + d²)。你算一下,心里就有数了。
避坑指南:我曾经在项目里直接用了默认惯性值,结果机器人一跑就翻。后来发现是izz设得太小,底盘绕Z轴转得太灵活。记住:惯性不是随便填的,它决定了机器人转动的「手感」。
2.4 轮子与关节:<joint>的奥秘
轮子不能焊死在底盘上,得用<joint>连接。差速轮用revolute关节,绕Y轴旋转。
<joint name="left_wheel_joint" type="revolute">
<parent>chassis</parent>
<child>left_wheel</child>
<pose>-0.15 0.2 -0.05 0 0 0</pose>
<axis>
<xyz>0 1 0</xyz>
<limit>
<lower>-1e16</lower>
<upper>1e16</upper>
</limit>
</axis>
</joint>
注意pose的写法。它是子link相对于父link的偏移。我刚开始总搞混——以为pose是子link在世界中的位置。其实不是,它是相对于父link的。
轮子本身也是一个link,但简单很多:
<link name="left_wheel">
<pose>0 0 0 0 0 0</pose>
<inertial>
<mass>0.2</mass>
<inertia>
<ixx>0.0001</ixx>
<iyy>0.0001</iyy>
<izz>0.0002</izz>
</inertia>
</inertial>
<collision name="collision">
<geometry>
<cylinder>
<radius>0.05</radius>
<length>0.04</length>
</cylinder>
</geometry>
</collision>
<visual name="visual">
<geometry>
<cylinder>
<radius>0.05</radius>
<length>0.04</length>
</cylinder>
</geometry>
</visual>
</link>
右轮同理,只是pose的Y坐标取反。万向轮用ball关节,让它自由转动。
2.5 让机器人动起来:<sensor>与插件
光有模型还不够,得让它能动。差速轮需要两个东西:一个diff_drive插件,一个imu传感器。
<plugin name="diff_drive" filename="libDiffDrivePlugin.so">
<left_joint>left_wheel_joint</left_joint>
<right_joint>right_wheel_joint</right_joint>
<wheel_separation>0.4</wheel_separation>
<wheel_diameter>0.1</wheel_diameter>
<torque>5.0</torque>
</plugin>
<link name="chassis">
<sensor name="imu" type="imu">
<always_on>1</always_on>
<update_rate>100</update_rate>
</sensor>
</link>
这里wheel_separation是左右轮间距,wheel_diameter是轮子直径。这两个参数决定了机器人的转弯半径。我见过有人把间距写错,结果机器人原地打转——说白了就是左右轮速度算反了。
注意:插件名libDiffDrivePlugin.so是Gazebo自带的,不需要额外安装。但如果你用的是Gazebo Fortress或更高版本,插件名可能不同。建议先gazebo --version确认版本。
2.6 完整模型与知识体系
好了,把上面所有零件拼起来,就是一个完整的差速轮机器人。我画了一张图,帮你理清SDFormat的核心结构:
这张图把SDFormat的核心逻辑讲透了。你写模型时,就照着这个结构填内容。先link,再joint,最后plugin。顺序别搞反。
2.7 测试你的模型
写完后,保存为my_robot.sdf。用Gazebo加载:
gazebo --verbose my_robot.sdf
如果一切正常,你会看到一个蓝色底盘的机器人。在Gazebo的「Topic」面板里,找到/my_diff_drive/cmd_vel,发布一个速度指令:
gz topic -t /my_diff_drive/cmd_vel -m gz.msgs.Twist -p "linear: {x: 0.5}, angular: {z: 0.2}"
机器人应该会向前走并转弯。如果不动,检查两点:一是关节名称是否和插件里一致,二是wheel_separation是否和实际间距匹配。
调试技巧:我习惯在Gazebo里打开「View -> Joints」,能看到每个关节的状态。如果关节显示红色,说明连接有问题。另外,--verbose参数会打印详细日志,90%的问题都能从日志里找到线索。
好了,你的第一个SDFormat机器人已经跑起来了。它虽然简单,但骨架完整。后面加激光雷达、加摄像头、加机械臂,都是在这个基础上「搭积木」。记住:SDFormat的核心就是link、joint、plugin三件套。吃透它们,Gazebo里没有你搭不出来的机器人。
可可精品资料库