4. 仿真建模基础

URDF,全称 Unified Robot Description Format,是 ROS 生态里描述机器人结构的标准语言。说白了,它就是一份 XML 文件,告诉仿真器你的机器人长什么样、关节怎么动、质量分布在哪。

我刚开始做人形机器人仿真时,觉得 URDF 就是个「零件清单」。后来踩了不少坑才明白——URDF 写得好不好,直接决定了仿真能不能跑起来、跑得稳不稳。

核心要点:URDF 建模的本质是「树形结构」——从 base_link 开始,通过 joint 连接各个 link,形成一棵完整的运动学树。人形机器人通常以躯干为根节点,四肢和头部为子节点。

人形机器人 URDF 树形结构 torso_link(躯干) head_link(头部) left_arm_link right_arm_link left_leg_link right_leg_link left_forearm_link right_forearm_link left_shank_link right_shank_link left_foot_link right_foot_link 每个 link 通过 joint 连接,joint 定义父子关系、类型、位置和旋转轴

4.1 定义 link:给机器人「长肉」

每个 link 代表一个刚体部件。你需要告诉 URDF 三件事:几何形状、视觉外观、物理属性。

我个人习惯先画几何,再调惯性参数。因为惯性参数直接影响仿真稳定性——你想想看,如果质心位置写错了,机器人站着都会倒。

<link name="torso_link">
  <visual>
    <geometry>
      <box size="0.4 0.25 0.3"/>
    </geometry>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <material name="torso_mat">
      <color rgba="0.8 0.8 0.8 1.0"/>
    </material>
  </visual>
  <collision>
    <geometry>
      <box size="0.4 0.25 0.3"/>
    </geometry>
  </collision>
  <inertial>
    <mass value="8.0"/>
    <inertia ixx="0.15" ixy="0.0" ixz="0.0"
             iyy="0.12" iyz="0.0"
             izz="0.10"/>
  </inertial>
</link>

我的经验:躯干是人形机器人的「重心锚点」。我一般把躯干质量设为 6-10 kg,惯性矩按长方体公式估算:I = (1/12) * m * (w² + h²)。别偷懒用默认值,否则仿真时机器人会「飘」起来。

4.2 定义 joint:给机器人「装关节」

joint 是 URDF 的灵魂。它决定了两个 link 之间怎么动。人形机器人常用的 joint 类型就三种:

关节类型 自由度 典型应用 注意事项
revolute 1 旋转 肩关节、髋关节、膝关节 必须设置 limit
continuous 1 无限旋转 头部旋转(可选) 不需要 limit
fixed 0 躯干与腰部连接 刚性连接

嗯,这里要注意——revolute 关节必须配置 limit,否则 Gazebo 会报错。我曾经因为忘了写膝关节的 limit,仿真器直接崩溃,排查了半天才发现问题。

<joint name="left_shoulder_joint" type="revolute">
  <parent link="torso_link"/>
  <child link="left_arm_link"/>
  <origin xyz="0.2 0.15 0.1" rpy="0 0 0"/>
  <axis xyz="0 0 1"/>
  <limit lower="-1.57" upper="1.57" effort="30.0" velocity="2.0"/>
  <dynamics damping="0.1" friction="0.05"/>
</joint>

避坑指南:我曾经在配置髋关节时,把 axis 写成了 "1 0 0"(绕 X 轴旋转),结果机器人走路时腿往侧面劈叉。人形机器人的髋关节屈伸应该绕 Y 轴(axis="0 1 0"),外展内收才绕 X 轴。别搞混了。

4.3 配置关节限位:别让机器人「掰断」自己

关节限位是安全红线。人形机器人的关节活动范围有限,写大了仿真会穿模,写小了动作做不出来。

我整理了一份常用限位参考表,基于我做过的一个 1.2m 高的人形机器人项目:

关节 下限 (rad) 上限 (rad) 最大力矩 (Nm) 最大速度 (rad/s)
肩关节(俯仰) -2.09 2.09 30 3.0
肩关节(外展) -0.52 1.57 25 2.5
肘关节 -2.09 0.0 20 2.0
髋关节(俯仰) -1.57 1.57 50 3.0
膝关节 -2.09 0.0 60 2.5
踝关节 -0.79 0.79 30 2.0

小技巧:effort(最大力矩)别设太大。我刚开始做双足行走时,把膝关节力矩设了 100 Nm,结果仿真里机器人一脚踩下去,地面直接穿透。后来改成 60 Nm,配合合适的 PD 增益,走路就稳了。

4.4 惯性参数:仿真稳定的「隐形之手」

惯性参数是 URDF 里最容易糊弄、也最容易出问题的地方。说白了,它决定了机器人「有多重、重心在哪、转动惯量多大」。

三个关键参数:

  • mass:质量,单位 kg。每个 link 都要写,不能为 0。
  • origin:质心位置,相对于 link 坐标系。默认是 (0,0,0),但实际零件质心往往有偏移。
  • inertia:转动惯量矩阵,6 个独立分量(ixx, ixy, ixz, iyy, iyz, izz)。

我见过有人把所有 link 的 inertia 写成一样的,结果仿真时机器人像果冻一样抖动。为什么会这样?因为惯性矩不匹配,物理引擎算出来的动力学响应是错的。

<!-- 左臂 link 的惯性参数 -->
<inertial>
  <mass value="2.5"/>
  <origin xyz="0.0 0.0 -0.1"/>
  <inertia ixx="0.02" ixy="0.0" ixz="0.0"
           iyy="0.015" iyz="0.0"
           izz="0.008"/>
</inertial>

快速估算公式:对于长方体,Ixx = (1/12) * m * (h² + d²),Iyy = (1/12) * m * (w² + d²),Izz = (1/12) * m * (w² + h²)。其中 w、h、d 分别是长、高、深。对于圆柱体,Izz = (1/2) * m * r²。

4.5 完整 URDF 文件结构

把上面所有零件拼起来,就是一个完整的人形机器人 URDF。我建议按这个顺序组织:

  1. 头部:head_link + head_joint(连接躯干)
  2. 躯干:torso_link(根节点)
  3. 左臂:left_arm_link + left_shoulder_joint → left_forearm_link + left_elbow_joint
  4. 右臂:right_arm_link + right_shoulder_joint → right_forearm_link + right_elbow_joint
  5. 左腿:left_leg_link + left_hip_joint → left_shank_link + left_knee_joint → left_foot_link + left_ankle_joint
  6. 右腿:right_leg_link + right_hip_joint → right_shank_link + right_knee_joint → right_foot_link + right_ankle_joint
<robot name="humanoid_robot">
  <!-- 头部 -->
  <link name="head_link">...</link>
  <joint name="head_joint" type="fixed">...</joint>
  
  <!-- 躯干 -->
  <link name="torso_link">...</link>
  
  <!-- 左臂 -->
  <link name="left_arm_link">...</link>
  <joint name="left_shoulder_joint" type="revolute">...</joint>
  <link name="left_forearm_link">...</link>
  <joint name="left_elbow_joint" type="revolute">...</joint>
  
  <!-- 右臂 -->
  <link name="right_arm_link">...</link>
  <joint name="right_shoulder_joint" type="revolute">...</joint>
  <link name="right_forearm_link">...</link>
  <joint name="right_elbow_joint" type="revolute">...</joint>
  
  <!-- 左腿 -->
  <link name="left_leg_link">...</link>
  <joint name="left_hip_joint" type="revolute">...</joint>
  <link name="left_shank_link">...</link>
  <joint name="left_knee_joint" type="revolute">...</joint>
  <link name="left_foot_link">...</link>
  <joint name="left_ankle_joint" type="revolute">...</joint>
  
  <!-- 右腿 -->
  <link name="right_leg_link">...</link>
  <joint name="right_hip_joint" type="revolute">...</joint>
  <link name="right_shank_link">...</link>
  <joint name="right_knee_joint" type="revolute">...</joint>
  <link name="right_foot_link">...</link>
  <joint name="right_ankle_joint" type="revolute">...</joint>
</robot>

最后提醒一句:写完 URDF 后,一定要用 check_urdf 命令验证。我每次写完都会跑一遍:check_urdf my_robot.urdf。它会告诉你有没有 missing link、joint 循环、惯性参数缺失等问题。别跳过这步,血的教训。