启元无真机二开实战:先在Mac上养一台Q1------官方URDF到MuJoCo数字孪生的完整链路
文章目录
- 启元无真机二开实战:先在Mac上养一台Q1------官方URDF到MuJoCo数字孪生的完整链路
-
-
-
- 写在最前面
- [一、先把两件事分开:SDK 开发环境与机器人模型](#一、先把两件事分开:SDK 开发环境与机器人模型)
- [二、官方 q1-urdf 包里有什么](#二、官方 q1-urdf 包里有什么)
-
- [2.1 一次文件盘点](#2.1 一次文件盘点)
- [2.2 一个真实的坑:launch 文件是 ROS1 的](#2.2 一个真实的坑:launch 文件是 ROS1 的)
- [三、在 macOS 上加载官方 MuJoCo 模型](#三、在 macOS 上加载官方 MuJoCo 模型)
-
- [3.1 一行安装,三个数字](#3.1 一行安装,三个数字)
- [3.2 静置实验:零位姿态不是站立姿态](#3.2 静置实验:零位姿态不是站立姿态)
- 四、交叉验证:URDF、MuJoCo,与第三份"收窄版"
-
- [4.1 关节限位:URDF 与 q1_v3_mc.xml 零偏差](#4.1 关节限位:URDF 与 q1_v3_mc.xml 零偏差)
- [4.2 力矩字段:三处数字互相咬合](#4.2 力矩字段:三处数字互相咬合)
- [4.3 三份 MJCF 的关系:一次小型文档考古](#4.3 三份 MJCF 的关系:一次小型文档考古)
- [五、复现 SDK 控制回路:100Hz 节拍下的半余弦跟踪](#五、复现 SDK 控制回路:100Hz 节拍下的半余弦跟踪)
-
- [5.1 官方示例的控制参数](#5.1 官方示例的控制参数)
- [5.2 MJCF 只有 motor,位置伺服要自己补](#5.2 MJCF 只有 motor,位置伺服要自己补)
- [5.3 跟踪实验与误差分析](#5.3 跟踪实验与误差分析)
- 六、数字孪生的边界:它能验证什么,不能验证什么
- 七、真机到位后的复用清单
- 写在最后
- 参考资料
- 启元机器人开发者社区
-
-
写在最前面
版权声明:本文为原创技术实践记录,遵循 CC 4.0 BY-SA 协议。转载请注明出处。
本文依据启元官方 AtomGit 组织(atomgit.com/primebot)公开仓库与 Q1 SDK 示例代码梳理,q1-sdk 引用锚定提交
7310314eee058323a2c15ab1f4f947df4112573e(tag v1.0.0),q1-urdf 为 V3.0(2026-09-24 发布),资料查阅日期为 2026 年 10 月 23 日。全部仿真在 Apple 芯片 macOS(arm64)+ MuJoCo 3.17.0 + Python 3.13 上真实执行,文中每个数字都能在配套脚本里复算出来。
最近在跟启元机器人(PrimeBot)的二次开发生态。官方的《PrimeBot SDK 快速入门与集成指南》在系统环境一节写得很明确:推荐 Ubuntu 22.04 + ROS2 Humble,"暂不支持在 Mac、Windows 系统下进行开发"。对绝大多数手持 MacBook 的开发者来说,这句话基本等于劝退------为了折腾一个机器人,难道还要专门装一台 Ubuntu?
但这句话约束的其实只是一条链路:SDK 里 aimdk_msgs 自定义消息的 colcon 编译、以及编译产物在 ROS2 运行时里的加载。它没有约束另一样东西------机器人模型。启元把 Q1 的完整 URDF 描述包开源在了 AtomGit 上,里面除了 ROS 生态的 urdf/ 目录,还附带了三份 MuJoCo 格式的 XML 模型和 24 个 STL 网格文件。MuJoCo 官方直接发布 macOS arm64 的 pip wheel,装它只需要一行命令。换句话说,在真机和 Ubuntu 都还没就位之前,我们完全可以先在 Mac 上"养"一台 Q1:加载官方模型、核对关节限位、复现 SDK 示例的控制节奏,把运动学和轨迹规划这一层的工作提前做完。
这篇文章就是这条路的完整记录。它不是一篇"模型文件介绍",我会把三份官方 XML 之间的关系盘清楚(这里有一个值得写下来的发现),会做一次静置自由落体实验(结果本身就是一个坑),最后用 SDK 官方示例里的半余弦插值和 100Hz 发布节拍,在数字孪生里复现一次肘部伺服跟踪,并诚实地讨论这条链路能验证什么、不能验证什么。
#mermaid-svg-PjORJDEZCusXjnE3{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-PjORJDEZCusXjnE3 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-PjORJDEZCusXjnE3 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-PjORJDEZCusXjnE3 .error-icon{fill:#552222;}#mermaid-svg-PjORJDEZCusXjnE3 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-PjORJDEZCusXjnE3 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-PjORJDEZCusXjnE3 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-PjORJDEZCusXjnE3 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-PjORJDEZCusXjnE3 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-PjORJDEZCusXjnE3 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-PjORJDEZCusXjnE3 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-PjORJDEZCusXjnE3 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-PjORJDEZCusXjnE3 .marker.cross{stroke:#333333;}#mermaid-svg-PjORJDEZCusXjnE3 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-PjORJDEZCusXjnE3 p{margin:0;}#mermaid-svg-PjORJDEZCusXjnE3 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-PjORJDEZCusXjnE3 .cluster-label text{fill:#333;}#mermaid-svg-PjORJDEZCusXjnE3 .cluster-label span{color:#333;}#mermaid-svg-PjORJDEZCusXjnE3 .cluster-label span p{background-color:transparent;}#mermaid-svg-PjORJDEZCusXjnE3 .label text,#mermaid-svg-PjORJDEZCusXjnE3 span{fill:#333;color:#333;}#mermaid-svg-PjORJDEZCusXjnE3 .node rect,#mermaid-svg-PjORJDEZCusXjnE3 .node circle,#mermaid-svg-PjORJDEZCusXjnE3 .node ellipse,#mermaid-svg-PjORJDEZCusXjnE3 .node polygon,#mermaid-svg-PjORJDEZCusXjnE3 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-PjORJDEZCusXjnE3 .rough-node .label text,#mermaid-svg-PjORJDEZCusXjnE3 .node .label text,#mermaid-svg-PjORJDEZCusXjnE3 .image-shape .label,#mermaid-svg-PjORJDEZCusXjnE3 .icon-shape .label{text-anchor:middle;}#mermaid-svg-PjORJDEZCusXjnE3 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-PjORJDEZCusXjnE3 .rough-node .label,#mermaid-svg-PjORJDEZCusXjnE3 .node .label,#mermaid-svg-PjORJDEZCusXjnE3 .image-shape .label,#mermaid-svg-PjORJDEZCusXjnE3 .icon-shape .label{text-align:center;}#mermaid-svg-PjORJDEZCusXjnE3 .node.clickable{cursor:pointer;}#mermaid-svg-PjORJDEZCusXjnE3 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-PjORJDEZCusXjnE3 .arrowheadPath{fill:#333333;}#mermaid-svg-PjORJDEZCusXjnE3 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-PjORJDEZCusXjnE3 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-PjORJDEZCusXjnE3 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PjORJDEZCusXjnE3 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-PjORJDEZCusXjnE3 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PjORJDEZCusXjnE3 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-PjORJDEZCusXjnE3 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-PjORJDEZCusXjnE3 .cluster text{fill:#333;}#mermaid-svg-PjORJDEZCusXjnE3 .cluster span{color:#333;}#mermaid-svg-PjORJDEZCusXjnE3 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-PjORJDEZCusXjnE3 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-PjORJDEZCusXjnE3 rect.text{fill:none;stroke-width:0;}#mermaid-svg-PjORJDEZCusXjnE3 .icon-shape,#mermaid-svg-PjORJDEZCusXjnE3 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-PjORJDEZCusXjnE3 .icon-shape p,#mermaid-svg-PjORJDEZCusXjnE3 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-PjORJDEZCusXjnE3 .icon-shape .label rect,#mermaid-svg-PjORJDEZCusXjnE3 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-PjORJDEZCusXjnE3 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-PjORJDEZCusXjnE3 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-PjORJDEZCusXjnE3 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 真机链路(需 Ubuntu 22.04 + 实体Q1)
数字孪生链路(本文,macOS 可跑)
轨迹与参数原样复用
q1-urdf 官方包
URDF + 3份MuJoCo XML + 24个STL
模型加载与交叉校验
运动学 / 轨迹规划
限位检查 / 时序设计
Skill 逻辑 dry-run
PrimeBot SDK
aimdk_msgs colcon 编译
ROS2 Humble + FastDDS
有线直连 10.1.1.99
SetMcAction 状态机
BIPED_CUSTOM_UPPER
100Hz joint/command
真机执行
一、先把两件事分开:SDK 开发环境与机器人模型
官方快速入门对环境的描述集中在"编译与通讯"这一侧:Ubuntu 22.04.x LTS、ROS2 Humble、colcon、基于 FastDDS 的局域网通信,开发 PC 有线网卡配静态 IP 10.1.1.99,机器人侧运控板 10.1.1.100、大脑板 10.1.1.101。这套约束的根源在于 aimdk_msgs 这个 ROS2 消息包要用 rosidl 生成 C++/Python 绑定,而这套工具链官方只在 Ubuntu 上验证过。
但机器人模型是另一类资产。URDF 是 XML 描述的连杆-关节树,MuJoCo 的 MJCF 同样是 XML,STL 是三角网格------它们不含任何 ROS 运行时依赖,本质上是一组"机器人的结构说明书"。启元把它们开源出来(q1-urdf 仓库),目的就是让开发者可以在 MuJoCo、Gazebo、Isaac 乃至任意支持 URDF/MJCF 的工具链里重建这台机器人的几何与动力学。
所以"Mac 不能开发"和"Mac 不能碰机器人模型"是两件事。前者是官方文档的准确表述,后者是对它的误读。把模型层的工作提前到 Mac 上完成,等真机或 Ubuntu 环境就位时直接对接,本文补上 macOS 原生仿真这一角。
二、官方 q1-urdf 包里有什么
2.1 一次文件盘点
从 https://atomgit.com/primebot/q1-urdf 克隆(或从开发者社区下载页取 q1_v3.zip,约 11 MB),目录结构如下:
text
q1-urdf/
├── urdf/
│ ├── q1_v3.urdf # 主描述文件(SolidWorks 导出)
│ ├── q1_v3_no_collision.urdf # 去碰撞体版本
│ └── q1_v3_with_points.urdf # 带点云标记版本
├── xml/
│ ├── q1_v3.xml # MuJoCo 模型(收窄限位版,见 4.3 节)
│ ├── q1_v3_full_range.xml # MuJoCo 模型(全限位版)
│ └── q1_v3_mc.xml # MuJoCo 模型(带 22 个力矩执行器)
├── meshes/ # 24 个 STL 网格
├── config/
│ └── joint_names_Q1-V2.0-URDF-0601.yaml
├── launch/
│ ├── display.launch # ROS1 roslaunch 格式
│ └── gazebo.launch # ROS1 roslaunch 格式
├── CMakeLists.txt # catkin 构建
└── package.xml # format=2,buildtool_depend=catkin
用脚本解析主 URDF 文件,得到的机器人体检报告是:26 个 link、25 个 joint(其中 22 个 revolute 可控关节、3 个 fixed)、<mass> 字段合计 16.92 kg、24 个网格引用与 meshes/ 目录一一对应。22 个可控关节的构成为双腿各 6(髋 pitch/roll/yaw、膝、踝 pitch/roll)、双臂各 4(肩 pitch/roll/yaw、肘)、腰 2(roll/yaw)。另外三个关节------左右腕 yaw 与头颈------在 URDF 里登记为 fixed,不在可控之列。这与产品页标称的"22 个自由度、约 15 kg、站立高度 88 cm"(参考资料 5)对得上------URDF 里的 16.92 kg 是模型各连杆质量字段之和,与整机含电池、外壳的标称重量存在口径差,引用时应注明。
2.2 一个真实的坑:launch 文件是 ROS1 的
包内的 package.xml 写着 <buildtool_depend>catkin</buildtool_depend>,launch/ 下两个文件是 roslaunch 1 格式,display.launch 里还硬编码了一个旧路径 $(find Q1-V2.0-URDF-0601)/urdf/Q1-V2.0-URDF-0601.urdf------而包内实际文件名已经是 q1_v3.urdf。也就是说,在 ROS2 Humble 下直接 ros2 launch 这个包会失败,需要自己写一个 ROS2 版 launch 并修正路径。这大概率是 SolidWorks 导出工具链的默认产物没有随版本迭代清理。走 MuJoCo 路线则可以完全绕开这一段,这也是本文选 MuJoCo 而不是 RViz/Gazebo 的原因之一。
三、在 macOS 上加载官方 MuJoCo 模型
3.1 一行安装,三个数字
Apple 芯片 Mac 上只需要:
bash
python3 -m venv ~/workspace/q1-dt && source ~/workspace/q1-dt/bin/activate
pip install mujoco # 实测版本 3.17.0,wheel 原生支持 macOS arm64
加载官方带执行器的模型文件:
python
import mujoco
model = mujoco.MjModel.from_xml_path("q1-urdf/xml/q1_v3_mc.xml")
print(model.nq, model.nv, model.nu) # 29 28 22
三个数字值得解释一下,它们是理解这台机器人"数字身体"的第一层:nq=29 是广义坐标维度------22 个关节角,加浮动基座的 3 维位置与 4 维单位四元数(22 + 7 = 29);nv=28 是广义速度维度(基座 6 维 + 22 个一维关节速度);nu=22 是执行器数量,恰好等于可控关节数,说明官方给每个可控关节都配了一个执行器。模型里 27 个 body、71 个 geom、总质量 16.92 kg,与 URDF 解析结果一致。
3.2 静置实验:零位姿态不是站立姿态
新加载的模型所有关节角默认为 0(qpos 除基座外全零),直接步进仿真看看会发生什么:
python
data = mujoco.MjData(model)
mujoco.mj_forward(model, data)
print(data.qpos[2]) # 0.419 ------ 基座初始高度
for _ in range(3000): # timestep=0.001s,即仿真 3 秒
mujoco.mj_step(model, data)
print(data.qpos[2]) # 0.238
实测结果:基座高度从 0.419 m 掉到 0.238 m,机器人在仿真里瘫倒在地,但整个过程数值健康(qpos 全部有限,无发散、无 NaN)。这个"摔倒"不是模型坏了,而是暴露了一个容易踩的认知坑:URDF/MJCF 的关节零位不等于机器人的站立位 。对照 SDK 官方示例 upper_body_control.py 里的 DEFAULT_STAND_POSITIONS,站立姿态下左肘关节角是 1.0187 rad(约 58.4°),左右肩 pitch 也各有 0.017 rad 的预压角------把这些真实站立角度赋给 qpos,模型才会稳定站住。MuJoCo 模型文件里没有内置这个初始姿态(q1_v3_mc.xml 的 pelvis 只给了 pos="0 0 0.419"),所以拿默认零位做静置、做质心分析、做落体测试,得到的都会是"瘫倒的机器人"。写任何基于位姿的算法前,第一件事是把 SDK 示例里的站立姿态表搬进仿真。
下面两张图是这个坑的直观对照,都是 MuJoCo 官方离屏渲染器(mujoco.Renderer,macOS Metal 后端)输出的真实仿真画面,非示意图:

图1|站立姿态渲染图。上肢 10 个关节角取自官方 upper_body_control.py 的 DEFAULT_STAND_POSITIONS(锚定提交见文首),腿部 6 对关节为本文人工设定的微屈角(髋 -0.12 / 膝 0.24 / 踝 -0.11 rad,非官方值,仅用于展示"灌入合理姿态后模型可正常站立")。基座高度 0.419 m。

图2|默认零位自由仿真 1.5 秒时刻的静帧(基座高度 0.419 → 0.240 m)。关节全部为 0 的"机器人"已经瘫倒在地面网格上------模型与仿真都工作正常,不正常的是把零位当站立的假设。
四、交叉验证:URDF、MuJoCo,与第三份"收窄版"
数字孪生最怕的是模型与真机不符。官方包里有四份描述文件(1 份 URDF + 3 份 MJCF),它们之间的关系没有任何文档说明,只能自己盘。下面是配套脚本 verify_model.py 的全部校验逻辑与结果。
4.1 关节限位:URDF 与 q1_v3_mc.xml 零偏差
脚本逐关节比对 URDF <limit lower upper> 与 MJCF range 字段,22 个关节全部一致,偏差为 0(阈值 1e-3 rad)。顺带两个可以量出来的结构事实:左膝关节范围是 0°, 140°,下限恰好为 0,意味着模型层面就不允许膝反曲;髋 pitch 的 -175°, 90° 给了大腿几乎向后完整的摆动空间。
text
[规模] URDF 可动关节: 22
[规模] MuJoCo: nq=29 nv=28 nu=22 nbody=27 ngeom=71
[规模] MuJoCo 可动关节: 22 总质量: 16.92 kg
[限位] 不一致条目: 0
[自由度] 左膝关节 0.0° → 140.0°,下限恰为 0(结构上排除膝反曲)
[力矩] 髋/膝 36 N·m,踝 22 N·m(URDF effort 与 MJCF ctrlrange 一致)
[仿真] timestep=0.001s, 静置初始高度 0.419 m → 3s 后 0.238 m, 数值健康=True

图3|运行效果图
4.2 力矩字段:三处数字互相咬合
URDF 的 <limit effort> 标注了每个关节的标称力矩:10 个 36 N·m 关节(髋 pitch/roll/yaw、膝,每腿 5 个)与 12 个 22 N·m 关节(踝、肩、肘)。q1_v3_mc.xml 的 <actuator> 块里有一个细节:作者保留了一整段被 <!-- --> 注释掉的旧执行器定义(ctrlrange 为 ±40/±36/±30 三档),当前生效的是注释块之后的 22 个 <motor>------10 个 ±36、12 个 ±22,与 URDF effort 字段逐关节、逐档位完全吻合。用正则解析时务必先剥掉注释块,否则会把旧值统计进来(我第一次就数出了 43 个 motor)。也就是说,关节极限力矩这个参数在 URDF 和 MJCF 两处是互相印证的,可以放心拿来做轨迹的力矩可行性检查。

图4|22 个可控关节的活动范围与标称力矩全景。数据直接解析自官方 q1_v3.urdf(V3.0),按力矩档位着色;横轴为关节角(度),线段长度为 URDF limit 登记的 lower, upper。配套脚本 joint_limits_chart.py 可复现。
4.3 三份 MJCF 的关系:一次小型文档考古
这是本次盘点最有意思的发现。三份 MJCF 的分工此前没有任何文档说明,实测比对结果:
| 文件 | 关节限位 | 执行器 | 用途推断 |
|---|---|---|---|
q1_v3.xml |
收窄版(22 关节合计比 URDF 少 6.192 rad,肩 roll 为最大例外) | 无 | 纯可视化/几何查看 |
q1_v3_full_range.xml |
与 URDF 完全一致(22/22) | 无 | 全限位几何参考 |
q1_v3_mc.xml |
与 URDF 完全一致(22/22) | 22 个 motor | 仿真主战场 |
q1_v3.xml 的收窄模式逐关节看很有层次:20 个关节是两端各收约 0.1 rad(5.7°)的均匀安全余量------膝 0, 140° 收为 5.7°, 134.1°,髋 yaw -90°, 90° 收为 -84.2°, 84.2°;但两个肩 roll 关节是例外,上限从 160° 直接压到 88.2°(下侧只收 5°),意味着在这个模型里手臂侧举被限制在水平位置,无法举过头顶 。前者像导出工具链的固定余量,后者则明显是刻意的功能限制(可能与整机外壳干涉或演示安全有关,官方未说明,此处仅作数据陈述)。结论很实际:做轨迹规划时用 q1_v3_mc.xml(限位与 URDF 一致且带执行器),不要拿 q1_v3.xml 当限位依据------否则不仅整体边界小一圈,挥手、比心这类过顶动作还会被无端判为不可行。
五、复现 SDK 控制回路:100Hz 节拍下的半余弦跟踪
有了可信的"数字身体",下一步是把 SDK 的控制节奏搬进来。这一步的价值在于:等你拿到真机时,动作生成的时序设计已经在孪生里过了第一遍。
5.1 官方示例的控制参数
q1-sdk/examples/python/upper_body_control.py(锚定提交见文首)定义了上肢自定义关节控制的完整节奏,核心参数摘录:
python
PUBLISH_RATE_HZ = 100.0 # 命令话题发布频率
DEFAULT_STIFFNESS = 20.0 # 关节刚度增益,随命令一起下发
DEFAULT_DAMPING = 3.0 # 关节阻尼增益
def smooth_step(value: float) -> float:
"""SDK 同款半余弦缓动:姿态切换不是线性插值"""
value = max(0.0, min(1.0, value))
return 0.5 - 0.5 * math.cos(math.pi * value)
命令通过话题 /aima/mc/custom/joint/command 下发,消息 McCustomJointCommand 携带 10 个上肢关节(腰 2 + 双臂各 4)的 JointCommand 数组,每个元素含 position / velocity / effort / stiffness / damping 五个字段------注意 stiffness 和 damping 是随每条命令下发的伺服增益,这意味着 SDK 侧的关节控制本质上是位置伺服语义:上层给目标角度,机内 PD 控制器负责执行。
5.2 MJCF 只有 motor,位置伺服要自己补
这里出现另一项值得记录的发现:q1_v3_mc.xml 的 <actuator> 块里 22 个执行器全部是 <motor>(力矩输入),没有任何 <position> 执行器。也就是说官方 MJCF 只能"直接拧力矩",而 SDK 示例的语义是"发目标角度"。两者之间的落差,恰好就是真机上 MC 层(运控算法)替开发者做掉的那部分------PD 闭环在机器人内部,SDK 文档没有给出机内 PD 的确切参数。
要在数字孪生里复现位置伺服行为,可以用 MuJoCo 编译器 API 给指定关节追加一个 position 执行器,增益取 SDK 示例公开的 stiffness/damping 值,力矩上限取 URDF effort:
python
# 对 left_elbow_joint 注入位置伺服(URDF: limit[-0.87266, 1.6581], effort=22)
inject = (' <position name="servo_left_elbow_joint" joint="left_elbow_joint" '
'ctrlrange="-0.87266 1.6581" forcerange="-22 22" '
'kp="20" kv="3" forcelimited="true"/>\n </actuator>')
xml = MJCF_TEXT.replace(" </actuator>", inject)
model = mujoco.MjModel.from_xml_string(xml) # 注意 meshdir 相对路径,需落盘加载
必须向读者声明口径:这个 kp=20/kv=3 的执行器是对真机 MC 层行为的代理建模,参数取自 SDK 示例的公开默认值,不代表真机关节控制器的实际整定。实验的意义不在"预测真机表现",而在验证轨迹时序在位置伺服语义下是否自洽。
5.3 跟踪实验与误差分析
参考轨迹完全照 SDK 示例的生成方式构造:静止 1s,半余弦屈肘 0→1.0 rad 用时 2s,到位保持 1s,半余弦回落 2s,再静止收尾;命令以 100Hz 更新,物理步进 1kHz。实测结果(results_elbow.json):
| 指标 | 数值 | 说明 |
|---|---|---|
| 上升段最大跟踪误差 | 0.1027 rad(5.9°) | 出现在半余弦速度峰值附近 |
| 上升段误差 RMSE | 0.0748 rad(4.3°) | 全程滞后主导,非振荡 |
| 保持段平均误差 | -0.0327 rad(-1.9°) | 稳态滞后:重力矩与 kp 的平衡残差 |
| 下降段最大正偏差 | 0.1231 rad(7.1°) | 回落时参考快于实际,本质仍是滞后 |

图5|100Hz 命令节拍下的左肘伺服跟踪。上图为参考轨迹(半余弦,0→57.3°→0)与仿真实际角度;下图为二者之差。误差曲线呈"上升段负、下降段正"的反对称形态,是典型的位置伺服滞后特征而非欠阻尼振荡。数据来源:elbow_follow.py 实测输出 tracking.csv。

图6|跟踪过程的四个 MuJoCo 渲染关键帧(t=1.5/2.5/3.5/6.5s,肘角取 tracking.csv 实测列,其余关节保持图1 姿态)。对比四帧左臂:3.5s 屈肘最深、6.5s 已回落接近伸直,与图4 曲线逐点吻合;每帧"参考/实际"之差即图4 误差曲线的空间形态。
保持段那 -1.9° 的稳态误差是个教科书级的现象:位置伺服在重力负载下必然存在静态误差,kp × err = τ_gravity,kp=20 时前臂重力矩大约对应 0.03 rad 的滞后。这解释了两件事:其一,SDK 把 stiffness/damping 设计成每条命令可下发,正是为了让上层按负载调整这个刚度;其二,carry-clothes 技能在 manifest 里对上肢命令显式声明 stiffness: 20.0, damping: 3.0 并锁 100Hz 节拍(参考资料 1,primebot/carry-clothes 仓库 manifest.json),说明社区技能已经在按这套伺服语义做工程约定。对轨迹设计者的直接启示是:半余弦缓动把角速度峰值压在线段中点,最大跟踪误差同样出现在那里------如果动作对到位精度敏感,应该在关键姿态处安排保持段收敛,而不是让参考轨迹匀速扫过。
六、数字孪生的边界:它能验证什么,不能验证什么
这一节回答"做到这里,我到底获得了什么"。先把话说透:数字孪生验证的是"结构层"的正确性,验证不了"行为层"。
可以在这台 Mac 上完成并有意义的工作:关节空间轨迹规划与限位可行性检查(本文第四节的交叉校验就是它的前提);正运动学与关键帧姿态设计(把 DEFAULT_STAND_POSITIONS 之类的姿态表可视化);100Hz 命令节拍与示教数据的时间对齐验证;rpk 技能逻辑的 dry-run(官方 carry-clothes 仓库自带 --sim 模式,python3 main.py --sim 在本机 9.2 秒跑完 10 阶段任务链路,完全不需要 ROS2);以及未来接上 VLM/检测模型后的图像-意图链路联调。
不能验证的,同样要列清楚:步态与全身平衡(Q1 的运控算法在真机侧,MC 层对本文是黑盒,MuJoCo 里没有它的替身);力控行为(URDF/MJCF 的接触参数只有 friction="0.6" 这类手工值,碰撞刚度与真实脚垫橡胶完全不同);机内 PD 的真实整定(第五节的 kp/kv 是代理);通信链路时序(FastDDS 局域网丢包、时间同步这些只有真机能暴露);以及预设动作与示教 CSV 的真机重放效果。一句话边界:能在仿真里复现的问题,别留到真机上查;但仿真通过不等于真机通过,中间隔着整个 MC 层。
七、真机到位后的复用清单
数字孪生阶段产出的所有东西都应当可以原样迁移。真机到货后按这个顺序对接(均出自官方快速入门与 SDK 示例,标注了出处章节):
- 手机 App 或 SSH 切开发者模式:
ssh run@10.1.1.100后执行aima mode edit,选develop→basic,断电重启生效(Q1 快速入门 §2.1.3;注意 T1 文档与 carry-clothes 仓库写的是yamo mode edit,两机型命令不同名,上手前以随机附带的对应机型文档为准); - 开发 PC 有线网卡配静态 IP
10.1.1.99/24,ping 10.1.1.100与10.1.1.101验证连通(§2.1.1); - Ubuntu 22.04 + ROS2 Humble 环境编译
aimdk_msgs:colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release,之后每个终端 source SDK 的install/setup.bash(§2.2.1); - 不编译 SDK 先验证发现:
ros2 node list应出现/hal_camera、/mc_ros2_node等节点(§2.1.3); - 跑官方
upper_body_control.py示例,确认 100Hz 话题上真机响应与孪生趋势一致; - 把第五节生成的参考轨迹(tracking.csv 的 reference 列)以相同节拍灌入真机,对比两条实际曲线------这是数字孪生对真机调试的第一份"投名状"。

图7|官方桌面端的仿真入口,与本文命令行链路互为补充
写在最后
这条链路走下来,Mac 上这台"假 Q1"给我的价值不是玩具式的把玩,而是把二开前期最耗神的一类工作------结构理解、限位确认、时序设计------从"等真机、等 Ubuntu、等环境"的串行阻塞里摘了出来,变成现在就能推进的并行工作。启元把 URDF、MuJoCo XML、带执行器模型和 SDK 示例代码一起开源在 AtomGit 上,这个动作本身值得肯定:模型文件是可验证的,本文第四节的每一个交叉校验结论都来自公开数据的直接比对,不需要任何内部信息。
下一步我会把这条孪生链路接到 rpk 技能层:用官方 q1_v3_mc.xml 替换 carry-clothes 技能里的 Mock 后端,让技能的状态机驱动真实的物理仿真,那才是"数字孪生验证 Skill"的完整闭环。真机名额到手后,再回来补第七节第 6 步的对照数据。
参考资料
1 启元机器人官方 AtomGit 组织(q1-urdf V3.0 / q1-sdk / carry-clothes 等 17 个公开仓库)。访问日期 2026-10-23。https://atomgit.com/primebot
2 PRIMEBOT/q1-sdk,锚定提交 7310314eee058323a2c15ab1f4f947df4112573e(tag v1.0.0)。本文引用的控制参数与话题定义出自 examples/python/upper_body_control.py 与 aimdk_msgs/mc/motion/McCustomJointCommand.msg、aimdk_msgs/hal/msg/JointCommand.msg。https://atomgit.com/primebot/q1-sdk
3 PRIMEBOT/q1-urdf:urdf/q1_v3.urdf、xml/q1_v3.xml、xml/q1_v3_full_range.xml、xml/q1_v3_mc.xml。本文全部限位/力矩/质量数据解析自这四个文件。https://atomgit.com/primebot/q1-urdf
4 启元机器人开发者社区下载页(Q1 SDK v1.0.0.0、URDF V3.0、PrimeConsole 1.0.2 的发布版本依据)。访问日期 2026-10-23。https://dev.primebot.com/download
5 启元 Q1 官方产品页(88 cm / 约 15 kg / 22 自由度标称值)。https://www.primebot.com/q1.html
6 MuJoCo 官方文档:XML 参考(actuator 类型、qpos/nv/nu 语义)与 pip 分发说明。https://mujoco.readthedocs.io
本文全部实验的配套工程(verify_model.py / elbow_follow.py / joint_limits_chart.py / render_shots.py 及 results.json、tracking.csv 原始输出与官方 URDF 快照)已开源:atomgit.com/wodekouwei/q1-macos-digital-twin(锚定 tag v1.0.0)。
启元机器人开发者社区
想在自己的设备上走通这条链路,或获取 Q1/T1 SDK、URDF、PrimeConsole 与技能开发资料,欢迎访问 启元机器人开发者社区。