4. 创建第一艘智能船舶模型

在ROS2智能船舶仿真中,船模是整个系统的物理基础。URDF(Unified Robot Description Format)用一棵XML树描述船体的连杆(link)、关节(joint)、几何外形、质量与惯性张量,以及推进器、舵等执行机构的连接关系。本节以一艘小型双推进器带舵的无人艇为教学对象,从零写出一个可在Gazebo中加载的船模,并说明每个参数为什么这样取、加载后如何判断模型是否正确。

一、先确定坐标系与船体尺寸约定

建模前必须先固定坐标约定,否则后续惯性参数、关节轴方向和推进力方向都会混乱。本节采用ROS标准约定:X轴指向船艏,Y轴指向左舷,Z轴向上,原点位于船体几何中心处的设计水线面。船体主尺度取教学示例值:总长 L=2.0 m,型宽 B=0.8 m,型深 H=0.35 m,设计吃水 d=0.15 m。船体用长方体近似(后续章节可替换为更精细的网格),其几何中心位于原点,因此船体link的视觉与碰撞几何都写成 size="2.0 0.8 0.35",中心偏移为0。

需要特别说明:长方体近似只用于入门阶段验证URDF流程与Gazebo加载,其流体动力特性与真实船型差异很大,不能用于操纵性定量分析。这一点在后续引入水动力插件时必须替换。

二、质量、质心与惯性张量

URDF中每个link必须给出 <inertial>,包含质量、质心位置和3×3惯性张量(只填对角线ixx、iyy、izz及惯性积ixy、ixz、iyz,对称阵)。质量取船体空载质量 m=25 kg。质心假设位于原点略偏下,取 origin xyz="0 0 -0.02",模拟设备布置偏低带来的稳心改善。

对均质长方体,绕质心轴的惯性矩为:

Ixx = m/12 * (B² + H²) = 25/12 * (0.8² + 0.35²) ≈ 1.59 kg·m²
Iyy = m/12 * (L² + H²) = 25/12 * (2.0² + 0.35²) ≈ 8.59 kg·m²
Izz = m/12 * (L² + B²) = 25/12 * (2.0² + 0.8²) ≈ 9.67 kg·m²

由于船体近似为对称长方体,惯性积ixy、ixz、iyz均取0。注意Izz(绕竖轴,即艏摇惯量)明显大于Ixx(横摇惯量),这符合细长船体的物理直觉,也是后续观察船体转向响应快慢的依据。若把船体质量随意写成1 kg或惯性全填1,Gazebo中船会表现得像纸片,一推就翻,这是初学者最常见的错误。

三、推进器与舵的关节定义

本节采用“左右两个推进器+单舵”的布置。推进器不建模为真实螺旋桨叶片,而用一个短圆柱表示桨毂与推力方向指示,通过连续关节(continuous)绕自身轴线旋转,转速由 ros2_control 或Gazebo插件驱动;舵用转动关节(revolute),限定在±35°范围内摆动。

左推进器安装在船艉 xyz="-0.95 0.25 -0.10",右推进器镜像为 xyz="-0.95 -0.25 -0.10",关节轴均为 axis xyz="1 0 0"(绕X轴旋转,即桨盘垂直于船艏方向)。舵安装在 xyz="-1.0 0 -0.12",关节轴为 axis xyz="0 0 1"(绕Z轴摆动,改变舵角)。关节的 parent 均为 base_link,child 分别为 prop_left、prop_right、rudder。

舵关节需写限位:<limit lower="-0.61" upper="0.61" effort="20" velocity="2.0"/>,其中0.61 rad约等于35°。推进器连续关节无角度限位,但应给出 effort 与 velocity,例如 effort="5" velocity="100",表示最大驱动力矩5 N·m、最高转速约100 rad/s(约955 rpm),这些是教学示例值,实际应按所选电机与桨匹配确定。

推进器与舵的link也要写 <inertial>,哪怕质量很小(如0.3 kg)。Gazebo对缺少惯性的link会报警告,甚至导致关节行为异常。

四、在Gazebo中加载与观察

URDF本身不含仿真属性,需要在文件末尾追加 <gazebo> 标签:为 base_link 指定材质颜色,为船体添加浮力与阻尼插件(入门阶段可先用 libgazebo_ros_buoyancy.so 或简化的位姿控制),并为推进器关节挂接 libgazebo_ros_joint_velocity_controller.so。随后用以下命令启动:

ros2 launch my_boat_description boat_gazebo.launch.py
# 或手动加载
ros2 run gazebo_ros spawn_entity.py -entity smart_boat \
  -file install/my_boat_description/share/my_boat_description/urdf/boat.urdf

加载后按以下顺序观察判断:第一,在Gazebo左侧模型树中确认 smart_boat 下出现 base_link、prop_left、prop_right、rudder 四个link,说明URDF树解析成功;第二,用 ros2 run tf2_tools view_frames 检查关节变换是否连通,若某关节断开,通常是parent/child写错或link名拼写不一致;第三,在Gazebo中给推进器关节发速度指令,观察船体是否沿X轴前进、左右推进器差速时是否产生艏摇,舵角变化时航向是否随之改变。若船体原地打转或下沉穿地,应优先检查惯性张量是否为零、碰撞几何是否与视觉几何一致、浮力插件参数是否匹配质量。

双推进器带舵无人艇 URDF 结构关系 X 船艏 Z 上 Y 左舷 base_link(船体 2.0×0.8×0.35 m,25 kg) 艏向 +X 左桨 右桨 舵 continuous 关节,轴 +X continuous 关节,轴 +X revolute 关节,轴 +Z,±35° 惯性参数(长方体近似) m = 25 kg,质心 (0, 0, −0.02) Ixx ≈ 1.59 Iyy ≈ 8.59 Izz ≈ 9.67 kg·m² 惯性积 ixy = ixz = iyz = 0 关节关键字段 推进器:effort 5,velocity 100 舵:lower −0.61,upper 0.61 舵:effort 20,velocity 2.0 parent 均为 base_link 加载后检查顺序 ① 模型树出现 4 个 link ② tf2 关节变换连通 ③ 差速前进 / 艏摇正常 ④ 舵角改变航向响应

五、一个可直接参考的URDF骨架

把上述参数落到文件里,核心结构如下(省略了visual/collision的重复细节):

<robot name="smart_boat">
  <link name="base_link">
    <inertial>
      <origin xyz="0 0 -0.02"/>
      <mass value="25.0"/>
      <inertia ixx="1.59" iyy="8.59" izz="9.67"
               ixy="0" ixz="0" iyz="0"/>
    </inertial>
    <visual><geometry><box size="2.0 0.8 0.35"/></geometry></visual>
    <collision><geometry><box size="2.0 0.8 0.35"/></geometry></collision>
  </link>

  <link name="prop_left">
    <inertial><mass value="0.3"/>
      <inertia ixx="0.001" iyy="0.001" izz="0.001"
               ixy="0" ixz="0" iyz="0"/></inertial>
  </link>
  <joint name="prop_left_joint" type="continuous">
    <parent link="base_link"/><child link="prop_left"/>
    <origin xyz="-0.95 0.25 -0.10"/>
    <axis xyz="1 0 0"/>
    <limit effort="5" velocity="100"/>
  </joint>

  <joint name="rudder_joint" type="revolute">
    <parent link="base_link"/><child link="rudder"/>
    <origin xyz="-1.0 0 -0.12"/>
    <axis xyz="0 0 1"/>
    <limit lower="-0.61" upper="0.61" effort="20" velocity="2.0"/>
  </joint>
</robot>

右推进器与舵的link定义方式相同,只需镜像Y坐标。写完后用 check_urdf boat.urdf 做语法与树结构校验,输出中应看到 base_link 下挂三个子link,且无“link not connected”提示。

六、常见问题与判断依据

加载后若模型在Gazebo中“抖动”或“缓慢下沉”,通常不是URDF语法问题,而是质量与浮力不匹配:25 kg船体需要排开约0.025 m³水体,若浮力插件体积参数写错,船会持续下沉。若推进器转动但船不动,检查关节是否被Gazebo插件正确接管、effort 是否过小、以及碰撞几何是否与地面或水面发生穿透。若船体一转向就侧翻,多半是Ixx过小或质心过高,应复核惯性张量与质心Z偏移。把这些现象与参数对应起来,才算真正完成了“加载并观察”这一环节,而不是仅仅看到模型出现在场景里。