4. 多机器人模型创建
好,咱们进入正题。
这一章,我们要亲手打造一个差分驱动小车的URDF模型。然后,把它“复制”成多份,跑在同一个仿真环境里。说白了,就是让好几台小车各干各的活,互不干扰。
我刚开始做多机器人仿真时,踩过一个坑:两台小车一启动,tf树全乱套了。一个叫“base_link”,另一个也叫“base_link”,坐标系直接打架。后来我才明白——命名空间和tf前缀,是解决这个问题的关键。
4.1 差分驱动小车URDF模型
先从小车本身说起。差分驱动,就是左右两个轮子独立驱动。通过转速差实现转向。结构简单,控制也直观。
我习惯把模型拆成三部分:车体、左右驱动轮、万向轮(或从动轮)。
下面是一个精简版的URDF。注意,我故意省略了部分视觉和碰撞细节,只保留核心关节和连杆。
<robot name="diff_drive_robot">
<!-- 车体 -->
<link name="base_link">
<visual>
<geometry>
<box size="0.4 0.3 0.1"/>
</geometry>
<origin xyz="0 0 0.05" rpy="0 0 0"/>
</visual>
<collision>
<geometry>
<box size="0.4 0.3 0.1"/>
</geometry>
</collision>
<inertial>
<mass value="2.0"/>
<inertia ixx="0.01" ixy="0" ixz="0" iyy="0.01" iyz="0" izz="0.02"/>
</inertial>
</link>
<!-- 左轮 -->
<link name="left_wheel">
<visual>
<geometry>
<cylinder radius="0.05" length="0.04"/>
</geometry>
<origin xyz="0 0 0" rpy="1.5708 0 0"/>
</visual>
<collision>
<geometry>
<cylinder radius="0.05" length="0.04"/>
</geometry>
</collision>
<inertial>
<mass value="0.2"/>
<inertia ixx="0.0001" ixy="0" ixz="0" iyy="0.0001" iyz="0" izz="0.0002"/>
</inertial>
</link>
<!-- 左轮关节 -->
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="left_wheel"/>
<origin xyz="-0.15 0.2 0.05" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>
<!-- 右轮 -->
<link name="right_wheel">
<!-- 与左轮类似,略 -->
</link>
<joint name="right_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="right_wheel"/>
<origin xyz="-0.15 -0.2 0.05" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>
<!-- 万向轮 -->
<link name="caster_wheel">
<visual>
<geometry>
<sphere radius="0.03"/>
</geometry>
</visual>
</link>
<joint name="caster_wheel_joint" type="fixed">
<parent link="base_link"/>
<child link="caster_wheel"/>
<origin xyz="0.15 0 0.03" rpy="0 0 0"/>
</joint>
</robot>
嗯,这里要注意:left_wheel_joint 的旋转轴是 0 1 0,也就是绕Y轴转。为什么?因为轮子朝前滚动时,旋转轴就是横向的。我见过有人写成绕X轴,结果轮子在地上“画圈”……
4.2 多机器人命名空间
单台小车跑起来没问题。但两台、三台呢?
ROS里,每个节点、话题、参数都有全局名称。如果两台小车都发布 /cmd_vel,那到底控制谁?
解决办法:给每台小车一个独立的命名空间。
比如,启动文件里这样写:
<group ns="robot1">
<node name="spawn_model" pkg="gazebo_ros" type="spawn_model"
args="-urdf -model robot1 -param robot_description -x 0.0 -y 0.0 -z 0.1"/>
</group>
<group ns="robot2">
<node name="spawn_model" pkg="gazebo_ros" type="spawn_model"
args="-urdf -model robot2 -param robot_description -x 1.0 -y 0.0 -z 0.1"/>
</group>
这样,robot1的话题就变成了 /robot1/cmd_vel,robot2的是 /robot2/cmd_vel。互不冲突。
$(arg robot_name) 动态生成命名空间。这样改一个参数就能换一台车,不用复制粘贴整个group。
4.3 tf树与多机器人
tf树是机器人坐标系的“家谱”。单台小车时,根节点通常是 base_link。但多台小车呢?
如果每台小车的根都叫 base_link,tf树就乱了。ROS不知道哪个 base_link 对应哪台车。
解决方案:给每台小车的tf加上前缀。
在URDF里,可以这样设置:
<robot name="diff_drive_robot">
<!-- 所有link和joint的名称保持不变 -->
</robot>
然后在启动时,通过 robot_state_publisher 的 tf_prefix 参数来区分:
<node pkg="robot_state_publisher" type="robot_state_publisher" name="robot_state_publisher">
<param name="tf_prefix" value="robot1"/>
</node>
这样,robot1的tf树就变成了:
robot1/base_linkrobot1/left_wheelrobot1/right_wheel
robot2的则是:
robot2/base_linkrobot2/left_wheelrobot2/right_wheel
你看,这样tf树就清晰了。每个坐标系都带着“身份证”。
4.4 整体架构图
下面这张图,展示了多机器人系统的核心逻辑。我画成了SVG,方便你理解。
说白了,这张图就讲了一件事:命名空间管通信,tf前缀管坐标。两者缺一不可。
4.5 实战避坑指南
最后,分享几个我实际项目中踩过的坑:
- tf前缀不要带斜杠。比如
robot1_可以,但/robot1/不行。斜杠会让tf解析出错。 - 检查所有节点是否都加了前缀。有时候你只改了
robot_state_publisher,但忘了改joint_state_publisher,结果关节状态还是全局的。 - 用
tf_echo验证。启动后,跑一下rosrun tf tf_echo /robot1/base_link /robot1/left_wheel,看看能不能正确输出变换。这是最直接的检查方法。
嗯,这一章的内容就到这。你动手试试,把上面的URDF和launch文件跑起来。遇到问题,回头看看避坑指南。
可可精品资料库