照着模板拼 URDF 只能得到”能显示”的模型;工程上你要的是”仿真行为可信“的模型。本文把建模背后被跳过的数学补上:joint origin 的 rpy 到底是什么旋转顺序 → 惯量张量怎么算、Fusion360 的数值为什么是错的(单位陷阱)→ transmission 减速比的力矩/速度换算 → xacro 参数化几何推导 → Gazebo 摩擦与阻尼调参 → 验证工具链。最后是 RoboMaster 云台/麦轮车这类实战模型的建模要点。
一、坐标变换:URDF 就是一棵 TF 树
树形结构的本质
URDF 里每个 <joint> 声明”child link 坐标系在 parent link 坐标系中的位姿“:
1 | <joint name="yaw_joint" type="revolute"> |
整个机器人 = 一棵有向树:每个 link 只能有一个父 joint,不能有环。TF 树就是这棵树运行时发布的结果——rviz 里看到的坐标系就是逐个 <origin> 连乘出来的。关节的旋转只改变 child 系的姿态(绕 parent 系中定义的 axis 旋转),位置不受影响。
rpy 的旋转顺序:为什么是 Rz·Ry·Rx
URDF 的 rpy="roll pitch yaw" 约定为:先绕 X 转 roll,再绕新 Y 转 pitch,最后绕新 Z 转 yaw(内旋),等价于固定轴外旋:
$$R = R_z(\text{yaw})\cdot R_y(\text{pitch})\cdot R_x(\text{roll})$$
注意矩阵乘法的顺序不能换——rpy="0 0 90°" 和 rpy="90° 0 0" 给出的姿态完全不同。KDL/ROS 内部全部用这个约定,写 URDF 和读 TF 时保持同一约定,否则旋转对不上。
万向锁:当 pitch = ±90° 时,roll 和 yaw 的旋转轴重合,丢失一个自由度,欧拉角表示退化(同一姿态有无穷多种 rpy 表示)。URDF 里一般碰不到(关节轴多是单轴),但从 rpy 转四元数、做插值时一定绕开 ±90°。
rpy → 四元数(Rz·Ry·Rx 约定):
$$w = c_y c_p c_r + s_y s_p s_r, \quad x = c_y c_p s_r - s_y s_p c_r$$
$$y = c_y s_p c_r + s_y c_p s_r, \quad z = s_y c_p c_r - c_y s_p s_r$$
($c_\cdot=\cos(\cdot/2), s_\cdot=\sin(\cdot/2)$)。四元数的好处是无万向锁、插值平滑,TF 内部就是用四元数存储姿态的。
传感器坐标系:optical frame 的坑
相机、雷达的坐标系约定和机器人不同(相机 z 轴朝前、y 轴朝下)。URDF 里要加一个 fixed joint 把传感器转到 optical 系,例如相机(绕 X 转 -90°):
1 | <joint name="camera_optical_joint" type="fixed"> |
忘了这个转换,点云/图像在 rviz 里就是”歪的”,SLAM 建图直接失败。
二、惯量:仿真可信度的根基
基本形状的惯量张量(推导结论)
惯性张量是 3×3 对称矩阵,对角线是绕各轴的转动惯量。常见形状(质量 m 均匀分布):
| 形状 | 轴向惯量 |
|---|---|
| 长方体 $a\times b\times c$ | $I_{xx}=\frac{1}{12}m(b^2+c^2)$,$I_{yy}=\frac{1}{12}m(a^2+c^2)$,$I_{zz}=\frac{1}{12}m(a^2+b^2)$ |
| 圆柱(半径 r、长 l) | $I_{zz}=\frac{1}{2}mr^2$(绕轴线),$I_{xx}=I_{yy}=\frac{1}{12}m(3r^2+l^2)$ |
| 球体 | $I=\frac{2}{5}mr^2$ |
| 实心长方体绕质心 | 同上表,非对角项为 0 |
以圆柱绕轴线的 $\frac{1}{2}mr^2$ 为例,积分思路:把圆柱切成半径 $\rho$ 的圆环,每环质量 $dm = \frac{m}{\pi r^2 l}\cdot 2\pi\rho,d\rho,l$,则
$$I_{zz} = \int_0^r \rho^2 dm = \frac{2m}{r^2}\int_0^r \rho^3 d\rho = \frac{1}{2}mr^2$$
平行轴定理:质心不在原点时
CAD 导出的惯性通常是”相对于质心“的,而 URDF 的 <inertial><origin> 可以指定质心位置。若质心相对 link 原点偏移 $d$,绕 link 原点的惯量要加一项:
$$I = I_c + md^2$$
算例:一根长 0.4m、质量 2kg 的均匀连杆,质心在 link 原点(中点)时 $I_{zz}=\frac{1}{12}ml^2 = \frac{1}{12}\times2\times0.16 = 0.0267,\text{kg·m}^2$;若把 <inertial><origin> 设在端点(偏移 0.2m),则 $I_{zz} = 0.0267 + 2\times0.04 = 0.1067,\text{kg·m}^2$——差了 4 倍。写错质心位置,仿真里的摆动频率、稳定性全部失真。
Fusion360 单位陷阱:×10⁻⁹ 是怎么来的
Fusion360 导出的惯性默认单位是 g·mm²(克·平方毫米),URDF 要求 kg·m²:
$$1,\text{g·mm}^2 = 10^{-3},\text{kg} \times (10^{-3},\text{m})^2 = 10^{-9},\text{kg·m}^2$$
所以所有数值 ×10⁻⁹:原笔记里的 689.2 → 6.892E-07,验证:$689.2\times10^{-9} = 6.892\times10^{-7}$ ✓。忘了换算 = 惯量小了 9 个数量级 = Gazebo 里模型轻如纸、一碰就飞。
惯量错误的表现与自检
| 症状 | 原因 |
|---|---|
| 模型静止时自己缓慢旋转/漂移 | 惯量张量非对角项乱填或质心严重偏离 |
| 控制增益调大就发散、仿真震荡 | 惯量比真实小几个数量级(单位没换算) |
| 倒下/碰撞后疯狂抖动 | 惯量过小 + 碰撞摩擦参数不匹配 |
自检方法:check_urdf 只查语法,查不了物理;拿已知质量的手动算一遍(如电机转子:M3508 转子约 100g、半径 ~2cm,$I \approx \frac{1}{2}\times0.1\times0.02^2 = 2\times10^{-5},\text{kg·m}^2$),和模型里的数量级对比。注意 URDF 的 <inertial> 缺失时 Gazebo 会用默认密度从几何体估算,算出来的质量与真实材料差几个数量级——每个 link 都要手填惯量,别指望自动。
三、Joint 与 Transmission:驱动链路
joint 类型与 limit 的物理含义
| type | 自由度 | 说明 |
|---|---|---|
| fixed | 0 | 刚性连接(传感器、支架) |
| revolute | 1 | 有限角度旋转,必须给 limit |
| continuous | 1 | 无限旋转(轮子) |
| prismatic | 1 | 直线滑动 |
| floating | 6 | 自由漂浮(无人机、机械臂末端调试) |
<limit> 里的 effort 是关节最大力矩(N·m),velocity 是最大角速度(rad/s)——Gazebo 的关节执行器会按这两个值饱和。**effort 不设或设得极大,Gazebo 里就是”大力出奇迹”**,模型会显得异常”暴躁”。
transmission:减速比 N:1 的力矩/速度换算
1 | <transmission name="wheel_trans"> |
mechanicalReduction = 50 表示电机转 50 圈,关节转 1 圈(减速比 50:1)。物理后果:
$$\tau_{joint} = N\cdot\tau_{motor} = 50\tau_{motor}, \qquad \omega_{joint} = \omega_{motor}/N$$
所以减速箱是”力矩放大器 + 速度衰减器“:M3508 配减速箱后关节侧力矩翻几十倍,但关节最高转速也相应除以减速比——云台/底盘选型时,先定关节需要的峰值力矩和最大转速,再反推电机侧需求,两个约束都要满足。
控制器与硬件接口对应(ros_control):
| 接口 | 控制器 | 说明 |
|---|---|---|
EffortJointInterface |
joint_effort_controller | 力矩/电流环 |
VelocityJointInterface |
joint_velocity_controller | 速度环 |
PositionJointInterface |
joint_position_controller | 位置环 |
| Joint State | joint_state_controller | 状态发布 |
ROS2 里 transmission 写法不变,但接口描述移入 <ros2_control> 标签:
1 | <ros2_control name="RobotSystem" type="system"> |
四、xacro:把几何变成公式
参数化:改一个数,全车跟着变
四轮小车最典型的做法:轮距、轴距、车体尺寸定义为 property,轮子位置用公式算而不是硬编码:
1 | <xacro:property name="base_length" value="0.4"/> |
${} 里支持完整 Python 表达式(**、pi、三角函数)。轮子 y 坐标 = ±轮距/2、z 坐标 = -轮半径——这些”几何关系”写进公式后,改一个 wheel_radius,轮子位置、惯量自动全对,不可能出现”轮子悬空”这类低级错误。
组织多文件
1 | <xacro:include filename="$(find my_robot)/urdf/common.xacro"/> <!-- 公共 property/宏 --> |
宏定义文件里只放 <xacro:macro>,实例化放到主文件——否则重复 include 会重复定义。
五、碰撞与 Gazebo 调参
为什么 STL 碰撞会卡:GJK 的成本
碰撞检测里,球/盒/圆柱是解析几何,一算就出结果;三角网格(STL)要走 GJK 等迭代算法,每帧每个接触点都要迭代,面数越多越慢。规则:
- visual 用 STL(好看),collision 用基本体包裹(快)——尺寸略大于真实轮廓(安全边距 1~2mm)
- 必须用网格时,用凸包(convex hull)代替原始网格:凸包是 GJK 的最爱,几百个面的凸包比几万个面的凹网格快一个数量级
- 单个 STL 控制在 2MB 以内(fusion2urdf 导出的网格记得简化)
摩擦与阻尼:模型为什么打滑/塌陷/乱晃
| 现象 | 参数 |
|---|---|
| 轮子原地打滑、爬不上坡 | <gazebo reference="wheel_link"><mu1>1.0</mu1><mu2>1.0</mu2></gazebo>(mu1/mu2 是两方向摩擦系数,橡胶对地面 0.8~1.2) |
| 关节松垮、模型软绵绵 | <gazebo reference="joint_name"><jointDamping>0.1</jointDamping></gazebo>(关节阻尼,模拟轴承/减速器摩擦) |
| 静止时抖个不停 | 接触刚度 kp/kd 默认值偏”硬”,或惯量过小;先查惯量再调 kp |
注意 Gazebo 的关节是”理想执行器+阻尼”模型,没有真实电机的电流环/速度环特性。仿真里调好的 PID 增益直接搬到真机往往太激进——真机有电流饱和、反电动势、机械谐振。反过来,真机参数进仿真也要打折。仿真验证的是运动学/算法逻辑,不是控制参数的绝对值。
六、验证工具链
1 | # 1. 语法与树结构 |
常见错误信息解读:
| 报错 | 原因 | 解法 |
|---|---|---|
root link ... has an inertia ... KDL does not support |
KDL 不允许根 link 带惯量(融合建图等工具链要求) | 加一个空 dummy link 做根,fixed joint 接 base_link |
No link found for ... |
joint 的 parent/child 拼写不一致 | 检查 name 严格匹配(区分大小写) |
| 模型在 RViz 里灰白无颜色 | 缺 joint_state_publisher 或 visual 没给 material |
装 ros-noetic-joint-state-publisher,给 <material> 设颜色 |
| Gazebo 里模型爆炸/穿透地面 | 惯量单位没换算 / 碰撞几何与 visual 不重合 | 检查 ×10⁻⁹、collision origin |
spawner 找不到 controller_manager |
launch 里用了旧式 spawner | 用 controller_manager 的 spawner 节点(或 ROS2 的 spawner 可执行文件) |
七、实战:云台与麦轮车建模要点
- base_link 是质心:base_link 的
<inertial><origin>放整车(不含轮子)质心,joint origin 从质心出发——写反了整车的动态就全错 - 云台两轴:yaw 关节轴沿 z(
axis="0 0 1"),pitch 关节轴沿 y(axis="0 1 0"),且 pitch 的 parent 是 yaw 的 child——树的层级就是机械的层级 - 麦轮/全向轮:轮子绕自身轴 continuous 旋转 + 辊子被动轴,碰撞用圆柱近似即可(辊子细节留给 visual)
- fusion2urdf 规范:零部件名只用字母数字、单个实体、刚体/旋转/滑动联接、子-父顺序、必须有一个
base_link、参数精确定位不用鼠标拖 - STL 缩放:米制模型
scale="0.001 0.001 0.001"(毫米转米),毫米制不用——导出前确认单位,错了整个模型尺寸差 1000 倍 - 惯性换算:Fusion360 惯性值全部 ×10⁻⁹,选”相对于质心”的数值
八、踩坑清单(实战总结)
- rpy 顺序记反:URDF 是 Rz·Ry·Rx,写
rpy="0 0 90"和rpy="90 0 0"是两种姿态;pitch 接近 ±90° 时小心万向锁 - 惯量单位不换算:g·mm² 当 kg·m² 用,模型轻 9 个数量级,Gazebo 直接放飞
- 质心偏移忘用平行轴定理:惯量差 4 倍,摆动频率全错
- collision 直接用 STL:仿真卡顿,用凸包/基本体包裹
- effort/velocity 不设:关节”力大无穷”,控制对比失真
- 传感器光学坐标系:不加 fixed 转换,点云歪的、SLAM 建图失败
- xacro 硬编码尺寸:改参数要改十几个地方,用 property +
${}公式 - 惯量缺失靠 Gazebo 自动估算:默认密度与真实材料差几个数量级
- 仿真 PID 直接上真机:Gazebo 关节没有电流环/谐振,参数要打折重调
- 树结构成环:URDF 必须是树,一个 link 只能有一个父 joint