4. AUV模型构建与URDF/Xacro
在ROS2与Gazebo的水下仿真中,AUV模型不是“画出来”的,而是“描述出来”的。URDF(Unified Robot Description Format)用一棵以base_link为根的连杆—关节树描述机器人的运动学结构,Xacro则在URDF之上提供宏、参数、数学表达式和文件包含能力,让同一套模型描述可以复用于不同尺寸、不同传感器配置的AUV。Gazebo并不直接读取URDF,而是通过gazebo_ros的robot_state_publisher与spawn_entity服务把URDF转换后的SDF加载进仿真世界,因此URDF中的几何、惯性、碰撞与传感器插件配置,最终都会决定仿真中的物理行为是否可信。
本节以一个典型的鱼雷型AUV为例:主体为直径0.2 m、长1.0 m的圆柱,尾部十字舵,前部搭载IMU、DVL与深度计,尾部一个推进器。我们按“几何—惯性—关节—传感器—Gazebo扩展”的顺序组织模型,但需要强调的是,这五部分在文件中是交织的,惯性参数必须与几何尺寸一致,传感器安装位置必须与关节树一致,否则仿真会出现姿态漂移、传感器数据错位等难以排查的问题。
坐标系约定与连杆划分
建模前必须先固定坐标约定,并在全文与图示中保持一致。本节采用ROS标准:base_link原点位于AUV浮心与艏艉几何中心附近,x轴指向艏部(前进方向),y轴指向左舷,z轴指向上方,符合右手系。所有传感器、推进器的安装位置都以base_link为参照给出xyz与rpy。连杆划分不宜过细:对于刚性AUV,主体、舵面、推进器可各为一个link,舵面与主体之间用revolute关节连接,推进器用continuous关节连接,这样既保留了可动部件,又不会让惯性矩阵计算变得臃肿。
几何与碰撞:visual 与 collision 必须分开写
URDF中每个link的visual决定外观,collision决定物理碰撞,二者可以不同。对AUV主体,visual用cylinder即可,但collision若也用圆柱,Gazebo中会得到过于光滑的接触面,导致AUV搁浅在池底时出现滑动。更合理的做法是collision使用略小的圆柱或capsule近似,并在需要时用多个基本体拼接近似艏部曲面。示例主体几何如下:
<link name="base_link">
<visual>
<geometry><cylinder radius="0.1" length="1.0"/></geometry>
<material name="hull_yellow">
<color rgba="0.95 0.75 0.1 1.0"/>
</material>
</visual>
<collision>
<geometry><cylinder radius="0.095" length="0.98"/></geometry>
</collision>
<inertial>
<mass value="12.0"/>
<inertia ixx="0.12" ixy="0" ixz="0"
iyy="1.02" iyz="0" izz="1.02"/>
</inertial>
</link>
注意inertia的六个分量必须满足正定与三角不等式约束,否则Gazebo会报“inertia matrix is not positive definite”并拒绝加载。对质量12 kg、半径0.1 m、长1.0 m的均匀圆柱,绕x轴(轴向)的转动惯量为0.5·m·r² = 0.06,绕y、z轴为m·(3r²+L²)/12 ≈ 1.03。上例中ixx取0.12是考虑了内部电池与电子舱偏离轴线的合理假设值,实际建模时应按质量分布估算,而不是随手填数。
Xacro参数化:让尺寸与质量可复用
把上述数值硬编码进URDF,换一个直径或长度的AUV就要全文替换。Xacro的做法是先声明参数,再用${}表达式引用:
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="auv">
<xacro:property name="hull_r" value="0.1"/>
<xacro:property name="hull_l" value="1.0"/>
<xacro:property name="hull_m" value="12.0"/>
<xacro:macro name="cylinder_inertia" params="m r l">
<inertial>
<mass value="${m}"/>
<inertia ixx="${0.5*m*r*r}" ixy="0" ixz="0"
iyy="${m*(3*r*r+l*l)/12}" iyz="0"
izz="${m*(3*r*r+l*l)/12}"/>
</inertial>
</xacro:macro>
<link name="base_link">
<visual>
<geometry><cylinder radius="${hull_r}" length="${hull_l}"/></geometry>
</visual>
<collision>
<geometry><cylinder radius="${hull_r*0.95}" length="${hull_l*0.98}"/></geometry>
</collision>
<xacro:cylinder_inertia m="${hull_m}" r="${hull_r}" l="${hull_l}"/>
</link>
</robot>
这样修改hull_r一处,几何与惯性同步更新,避免了“改了尺寸忘了改惯量”的典型错误。Xacro还支持xacro:include把传感器宏拆到独立文件,例如sensors.xacro中定义imu_macro,主文件只负责装配。
关节与传感器:安装偏移决定数据正确性
传感器在URDF中通常作为独立link,通过fixed关节挂到base_link上,关节的origin就是安装偏移。以IMU为例,若实际安装在艏部距中心0.35 m、轴线偏上0.02 m处:
<link name="imu_link">
<xacro:cylinder_inertia m="0.05" r="0.02" l="0.03"/>
</link>
<joint name="imu_joint" type="fixed">
<parent link="base_link"/>
<child link="imu_link"/>
<origin xyz="0.35 0 0.02" rpy="0 0 0"/>
</joint>
这个偏移不是装饰:Gazebo的IMU插件输出的角速度与线加速度是在imu_link坐标系下计算的,若偏移写错,AUV做俯仰机动时IMU读到的线加速度会缺少ω×r项对应的分量,控制器调参时就会表现为“姿态估计滞后”。同理,DVL应朝下安装,其rpy常写为0 1.5708 0使测量轴对准-z;深度计则只需位置正确。舵面用revolute关节,需给出axis、limit(如±0.5 rad)与dynamics阻尼;推进器用continuous关节,配合Gazebo的libgazebo_ros_force或自定义推力插件。
导入Gazebo:插件与SDF扩展
URDF本身不含物理引擎参数,需通过<gazebo>标签注入。典型配置包括:为base_link设置mu1/mu2摩擦系数、为水下环境添加hydrodynamics插件(提供附加质量与阻尼)、为IMU/DVL挂载libgazebo_ros_imu_sensor.so等传感器插件。启动时用robot_state_publisher发布TF,再用ros2 run gazebo_ros spawn_entity.py -topic robot_description -entity auv把模型生成到世界中。若模型加载后立即下坠或抖动,优先检查惯性矩阵是否正定、collision是否与visual严重不符、以及base_link的inertial是否遗漏。
check_urdf与xacro展开后先做静态校验:xacro auv.urdf.xacro > /tmp/auv.urdf && check_urdf /tmp/auv.urdf,确认连杆树无环、关节父子关系正确,再进入Gazebo,可省去大量“模型不显示”的排查时间。最后需要建立一条判断依据:模型“能加载”不等于“建模正确”。可在Gazebo中给AUV一个初始横滚角,观察它是否按预期恢复;用ros2 topic echo /imu/data检查静止时角速度是否接近零、线加速度z分量是否约为9.8;用ros2 run tf2_tools view_frames确认imu_link、dvl_link相对base_link的变换与设计偏移一致。这三项检查通过,才说明几何、惯性、关节与传感器四部分在仿真中形成了自洽的模型。