BACK_TO_BASE
URDF的建模思路(2)
Engineering Notebook // Build Log
/
21:07:21
/
NOTEBOOK_ENTRY

URDF的建模思路(2)

第二部分主要分析link和Joint,然后介绍草图到urdf的全过程,并且给出一个实践 Link(连杆) 基本Link定义 几何形状类型 1. 盒子(Box) 2. 圆柱(Cylinder) 3. 球体(Sphere) 4. 网格模型(Mesh) 坐标系说明 xyz : 位置偏移(x, y, z)单位:米 rpy : 旋转角度(roll, pitch, yaw)单位:弧度 roll: 绕X轴旋转 pitch: 绕Y轴旋转 yaw: 绕Z…

Notebook Time
2 min
Image Frames
1
View Tracks
59
URDF
FIELD_GUIDE

FIELD GUIDE

Use the guide rail to jump between sections.

第二部分主要分析link和Joint,然后介绍草图到urdf的全过程,并且给出一个实践

Link(连杆)

基本Link定义

<link name="base_link">
  <!-- 视觉属性 -->
  <visual>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <geometry>
      <box size="1 1 1"/>
    </geometry>
    <material name="blue">
      <color rgba="0 0 1 1"/>
    </material>
  </visual>

  <!-- 碰撞属性 -->
  <collision>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <geometry>
      <box size="1 1 1"/>
    </geometry>
  </collision>

  <!-- 惯性属性 -->
  <inertial>
    <origin xyz="0 0 0" rpy="0 0 0"/>
    <mass value="1.0"/>
    <inertia ixx="1.0" ixy="0.0" ixz="0.0"
             iyy="1.0" iyz="0.0"
             izz="1.0"/>
  </inertial>
</link>

几何形状类型

1. 盒子(Box)

<geometry>
  <box size="长 宽 高"/>
</geometry>

2. 圆柱(Cylinder)

<geometry>
  <cylinder radius="半径" length="长度"/>
</geometry>

3. 球体(Sphere)

<geometry>
  <sphere radius="半径"/>
</geometry>

4. 网格模型(Mesh)

<geometry>
  <mesh filename="package://package_name/meshes/model.stl" scale="1 1 1"/>
</geometry>

坐标系说明

  • xyz: 位置偏移(x, y, z)单位:米
  • rpy: 旋转角度(roll, pitch, yaw)单位:弧度
    • roll: 绕X轴旋转
    • pitch: 绕Y轴旋转
    • yaw: 绕Z轴旋转

Joint(关节)

关节类型

  1. fixed(固定关节): 不允许运动
  2. revolute(旋转关节): 绕轴旋转,有角度限制
  3. continuous(连续旋转关节): 绕轴旋转,无角度限制
  4. prismatic(滑动关节): 沿轴线性移动
  5. floating(浮动关节): 6自由度运动
  6. planar(平面关节): 平面内运动

关节定义示例

Fixed Joint(固定关节)

<joint name="base_to_link1" type="fixed">
  <parent link="base_link"/>
  <child link="link1"/>
  <origin xyz="0 0 0.5" rpy="0 0 0"/>
</joint>

Revolute Joint(旋转关节)

<joint name="joint1" type="revolute">
  <parent link="link1"/>
  <child link="link2"/>
  <origin xyz="0 0 0.5" rpy="0 0 0"/>
  <axis xyz="0 0 1"/>
  <limit lower="-1.57" upper="1.57" effort="10" velocity="1.0"/>
  <dynamics damping="0.7" friction="0.0"/>
</joint>

Continuous Joint(连续旋转关节)

<joint name="wheel_joint" type="continuous">
  <parent link="base_link"/>
  <child link="wheel"/>
  <origin xyz="0 0 0" rpy="0 0 0"/>
  <axis xyz="0 1 0"/>
  <limit effort="10" velocity="1.0"/>
</joint>

Prismatic Joint(滑动关节)

<joint name="slider_joint" type="prismatic">
  <parent link="base_link"/>
  <child link="slider"/>
  <origin xyz="0 0 0" rpy="0 0 0"/>
  <axis xyz="0 0 1"/>
  <limit lower="0" upper="1.0" effort="10" velocity="1.0"/>
</joint>

关节参数说明

  • parent: 父连杆名称
  • child: 子连杆名称
  • origin: 关节相对于父连杆的位置和姿态
  • axis: 运动轴方向(xyz向量)
  • limit: 运动限制
    • lower: 下限(弧度或米)
    • upper: 上限(弧度或米)
    • effort: 最大力/力矩
    • velocity: 最大速度
  • dynamics: 动力学参数
    • damping: 阻尼系数
    • friction: 摩擦系数

从草图到URDF的完整流程

让我们通过一个实际案例,展示从概念到URDF的完整过程。

案例:设计一个简单的巡检机器人

需求分析

  • 功能:室内巡检,携带相机和激光雷达
  • 环境:平坦地面,需要通过门框
  • 尺寸:宽度<0.6m,高度<1.2m
  • 负载:传感器总重约2kg
  • 速度:最大0.5m/s

第一步:概念设计

草图设计:

顶视图:
    +------------------+
    |    [激光雷达]     |
    |                  |
    |   +---------+    |
    |   | 控制器  |    |
    |   +---------+    |
    |                  |
    +------------------+
   ●                  ●
  左轮               右轮

侧视图:
    [激光雷达]
        |
    +-------+
    |       | ← 0.3m高
    |  主体 |
    +-------+
        |
       ●  ← 轮子半径0.1m

第二步:参数确定

部件尺寸(m)质量(kg)备注
基座0.5×0.4×0.38包含电池和控制器
左轮R=0.1, W=0.051驱动轮
右轮R=0.1, W=0.051驱动轮
支撑轮R=0.050.5万向轮
激光雷达R=0.05, H=0.040.3安装在顶部

第三步:坐标系设计

base_link坐标系:
- 原点:基座几何中心
- X轴:向前
- Y轴:向左
- Z轴:向上

左轮位置:
- X: 0 (与基座中心对齐)
- Y: 0.225 (基座宽度的一半 + 轮宽的一半)
- Z: 0 (与基座底部平齐)

右轮位置:
- X: 0
- Y: -0.225
- Z: 0

激光雷达位置:
- X: 0
- Y: 0
- Z: 0.15 (基座高度的一半)

第四步:惯性矩阵计算

基座(盒子):


ixx = (1/12) × 8 × (0.4² + 0.3²) = 0.167
iyy = (1/12) × 8 × (0.5² + 0.3²) = 0.227
izz = (1/12) × 8 × (0.5² + 0.4²) = 0.273

轮子(圆柱):


ixx = (1/12) × 1 × (3×0.1² + 0.05²) = 0.0027
iyy = (1/12) × 1 × (3×0.1² + 0.05²) = 0.0027
izz = (1/2) × 1 × 0.1² = 0.005

第五步:编写URDF

现在我们有了所有参数,可以开始编写URDF了(见下面的完整示例)。

第六步:验证测试

# 1. 语法检查
check_urdf patrol_robot.urdf

# 2. 可视化
roslaunch urdf_tutorial display.launch model:=patrol_robot.urdf

# 3. 在RViz中检查:
#    - 坐标系方向是否正确
#    - 轮子位置是否对称
#    - 激光雷达是否在正确位置
#    - 使用joint_state_publisher测试轮子转动

# 4. Gazebo仿真测试
roslaunch gazebo_ros empty_world.launch
rosrun gazebo_ros spawn_model -file patrol_robot.urdf -urdf -model patrol_robot

第七步:问题排查与优化

常见问题:

  1. 机器人在Gazebo中倒下

    • 检查质心位置
    • 增加基座质量
    • 降低重心高度
  2. 轮子转动方向错误

    • 检查axis参数
    • 调整rpy角度
  3. 碰撞检测异常

    • 简化碰撞几何
    • 检查碰撞体是否重叠
  4. 性能问题

    • 使用简单几何作为碰撞体
    • 减少mesh的面数

设计检查清单

在完成URDF后,使用此清单进行检查:

结构检查

  • 有且仅有一个base_link作为根节点
  • 所有link都通过joint连接到树中
  • 没有循环依赖
  • 命名规范一致

坐标系检查

  • 遵循ROS坐标系约定(X前Y左Z上)
  • 关节轴方向正确
  • 对称部件位置对称

物理参数检查

  • 所有link都有质量(非零)
  • 惯性矩阵正定(对角元素为正)
  • 质心位置合理
  • 关节限位合理

几何检查

  • 视觉几何与实际尺寸匹配
  • 碰撞几何简化合理
  • 没有明显的几何穿透

功能检查

  • 关节运动范围满足需求
  • 没有自碰撞
  • 传感器位置合理
  • 执行器参数合理

调试与优化

常见问题诊断

问题1:机器人在RViz中显示不正确

症状:

  • 部件位置错误
  • 部件方向错误
  • 部件缺失

诊断步骤:

# 1. 检查URDF语法
check_urdf your_robot.urdf

# 2. 查看TF树
rosrun tf view_frames
# 会生成frames.pdf,检查坐标系关系

# 3. 在RViz中显示TF
# 添加TF display,检查每个坐标系的位置和方向

# 4. 使用joint_state_publisher
# 手动调整关节角度,观察运动是否正确

解决方法:

  • 检查origin的xyz和rpy参数
  • 检查parent和child是否正确
  • 检查关节类型是否合适

问题2:机器人在Gazebo中行为异常

症状:

  • 机器人倒下或飞走
  • 关节运动不正常
  • 碰撞检测错误

诊断步骤:

# 1. 检查惯性参数
# 在Gazebo中查看质心位置
gz topic -e /gazebo/default/physics/contacts

# 2. 检查碰撞几何
# 在Gazebo中显示碰撞体
# View -> Collisions

# 3. 检查关节动力学
gz joint -m your_robot -j joint_name

解决方法:

  • 确保所有link都有非零质量
  • 检查惯性矩阵是否正定
  • 简化碰撞几何
  • 添加适当的阻尼和摩擦

问题3:关节运动方向错误

症状:

  • 关节转动方向与预期相反
  • 关节轴方向不对

诊断方法:

<!-- 在RViz中测试 -->
<!-- 1. 使用joint_state_publisher -->
<!-- 2. 调整关节角度为正值 -->
<!-- 3. 观察实际运动方向 -->

解决方法:

  • 调整axis参数(改变符号)
  • 调整origin的rpy参数
  • 重新设计坐标系

问题4:性能问题

症状:

  • Gazebo运行缓慢
  • RViz显示卡顿

解决方法:

<!-- 1. 简化碰撞几何 -->
<collision>
  <geometry>
    <!-- 使用box/cylinder/sphere代替mesh -->
    <box size="1 1 1"/>
  </geometry>
</collision>

<!-- 2. 降低mesh精度 -->
<visual>
  <geometry>
    <!-- 使用低面数的mesh -->
    <mesh filename="low_poly_model.stl"/>
  </geometry>
</visual>

<!-- 3. 减少传感器更新频率 -->
<update_rate>10</update_rate>  <!-- 降低频率 -->

优化技巧

1. 质量分布优化

目标:提高稳定性和运动性能

<!-- 降低重心 -->
<inertial>
  <!-- 将质心向下偏移 -->
  <origin xyz="0 0 -0.05" rpy="0 0 0"/>
  <mass value="10"/>
  <inertia .../>
</inertial>

<!-- 增加基座质量 -->
<!-- 使移动平台更稳定 -->

2. 关节限位优化

<!-- 避免自碰撞 -->
<limit lower="-1.57" upper="1.57" effort="10" velocity="1.0"/>

<!-- 添加安全余量 -->
<!-- 实际物理限位:-90° to +90° -->
<!-- URDF中设置:-85° to +85° -->
<limit lower="-1.48" upper="1.48" effort="10" velocity="1.0"/>

3. 动力学参数优化

<!-- 添加阻尼,使运动更平滑 -->
<dynamics damping="0.7" friction="0.1"/>

<!-- 不同关节使用不同参数 -->
<!-- 大关节:高阻尼 -->
<dynamics damping="1.0" friction="0.2"/>

<!-- 小关节:低阻尼 -->
<dynamics damping="0.3" friction="0.05"/>

4. 碰撞几何优化

<!-- 原则:碰撞几何尽量简单 -->

<!-- 不好的做法 -->
<collision>
  <geometry>
    <mesh filename="complex_model.stl"/>  <!-- 10000个面 -->
  </geometry>
</collision>

<!-- 好的做法 -->
<collision>
  <geometry>
    <cylinder radius="0.1" length="0.5"/>  <!-- 简单几何 -->
  </geometry>
</collision>

<!-- 或使用简化的mesh -->
<collision>
  <geometry>
    <mesh filename="simplified_model.stl"/>  <!-- 100个面 -->
  </geometry>
</collision>

5. 使用Xacro进行参数化

<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="my_robot">

  <!-- 定义参数,便于调整 -->
  <xacro:property name="base_length" value="0.6"/>
  <xacro:property name="base_width" value="0.4"/>
  <xacro:property name="base_height" value="0.2"/>
  <xacro:property name="wheel_radius" value="0.1"/>
  <xacro:property name="wheel_width" value="0.05"/>
  <xacro:property name="wheel_base" value="${base_width + wheel_width}"/>

  <!-- 使用参数 -->
  <link name="base_link">
    <visual>
      <geometry>
        <box size="${base_length} ${base_width} ${base_height}"/>
      </geometry>
    </visual>
  </link>

  <!-- 定义宏,避免重复代码 -->
  <xacro:macro name="wheel" params="prefix reflect">
    <link name="${prefix}_wheel">
      <visual>
        <geometry>
          <cylinder radius="${wheel_radius}" length="${wheel_width}"/>
        </geometry>
      </visual>
      <collision>
        <geometry>
          <cylinder radius="${wheel_radius}" length="${wheel_width}"/>
        </geometry>
      </collision>
      <inertial>
        <mass value="1.0"/>
        <inertia ixx="0.0027" ixy="0" ixz="0"
                 iyy="0.0027" iyz="0"
                 izz="0.005"/>
      </inertial>
    </link>

    <joint name="${prefix}_wheel_joint" type="continuous">
      <parent link="base_link"/>
      <child link="${prefix}_wheel"/>
      <origin xyz="0 ${reflect * wheel_base/2} 0" rpy="-1.5708 0 0"/>
      <axis xyz="0 0 1"/>
    </joint>
  </xacro:macro>

  <!-- 使用宏创建左右轮 -->
  <xacro:wheel prefix="left" reflect="1"/>
  <xacro:wheel prefix="right" reflect="-1"/>

</robot>

测试流程

阶段1:静态测试

# 1. 语法检查
check_urdf robot.urdf

# 2. 可视化检查
roslaunch urdf_tutorial display.launch model:=robot.urdf

# 3. 检查项:
#    - 所有部件都显示了吗?
#    - 位置和方向正确吗?
#    - 颜色和材质正确吗?

阶段2:运动学测试

# 1. 启动joint_state_publisher
roslaunch urdf_tutorial display.launch model:=robot.urdf gui:=true

# 2. 测试每个关节:
#    - 移动滑块,观察关节运动
#    - 检查运动方向是否正确
#    - 检查运动范围是否合理
#    - 检查是否有自碰撞

阶段3:动力学测试

# 1. 在Gazebo中加载
roslaunch gazebo_ros empty_world.launch
rosrun gazebo_ros spawn_model -file robot.urdf -urdf -model my_robot

# 2. 测试项:
#    - 机器人是否稳定?
#    - 重力影响是否正确?
#    - 关节运动是否平滑?
#    - 碰撞检测是否正确?

# 3. 施加力测试
gz model -m my_robot -f 10 0 0  # 施加10N的力

阶段4:集成测试

# 1. 加载控制器
roslaunch my_robot_control control.launch

# 2. 发送控制命令
rostopic pub /cmd_vel geometry_msgs/Twist "..."

# 3. 测试传感器
rostopic echo /scan  # 激光雷达
rostopic echo /camera/image_raw  # 相机

# 4. 记录数据
rosbag record -a

完整示例

简单两轮机器人

<?xml version="1.0"?>
<robot name="simple_robot">

  <!-- 基座 -->
  <link name="base_link">
    <visual>
      <geometry>
        <box size="0.6 0.4 0.2"/>
      </geometry>
      <material name="blue">
        <color rgba="0 0 0.8 1"/>
      </material>
    </visual>
    <collision>
      <geometry>
        <box size="0.6 0.4 0.2"/>
      </geometry>
    </collision>
    <inertial>
      <mass value="10"/>
      <inertia ixx="0.4" ixy="0" ixz="0"
               iyy="0.4" iyz="0"
               izz="0.2"/>
    </inertial>
  </link>

  <!-- 左轮 -->
  <link name="left_wheel">
    <visual>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
      <material name="black">
        <color rgba="0 0 0 1"/>
      </material>
    </visual>
    <collision>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
    </collision>
    <inertial>
      <mass value="1"/>
      <inertia ixx="0.01" ixy="0" ixz="0"
               iyy="0.01" iyz="0"
               izz="0.01"/>
    </inertial>
  </link>

  <!-- 右轮 -->
  <link name="right_wheel">
    <visual>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
      <material name="black">
        <color rgba="0 0 0 1"/>
      </material>
    </visual>
    <collision>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
    </collision>
    <inertial>
      <mass value="1"/>
      <inertia ixx="0.01" ixy="0" ixz="0"
               iyy="0.01" iyz="0"
               izz="0.01"/>
    </inertial>
  </link>

  <!-- 左轮关节 -->
  <joint name="left_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="left_wheel"/>
    <origin xyz="0 0.225 0" rpy="-1.5708 0 0"/>
    <axis xyz="0 0 1"/>
  </joint>

  <!-- 右轮关节 -->
  <joint name="right_wheel_joint" type="continuous">
    <parent link="base_link"/>
    <child link="right_wheel"/>
    <origin xyz="0 -0.225 0" rpy="-1.5708 0 0"/>
    <axis xyz="0 0 1"/>
  </joint>

</robot>

简单机械臂

<?xml version="1.0"?>
<robot name="simple_arm">

  <!-- 基座 -->
  <link name="base_link">
    <visual>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
      <material name="grey">
        <color rgba="0.5 0.5 0.5 1"/>
      </material>
    </visual>
    <collision>
      <geometry>
        <cylinder radius="0.1" length="0.05"/>
      </geometry>
    </collision>
    <inertial>
      <mass value="2"/>
      <inertia ixx="0.01" ixy="0" ixz="0"
               iyy="0.01" iyz="0"
               izz="0.01"/>
    </inertial>
  </link>

  <!-- 第一节臂 -->
  <link name="link1">
    <visual>
      <origin xyz="0 0 0.25" rpy="0 0 0"/>
      <geometry>
        <cylinder radius="0.05" length="0.5"/>
      </geometry>
      <material name="red">
        <color rgba="0.8 0 0 1"/>
      </material>
    </visual>
    <collision>
      <origin xyz="0 0 0.25" rpy="0 0 0"/>
      <geometry>
        <cylinder radius="0.05" length="0.5"/>
      </geometry>
    </collision>
    <inertial>
      <origin xyz="0 0 0.25" rpy="0 0 0"/>
      <mass value="1"/>
      <inertia ixx="0.02" ixy="0" ixz="0"
               iyy="0.02" iyz="0"
               izz="0.001"/>
    </inertial>
  </link>

  <!-- 第二节臂 -->
  <link name="link2">
    <visual>
      <origin xyz="0 0 0.2" rpy="0 0 0"/>
      <geometry>
        <cylinder radius="0.04" length="0.4"/>
      </geometry>
      <material name="green">
        <color rgba="0 0.8 0 1"/>
      </material>
    </visual>
    <collision>
      <origin xyz="0 0 0.2" rpy="0 0 0"/>
      <geometry>
        <cylinder radius="0.04" length="0.4"/>
      </geometry>
    </collision>
    <inertial>
      <origin xyz="0 0 0.2" rpy="0 0 0"/>
      <mass value="0.5"/>
      <inertia ixx="0.01" ixy="0" ixz="0"
               iyy="0.01" iyz="0"
               izz="0.001"/>
    </inertial>
  </link>

  <!-- 关节1:基座到第一节臂 -->
  <joint name="joint1" type="revolute">
    <parent link="base_link"/>
    <child link="link1"/>
    <origin xyz="0 0 0.025" rpy="0 0 0"/>
    <axis xyz="0 0 1"/>
    <limit lower="-3.14" upper="3.14" effort="10" velocity="1.0"/>
    <dynamics damping="0.7"/>
  </joint>

  <!-- 关节2:第一节臂到第二节臂 -->
  <joint name="joint2" type="revolute">
    <parent link="link1"/>
    <child link="link2"/>
    <origin xyz="0 0 0.5" rpy="0 0 0"/>
    <axis xyz="0 1 0"/>
    <limit lower="-1.57" upper="1.57" effort="10" velocity="1.0"/>
    <dynamics damping="0.7"/>
  </joint>

</robot>

最佳实践

1. 命名规范

  • 使用描述性名称:base_link, left_wheel, arm_joint1
  • 保持一致的命名风格
  • 避免使用特殊字符和空格

2. 坐标系设置

  • 遵循ROS坐标系约定(X前,Y左,Z上)
  • 关节轴方向要明确
  • 注意父子连杆的相对位置

3. 物理参数

  • 质量单位:千克(kg)
  • 长度单位:米(m)
  • 角度单位:弧度(rad)
  • 惯性矩阵必须正定

4. 惯性矩阵计算

常见几何体的惯性矩阵:

盒子(Box)

圆柱(Cylinder)

球体(Sphere)

5. 验证URDF

使用ROS工具验证URDF文件:

# 检查URDF语法
check_urdf my_robot.urdf

# 生成PDF可视化
urdf_to_graphiz my_robot.urdf

# 在RViz中查看
roslaunch urdf_tutorial display.launch model:=my_robot.urdf

6. 常见错误

  • 忘记定义base_link:每个机器人必须有一个根连杆
  • 关节轴方向错误:导致运动方向不符合预期
  • 惯性参数为零:在Gazebo仿真中会出错
  • 碰撞几何过于复杂:影响仿真性能
  • 父子关系循环:形成闭环会导致错误

7. 优化建议

  • 碰撞几何使用简化形状(box, cylinder, sphere)
  • 视觉几何可以使用详细的mesh模型
  • 合理设置关节限位,避免自碰撞
  • 为每个运动关节添加阻尼参数