
如何造一台人形机器人:从关节模组到 sim-to-real 的七层约束链
一台人形机器人不是七个模块的拼装,是一叠互相设边界条件的物理方程。本文沿整机技术栈逐层往下算:关节电机模组(声明扭矩 vs 准静态 CoP 真实需求,膝裕度跨三家厂商收敛到 2.23–2.42×,BHL 膝只有 1.51×)→ IMU(杆臂伪加速度是器件噪声底的 21,752 倍,安装位置比规格重要四个数量级)→ 材料与结构(X1 三版 BOM 的材质迁移史:进了闭链传动路径的零件全部升级到 7075-T6 / TC4 / 17-4PH)→ 传感器(先定观测空间再定采购清单)→ 电池与 BMS(「所有关节同时发力」在物理上不成立:G1 Σ 峰值 46,062 W 对 421 Wh 包等于 109 C)→ 软件控制与 CAN 下位板卡(22 节点 500 Hz 单总线负载率 130.9%,必须拆四路)→ 仿真训练与 sim-to-real:域随机化、sim2sim、零点标定(ATOM01 腰部 yaw 装机偏置 2.093 rad、验收门限 |q| < 1e-2 rad、四家驱动器里三家 write_motor_flash() 是空实现),以及 13 个真实失败模式里 10 个是硬件标定问题,不是仿真不够真。全部数字来自五台机器公开模型文件、部署与标定源码的程序化解析,以及厂商官方规格,可复算。
引言:一台人形机器人是一叠互相约束的物理方程
「怎么造一台人形机器人」这个问题,绝大多数回答停在两种极端:一种是发布会话术(「40 个自由度、2 小时续航、端到端大模型」),另一种是采购清单(「买 20 个关节模组、一块 NUC、一个 IMU」)。前者不解释任何一个数字从哪来,后者不解释为什么是这些数字。
本文按整机技术栈从上到下走一遍:关节电机模组 → IMU → 材料与结构设计 → 传感器 → 电池与 BMS → 软件控制/驱动/CAN 下位板卡 → 仿真训练与 sim-to-real。每一层都问同一个问题:这个参数是怎么被上下游约束死的?因为人形机器人不是模块拼装,而是一叠互相锁死的物理方程——踝关节扭矩决定脚掌能压出多大力臂,力臂决定 IMU 装在哪,IMU 的杆臂伪加速度决定它比传感器噪声底大几个数量级,整机峰值功率决定电池包必须能承受多少 C,而电池包的 C 率上限又反过来限制了「所有关节同时发力」这件事在物理上能不能发生。
我们用两组参照系。第一组是可复现的开源参照:UC Berkeley 的 Berkeley Humanoid Lite(下称 BHL,arXiv:2504.17249),22 个自由度、16.33 kg,从电机型号、FOC 固件、3D 打印参数到 Isaac Lab 训练配置全部公开在官方文档站,是唯一一台「照着做能真做出来」的样本。第二组是工业级参照:Unitree G1(29 dof / 35.12 kg)、Unitree H1(10 dof / 51.65 kg)、智元灵犀 X1(腿部 12 dof / 35.32 kg)、RoboParty ATOM01(23 dof / 33.76 kg)。所有扭矩、速度、质量、惯量数字来自这几台机器公开的 URDF / MJCF 文件程序化解析(G1 URDF、H1 URDF、X1 URDF 与 MJCF、ATOM01 URDF);电池包参数来自厂商官方规格;BMS 器件栈来自 TI / ADI / Renesas 公开资料;零部件型号与规格来自 RobotWorld 知识库已收录的 873 条零部件档案(每条均可在站内零部件库查到原始 specs)。全文所有外链在文末「数据来源与解析口径」一节按类别集中列出。
本文不替任何厂商背书,也不做「哪台更好」的排名。我们做的是把每一层的工程约束讲清楚,并给出可复算的数字。文末附完整的解析口径与数据来源。
整机技术栈:七层,每层都在给下一层设边界条件
flowchart TD A["1. 关节电机模组
无框电机 + 减速器 + 双编码器 + 驱动器
决定:扭矩 / 速度 / 反驱性 / 反射惯量"] B["2. 结构与材料
闭链连杆 / 7075-T6 / TC4 / 17-4PH
决定:质量分布 → 各关节静力矩需求"] C["3. IMU
安装位置 + 杆臂 r
决定:状态估计精度上限"] D["4. 传感器
视觉 / 激光 / 六维力 / 触觉 / 关节编码器
决定:策略观测空间"] E["5. 电池 + BMS
串数 S / 容量 Ah / C 率上限 / 双路径电流采样
决定:可交付峰值功率与续航"] F["6. 下位板卡与总线
FOC 电流环 → CAN 总线拓扑 → 机载算力
决定:控制频率与节点数上限"] G["7. 仿真与 sim-to-real
Isaac Lab 训练 → MuJoCo sim2sim → ONNX 上机
决定:策略能否落到真实硬件"] B --> A A --> E B --> C A --> D E --> F A --> F C --> G D --> G F --> G G -.->|域随机化反过来要求硬件留裕度| A
第一层:关节电机模组——先把扭矩预算算清楚,再谈买什么
关节模组占整机 BOM 的 40–60%,其中减速器又占单个模组的 30–50%。这是全机最贵的一块,也是最容易买错的一块。买错的典型形态是「看峰值扭矩下单」,而峰值扭矩恰恰是 URDF 里最不可靠的一个字段。
1.1 五台机器人的声明扭矩全景
把五份 URDF 里所有 revolute 关节的 effort(峰值扭矩,Nm)与 velocity(峰值角速度,rad/s)逐项相乘再求和,得到「全关节同时打到峰值」的理论总功率。结果如下:
| 机器人 | 质量 (kg) | revolute 关节 | Σ effort (Nm) | Σ 峰值功率 (W) | Nm/kg | W/kg | W/Nm |
|---|---|---|---|---|---|---|---|
| Berkeley Humanoid Lite | 16.33 | 22 | 440 | 6,600 | 26.9 | 404 | 15.00 |
| RoboParty ATOM01 | 33.76 | 23 | 1,458 | 30,024 | 43.2 | 889 | 20.59 |
| AgiBot 灵犀 X1(仅腿部) | 35.32 | 12 | 1,320 | 12,800 | 37.4 | 362 | 9.70 |
| Unitree H1 | 51.65 | 10 | 1,880 | 36,720 | 36.4 | 711 | 19.53 |
| Unitree G1(29 dof) | 35.12 | 29 | 1,464 | 46,062 | 41.7 | 1,312 | 31.46 |
最后一列 W/Nm(每牛米扭矩配多少瓦)是这张表里最有信息量的一列,它衡量的是速度设计意图而不是力量设计意图。五台机器从 9.7 一路排到 31.5,把整机设计意图排成了一条谱:X1 的 9.70 是全场最低,它的髋 pitch / 髋 roll / 膝全部是 150 Nm 却只配 8 rad/s(约 76 rpm),踝 80 Nm 配 10 rad/s——这是彻底的「大扭矩、低转速」准静态设计,关节按承载定而不按摆速定。BHL 的 15.00 是另一种低值:它 22 个关节清一色 20 Nm @ 15 rad/s,单档打天下,低 W/Nm 来自「一个规格」而不是「一种意图」。ATOM01 的 20.59 和 H1 的 19.53 落在中带,但两者的中带是拼出来的:ATOM01 是 9 个关节 120 Nm @ 25 rad/s(约 239 rpm,双腿 8 个加腰)配 14 个关节 27 Nm @ 8 rad/s(双踝 4 个加双臂 10 个),H1 是 200 Nm @ 23 rad/s 的髋配 300 Nm @ 14 rad/s 的膝。G1 的 31.46 是全场最高,髋 88 Nm 配 32 rad/s(306 rpm)、踝 50 Nm 配 37 rad/s(353 rpm),这是按动态步态甚至奔跑设计的速度谱。
把同位置的关节直接对起来看,这个差异有多硬:G1 髋 pitch 88 Nm × 32 rad/s = 2,816 W,X1 髋 pitch 150 Nm × 8 rad/s = 1,200 W。G1 的扭矩只有 X1 的 59%,功率却是它的 2.3 倍——因为功率是扭矩与转速的乘积,而转速差了 4 倍。同样位置的一个关节,配 8 rad/s 和配 32 rad/s 是两个完全不同的电机、两套完全不同的减速比、两种完全不同的整机定位。这个差异会一路传导到减速器选型(高转速档要低减速比、要行星而非谐波)、电池 C 率(第 5 层)和总线带宽(第 6 层)。
({
"_height": "420px",
"backgroundColor": "transparent",
"grid": { "left": 70, "right": 40, "top": 46, "bottom": 70 },
"tooltip": {
"trigger": "axis",
"axisPointer": { "type": "shadow" },
"backgroundColor": "rgba(15,23,42,0.95)",
"borderColor": "#3ddc97",
"borderWidth": 1,
"textStyle": { "color": "#e2e8f0" },
"formatter": function (p) {
var d = p[0];
var extra = {
"BHL": "22 关节 · Σ440 Nm · 16.33 kg · 26.9 Nm/kg",
"ATOM01": "23 关节 · Σ1,458 Nm · 33.76 kg · 43.2 Nm/kg",
"X1 (腿)": "12 关节 · Σ1320 Nm · 35.32 kg · 37.4 Nm/kg",
"H1": "10 关节 · Σ1880 Nm · 51.65 kg · 36.4 Nm/kg",
"G1": "29 关节 · Σ1464 Nm · 35.12 kg · 41.7 Nm/kg"
};
return "" + d.name + "
峰值功率密度: " + d.value + " W/kg
" + (extra[d.name] || "");
}
},
"xAxis": {
"type": "category",
"data": ["BHL", "ATOM01", "X1 (腿)", "H1", "G1"],
"axisLabel": { "color": "#94a3b8", "fontSize": 12 },
"axisLine": { "lineStyle": { "color": "#334155" } }
},
"yAxis": {
"type": "log",
"name": "W/kg(对数)",
"nameTextStyle": { "color": "#94a3b8" },
"axisLabel": { "color": "#94a3b8" },
"splitLine": { "lineStyle": { "color": "rgba(148,163,184,0.15)" } }
},
"series": [{
"type": "bar",
"barWidth": "46%",
"data": [
{ "value": 404, "itemStyle": { "color": "#3ddc97" } },
{ "value": 889, "itemStyle": { "color": "#4aa8f0" } },
{ "value": 362, "itemStyle": { "color": "#e0b341" } },
{ "value": 711, "itemStyle": { "color": "#f0708a" } },
{ "value": 1312, "itemStyle": { "color": "#9d7bf0" } }
],
"label": { "show": true, "position": "top", "color": "#e2e8f0", "formatter": "{c} W/kg" },
"markLine": {
"symbol": "none",
"lineStyle": { "color": "#f0708a", "type": "dashed" },
"label": { "color": "#f0708a", "formatter": "G1 = 3.2× BHL" },
"data": [{ "yAxis": 404 }]
}
}]
})
1.2 声明扭矩 vs 真实需求:准静态 CoP 模型
URDF里的 effort 是厂商声明的关节峰值扭矩,它不告诉你这个扭矩够不够,也不告诉你它是按什么工况定的。要判断够不够,最简单的可复算模型是单腿支撑、准静态、压力中心(CoP)移到脚尖:整机重量 $m$ 由一条腿承担,CoP 从踝关节轴心前移到脚尖,力臂为脚掌长度 $L_{foot}$,此时踝关节必须提供的俯仰力矩为
$$\tau_{ankle} = m \cdot g \cdot d_{cop}, \qquad d_{cop} \le L_{foot}$$
其中 $d_{cop}$ 是踝轴到 CoP 的水平距离。$d_{cop} = L_{foot}$ 是最坏情况(全部重量压在脚尖,即起步/蹬地的瞬间)。反解可得这台机器设计上允许 CoP 前移到多远:
$$d_{cop}^{max} = \frac{\tau_{ankle}^{declared}}{m \cdot g}$$
膝关节与髋关节的需求则不能用「假设一个关节角」来算——那样得到的数字会随假设漂。我们的做法是对物理目标做逆运动学(IK)反解,再对解出来的姿态做静力平衡。三个姿态目标全部是与关节角约定无关的几何量:小腿相对铅垂线前倾 40°(膝在踝正前方)、髋-膝-踝三点内夹角 100°(即屈膝 80°)、脚底保持水平且脚尖朝前。用牛顿迭代对 $(q_{hip}, q_{knee}, q_{ankle})$ 三个未知量解这三个残差,五台机器全部收敛到 $10^{-11}$ 量级以下(ATOM01 残差 $7.1\times10^{-15}$、G1 $1.6\times10^{-14}$、BHL $1.6\times10^{-14}$),解出的姿态再逐项回验几何:小腿前倾恰好 40.000°、膝内角恰好 100.000°、脚底倾角 0.000°。骨盆高度也随之从站姿落到蹲姿(ATOM01 0.5950 → 0.4664 m、H1 0.8700 → 0.6828 m、BHL 0.4100 → 0.3374 m),这是姿态真的被解出来而不是被假设出来的旁证。
力矩口径是:单腿支撑(最坏情况),地面反力等于整机重力 $W = mg$,作用点取整机质心在该支撑脚掌上的铅垂投影(准静态下这等价于静态 CoP);关节需求力矩 = 该关节远端子树(沿 URDF 运动链取 child 闭包)各连杆重力对关节轴的重力矩,加上地面反力对同一轴的重力矩:
$$\tau_j = \left(\vec p_{cop} - \vec p_j\right) \times \vec F_{GRF} \cdot \hat a_j + \sum_{l \in \text{distal}(j)} \left(\vec p_l - \vec p_j\right) \times (-m_l g\,\hat z) \cdot \hat a_j$$
其中 $\hat a_j$ 是 URDF 声明的关节轴在零位世界系下的单位向量。这个口径有一个自检:把姿态设为零位(直立)再算一遍,如果力臂定义或质量累加有错,残差不会是零。五台机器在零位下的踝/膝/髋力矩全部落在 $|τ| \le 12.04$ Nm(ATOM01 2.61、X1 −0.59、G1 −6.59、BHL −0.13、H1 12.04),而 H1 这 12 Nm 不是误差——它的零位姿态下踝轴到质心投影本身就有 73 mm 的偏距(脚掌 0.30 m 长、质心偏后),是真实的静态偏载。除 H1 外其余四台零位残差都在 CoP 偏距 3–33 mm 对应的量级内,模型自洽。
| 机器人 | 质量 (kg) | 踝轴→脚跟 (m) | 踝轴→脚尖 (m) | 踝 effort (Nm) | 可达 CoP 力臂 $d_{cop}^{max}$ (m) | 占脚尖力臂 | 40° 屈膝需求|踝/膝/髋 (Nm) | 裕度|踝/膝/髋 |
|---|---|---|---|---|---|---|---|---|
| BHL | 16.33 | 0.074 | 0.146 | 20 | 0.125 | 86% | 1.96 / 13.23 / 0.10 | 10.20× / 1.51× / 198.8× |
| ATOM01 | 33.76 | 0.054 | 0.104 | 27 | 0.082 | 78% | 11.91 / 49.52 / 2.61 | 2.27× / 2.42× / 46.0× |
| Unitree G1 | 35.12 | 0.066 | 0.142 | 50 | 0.145 | 102% | 5.79 / 57.96 / 5.52 | 8.63× / 2.40× / 15.9× |
| Unitree H1 | 51.65 | 0.100 | 0.200 | 40 | 0.079 | 39% | 7.12 / 134.48 / 12.32 | 5.62× / 2.23× / 16.2× |
| AgiBot X1 | 35.32 | 0.095 | 0.105 | 80 | 0.231 | 220% | 10.02 / 61.95 / 0.21 | 7.98× / 2.42× / 718.8× |
表里踝/膝/髋力矩取绝对值(符号由各 URDF 自己的关节轴朝向决定,跨机器不可比);「占脚尖力臂」= $d_{cop}^{max}$ ÷(踝轴→脚尖距离),这是判断「能不能把 CoP 推到脚尖」的那个分母,比脚掌全长更严格——因为力矩是从踝轴算起的,脚跟那一段长度对踝俯仰不产生力臂。这张表给出三个非常硬的结论,是选型时最该记住的:
结论一:踝关节的静力需求其实很小,真正区分五台机器的是「CoP 能前移到多远」。40° 屈膝下蹲时踝的真实需求只有 1.96–11.91 Nm,五台机器没有一台踝欠配,裕度 2.27×(ATOM01)到 10.20×(BHL)。所以「踝扭矩够不够」是个伪问题,真问题是 $d_{cop}^{max}$ 占脚尖力臂的比例,它从 39% 到 220% 拉开了 5.6 倍:H1 只有 39%——40 Nm 的踝配 0.30 m 长的脚掌(脚尖力臂 0.200 m),意味着 CoP 永远不能移到脚尖力臂的前 39% 之外,这台机器在物理上就不允许做脚尖蹬地起步,只能全脚掌平踩式行走,这不是软件能修的,是硬件天花板。G1 102% 是刚好够压到脚尖、不多不少;X1 220% 是两倍于「CoP 到脚尖」所需的储备,多出来的部分正是留给动态蹬地、跳跃与落地冲击的;ATOM01 78% 与 BHL 86% 落在「能站能蹲、蹬不了地」的准静态带里。踝的 effort 与脚掌长度之比,就是步态类型的指纹。
结论二:膝关节是唯一被系统性按 2.2–2.4× 配置的关节,而且这个窄带跨了三家厂商。ATOM01 2.42×、X1 2.42×、G1 2.40×、H1 2.23×——RoboParty、智元、宇树三家的膝裕度收敛到 2.23–2.42 这个 8% 宽的带里。这不是巧合:静力需求的 2.2–2.4 倍,正好是「静力 + 摆动期角加速度项 $J\dot\omega$」的量级,说明膝是五台机器上唯一一个明确按动态工况定 effort 的关节。唯一的例外是 BHL 的 1.51×:它 22 个关节全部是同一个 M6C12 + 15:1 行星减速 + 3D 打印摆线盘的自制模组,统一 20 Nm @ 15 rad/s,膝是按「一个规格打天下、够用就行」配的,40° 深蹲时 13.23 Nm 的需求已经吃掉 2/3 的额定扭矩。这才是 BHL 真正的紧点——它明确写在硬件里,不藏着,代价是靠训练时的域随机化(第 7.3 节)让策略避开极限姿态。
结论三:髋关节在矢状面准静态下几乎不加载,它的真实工况在冠状面。五台机器的髋 pitch 需求只有 0.10–12.32 Nm,裕度 15.9×–718.8×,看起来像普遍过度设计 20–700 倍。原因在几何上:IK 解出的 40° 屈膝姿态让躯干-髋轴-质心投影几乎共线,重力矩臂趋零(X1 只剩 0.6 mm、ATOM01 7.9 mm)。所以 URDF 里给髋配的 88–200 Nm 根本不是为矢状面静力配的。把同一套口径转到冠状面单腿支撑(整机质心横移到支撑脚的脚掌中心,这是单腿站立的最坏情况),结论立刻反过来:
| 机器人 | 髋 roll 轴 → 脚掌中心的横向偏距 (m) | 单腿支撑髋 roll 需求 (Nm) | 髋 roll effort (Nm) | 裕度 |
|---|---|---|---|---|
| AgiBot X1 | 0.0004 | 0.1 | 150 | 1127× |
| Unitree G1 | 0.0024 | 0.8 | 88 | 108× |
| ATOM01 | 0.0039 | 1.1 | 120 | 108× |
| BHL | 0.0230 | 2.6 | 20 | 7.7× |
| Unitree H1 | 0.1155 | 58.5 | 200 | 3.42× |
偏距是结构布局的指纹:H1 是宽胯——髋 roll 轴在 $y = 0.0875$ m 而脚掌中心在 $y = 0.2030$ m,横向偏开 115.5 mm,单腿一站髋 roll 就要扛 58.5 Nm,3.42× 的裕度是这张表里最紧的一个数;G1 / X1 / ATOM01 的踝几乎正对髋轴(偏距 0.4–3.9 mm),冠状面髋几乎零载荷。H1 把髋三个轴全部配到 200 Nm,真正的原因不在矢状面的 12.32 Nm,而在这 58.5 Nm。只读矢状面会得出「H1 的髋过度设计 16 倍」的错误结论——这正是「声明扭矩必须声明工况」的最好例证。
({
"_height": "400px",
"backgroundColor": "transparent",
"legend": { "data": ["踝关节裕度", "膝关节裕度"], "textStyle": { "color": "#94a3b8" }, "top": 4 },
"grid": { "left": 70, "right": 40, "top": 56, "bottom": 46 },
"tooltip": {
"trigger": "axis",
"axisPointer": { "type": "shadow" },
"backgroundColor": "rgba(15,23,42,0.95)",
"borderColor": "#3ddc97",
"borderWidth": 1,
"textStyle": { "color": "#e2e8f0" },
"formatter": function (ps) {
var s = "" + ps[0].name + "";
for (var i = 0; i < ps.length; i++) { s += "
" + ps[i].marker + ps[i].seriesName + ": " + ps[i].value + "×"; }
var cop = {
"BHL": "踝轴→脚尖 0.146 m · d_cop 0.125 m(86%)",
"ATOM01": "踝轴→脚尖 0.104 m · d_cop 0.082 m(78%)",
"G1": "踝轴→脚尖 0.142 m · d_cop 0.145 m(102%)",
"H1": "踝轴→脚尖 0.200 m · d_cop 0.079 m(39%)",
"X1": "踝轴→脚尖 0.105 m · d_cop 0.231 m(220%)"
};
return s + "
CoP 可达力臂: " + (cop[ps[0].name] || "");
}
},
"xAxis": {
"type": "category",
"data": ["BHL", "ATOM01", "G1", "H1", "X1"],
"axisLabel": { "color": "#94a3b8" },
"axisLine": { "lineStyle": { "color": "#334155" } }
},
"yAxis": {
"type": "log",
"name": "裕度倍数(对数)",
"nameTextStyle": { "color": "#94a3b8" },
"axisLabel": { "color": "#94a3b8" },
"splitLine": { "lineStyle": { "color": "rgba(148,163,184,0.15)" } }
},
"series": [
{
"name": "踝关节裕度",
"type": "bar",
"barWidth": "30%",
"itemStyle": { "color": "#4aa8f0" },
"label": { "show": true, "position": "top", "color": "#e2e8f0", "formatter": "{c}×" },
"data": [10.20, 2.27, 8.63, 5.62, 7.98],
"markLine": {
"symbol": "none",
"lineStyle": { "color": "#f0708a", "type": "dashed", "width": 2 },
"label": { "color": "#f0708a", "formatter": "1.0× = 恰好满足静力" },
"data": [{ "yAxis": 1.0 }]
}
},
{
"name": "膝关节裕度",
"type": "bar",
"barWidth": "30%",
"itemStyle": { "color": "#e0b341" },
"label": { "show": true, "position": "top", "color": "#e2e8f0", "formatter": "{c}×" },
"data": [1.51, 2.42, 2.40, 2.23, 2.42]
}
]
})
x1.urdf 的 12 个腿部关节 effort 合计 1320 Nm,而 xyber_x1_serial.xml(MuJoCo)的 12 个电机 ctrlrange 合计只有 872 Nm,差 51.4%;踝关节从 80 Nm 掉到 18 Nm(−77%),髋 roll 从 150 Nm 掉到 50 Nm(−67%)。做选型时如果只读了一份文件,你算出来的裕度可能整台机器都是错的。任何扭矩预算都必须先声明「读的是哪一份文件」。1.3 模组内部:电机、减速器、编码器、驱动器四件事的耦合
一个关节模组不是一个零件,是四个零件的耦合设计:无框电机(出扭矩)+ 减速器(换转速/放大扭矩)+ 双编码器(闭环)+ 驱动器(FOC 电流环)。任何一个的选型都会锁死其余三个。
扭矩放大与转速折损是一阶关系。设减速比 $n$、电机转矩常数 $K_t$(Nm/A)、电机转子惯量 $J_r$,则输出端:
$$\tau_{out} = n \cdot \eta \cdot K_t \cdot I, \qquad J_{reflected} = n^2 \cdot J_r, \qquad \omega_{out} = \frac{\omega_{motor}}{n}$$
$n^2$ 这一项是整个关节设计的核心矛盾:减速比每提高一倍,扭矩翻倍(好),但反射到输出端的转子惯量翻四倍(坏)。惯量大了,关节就「笨」——不可反驱、阻抗控制带宽低、碰撞时把冲击全传给连杆。这正是「准关节驱动(QDD)」路线存在的原因:用小减速比(6–15:1)+ 大电流电机,牺牲扭矩密度换反驱性和带宽。BHL 就是纯 QDD(15:1 行星),H1/X1 的髋膝也是低比行星,而腕关节、灵巧手这类小扭矩高带宽位置才用 100:1 以上的谐波。
BHL 的电机表征文档把整个自建模组测得非常完整,是目前唯一能拿到的「从裸电机到关节」的公开实测数据(模组装配与齿轮箱自制见Building the Actuator):
| 参数 | T-Motor M6C12(BHL 主用) | T-Motor 5010-110KV(备选) | 说明 |
|---|---|---|---|
| 相电阻 R | 0.1886 Ω | 0.6193 Ω | 线-线实测折半,决定铜损 $I^2R$ |
| 相电感 L | 0.0325 mH | 0.0850 mH | 决定电流环带宽上限 $f_c \approx R/2\pi L$ |
| 转矩常数 $K_t$(实测) | 0.0919 Nm/A | 0.1176 Nm/A | 由 $K_t = 11.6955/K_V$ 换算(Δ 接法) |
| $K_V$ 实测 vs 标称 | 127.19 vs 150(−15%) | 99.44 vs 110(−9.6%) | 标称值系统性偏高,必须自测 |
| 转子惯量 $J_r$ | 9.942×10⁻⁵ kg·m² | 3.301×10⁻⁵ kg·m² | 15:1 后反射 0.0224 / 0.00743 kg·m² |
| 减速后输出 Nm/A | 1.378(20 Nm 需 14.5 A) | 1.764(20 Nm 需 11.3 A) | ODRI 4004-300KV×9 仅 0.351 → 57 A |
| 位置编码器 | AS5600 磁编(改电阻提升分辨率) | 同 | 磁铁粘在转子轴上,I²C 读一次 78 µs |
最后一行那个 78 µs 是个非常有教育意义的数字。BHL 用 I²C 读 AS5600,单次读耗时 78 µs,直接给出了位置环的频率上限约 12.8 kHz——而它的固件跑在 160 MHz 的 MCU 上、-O2 编译(Recoil FOC)。也就是说,在这个设计里限制控制频率的不是算力,是编码器接口的物理传输时间。换成 SPI 或把磁编换成增量式 + 中断计数,同样的 MCU 能把环路上一个数量级。这是「驱动/下位板卡」层最典型的坑:预算算力之前先算接口。
同理,$K_V$ 标称比实测高 15% 这件事,意味着直接按标称 $K_t$ 设计的电流限幅会算错 15%:你以为 14.5 A 出 20 Nm,实际出 17 Nm,或者反过来你为了出 20 Nm 打到 17 A,超出驱动器的热设计。BHL 的做法是每台电机上电前做一次堵转/空载表征,把实测 R、L、$K_t$ 写进固件参数表。任何严肃的模组方案都应该有这一步。
1.4 反射惯量:远端关节是「电机主导」,近端是「连杆主导」
把 BHL 每个关节下游所有连杆的惯量(按平行轴定理折算到该关节轴,输入为 官方 URDF 的 mass / inertia / origin)与转子反射惯量 $n^2 J_r = 0.0224$ kg·m² 做比值,得到一张决定「这个关节该怎么控制」的表:
| 关节 | 下游连杆惯量 (kg·m²) | 转子反射惯量 (kg·m²) | 比值 (转子/连杆) | 主导方 | 控制含义 |
|---|---|---|---|---|---|
| 肘 roll | 0.00022 | 0.0224 | 103× | 电机 | 几乎全是转子惯量,反驱困难,需阻抗/导纳控制 |
| 踝 roll | 0.00098 | 0.0224 | 22.8× | 电机 | 接触关节却是电机主导 → 落地冲击全靠电流环吃 |
| 肩 yaw | 0.00082 | 0.0224 | 27.2× | 电机 | 小连杆 + 大减速比,典型电机主导 |
| 踝 pitch | 0.00765 | 0.0224 | 2.9× | 电机 | 过渡区 |
| 肘 pitch | 0.01276 | 0.0224 | 1.8× | 电机 | 过渡区 |
| 膝 pitch | 0.05608 | 0.0224 | 0.4× | 连杆 | 连杆主导,反驱性好,可直接力控 |
| 肩 pitch / roll | 0.068 / 0.073 | 0.0224 | 0.3× | 连杆 | 连杆主导 |
| 髋 yaw | 0.08021 | 0.0224 | 0.3× | 连杆 | 连杆主导 |
| 髋 pitch | 0.18273 | 0.0224 | 0.1× | 连杆 | 纯连杆主导,转子惯量可忽略 |
| 髋 roll | 0.19930 | 0.0224 | 0.1× | 连杆 | 纯连杆主导 |
({
"_height": "440px",
"backgroundColor": "transparent",
"grid": { "left": 130, "right": 70, "top": 30, "bottom": 50 },
"tooltip": {
"trigger": "axis",
"axisPointer": { "type": "shadow" },
"backgroundColor": "rgba(15,23,42,0.95)",
"borderColor": "#9d7bf0",
"borderWidth": 1,
"textStyle": { "color": "#e2e8f0" },
"formatter": function (p) {
var d = p[0];
return "" + d.name + "
转子/连杆惯量比: " + d.value + "×
" +
(d.value > 1 ? "电机主导 → 反驱差,需阻抗控制" : "连杆主导 → 反驱好,可直接力控");
}
},
"xAxis": {
"type": "log",
"name": "比值(对数)",
"nameTextStyle": { "color": "#94a3b8" },
"axisLabel": { "color": "#94a3b8" },
"splitLine": { "lineStyle": { "color": "rgba(148,163,184,0.15)" } }
},
"yAxis": {
"type": "category",
"data": ["髋 roll", "髋 pitch", "肩 roll", "肩 pitch", "髋 yaw", "膝 pitch", "肘 pitch", "踝 pitch", "踝 roll", "肩 yaw", "肘 roll"],
"axisLabel": { "color": "#94a3b8", "fontSize": 12 },
"axisLine": { "lineStyle": { "color": "#334155" } }
},
"series": [{
"type": "bar",
"barWidth": "55%",
"data": [
{ "value": 0.11, "itemStyle": { "color": "#3ddc97" } },
{ "value": 0.12, "itemStyle": { "color": "#3ddc97" } },
{ "value": 0.31, "itemStyle": { "color": "#3ddc97" } },
{ "value": 0.33, "itemStyle": { "color": "#3ddc97" } },
{ "value": 0.28, "itemStyle": { "color": "#3ddc97" } },
{ "value": 0.4, "itemStyle": { "color": "#3ddc97" } },
{ "value": 1.76, "itemStyle": { "color": "#e0b341" } },
{ "value": 2.93, "itemStyle": { "color": "#e0b341" } },
{ "value": 22.8, "itemStyle": { "color": "#f0708a" } },
{ "value": 27.3, "itemStyle": { "color": "#f0708a" } },
{ "value": 103.4, "itemStyle": { "color": "#f0708a" } }
],
"label": { "show": true, "position": "right", "color": "#e2e8f0", "formatter": "{c}×" },
"markLine": {
"symbol": "none",
"lineStyle": { "color": "#94a3b8", "type": "dashed", "width": 2 },
"label": { "color": "#94a3b8", "formatter": "1.0× 分界" },
"data": [{ "xAxis": 1.0 }]
}
}]
})
这张图解释了为什么「一套 PD 参数打全身」在真机上一定会失败。髋关节的转子惯量只占下游的 10%,你直接给扭矩指令,连杆立刻响应;肘 roll 的转子惯量是连杆的 103 倍,你给的扭矩先拿去加速转子,连杆几乎不动。G1 的 legged_gym 配置正是这么分组的:髋 $K_p=100$、膝 $K_p=150$、踝 $K_p=40$ Nm/rad,阻尼髋 2 / 膝 4 / 踝 2——膝刚度最高是因为它连杆主导且要扛 61.9 Nm 静力矩,踝刚度最低是因为它电机主导且是接触关节,硬了会把地面冲击直接打进基座。
1.5 减速器与模组选型:知识库里的真实货架
把上面的约束翻译成采购语言,就是三类减速器各自的地盘:
| 类型 | 典型减速比 | 背隙 | 反驱性 | 用在哪 | 知识库代表型号 |
|---|---|---|---|---|---|
| 行星(QDD) | 6–48:1 | 10–15 arcmin | 好 | 髋、膝、踝、肩——需要冲击吸收与力控 | CubeMars AKH70-48(48:1,74/222 Nm,159 Nm/kg,¥4888);BHL 自制 15:1 摆线盘 |
| 谐波(strain wave) | 50–160:1 | ≤10 arcsec | 差(不可反驱) | 腕、腰 yaw、手指——小扭矩高精度 | Harmonic Drive CSD-2A(50–160:1,峰值 12–823 Nm);绿的谐波 LHD 超薄中空轴(轴向长度较 LHS 缩短约 50%);来福 FS 系列 |
| 摆线/RV | 27–192:1 | <1 arcmin | 差 | 工业基座/肩轴,抗 5× 额定冲击;人形少见(太重) | Nabtesco RV-E/RV-C(额定 72–4900 Nm,瞬时冲击 5×);双环传动 SHPR-E |
| 行星滚柱丝杠(线性) | 按导程 | — | 中 | 线性髋/膝/踝(Optimus 路线) | Ewellix/Schaeffler(直径 8–240 mm,导程 2–50 mm,线速度 ≤1.8 m/s,加速度 ≤12,000 rad/s²);Rollvis RV/HRV |
选型上的实际判据只有三条。第一,先看反驱性再看扭矩密度:接触关节(踝)和需要力控的关节(膝、肩)如果上了谐波,你就永久失去了「用手推着它走」的能力,碰撞时也没有机械缓冲,全部冲击由电流环和连杆承担。第二,谐波怕摔——柔轮是薄壁弹性件,一次跌落冲击就可能造成永久变形,这在整机调试阶段是真实风险,所以行业主流是把谐波放在手臂远端(摔不着)而不是腿上。第三,看扭矩密度要连着看重量:CubeMars AKH70-48 的 159 Nm/kg 是按峰值扭矩算的,额定只有 74 Nm、1396 g,即额定密度 53 Nm/kg。厂商规格表里的峰值扭矩通常是 3 秒值,连续工作点要按额定算,否则会严重高估模组的持续能力。
一体化关节模组的货架形态已经很成熟,可直接对照整机需求选档:
| 模组 | 额定 / 峰值扭矩 | 重量 | 供电 | 编码器 | 总线 | 特点 |
|---|---|---|---|---|---|---|
| 优宝特 BM 系列(M5–M200) | 5.5–60 / 12–200 Nm | 280–4700 g | 24/42/72 VDC | 双绝对值 14 bit,断电位置记忆 | CAN 1 Mbps | 覆盖全身档位,一个系列打通 |
| 睿尔曼 WHG 系列 | 10–360 Nm | 0.8–5.77 kg | 24/48 V | 16 bit 增量 + 18 bit 绝对 | CAN FD | 带电磁抱闸,减速比 100/160 |
| 魔法原子 H70 | 30 / 96 Nm | 900 g | 48 V | — | CAN / EtherCAT | φ70×96 mm,12 mm 中空轴,30 rpm |
| CubeMars AKH70-48 | 74 / 222 Nm | 1396 g | 48 V(额定 6 A / 峰值 18 A) | 双 21 bit 磁编 | 双 CAN | 行星 48:1,背隙 12 arcmin,FOC 一体化 |
| Wonik Allegro Hand V4(手) | 0.70 Nm/关节 | 1.08 kg/整手 | 24 V / 100 W | 电位器 0.002° | CAN 333 Hz | 16 关节力矩控制,1:369 减速比 |
这张表里唯一需要单独强调的是「断电位置记忆」和「电磁抱闸」不是可选项。人形机器人一旦掉电,关节若没有绝对值多圈编码器或抱闸,整机就会在重力下垮塌——16–52 kg 的机器从站立姿态垮下来,最先损坏的恰恰是最贵的谐波减速器。多圈绝对值编码器(如多摩川 SA 系列,17–27 bit/圈 + 16 bit 多圈,无电池版 TS5966N)保证上电即知道姿态,抱闸保证髋膝在断电时能撑住自重。这两项的成本远低于一次摔机。
第二层:IMU——安装位置比器件规格重要四个数量级
IMU 是人形机器人唯一一个「装错位置就整机失效」的传感器。原因不是精度不够,而是杆臂效应(lever-arm effect):IMU 测的是它自己所在那一点的加速度,而状态估计器要的是基座质心的加速度,两者之间差一个由角运动产生的项。
设 IMU 安装点 $P$ 相对基座参考点 $O$ 的位置矢量为 $\vec r$,则两点加速度的关系是刚体运动学恒等式:
$$\vec a_P = \vec a_O + \dot{\vec\omega} \times \vec r + \vec\omega \times (\vec\omega \times \vec r)$$
第二项是切向项(角加速度 × 杆臂),第三项是向心项(角速度平方 × 杆臂)。这两项与重力、与真实平移加速度混在同一个读数里,估计器分不出来。它们的大小完全由 $\vec r$ 决定,而 $\vec r$ 是你在结构图上随手定的一个安装位置。
2.1 用 BHL 的真实安装位置算一遍
BHL 的 URDF里显式声明了一个 imu 关节(type="fixed",parent 为 base):xyz = (0.061722, 0, 0.68),parent 是 base。也就是 IMU 装在躯干内、基座原点前方 6.17 cm、上方 68 cm 处。IMU 到躯干质心的距离只有 0.065 m,但到踝关节/基座原点的杆臂在俯仰方向上是 0.683 m,在偏航方向上是 0.0617 m——相差 11 倍。
把典型的行走/抗扰角运动代进去:
| 工况 | 角加速度 $\dot\omega$ (rad/s²) | 角速度 $\omega$ (rad/s) | 杆臂方向 | 切向项 (m/s²) | 向心项 (m/s²) | 合成伪加速度 | 相当于 BNO085 噪声底的倍数 |
|---|---|---|---|---|---|---|---|
| 缓慢转身 | 20 | 3 | 俯仰 r=0.683 m | 13.66 | 6.15 | 14.98 m/s² = 1.53 g | 8,701× |
| 偏航 r=0.0617 m | 1.23 | 0.56 | 1.35 m/s² = 0.14 g | 786× | |||
| 快速转身 / 抗扰 | 50 | 6 | 俯仰 r=0.683 m | 34.15 | 24.59 | 42.08 m/s² = 4.29 g | 21,752× |
| 偏航 r=0.0617 m | 3.08 | 2.22 | 3.80 m/s² = 0.39 g | 1,965× | |||
| 跌倒/落地冲击 | 100 | 10 | 俯仰 r=0.683 m | 68.30 | 68.30 | 96.59 m/s² = 9.85 g | 43,503× |
| 偏航 r=0.0617 m | 6.17 | 6.17 | 8.73 m/s² = 0.89 g | 3,930× |
把这张表和器件规格放在一起看,结论就出来了。BNO085 的加速度计 RMS 噪声是 0.00157 m/s²,IM10A 是 0.00736 m/s²。而 BHL 那个 0.683 m 的俯仰杆臂在「快速转身」工况下产生 34.15 m/s² 的切向伪加速度,是 BNO085 噪声底的 21,752 倍;跌倒工况下是 43,503 倍。
器件噪声在这个量级面前完全不重要。你花十倍价钱把加速度计噪声从 1.57 mg 降到 0.16 mg,换来的是 0.015% 的改善;而把 IMU 从躯干顶端(0.683 m)挪到骨盆中心(0.1 m),伪加速度直接降 85%。这就是为什么所有人形机器人(Unitree、Tesla、Figure、Agility)都把 IMU 装在骨盆/躯干下部靠近质心的位置,而不是装在头部或胸腔上部——头部位置视觉好、散热好、装配方便,但杆臂最长,是最差的 IMU 位置。
2.2 为什么杆臂伪加速度是「可补偿的」,而噪声不是
杆臂效应有一个关键性质:它是确定性的。只要你知道 $\vec r$ 和 $\vec\omega, \dot{\vec\omega}$(陀螺仪直接给),就能把这两项精确减掉:
$$\vec a_O = \vec a_P - \dot{\vec\omega} \times \vec r - \vec\omega \times (\vec\omega \times \vec r)$$
这正是 BHL 在 URDF 里显式声明 imu 关节坐标的原因——那个 xyz=(0.061722, 0, 0.68) 不是给渲染用的,是给状态估计器做杆臂补偿用的。仿真里的 IMU 帧和真机上的物理安装位置必须逐毫米对上(URDF 与 MJCF 都把 imu 作为一等公民建模),否则 sim-to-real 时策略看到的基座加速度会有一个系统性偏差,而且这个偏差只在角运动时出现(静止站着完全看不出来),是最难调的一类 bug。
器件噪声则是随机的,只能靠滤波和积分时间压低,压不掉。所以选 IMU 的优先级是:① 安装位置(杆臂,量级 10³–10⁴)→ ② 零偏稳定性(决定长时间漂移)→ ③ 带宽(决定能否跟上 100 Hz 以上的冲击)→ ④ RMS 噪声(量级 10⁰)。绝大多数选型讨论把顺序完全搞反了。
2.3 器件档位与货架
| 参数 | BNO085(消费级,BHL 早期) | IM10A(BHL 现推荐) | Xsens MTi-620(工业级 VRU) | 华依 IMU3000(导航级) |
|---|---|---|---|---|
| 加速度计量程 | ±16 g | ±16 g | — | ±4 g |
| 加速度分辨率 | 1 mg/LSB | 0.5 mg/LSB | — | — |
| 加速度 RMS 噪声 | 0.16 mg | 0.75–1 mg | — | 偏置不稳定性 ≤0.015 mg |
| 加速度零偏漂移 | ±150 mg | ±20–40 mg | — | 偏置稳定性 ≤0.05 mg |
| 陀螺量程 | ±2000 °/s | ±2000 °/s | 2000 °/s | ±250 °/s |
| 陀螺 RMS 噪声 | 0.014 °/s | 0.028–0.07 °/s | 0.007 °/s/√Hz | ARW 0.15 °/√hr |
| 陀螺零偏 | ±1 °/s | ±0.5–1 °/s | in-run bias stability 8 °/h | 偏置不稳定性 ≤0.8 °/h |
| 带宽 | 8–1000 Hz | 5–256 Hz | 输出最高 2 kHz | 200 Hz |
| 磁力计 | ±13 Gauss | ±2 Gauss | 有(带气压计) | —(走 GNSS 组合) |
| 接口 | I²C / SPI | USB 直连 | UART / CAN / RS232 | — |
| 重量 / 尺寸 | — | — | 8.9 g / 28×31.5×13 mm | 22×22×5 mm |
| 定位 | 原型验证 | 开源人形首选 | 工业/研究 | 车规导航(ASIL-B) |
这张表里有一个反直觉的事实:BHL 从 BNO085 换到 IM10A,噪声指标是变差的(RMS 0.16 mg → 0.75–1 mg,陀螺噪声 0.014 → 0.028–0.07 °/s),但零偏漂移大幅改善(加速度 ±150 mg → ±20–40 mg)。BHL 的IMU 对比文档给出的理由是工程性的:IM10A 走 USB 直连,不需要 Arduino Nano 中转,少一层协议栈和少一个丢包源。对状态估计而言,零偏稳定性比 RMS 噪声重要得多——噪声可以被滤波平均掉($\propto 1/\sqrt{N}$),零偏是系统误差,积分一次就变成速度漂移、积分两次变成位置漂移。人形机器人靠 IMU 做姿态和接触检测,不靠它做绝对定位,所以「低漂移 + 简单可靠的数据链路」压倒「低噪声 + 复杂链路」。
还有一个容易被忽略的量程问题:跌倒工况下杆臂伪加速度合成达到 9.85 g,加上真实冲击,总读数很容易超过 ±16 g 量程。加速度计一旦削顶(clipping),姿态估计立刻发散,而这恰恰发生在最需要姿态估计的跌倒瞬间。所以 IMU 量程要按「杆臂伪加速度 + 冲击 + 重力」的总和留裕度,不是按行走工况留。导航级器件(华依 IMU3000 只有 ±4 g)在这类冲击场景下会直接削顶——它们的定位是车/无人机的平稳载体,不是会摔的双足机器人。
第三层:材料与结构设计——BOM 里的材质迁移是一部受力路径史
结构设计的核心矛盾只有一个:整机质量 $m$ 同时出现在扭矩需求($m g d$)和续航预算($E/P$)的分子上,也出现在惯量($J \propto m r^2$)里。减 1 kg 腿部远端质量,同时降低踝膝髋的静力矩需求、降低下游连杆惯量、降低整机功耗,是三赢;而减 1 kg 躯干质量只降低功耗和总静力矩,对惯量几乎无益(因为躯干靠近旋转轴,$r$ 小)。所以减重的优先级永远是远端 > 近端,脚的每一克都比胸腔的每一克贵。
3.1 X1 三版 BOM 的换料记录:材料升级方向不是随机的
智元 X1 的硬件仓库是三版 BOM 全部公开的唯一一台机器(agibot_x1_hardware 下按日期分目录:智元灵犀X1_20241024 / _20250108 / _20250307,每版含总 BOM xlsx、STEP 图纸与装机 SOP)。⚠️ 该仓库没有 LICENSE 文件,按著作权法默认「保留所有权利」,可参考但不可直接二次分发;相比之下 BHL(MIT + CC BY-SA 4.0)与 ATOM01(GPL-3.0)的授权是明确的。把三版 BOM 逐行对账,可以看到一次完整的材料迭代:
| 维度 | 首版 BOM(2024-10-24) | 第三版 BOM(2025-03-07) | 变化含义 |
|---|---|---|---|
| 材质标注种类 | 6 种 | 11 种 | 从「有什么用什么」到「按受力选料」 |
| 带材质标注的行数 | 100 行 | 167 行 | 材料从可选项变成必填字段 |
| 6061-T6 铝合金 | 50 处 | 大幅减少 | 被 7075-T6 / 17-4PH 替换 |
| 3D 打印树脂/ABS | 43 处 | 保留在非承力位置 | 原型件转正为外壳/支架件 |
| 腕十字轴 | 40Cr | 17-4PH | 沉淀硬化不锈钢,抗交变载荷 + 耐腐蚀 |
| 腰部支撑十字架 | 6061 | 17-4PH | 闭链机构核心承力件 |
| 脚踝摆臂 / 脚掌支撑 / 腰部连杆组件 | 6061 | 7075-T6 | 屈服强度 276 → 503 MPa(+82%) |
| 腰部连杆组件-5 / 脚踝连杆 | 6061 / 「钛合金」(未标牌号) | TC4(Ti-6Al-4V) | 比强度最高,用在最难减重的位置 |
这背后的工程逻辑值得展开,因为它是所有自研结构都要走的一步。6061-T6 的问题不是强度不够,是疲劳和接触应力不够。屈服 276 MPa 对于一次性静载完全够用,但闭链连杆的销轴承受的是微动磨损(fretting)+ 交变接触应力——两个面在高压下反复微幅相对滑动,这是铝合金最差的工况,会在远低于屈服强度的应力下产生疲劳裂纹。换成 17-4PH(沉淀硬化不锈钢,H900 时效后屈服 1170 MPa)或 7075-T6(屈服 503 MPa)是把疲劳极限和表面硬度一起提上去。TC4 用在腰部连杆是因为那里同时要求高强度和低质量,而 TC4 的密度只有 4.43 g/cm³(7075 是 2.81,钢是 7.85),比强度最高,代价是加工困难、成本高、导热差。
3.2 三种制造路线的成本结构
ATOM01 是唯一公开完整 BOM 单价的整机(散件清单 xlsx,随 CNC 加工 / 3D 打印 图纸一起发布),它的成本结构给出了「开源复现」路线的真实价格分布:
| 成本项 | 金额(¥) | 占比 | 说明 |
|---|---|---|---|
| 电机(23 台 DM 系列) | 31,187 | 62.7% | 9 台 DM-J10010L(URDF effort 120 Nm @ 25 rad/s 档)+ 14 台 DM-4340(27 Nm @ 8 rad/s 档) |
| CNC 机加工(68 件) | 15,670 | 31.5% | 结构件全金属,单件均价 ¥230 |
| 传感 + 计算 + 电池 + 线缆 + 紧固件 | 约 2,850 | 5.8% | 五项加起来不到电机的 1/10 |
| 合计 | ≈49,700 | 100% | 表内三处总额不一致(49,713 / 49,743.53 / 49,652.53),差 91 元 = 0.18% |
94% 的钱花在电机和 CNC 上,这个数字彻底改变了优化方向。想降成本,减传感器、换便宜 IMU、用国产计算板全部加起来也只有 6% 的空间;真正的杠杆只有两个——降电机单价(换国产模组或自研 QDD)和降 CNC 件数(用 3D 打印/铸造替代部分结构件)。BHL 走的正是第二条路:22 个关节全部用 3D 打印摆线盘 + 市售 T-Motor 电机自制模组(打印参数见官方 3D Printing Instructions),结构件以 Bambu Lab X1C 打印的 PLA 为主,整机成本压到 ATOM01 的几分之一,代价就是前面算出的膝关节 1.51× 裕度——22 个关节共用一个规格,膝是唯一在 40° 深蹲下吃掉 2/3 额定扭矩的位置。
3.3 结构设计的三个隐藏约束
约束一:碰撞体不是可选项。X1 的 URDF里 58 个连杆只有 3 个带 collision geometry(3/58)。这意味着在仿真里,它的自碰撞检测基本是关闭的——手臂可以穿进躯干,脚可以穿过小腿。策略在这种仿真里训出来,上真机第一件事就是撞到自己的身体。复现时必须自己补碰撞体,而这需要知道每个连杆的真实包络尺寸,也就是需要 CAD。没有碰撞体的 URDF 不是「简化模型」,是一个物理上不成立的模型。
约束二:左右对称件不能直接复制惯量。X1 的左腕连杆惯量是从右腕复制过来忘了改镜像符号的(惯性积 $I_{xy}, I_{yz}$ 的符号必须翻转)。这个错误在仿真里几乎看不出后果(腕部惯量占比极小),但它揭示了一个普遍问题:镜像件在 CAD 里是几何镜像,在 URDF 里必须是惯量张量的符号镜像。批量导出时这是最容易出错的一步,也是自研结构必须写脚本校验的一步。
约束三:线束是最便宜也最容易断的零件。行业数据里线束是 BOM 上最便宜的一行,却是柔顺疲劳风险最高的一行(ATOM01 专门发布了一份 走线说明 PDF)。人形机器人有 30 个左右的活动关节,每个关节处的线束都要跟随关节全行程反复弯曲/扭转。BHL 的 BOM 与线束清单给出了开源级的具体做法:14 AWG 硅胶线走动力(白/红为正、黑为地),30 AWG 走 CAN 信号,主干用 XT60、分支用 XT30,调试节点用 WAGO 快接,上位机侧接 USB-CAN 适配器。动力线与信号线分开走线、分开线径,是因为 FOC 的相电流是高频 PWM 斩波,会与 CAN 差分对产生共模干扰——把 14 AWG 动力和 30 AWG CAN 绑成一束走同一个关节,是 CAN 偶发丢帧最常见的物理原因。
torso_joint,14 个小档 = 双踝 4 个加双臂 10 个),一一对应。但「这两档扭矩是多少」在同一台 ATOM01 上有三个不同的读数:rpo.urdf 的 effort 是 120 / 27 Nm;驱动层 dm_motor_driver.cpp 里 DM_Limit_Param 的 TauMax 钳位是 200 / 28 Nm(大档钳位比 URDF 高 67%,小档只差 3.7%);而 BOM 只给到型号 dm-j10010l.SLDPRT / DM 4340 rau 3505,不含任何扭矩数字。小档那两个读数几乎重合(27 vs 28)不是巧合——URDF 的 effort 就是照着驱动层的钳位写的;大档差 67% 则说明 URDF 主动收窄了上限,把「电机能给多少」和「整机允许用多少」分开了。速度档同样对得上:SpdMax 是 25 / 20 rad/s,URDF 的 velocity 是 25 / 8 rad/s——小档 8 rad/s 是被整机主动限到驱动上限的 40%。X1 则对不上:URDF 只声明 12 个 revolute(全部在腿上),而 v3 BOM 列了 28 个执行器单元——手臂与腰部的 15 个关节在 URDF 里被焊成了 fixed。这不是错误,而是一个信号:X1 开源的 URDF 是「腿部步态训练专用模型」,不是整机模型。拿它训手臂操作策略会一无所获。3.4 材料选择的判据表
| 位置 | 主导载荷 | 推荐材料 | 理由 | 不推荐 |
|---|---|---|---|---|
| 脚掌 / 踝摆臂 | 冲击 + 接触应力 + 磨耗 | 7075-T6 / TC4 + 表面硬化 | 落地冲击 3–5× 体重,反复微动 | 6061(疲劳差)、纯 PLA(蠕变) |
| 闭链连杆 / 销轴 | 交变载荷 + 微动磨损 | 17-4PH / SCM435 / 40Cr 调质 | 需要高疲劳极限 + 高表面硬度 | 铝合金(微动磨损敏感) |
| 大腿 / 小腿主结构 | 弯曲 + 需减重 | 7075-T6 或 TC4(预算充足) | 远端质量三赢,值得花钱 | 6061 厚壁补强(重量反增) |
| 躯干框架 | 刚度 + 安装基准 | 6061-T6 型材/板 | 靠近旋转轴,减重收益低,够用即可 | TC4(性价比差) |
| 外壳 / 支架 / 走线件 | 几乎无载荷 | 3D 打印(PLA/PETG/尼龙) | 迭代快、成本近零、可现场改 | CNC(浪费预算) |
| 减速器柔轮 | 循环弹性变形 | 专用弹簧钢(供应商保密) | 这是谐波减速器的核心 know-how | 自制(疲劳寿命不可控) |
3D 打印在人形结构里的正确定位是「非承力件的主力 + 承力件的原型」。BHL 把 3D 打印推进到了承力件(摆线盘),这是个例外,成立的前提是:它的关节是 QDD 低减速比,摆线盘承受的接触应力远低于谐波柔轮;而且它的整机只有 16.33 kg,远端载荷小。同一套打印件放到 51.65 kg 的 H1 上会立刻失效。PLA 的另一个隐藏问题是蠕变和玻璃化转变温度(约 60 °C)——电机紧邻的支架件在持续工作下会接近这个温度,PLA 会缓慢变形。承力或近热源位置应该用 PETG、尼龙或碳纤维增强件。
第四层:传感器——先定观测空间,再定采购清单
传感器选型最容易犯的错是「按品类买齐」:一台深度相机、一个激光雷达、两个六维力、几个触觉,装上去就以为完成了。正确的顺序是反过来的:先看策略需要哪些观测(observation space),再决定装什么。观测空间是可以在训练配置里逐维读出来的,它比任何产品介绍都准确。
4.1 从训练配置反推传感器需求
G1 的 legged_gym 训练配置(g1_config.py)给出了一个非常干净的样本:47 维观测、50 维特权观测(privileged)、12 维动作。把这 47 维拆开,就能看出步态策略真正需要什么传感器:
| 观测项 | 维度 | 来源传感器 | 是否必须真机可得 | 缺失后果 |
|---|---|---|---|---|
| 基座角速度 | 3 | IMU 陀螺 | 是 | 无法估计姿态,立刻摔 |
| 投影重力向量 | 3 | IMU 加速度计 + 姿态解算 | 是 | 不知道自己是歪的 |
| 速度指令(x/y/yaw) | 3 | 摇杆/上位机 | 是(外部输入) | 不知道要去哪 |
| 关节位置偏差 | 12 | 关节编码器 | 是 | 无位置闭环 |
| 关节速度 | 12 | 关节编码器(差分/驱动内置) | 是 | 无阻尼项,振荡 |
| 上一帧动作 | 12 | 策略自身记忆 | — | 动作不平滑 |
| 合计 | 47 (含 2 维常量/噪声位) |
这张表最反直觉的结论是:整个行走策略里没有视觉、没有激光雷达、没有六维力、没有触觉。一个能稳定行走、抗推、爬缓坡的双足策略,只需要 IMU + 关节编码器。50 维特权观测里才包含地形高度图、摩擦系数、基座真实速度、外力扰动——这些只在仿真里可得,用于 teacher-student 蒸馏,真机上不存在。
那 50 维特权观测的存在恰恰说明了另一件事:真机上缺的信息,靠域随机化和特权蒸馏来补,而不是靠加装传感器。摩擦系数在真机上不可测(只能靠脚底打滑反推),所以训练时直接在 $[0.1, 1.25]$ 区间随机;基座真实速度真机上只能靠状态估计器(IMU + 运动学 + 接触检测)融合,误差不可消除,所以训练时给观测加噪声;外力扰动不可测,所以每 5 秒随机推一把(最大 1.5 m/s)。传感器缺口的正确解法是随机化,不是采购。
这也解释了为什么关节编码器是唯一不能省的传感器。G1 配置里 12 维位置 + 12 维速度 = 24 维,占了 47 维观测的一半以上。而 BHL 的 I²C 读 AS5600 需要 78 µs,这 24 维观测的每一帧都要付这个代价——编码器接口的选择直接决定了观测的更新率上限,进而决定了策略能跑多快。
4.2 什么任务需要加装什么
行走不需要视觉,但操作、上下楼、人机协作都需要。按任务分层给判据:
| 任务 | 必需传感器 | 知识库代表型号 | 关键规格 | 为什么 |
|---|---|---|---|---|
| 平地行走 / 抗扰 | IMU + 关节编码器 | IM10A;多摩川 SA 系列(17–27 bit/圈 + 16 bit 多圈) | 陀螺零偏 ≤1 °/s;编码器绝对值 + 断电记忆 | 47 维观测全部来源 |
| 上下楼 / 崎岖地形 | + 深度相机 或 激光雷达 | RealSense D435i / D455(内置 IMU);Livox Mid-360 / Mid-360S;Ouster OS1 / Velodyne;Stereolabs ZED | 需覆盖脚前方 1–2 m 的地形高度图,更新率 ≥10 Hz | 把特权观测里的 height map 变成真机可测 |
| 手臂操作 / 接触装配 | + 腕部六维力/力矩 | 宇立 SRI C025XX–C060XX(人形专用,Fz 50–1000 N,Mx/My/Mz 5–40 Nm,精度 <0.5% F.S.,OD 45–60 mm / 高 9.2–28 mm / 重 30–330 g,EtherCAT/CAN FD/RS485);坤维 KWR75(φ75 mm,Fz 30–3000 N,重复性 0.1% F.S.,IP64);ATI Gamma / Nano17(Nano17 仅 9.07 g,分辨率 1/320 N,谐振 7200 Hz,适合手指级) | 带宽 ≥1 kHz(接触瞬态),过载 ≥3× F.S.(碰撞保护) | 接触力不可由关节扭矩反推(摩擦 + 惯量项污染) |
| 灵巧抓取 / 手内操作 | + 指尖触觉 / 视触觉 | 戴盟 DM-Tac X(41×24×18 mm / 19.2 g,触觉分辨率 384×288 = 110,592 点,120 Hz,IP67);Tekscan 压力阵列;Paxini GRM118 / DexH5 | 阵列分辨率 >100 点,采样 ≥100 Hz | 滑移检测只能靠触觉,视觉看不到接触面 |
| 遥操作 / 数据采集 | + 动捕 或 VR 追踪 | BHL 用 SteamVR 动捕做遥操作采集;OptiTrack / Vicon(工业) | 亚毫米定位,≥100 Hz | 模仿学习需要高质量专家轨迹 |
| 人机协作安全 | + 安全激光扫描仪 / 光幕 | Pilz、SICK、Omron、Leuze、Fortress Interlocks | 按 ISO 13482 / ISO 10218 定安全等级 | 这不是性能件,是合规件,不能省 |
六维力传感器这一行值得多说一句,因为它是操作任务里最贵也最容易被低估的传感器。核心原因是:关节扭矩不能用来反推末端接触力。末端力 $F_{ext}$ 与关节扭矩的关系是
$$\tau = J^T(q) \cdot F_{ext} + M(q)\ddot q + C(q,\dot q)\dot q + G(q) + \tau_{friction}$$
要从中解出 $F_{ext}$,你必须精确知道惯量矩阵 $M$、科氏项 $C$、重力项 $G$ 和摩擦 $\tau_{friction}$,还要对 $\ddot q$ 做二阶差分(噪声放大 $\propto 1/\Delta t^2$)。实际工程里这个「无传感器力估计」方案的精度通常在 10–30% 量级,而一个 0.5% F.S. 的六维力传感器直接给 0.5%。差两个数量级。所以「用电流环当力传感器」只在准静态、低速、无摩擦补偿需求时勉强可用(ATOM01 这类 27 Nm 踝关节 + 78% CoP 的准静态行走就是这个工况);一旦要做接触装配、插拔、擦拭这类需要力闭环的操作,六维力是硬需求。
选型时的三个坑:① 量程要按碰撞而不是按工作力选——宇立 C 系列标称过载 3×,部分型号每轴可承受 100 次 3× 过载循环,这个指标比量程本身重要,因为一次意外碰撞就报废一个传感器;② 解耦矩阵必须由厂商标定——六维力的六个通道天然耦合,厂商给的 6×6 解耦矩阵是随标定证书走的,自己算不出来;③ 重量计入远端惯量——腕部装一个 330 g 的六维力,等于给肘关节增加了 330 g 的远端负载,会直接改变第 1.4 节算出的转子/连杆惯量比。
第五层:电池与 BMS——「所有关节同时发力」在物理上不成立
这一层是整篇文章里最容易被糊弄过去、也最容易炸的一层。先把一个所有人都忽略的矛盾摆出来:URDF 声明的整机峰值功率,和电池包能提供的功率,差了三个数量级。
5.1 同时性悖论:109 C 的电池包不存在
G1 的 29 个关节声明扭矩与峰值速度逐项相乘求和,得到 46,062 W。它的官方电池包规格是 9000 mAh / 421.2 Wh / 46.8 V 标称 / 54.6 V 充电上限(H1 同理,15 Ah / 864 Wh / 67.2 V 上限)。由 54.6 ÷ 13 = 4.200 V/cell 可反推这是 13S 锂电组。如果 46,062 W 全部由这个包提供:
$$I = \frac{P}{V} = \frac{46062}{46.8} = 984 \text{ A}, \qquad C = \frac{I}{Q} = \frac{984}{9.0} = \mathbf{109\text{C}}$$
109 C 意味着这个包在 0.55 分钟(33 秒)内放空,而且没有任何商用锂电组能持续输出 109 C(高倍率 LiPo 的极限在 20–50 C 脉冲)。所以结论只有一个:URDF 里的 effort 从来不是按「所有关节同时到峰值」设计的,它是单关节的独立上限。
| 机器人 | 电池包 | 串数 S | Σ 峰值功率 (W) | 等效电流 (A) | 等效 C 率 | 可持续时间 | 2h 平均功率预算 (W) | 占 Σ 峰值比例 |
|---|---|---|---|---|---|---|---|---|
| Unitree G1 | 46.8 V / 9 Ah / 421.2 Wh(官方 ~2h 续航) | 13S(4.200 V/cell) | 46,062 | 984 | 109 C | 0.55 min | 210.6 | 0.46% |
| Unitree H1 | 57.6 V / 15 Ah / 864 Wh(67.2 V 上限) | 16S(4.200 V/cell) | 36,720 | 638 | 42.5 C | 1.41 min | 432.0 | 1.18% |
| RoboParty ATOM01 | 48 V / 15 Ah / 720 Wh(BOM 明列) | 13S 级 | 30,024 | 625.5 | 41.7 C | 1.44 min | 360.0 | 1.20% |
这张表里 「2h 平均功率预算」和「Σ 峰值功率」的比例是 0.46%–1.20%。这个巨大的落差给出了整机能耗设计的真正判据:
判据一:续航由平均功率决定,不由峰值功率决定。G1 跑 2 小时只需要平均 210.6 W,而它的单关节峰值就能到 2,816 W(髋 pitch,88 Nm × 32 rad/s = 60.2 A = 6.69 C)。也就是说,单个关节的一次蹬地就已经是整机 2 小时平均功率的 13 倍。行走是脉冲式负载:摆动期几乎不耗能,支撑期蹬地瞬间打满,中间靠电机的反电动势和连杆的弹性回收一部分。
判据二:可交付峰值由 BMS 的 C 率上限决定,不由电池包的 Wh 决定。三台机器的等效 C 率是 41.7 / 42.5 / 109 C,没有一台在物理上可交付——商用锂电组的持续放电极限在 3–10 C、脉冲 20–50 C,41.7 C 已经是脉冲上限的边缘,109 C 是任何化学体系都给不出的数字。值得注意的是 ATOM01(41.7 C)与 H1(42.5 C)落在同一个带里,尽管 ATOM01 的包容量(720 Wh)比 H1(864 Wh)小 17%、Σ 峰值功率(30,024 W)只有 H1(36,720 W)的 82%。这说明「换个大电池」不能解决峰值问题:C 率 = $P/(V \cdot Q)$,把 15 Ah 换成 30 Ah 只把 41.7 C 降到 20.9 C,仍在不可交付区间;真要降到 5 C,得把包做到 125 Ah(6 kWh),那是整机质量的量级改变。选电池时先问 C 率,再问 Wh——而且峰值必须靠 SoP 限幅、靠控制层的 dof_acc 惩罚(第 7.3 节)压下去,不能指望电芯。反过来,一个 400 Wh 但能持续 10 C 的包(4,000 W)在步态性能上远胜一个 800 Wh 只能 2 C 的包(1,600 W)。
判据三:辅助负载才是续航的真正杀手。210.6 W 的平均预算里,机载计算机(BHL 用 BeeLink N95 NUC,典型 10–25 W)、视觉传感器(深度相机 3–8 W)、散热风扇、通信、舵机抱闸保持电流加起来很容易占到 50–80 W,也就是平均预算的 25–40%。行走本身反而不是最大头。这解释了为什么很多演示机「站着不动也只能撑 2 小时」——站着不动时关节保持电流 + 全部辅助负载照样在烧。
ATOM01 的 720 Wh 包在不同平均功率下的续航可以直接算出来($t = E/P$),这张表可以作为整机能耗预算的模板:
| 平均功率 (W) | 等效 C 率 | 续航 (min) | 续航 (h) | 典型工况 |
|---|---|---|---|---|
| 1,200 | 1.67 C | 36 | 0.60 | 激烈动态步态 + 全传感器 + 高负载操作 |
| 800 | 1.11 C | 54 | 0.90 | 持续行走 + 操作 |
| 720 | 1.00 C | 60 | 1.00 | 1C 基准点(多数商用包的安全持续线) |
| 600 | 0.83 C | 72 | 1.20 | 常规行走 |
| 400 | 0.56 C | 108 | 1.80 | 慢速行走 + 计算负载 |
| 200 | 0.28 C | 216 | 3.60 | 准静态站立/坐姿操作(G1 的 2h 预算在这一档附近) |
| 100 | 0.14 C | 432 | 7.20 | 待机 + 计算(关节不通电) |
({
"_height": "400px",
"backgroundColor": "transparent",
"grid": { "left": 70, "right": 40, "top": 46, "bottom": 50 },
"tooltip": {
"trigger": "axis",
"axisPointer": { "type": "shadow" },
"backgroundColor": "rgba(15,23,42,0.95)",
"borderColor": "#f0708a",
"borderWidth": 1,
"textStyle": { "color": "#e2e8f0" },
"formatter": function (p) {
var d = p[0];
var ex = {
"G1": "46,062 W ÷ 46.8 V = 984 A ÷ 9 Ah
421.2 Wh 包 → 0.55 min 放空",
"H1": "36,720 W ÷ 57.6 V = 638 A ÷ 15 Ah
864 Wh 包 → 1.41 min 放空",
"ATOM01": "30,024 W ÷ 48 V = 625.5 A ÷ 15 Ah
720 Wh 包 → 1.44 min 放空"
};
return "" + d.name + "
等效 " + d.value + "C
" + (ex[d.name] || "");
}
},
"xAxis": {
"type": "category",
"data": ["G1", "H1", "ATOM01"],
"axisLabel": { "color": "#94a3b8", "fontSize": 13 },
"axisLine": { "lineStyle": { "color": "#334155" } }
},
"yAxis": {
"type": "log",
"min": 1,
"max": 200,
"name": "等效 C 率(对数)",
"nameTextStyle": { "color": "#94a3b8" },
"axisLabel": { "color": "#94a3b8" },
"splitLine": { "lineStyle": { "color": "rgba(148,163,184,0.15)" } }
},
"series": [{
"type": "bar",
"barWidth": "40%",
"data": [
{ "value": 109.4, "itemStyle": { "color": "#f0708a" } },
{ "value": 42.5, "itemStyle": { "color": "#f0708a" } },
{ "value": 41.7, "itemStyle": { "color": "#f0708a" } }
],
"label": { "show": true, "position": "top", "color": "#e2e8f0", "formatter": "{c} C" },
"markLine": {
"symbol": "none",
"lineStyle": { "color": "#3ddc97", "type": "dashed", "width": 2 },
"label": { "color": "#3ddc97", "formatter": "1C = 商用包安全持续线" },
"data": [{ "yAxis": 1.0 }]
}
}]
})
5.2 电池包本体:串数、电芯、封装、布置
人形机器人的电池包参数范围已经收敛得比较清楚了:包电压 48–96 V(Tesla Optimus 约 52 V,Figure 02 为 48 V),能量 1–5 kWh,12S–24S 加并联组,电芯用 NMC 或 LFP 的 18650 / 21700 / 软包,通信走 CAN / SMBus / I²C。三家参照机全部落在这个区间内(G1 13S/46.8 V,H1 16S/57.6 V,ATOM01 48 V)。
串数选择的判据是母线电压与关节模组供电电压匹配。第 1.5 节表里的模组供电档位是 24 / 42 / 48 / 72 VDC,这不是随便定的:48 V 是安全特低电压(SELV)上限附近的工程甜点,既能让 88–222 Nm 级模组拿到足够电流($I = P/V$,同样功率下 48 V 的电流是 24 V 的一半,铜损 $I^2R$ 降到四分之一),又不需要高压绝缘防护和高压作业资质。13S 满电 54.6 V 正好在 48 V 模组的输入范围内,这也是为什么 G1 和 ATOM01 都收敛到 13S 级。更高的串数(24S ≈ 88 V)只在需要 3 kW 以上单关节的超大机器上才划算,代价是绝缘、爬电距离、维修安全全套要求升级。
电芯选型的核心分歧是 NMC vs LFP:NMC 能量密度高(200–280 Wh/kg 电芯级)适合追求续航和减重,但热失控起始温度低;LFP 能量密度低(140–180 Wh/kg)但热稳定性好、循环寿命长(3000+ 次 vs 800–1500 次)、放电平台平。人形机器人在室内与人共处,热安全权重高于续航,LFP 是有理由的选择;但 LFP 的平坦放电曲线让 SOC 估算变难(见 5.3),需要更强的库仑计数和更频繁的重标定。供应商侧,BYD(刀片电池 + 自研 BMS)、Grepow(LiPo / 半固态,机器人高倍率包主力)、Molicel(高放电倍率)、Saft、EnerTech 是这一层的货架来源;高倍率场景优先看 Molicel/Grepow,能量密度场景看 BYD。
体积能量密度这一项可以直接算,而且它决定了电池在整机里的物理占比。G1的包尺寸是 120 × 80 × 182 mm = 1.747 L,装 421.2 Wh,即 241 Wh/L。这个数字意味着:421 Wh 就要占掉 1.75 L 的躯干容积,还要留出散热间隙和结构加强。想把续航翻倍,躯干体积要翻倍,而躯干体积翻倍会改变质量分布和转动惯量,进而改变第 1.2 节的全部扭矩需求。这是「加电池」从来不是局部改动的原因。能量密度提升(半固态、锂金属)对整机设计的影响,远大于对续航数字的影响。
TauMax 钳位给 200 / 28 Nm,读者能看出「电机能给」和「整机允许用」的差额。自研整机应该照抄这个口径。5.3 BMS:器件栈与双路径电流采样
BMS 不是「一个芯片」,是五类器件加一颗 MCU 的栈。这是目前公开资料里能确认的器件级选型:
| 功能块 | 作用 | 代表器件 | 选型判据 |
|---|---|---|---|
| 电芯监测 AFE | 逐节测电压/温度,是 BMS 的感官 | TI BQ79616(16 节)、BQ76952(3–16 节,电压精度 <10 mV,含自主均衡);ADI LTC6813-1;1–4 颗级联覆盖 12S–24S | 每颗管 12–16 节;级联通信要走隔离;电压精度决定 SOC 精度 |
| 电量计(fuel gauge) | SOC / SoH 估算 | TI BQ34Z100-G1;ADI LTC2944(库仑计数) | LFP 平坦曲线必须靠库仑计数,不能只靠 OCV 查表 |
| 保护 FET 驱动 | 过压/欠压/过流/过温时切断 MOSFET | TI BQ29700;Renesas ISL94202;Ablic/Seiko S-8261 | 必须是硬件路径,不能依赖 MCU 软件判断 |
| 均衡器 | 抹平节间电压差 | ADI LTC3300-1(变压器式双向主动均衡,单颗 6 节、最大 10 A 均衡电流、电荷转移效率最高 92%,可堆叠到 >1000 V);BQ76952 内置的是被动均衡(耗散型) | 被动均衡把能量烧成热,13S 以上建议主动均衡——它能在节间搬能量,均衡电流不随节数线性变热 |
| 隔离电流采样 | 测母线电流 | TI AMC1311(隔离放大器);Allegro ACS770(霍尔) | 分流器方案精度高但要隔离;霍尔方案天然隔离但漂移大 |
| 温度监测 | 电芯/模组/环境三点测温 | TI TMP117(±0.1 °C 数字)+ NTC 阵列 | 温度梯度决定热管理策略,单点测温不够 |
| BMS MCU | 状态机、SoP 计算、通信 | STM32(主流);TI TMS570LC4357、Renesas RH850、Infineon AURIX(车规/功能安全) | 需要功能安全认证时选带 lockstep 核的车规件 |
这个栈里最关键、也最少被讨论的是电流采样的双路径架构。Renesas 人形机器人 BMS 参考设计把这条线画得很明确:块图里「Comparators + Current Sense Amp + Gate Driver + Circuit Breaker/E-Fuse」与「Battery Front End + MCU」是两组独立器件,前者是微秒级的硬件保护路径,后者是毫秒级的测量与控制路径——两条都接在同一个分流器上,并联存在,不合一。该页面还给出了针对 24 V 与 48 V 两种母线架构的器件选型清单。
flowchart LR P["电池包 13S / 421 Wh"] --> SH["分流电阻
shunt"] SH --> FAST["路径 A:快速硬件保护
模拟比较器
→ 栅极驱动器
→ 切断 MOSFET"] SH --> MEAS["路径 B:ADC 测量
隔离放大器 AMC1311
→ MCU ADC"] MEAS --> SOP["SoP 计算
可交付功率上限"] MEAS --> SOC["库仑计数 SOC
ΔQ = ∫I(t)dt"] AFE["电芯监测 AFE
BQ79616 × N
逐节 V / T"] --> MCU["BMS MCU
STM32 / 车规件
状态机 + 重标定"] SOP --> MCU SOC --> MCU MCU --> BUS["CAN / SMBus / I²C
→ 上位机"] FAST -.->|不经过 MCU
零软件延迟| P
这个设计对机器人有特别的意义,因为它对应三种真实工况:① 行走脉冲放电(蹬地瞬间 6.7 C,持续几十毫秒,属于正常峰值,不能触发保护);② 持续操作放电(手臂长时间保持力矩,中等电流长时间,属于热累积问题,要按 SoP 降额);③ 安全关键的故障恢复爆发(跌倒后爬起,需要瞬时大功率,此时如果 BMS 因为电压跌落触发欠压保护就会锁死,机器人再也起不来)。第三种工况最难:BMS 必须区分「电池真的没电」和「大电流导致的瞬时压降」,前者要保护,后者要放行。判据是 SoP(State of Power,可交付功率上限)而不是瞬时电压——SoP 由 SOC、温度、内阻、老化程度共同算出,这正是路径 B 存在的理由。
SOC 估算本身是个带漂移的积分:$\Delta Q = \int I(t)\,dt$,电流采样误差和时间基准误差都会累积,所以需要周期性用 OCV(开路电压)重标定。而重标定要求电池静置——机器人不能停机静置 30 分钟等 SOC 校准。LFP 的平坦放电曲线让 OCV 重标定的可用窗口更窄。工程解法是:在充电阶段(此时机器人静止)做完整重标定,放电阶段只靠库仑计数 + AFE 电压做软约束。这也意味着充电策略是 BMS 设计的一部分,不是附属功能。
合规与安全标准这一层不能跳过:IEC 62133(便携二次电池安全)、UL 2054(家用与商用电池组)、IEC 61508(功能安全 SIL 定级),人机共处场景还要看 ISO 13482(个人护理机器人)。Renesas 人形机器人 BMS 参考设计把电气与热故障的快速保护列为系统收益的第一条,实际意义是:BMS 的硬件保护路径(图 16 路径 A)是过这些认证的核心证据,纯软件保护很难通过。自研电池包最大的隐性成本不是电芯,是认证。
最后是热管理。Renesas 那套参考设计把「Active Cooling Control + 专用 MCU + 3-Ph Gate Driver」单独画成一个功能块,也就是说主动散热是被当成一个独立子系统设计的,不是外挂风扇。原因是人形的躯干既是最大容积所在(1.75 L 的包必须放这里),也是最难散热的位置(被外壳包住、靠近电机热源、没有气流)。6.7 C 的蹬地脉冲在 9 Ah 包上就是 60 A,按内阻 20 mΩ 算瞬时热功率 $I^2R = 72$ W——虽然只持续几十毫秒,但重复几百次就是持续的热输入。没有主动热控的电池包,其 C 率上限会被温度迅速压下来,SoP 降额,步态性能随之衰减。这在实验室里表现为「跑 10 分钟一切正常,跑 30 分钟开始无力」,而且日志上看不出来,因为电压没到保护线。
第六层:软件控制、驱动与下位板卡——控制频率是被总线和接口算出来的
这一层要做三件事,而且必须按这个顺序:① 关节内的 FOC 电流环(kHz 级)→ ② 关节间的 CAN 总线(500 Hz–1 kHz)→ ③ 机载计算机上的策略推理(50–100 Hz)。三个频率层级相差一到两个数量级,各自有自己的物理上限,而整机性能由最低的那一层决定。
6.1 FOC:为什么必须是矢量控制
无刷电机的换向有三个层级,BHL 的 FOC 文档把它们讲得很清楚。六步换向(six-step)把三相逆变器分成 8 个状态(其中 6 个有效),每个状态给两相通电,转子每转 60° 电角度切换一次。优点是简单,缺点是扭矩脉动大(每 60° 一个台阶)、低速抖动、无法做力控。人形机器人的关节需要在零速附近输出精确扭矩(保持姿态),六步换向在这里直接失效。
矢量控制(FOC)的做法是通过 Clarke 与 Park 变换把三相电流投影到与转子同步旋转的 $dq$ 坐标系:
$$\begin{bmatrix} i_d \\ i_q \end{bmatrix} = \frac{2}{3}\begin{bmatrix} \cos\theta & \cos(\theta - \tfrac{2\pi}{3}) & \cos(\theta + \tfrac{2\pi}{3}) \\ -\sin\theta & -\sin(\theta - \tfrac{2\pi}{3}) & -\sin(\theta + \tfrac{2\pi}{3}) \end{bmatrix} \begin{bmatrix} i_a \\ i_b \\ i_c \end{bmatrix}$$
在这个坐标系里,$d$ 轴电流产生磁通(对扭矩无贡献,是纯损耗),$q$ 轴电流产生扭矩,两者解耦。于是「控制扭矩」退化成「把 $i_q$ 控到目标值、把 $i_d$ 控到 0」的两个独立 PI 环:
$$\tau = \frac{3}{2} p \left( \psi_f i_q + (L_d - L_q) i_d i_q \right), \qquad u_d = R i_d + L_d \frac{di_d}{dt} - \omega_e L_q i_q, \qquad u_q = R i_q + L_q \frac{di_q}{dt} + \omega_e (L_d i_d + \psi_f)$$
其中 $p$ 是极对数,$\psi_f$ 是永磁体磁链,$\omega_e$ 是电角速度。第三式里的 $\omega_e \psi_f$ 就是反电动势项,它是前馈补偿的关键——不做前馈时,电流环在高速段会因为反电动势追不上电压裕度而失去扭矩能力,表现为「低速扭矩准、高速扭矩塌」。这也解释了 1.3 节那个 $K_V$ 实测比标称低 15% 的问题:反电动势系数直接进这个方程,用标称值算前馈会有 15% 的系统误差。
电流环带宽的物理上限由电机时间常数决定:$\tau_e = L/R$。BHL 的 M6C12 是 $L = 0.0325$ mH、$R = 0.1886\ \Omega$,即 $\tau_e = 0.172\ \mu s$——这个时间常数极小,意味着电流环本身可以做到几十 kHz,真正的瓶颈不在电机,在采样与计算:ADC 采样 + Clarke/Park + 两个 PI + 反 Park + SVPWM,全套要在一个 PWM 周期内跑完。BHL 的固件跑在 160 MHz MCU 上、-O2 编译(Recoil FOC),而它的位置环被 78 µs 的 I²C 编码器读取卡死在 12 kHz 量级。这就是「算力不是瓶颈,接口才是」的完整证据链。
6.2 CAN 总线:一条总线最多挂几个关节
关节之间用 CAN 通信,这是全行业共识(RS485 用于低速/远距离的传感支路)。CAN 帧长与总线负载可以直接算。BHL 用的是 CANOpen 风格的帧结构:11 位 ID = 4 位功能码 + 7 位节点号,帧总长 111 / 119 / 135 bit(取决于数据长度与是否填充),波特率 1 Mbps。功能码覆盖 NMT(网络管理)、SDO(参数配置)、PDO1–4(周期数据),其中 PDO2 传位置+速度的指令与反馈(fp32),PDO3 传位置 + 前馈扭矩。
总线负载率 $\rho = N \cdot f \cdot L / R$,其中 $N$ 是节点数、$f$ 是每节点帧率、$L$ 是帧长(bit)、$R$ 是波特率。按 119 bit 帧、1 Mbps 算:
| 一条总线上的节点数 | 500 Hz 时负载率 | 1000 Hz 时负载率 | 70% 负载率下每节点可达频率 | 可行性 |
|---|---|---|---|---|
| 5 | 29.8% | 59.5% | 1,176 Hz | 舒适,可到 1 kHz |
| 6 | 35.7% | 71.4% | 980 Hz | 1 kHz 刚好触线 |
| 7 | 41.6% | 83.3% | 840 Hz | 1 kHz 超载,只能 500–800 Hz |
| 12 | 71.4% | 142.8% | 490 Hz | 不可行,500 Hz 已到极限 |
| 22(BHL 全身) | 130.9% | 261.8% | 267 Hz | 物理上不可能 |
最后一行是这一节最重要的数字:把 22 个关节挂在一条 CAN 总线上、跑 500 Hz,负载率是 130.9%——超过 100% 意味着帧根本发不完,总线会持续积压直到溢出。单总线单节点的物理上限只有 267 Hz,这对力控来说太低了。
BHL 的实际方案(见其 CAN 通信文档与关节 ID 映射)给出了标准答案:一条总线服务一条肢体。它用 4 路 CAN——CAN0 / CAN1 各接一条手臂(每条 5 个节点),CAN2 / CAN3 各接一条腿(每条 6 个节点)。按上表,5 节点是 29.8%、6 节点是 35.7%,都留有 2–3 倍裕度,可以稳定跑 500 Hz 甚至 1 kHz。每个关节的 CAN ID 与名字、行程范围(deg)、方向符号一起做成映射表,这张表就是 sim-to-real 的关键接口——仿真里的关节顺序和真机上的 CAN ID 必须一一对应,错一个就是左右手反向。
flowchart TD NUC["机载计算机
BeeLink N95 NUC · Ubuntu 22.04
策略推理 50 Hz(ONNX)"] USB["USB-CAN 适配器 ×4"] NUC -->|以太网/USB| USB USB --> B0["CAN0 · 左臂 5 节点
负载 29.8% @500Hz"] USB --> B1["CAN1 · 右臂 5 节点
负载 29.8% @500Hz"] USB --> B2["CAN2 · 左腿 6 节点
负载 35.7% @500Hz"] USB --> B3["CAN3 · 右腿 6 节点
负载 35.7% @500Hz"] B0 --> J0["关节 MCU
FOC 电流环 kHz 级
位置环 ≤12.8 kHz
(受 I²C 78µs 限制)"] B2 --> J2["关节 MCU
FOC 电流环 kHz 级"] J0 -.->|PDO2: pos+vel 指令/反馈 fp32
PDO3: pos + 前馈扭矩| J2 BMS["电池 BMS
独立 CAN / SMBus"] -.->|SoP / SOC / 故障| NUC IMU["IMU(IM10A)
USB 直连"] -.-> NUC
这套拓扑的普适规律是:节点数 ≤6,频率 ≥500 Hz,一条总线一条肢体。超过 6 个节点就要加总线或升级到 CAN FD(数据段可到 5–8 Mbps,同样节点数下负载率降一个量级)。这也解释了 1.5 节里睿尔曼 WHG 模组为什么标配 CAN FD——30 关节的整机如果坚持经典 CAN 1 Mbps,需要 5–6 条总线;换成 CAN FD 可以压到 2–3 条,省掉适配器和线束重量。EtherCAT 是另一条路线(魔法原子 H70 支持 CAN/EtherCAT 双模),它是主从式实时以太网,单链路可串几十个节点、周期可到 1 kHz 以上,代价是主站复杂度高、需要专用芯片(ESC)、且对线序和屏蔽要求严苛。开源社区几乎全用 CAN,工业整机正在向 CAN FD / EtherCAT 迁移。
6.3 三层控制的职责划分
| 层级 | 运行位置 | 典型频率 | 做什么 | 物理上限来自 |
|---|---|---|---|---|
| 电流环 / FOC | 关节内 MCU | 10–40 kHz | $i_q \to$ 扭矩、$i_d \to 0$,SVPWM 输出 | PWM 频率 + ADC 采样 + MCU 主频 |
| 关节伺服环 | 关节内 MCU | 1–12.8 kHz | 位置/速度 PID + 前馈扭矩,读编码器 | 编码器接口(I²C 78 µs → 12.8 kHz) |
| 总线通信 | CAN 链路 | 500 Hz – 1 kHz | PDO 收发指令与反馈 | 节点数 × 帧长 ÷ 波特率 |
| 全身控制 / WBC | 机载计算机 | 500 Hz – 1 kHz | 力矩分配、接触约束、平衡 | 求解器规模(QP 迭代次数) |
| RL 策略推理 | 机载计算机(ONNX) | 50 Hz | 观测 → 目标关节位置 | 网络规模(LSTM [32] 级很小,瓶颈在数据搬运) |
| 感知 / 视觉 | 机载计算机(GPU/NPU) | 10–30 Hz | 深度图 → 地形高度图 | 相机帧率 + 推理算力 |
这张表的读法是「上层给下层设目标,下层给上层设带宽」。50 Hz 的策略输出目标关节位置,500 Hz 的总线把它送到关节,1 kHz 的伺服环做位置闭环,10 kHz 的电流环做扭矩闭环。频率每降一级都要做插值/滤波,否则下层会看到阶梯指令而产生冲击。G1 配置里的 action_scale=0.25 和 decimation=4 正是这个衔接:策略每 4 个仿真步(即 50 Hz,若仿真跑 200 Hz)输出一次动作,动作值乘 0.25 后加到默认关节角上——0.25 这个系数限制了单帧的关节位移,防止策略输出跳变打穿下层伺服环的跟随能力。
PD 增益的物理意义在这一层要说清楚:$K_p$(刚度)决定「偏离目标位置多少就出多大力」,$K_d$(阻尼)决定「速度多快就出多大阻力」。G1 的 stiffness/damping的分组是髋 $K_p=100, K_d=2$、膝 $K_p=150, K_d=4$、踝 $K_p=40, K_d=2$(单位 Nm/rad 与 N·m·s/rad)。结合第 1.4 节的惯量比值就能看出这不是随便调的:膝(连杆主导,比值 0.4×)给最高刚度,因为它要扛 61.9 Nm 静力矩且转子惯量不构成阻碍;踝(电机主导,比值 22.8×)给最低刚度,因为它是接触关节,硬了会把地面冲击直接打进基座,还会让电机主导的关节在接触瞬间产生大电流尖峰。仿真里调出来的增益能不能直接上真机,取决于真机的反射惯量和仿真模型是否一致——这是 sim-to-real 最隐蔽的失配源之一。
第七层:仿真训练、sim-to-sim 与 sim-to-real
这一层是前面六层的收口。仿真训练的意义不是「先在电脑里跑一跑」,而是把硬件的不确定性转换成策略的鲁棒性:你在真机上测不准的摩擦系数、装不准的质量分布、算不出的杆臂误差,全部通过域随机化在训练时变成策略必须应对的分布,而不是上机后再去调。
7.1 训练环境:Isaac Lab 的完整链路
BHL 公开了目前最完整的一条可复现训练链路(Training Environment 文档 + 代码仓库),版本是钉死的:Isaac Lab 2.1.0 + Isaac Sim 4.5.0 + PyTorch 2.5.1 (cu121)。任务注册成两个 Gym 环境:
| 环境名 | 自由度 | 覆盖范围 | 用途 |
|---|---|---|---|
Velocity-Berkeley-Humanoid-Lite-v0 | 22 | 全身(双腿 12 + 双臂 10) | 整机速度跟踪行走 |
Velocity-Berkeley-Humanoid-Lite-Biped-v0 | 12 | 仅双腿,手臂锁定 | 先训步态,降低搜索空间 |
「先训 12 dof 双腿、再训 22 dof 全身」这个顺序不是偷懒,是必须的。22 维动作空间的策略搜索难度不是 12 维的 1.83 倍,而是指数级的;而手臂对行走稳定性的贡献主要是摆动惯量补偿,可以在腿部策略收敛后再加进去。这也解释了 X1 的 URDF 为什么把手臂焊成 fixed——那不是缺陷,那正是这个训练阶段的标准做法(见图 12)。
训练预算是 6000 iterations ≈ 2 小时(单张消费级 GPU)。训完用 play.py 导出 ONNX 模型和一份 configs/policy_latest.yaml 配置,这两个文件就是 sim-to-real 的全部交付物。ONNX 是关键选择:它让策略在真机上不依赖 Python/PyTorch 运行时,用 ONNX Runtime C++ 就能跑,避免 GC 停顿和依赖地狱。
G1 的 legged_gym 配置给出了网络与奖励的完整细节,可以作为调参基线:
| 项 | 取值 | 含义与陷阱 |
|---|---|---|
| 网络结构 | ActorCriticRecurrent,LSTM hidden [32] | 用 LSTM 是为了从历史观测里隐式估计不可测量(速度、地形、摩擦) |
| 观测 / 特权 / 动作 | 47 / 50 / 12 | 特权观测只在仿真可得,需蒸馏到 actor |
| 迭代次数 | 10,000 | BHL 只用 6,000 ≈ 2h;不同任务收敛速度差数倍 |
| 噪声标准差 | 0.8 | 观测噪声注入,模拟真机传感器误差 |
| 熵系数 | 0.01 | 探索强度,过大会导致动作抖动上真机 |
| action_scale / decimation | 0.25 / 4 | 策略 50 Hz,物理 200 Hz;0.25 限制单帧关节位移 |
7.2 域随机化:把测不准的东西随机掉
G1 的 domain_rand 配置里的随机化范围非常克制,每一项都对应一个真机上测不准的物理量:
| 随机化项 | 范围 | 对应真机的哪个不确定性 | 与硬件层的关联 |
|---|---|---|---|
| 地面摩擦系数 | [0.1, 1.25] | 地板材质、灰尘、湿度 | 决定第 1.2 节 CoP 能压多远而不打滑——低摩擦时踝扭矩再大也没用 |
| 基座附加质量 | [−1, +3] kg | 负载变化、URDF 质量标定误差 | 直接改变 $\tau = mgd$ 的全部扭矩需求和反射惯量比值 |
| 外力推搡 | 每 5 s 一次,最大 1.5 m/s | 碰撞、被人推 | 决定了第 2.1 节 IMU 杆臂伪加速度的实际工况上限($\dot\omega$ 50–100 rad/s²) |
| 观测噪声 | std 0.8 | IMU/编码器噪声 + 状态估计误差 | 注意这比 BNO085 的真实噪声底(0.00157 m/s²)大好几个量级——随机化的不是传感器噪声,是估计误差 |
最后一条特别值得注意。域随机化的量级不是照抄传感器数据表,而是照抄「整个感知链路的误差」。IMU 噪声底是 0.00157 m/s²,但状态估计器输出的基座速度误差在真机上是 0.1 m/s 量级(接触检测误判 + 足底打滑 + 积分漂移),所以随机化按后者设。这正好回到第 2 层的结论:杆臂伪加速度(1.39–9.85 g)比噪声底大 10³–10⁴ 倍,它才是估计误差的真正来源。把 IMU 装错位置,域随机化范围就得扩大,策略就得学得更保守,性能就下降——这条因果链是从结构图一路连到奖励函数的。
7.3 奖励函数:每一项都在对应一个硬件约束
G1 的奖励权重是硬件约束的镜像。把负权重的几项挑出来看:
| 奖励项 | 权重 | 惩罚的是什么 | 对应的硬件约束 |
|---|---|---|---|
tracking_lin_vel | +1.0 | —(唯一大正项,任务目标) | 速度指令跟踪 |
alive | +0.15 | —(活着就给分) | 摔倒代价极高,因为会损坏最贵的谐波减速器 |
base_height | −10.0 | 躯干高度偏离目标 | 过低=深蹲,会把膝关节推到裕度边缘(BHL 只有 1.51×);过高=膝伸直,无法吸收冲击 |
feet_swing_height | −20.0 | 摆动脚抬得过高/过低 | 抬太高浪费能量(撞第 5.1 节的平均功率预算);太低会拖地磨脚掌(第 3.4 节的磨耗件) |
dof_acc | −2.5e-7 | 关节角加速度 | 直接对应峰值扭矩 $= J\dot\omega + mgd$ 里的动态项,压它就是压第 1.2 节的 effort 需求 |
dof_acc 这一项的权重是 $-2.5\times10^{-7}$,数值极小,因为角加速度的量级是 $10^3$–$10^4$ rad/s²(平方后 $10^6$–$10^8$),乘上这个系数才落到与其他项可比的量级。它是硬件与软件之间最直接的一条线:仿真里惩罚角加速度,真机上就是在降低对关节峰值扭矩和电池 C 率的需求。把这个权重设为 0,策略会学会用剧烈甩腿来快速跟踪速度指令——仿真里表现更好(tracking 奖励高),真机上直接打爆电流限幅、触发 BMS 过流保护、烧电机。
这就是「仿真指标好 ≠ 真机能跑」的机制解释,也是为什么 sim-to-sim 这一步不能省。
7.4 sim-to-sim:用第二个物理引擎当验收测试
BHL 的 sim2sim 流程是:把 Isaac Lab 训出的 ONNX 策略放到 MuJoCo 里跑,用摇杆发速度指令,开两个线程(摇杆线程 + 策略推理线程)通过 UDP 与物理仿真通信——刻意模仿真机上的进程与网络结构,而不是在同一个进程里直接调用。
这一步的价值比看上去大得多。Isaac 用 PhysX,MuJoCo 用广义坐标动力学 + 凸优化接触求解,两者的接触模型、摩擦锥近似、求解器刚度都不同。一个只在 PhysX 里能走的策略,换到 MuJoCo 就摔,说明它学到的是 PhysX 的接触模型特性,不是真实的行走物理。sim-to-sim 是一次零成本的过拟合检测,它能在你花几周装配真机之前就告诉你策略是否学到了可迁移的东西。
用 UDP + 多线程而不是直接函数调用,是为了暴露真机上必然存在的三类问题:通信延迟、丢包、线程调度抖动。策略如果依赖「指令与反馈严格同步」,在 sim2sim 里就会先崩。真机上的 500 Hz CAN 总线(第 6.2 节)和 50 Hz 策略推理之间同样存在这种异步,而且 NUC 的 Linux 调度抖动比仿真环境更差。
flowchart LR A["① Isaac Lab 训练
Isaac Sim 4.5.0 + Lab 2.1.0
PhysX · 4096 并行环境
6000 iter ≈ 2 h"] B["② 导出
play.py → ONNX
+ policy_latest.yaml"] C["③ sim2sim 验收
MuJoCo(第二物理引擎)
摇杆线程 + 推理线程
经 UDP 通信"] D["④ sim2real
BeeLink N95 NUC
Ubuntu 22.04 · ONNX Runtime
4 路 CAN → 22 关节"] E["⑤ 遥操作数据采集
SteamVR 动捕
→ 操作策略模仿学习"] A --> B --> C --> D --> E C -.->|摔了 → 域随机化不足
或过拟合 PhysX| A D -.->|抖/偏 → 杆臂误差、零点偏移、编码器映射错、
CAN 丢帧、增益不匹配| A
7.5 零点标定:sim-to-real 之前唯一不能跳过的一步
前面六层讲的都是「这台机器该长什么样」,这一节讲的是「装配完成之后、策略上机之前,必须做的那一件事」。RoboParty 的部署文档把它写成了警告:「启动机器人前,确保机器人完成零点标定」——排在启动软件那一节的第一行,比安全操作指南的链接还靠前。
这一节按标定真正发生的顺序走八步:为什么不能省(恒定偏置不在训练分布里)→ 零点由什么决定(三个装配常量)→ 机械零位从哪来(标定件与整机判据)→ 覆盖了哪些关节(左右镜像落在配置的哪一层)→ 怎么写进去(两条路径、一道判据、三个失效点)→ 换一种做法会怎样(BHL 对照)→ 做不准要付多少代价(踝裕度、躯干雅可比、域随机化区间)→ 上电之后怎么回到零位(回位与灵巧手 homing)。一句话概括:一到四步说清零点是什么、由谁定、机械上怎么来、覆盖哪些关节;五、六步说清怎么写、怎么验;七、八步说清不准要付多少代价、上电之后怎么回到它。
为什么不能省:恒定偏置不在训练分布里,也不能用增益修
为什么这一步不能省,机制很简单:URDF 里每个 joint 的 origin、rpy、axis 和 limit 全部定义在「零位姿态」这个基准上。仿真里的零位是一个数学约定($q=0$ 就是零位,恒成立,不需要标定),真机上的零位是一个机械事实(由零件怎么装、电机怎么锁、线怎么接决定)。两者对不上,策略拿到的每一帧关节角都带一个恒定偏置,而这个偏置在训练分布里从来没出现过——域随机化随机的是摩擦、质量、增益(第 7.2 节),没有一项随机零点,因为仿真里零点恒等于 0,随机它没有物理对应物。恒定偏置也不能用 PD 增益修:增益只改响应快慢,不改稳态位置。
三个只能由装配决定的常量
零点是装配基准,不是电机属性。这一点在 ATOM01 的配置里看得最清楚。robots/rpo/robot.yaml 里有三个「软件算不出来、只能由装配决定」的常量:
一,motor_zero_offset:23 项里 22 项是 0.0,只有第 13 项(腰部 yaw,即 URDF 的 torso_joint)是 2.093 rad = 119.92°。更关键的是 README 对这个数的说明:如果是把腰部 yaw 转到限位块处标定,保留 2.093;如果是用 3D 打印标定件固定腰部 yaw 标定,把 2.093 改成 0.0。同一个常量、两个合法取值,取决于你用哪种装配工艺定零位。这就是「零点是装配基准」最直接的证据——它不在电机里,不在 URDF 里,在工艺文件里。
二,motor_sign:23 项里 6 项是 -1(电机 9/10/11/12/19/22)。方向符号同样是装配产物(电机安装朝向、相序、左右镜像),不是电机属性。它和零点一起构成「电机读数 → URDF 关节角」的那个仿射映射:$q_{urdf} = s \cdot (p_{motor} + z)$。这条映射写错任何一个符号,症状就是第 7.6 节失败表里的「某关节反向」——而它和零点错误在数据上无法区分,只能逐关节单点给指令肉眼确认。
三,close_chain_motor_idx: [4, 5, 10, 11]:左右踝的 pitch/roll 四个电机走闭链五杆解耦。decouple_rpo.cpp 里的机构参数是硬编码的:短臂 l_bar = 20 mm、左右安装间距 l_spacing = ±42.35 mm、两根连杆 l_rod = 180 / 110 mm、两个锚点 z = 180 / 110 mm,牛顿迭代解正逆解时还带 last_solution_ 热启动。闭链机构的零位不是「某个角等于 0」,而是「四杆处在某个特定构型」;零位偏一点,五杆正解可能跳到另一个分支或直接不收敛,而指令路径和反馈路径都要过这个解耦器(read_joints() 之后调 forward_close_chain(),reset_joints() 里也对同一对关节调 get_decoupleQVT()),所以一处零位错会同时污染观测和指令。这也解释了为什么 ATOM01 要给脚踝单独做一个标定件(见下):闭链踝的零位只能靠机械定位块定,靠人手摆是摆不出来的。
机械零位的载体:8 类标定件、24 页规则、一条整机判据
机械零位从哪来?答案是一堆 3D 打印的定位块。ATOM01 的 总3d打印标定件 目录里是 8 类 STEP 文件(腰部 1、肩部 2、肘部 2、大腿定位块 2、膝盖 2、大腿后侧 1、脚踝 2、脚部 1)加一份 24 页的《标定说明》。V1.0 的 01总标定件 是同一套东西的上一版,跨版本保留说明它不是临时工装而是设计的一部分。
这个「同一套」可以用 git blob SHA 逐字节验证,不用靠目录名猜:rpo_hardware 的 完整文件树(697 个条目)里,V1.0 那 9 个实体文件在 V2.0 里逐一以完全相同的 blob SHA 出现,一个都没重画(目录里第 10 个条目是个 14 字节的 SolidWorks 锁文件 ~$标定抢救.SLDPRT,V2.0 已清掉)——腰部 b6b0ca6ec6、肩部 8144bcd971、肘部 2f2cb37e7e、膝盖 943f94a6fc、脚踝 da5d9142bc、大腿后侧 a967d2ccdc、脚部 3f92bc6497、大腿定位块 7522f2914f,SHA 完全相同即字节完全相同;V2.0 的 总3d打印标定件/ 里那份《标定说明.pptx》同样是这 9 个之一,blob e6da34115a、7,751,363 字节,与 V1.0 逐字节一致。V2.0 在该目录下另外新增 4 个文件(大腿后侧缩小版.STEP、打印说明.docx、打印说明.pdf、一个 ~WRL2944.tmp 临时残留)。ATOM01 换代到 Roboto Origin,关节零点这件事在机械层一个字节都没改——这比「沿用了同一套工装」的说法强得多:SHA 相同意味着连导出参数都没动过。聚合仓库 roboto_origin(★2407,5673 个条目)modules/rpo_hardware/ 下的对应文件 SHA 与此逐一致,说明它确实是子仓库的快照而非另抄一份。
真正变了的只有两处,而且变得很有信息量。其一,V2.0 的 00_Docs/3D打印件标定说明.pptx 是一个独立 blob(8bbd395861,7,715,076 字节),逐页提取 <a:t> 文本与 V1.0 那份 24 页对比,24 页里只有第 16、17 页不同:第 16 页标题从「大腿内收肌标定件安装」改成「大腿标定件安装」,正文从「卡入大腿后侧的两个切除部分」改成「卡入大腿前侧的两个槽位」;第 17 页新增一个标注「机器人大腿前侧」。其余 22 页——包括腰部三角凸台卡入、肘部过盈锤入、膝盖定位销取出、脚踝滚转约束、脚后跟不要完全锤入,以及第 24 页那条整机级验收判据——文字逐字相同。一处大腿标定件的安装方位被修正,验收判据原样保留。
其二,V2.0 的 A2_3D_print/ 目录新增了 大腿后侧标定10mm.STEP(8d958cc085,88,157 字节,与 V1.0 那件不同)和 总3d打印标定件/大腿后侧缩小版.STEP(89eb6a71e0,137,032 字节)——同一个标定件出现了三个尺寸版本,外加一份 打印说明.pdf(f00facaf24,181,786 字节,V2.0 两处同名文件同 SHA)。这说明标定件在 V2.0 进入了按打印件公差迭代的阶段:过盈量靠打印误差这个机制在 V1.0 是「正好能用」,到 V2.0 变成要出 10 mm 版与缩小版来挑。标定件不是设计冻结件,它是随打印工艺一起收敛的工装。
24 页里的规则非常工程化:标定件「达到其所需要限制的自由度功能即可视作安装合理」,固定孔位可以留出不打螺丝;3D 打印本身的尺寸误差被当成过盈量使用,「使用橡胶锤捶打进行固定」;腰部用 M3x8 杯头螺丝(两侧 12 颗不用全拧,达到对正即可),大腿定位块用 M4x12;大腿内收肌标定件一次限制 4 个电机(V2.0 把这一页的标题从「内收肌标定件」改称「大腿标定件」,见上),脚踝标定件限制脚底板滚转,脚后跟标定件与前侧件「协同限制这两个电机转动」,且脚后跟那处三角凸台不要完全锤入(会拔不出来),两侧锤入深度一致即可。
验收判据在第 24 页,只有一句:「所有标定件安装完成后,胸腔前板与脚底板平面应保持垂直,若无明显肉眼可见偏差即说明标定可靠。」这是一个整机级几何判据,不是逐关节角度判据——它不需要量具、不需要通电,检查的正是「躯干坐标系与足底坐标系是否正交」,也就是策略观测里 base 姿态与足端接触面的相对关系。要说清楚它的边界:肉眼判垂直的精度在 1° 量级,所以它只能保证「没装错」,不能保证「装得准」;准不准由驱动层那道 $10^{-2}$ rad 的门限负责。两层判据是串联的,一层都不能省——只过机械层,你会得到一台「看起来对、姿态恒定偏 0.5°」的机器;只过驱动层,你会得到 23 个各自达标、但躯干与脚底不垂直的机器(每个关节 0.5° 沿运动链累积,见本节后面的踝裕度表)。
覆盖范围与左右镜像:符号和限位落在配置的哪一层
还有一处容易看漏:这 8 类标定件覆盖腿、腰、臂,但没有一件是给手指或头部用的——RPO 的可动关节就是 23 个(腿 12 + 腰 1 + 臂 10),rpo.urdf 里也正好是 23 个 revolute 关节。而 motor_zero_offset 23 项里只有腰部那一项非零,也就是说除腰以外,其余 22 个关节的零点都由标定件定在机械上,软件侧不需要再补偏置。
单腿 6 个关节的 limit 是:大腿 pitch −2.094 ~ +0.7854(约 −120°~+45°)、膝 −0.2 ~ +2.5、踝 pitch ±0.6、踝 roll ±0.5,大腿 yaw/roll 则是一对彼此正交、但相对机体轴整体转了 30° 的轴(axis 分别是 -0.5 0 -0.86603 与 0.86603 0 -0.5,点积为 0、各自模长 1,第一个与机体 $-z$ 夹角 30.00°、第二个与机体 $+x$ 夹角 30.00°,两者都落在 $xz$ 平面内,也就是「偏航轴 + 侧摆轴」张成的那个平面)。它仍然提供同样的 2 自由度子空间,但基底转了 30°:所以 left_thigh_yaw_joint 的读数在机体坐标系里是 $\cos30°$ 的偏航加 $\sin30°$ 的侧摆,left_thigh_roll_joint 同理反过来。腿之外的腰部 torso_joint 则是 ±3.14(整圈,实际不做限位约束)。
对零点标定的直接影响是:髋部任一关节的零点偏 $\delta$,在机体姿态上同时产生 $0.866\delta$ 与 $0.5\delta$ 两个分量——它不会被「只看偏航」或「只看侧摆」的单轴检查抓到,必须按斜置基底做矢量分解;这也是为什么第 24 页那条验收判据要检查整机正交性而不是逐个关节角。
左右腿这对轴的 axis 向量逐字相同(左右 thigh_yaw 都是 -0.5 0 -0.86603,左右 thigh_roll 都是 0.86603 0 -0.5),互换的是 limit 区间:左 yaw $[-1, +0.2]$ 对右 roll $[-1, +0.2]$、左 roll $[-0.2, +1]$ 对右 yaw $[-0.2, +1]$。也就是说髋部这一对不是靠电机反向镜像,而是靠把限位区间在两个关节名之间对调来镜像:髋 yaw/roll 的 motor_sign 左右都是 +1,镜像信息全部落在 limit 上,也说明 URDF 里关节名与实际运动方向之间的对应关系是左右错位的。
把那 6 个 $-1$ 摊开看,分布是右腿 4 个(电机 9/10/11/12 = 右 thigh_pitch / knee / ankle_pitch / ankle_roll)加右臂 2 个(电机 19/22 = 右 arm_pitch / elbow_pitch),左半侧 11 个关节与腰部全为 +1。
于是整机的左右镜像用了两套互不重叠的手段,11 对镜像关节按手段分成三组:
| 镜像手段 | 关节对(左 / 右) | 对数 | limit 左右关系 | motor_sign |
|---|---|---|---|---|
改 motor_sign,取 -1 | thigh_pitch、knee、ankle_pitch、ankle_roll、arm_pitch、elbow_pitch | 6 | 完全相同 | 右侧 -1,左侧 +1 |
把 limit 区间取反,或在两个关节名之间对调 | thigh_yaw、thigh_roll、arm_roll | 3 | 互为取反(髋部那对是对调) | 两侧都保持 +1 |
| 取反是恒等操作,看不出痕迹 | arm_yaw、elbow_yaw | 2 | $\pm1.57$ 的对称区间 | 两侧都保持 +1 |
对零点标定的直接含义是:镜像在配置里本有三个可能的落点(axis、motor_sign、limit),ATOM01 只用了后两个——11 对镜像关节的 axis 向量逐字相同,所有 23 个 joint 的 origin rpy 也全是 0 0 0,URDF 的坐标系链里不含任何左右翻转。这意味着左右件装反的错误不会在 axis 层面暴露,只会落在符号或限位层面,而这两种错位的症状并不相同。
落到髋部这一对上的后果是:把左右腿的 robot.yaml 配置或电机 ID 装反,不会表现为「关节反向」这种一眼能认出的症状,而表现为「限位区间错位」——关节往合法方向走却在软件里被记成越限,或在物理上撞到限位块而软件认为还有余量。这类错配在 7.6 节失败表里最接近「关节 ID 映射错」那一行,但它比 ID 映射错更隐蔽,因为读数符号看起来是对的。
零点怎么写进去:两条路径、一道判据、三个失效点
标定的执行有两条路径,分别对应「整机已装好」和「首次装机/检修后」两个场景。robot_interface.cpp::set_zeros() 的前置条件是硬编码的:电机必须已初始化(否则抛 Motors are not initialized),必须先拿到 command_mutex_(即推理必须停着,否则和策略指令打架),然后 exec_motors_parallel 对 23 个电机并行调 set_motor_zero()。它假设人已经把机器人摆到零位了。scripts/set_zero.py 是另一条路:逐电机 init_motor() → 切 MIT 模式 → 循环发 motor_mit_cmd(0, 0, 0, 1.0, 0)(位置 0、速度 0、扭矩 0、$K_p$ = 0、$K_d$ = 1.0,即纯阻尼模式,人可以徒手转动关节)→ 实时打印 get_motor_pos() 与 get_error_id(),按 Enter 写当前零点、按空格跳过该电机 → deinit_motor()。23 个电机按 set_zero.yaml 的四路 CAN 顺序(can0–can3,节点数 6/7/5/5)依次走。
「标定成功」有机器判据,不靠人的手感。motor_driver.hpp 里 judgment_accuracy_threshold = 1e-2 rad(= 0.573°),setup_sleep_time = 500 ms。四家驱动器的 set_motor_zero() 是同一个流程:发零点命令 → sleep 500 ms → refresh_motor_status() → 再 sleep 500 ms(注释写明 wait for motor to set zero)→ 打日志记录回读位置 → unlock_motor() → 判据 |pos| > 1e-2 则 logger_->warn("set zero error") 并返回 false,否则记 set zero success 返回 true。差别只在协议帧和持久化上:
| 驱动器 | 零点命令帧 | CAN 寻址 | write_motor_flash() 实现 | 断电后零点 |
|---|---|---|---|---|
| DM | 8 字节 [0xFF ×7, 0xFE] | can_id = motor_id,CAN / CAN-FD(BRS) | return true;(空实现) | 不保证 |
| EVO | 8 字节 [0xFF ×7, EVO_CMD_SET_ZERO = 0xFE] | 同上 | return true;(空实现) | 不保证 |
| LRO | 4 字节 [id_hi, id_lo, 0x00, LRO_CMD_SET_ZERO = 0x03] | CAN-FD 广播 can_id = 0x7FF | return true;(空实现) | 不保证 |
| XYN | 两步:SET_ZERO = 0x006 → SAVE_ZERO = 0x012 | 扩展帧 CAN_EFF_FLAG | (device_id << 12) | msg | 真实现:依次发 SAVE_BASIC = 0x0009 / SAVE_CTRL / SAVE_LIMIT,每步 sleep 500 ms | 写入 FLASH,保留 |
这张表里信息量最大的是最后一列的不对称。基类注释对 set_motor_id() 写得很直白:这类操作「Depending on the implementation (e.g., EVO or LRO), this may require a subsequent call to write_motor_flash() to persist across power cycles」。而 DM / EVO / LRO 三个驱动里的 write_motor_flash() 是 return true; ——它不报错,它只是什么都不做,而且上层拿到的是 true。只有 XYN 真的写了 FLASH。这就是第 7.6 节失败表里「标定值断电丢失」那一行的代码出处:不是电机坏了,是驱动层把持久化写成了空函数。工程结论有两条:装机零点必须有电机 FLASH 之外的第二份记录(ATOM01 写在 robot.yaml 的 motor_zero_offset,BHL 写在 calibration.yaml,两者都是进版本库的文本文件,不是电机里的字节);以及每次上电都要回读位置确认 $|q| < 10^{-2}$ rad 再启动策略,把 1 s 的等待换成一次可验证的检查。
但把这条判据放回调用链里读,会发现它在 ATOM01 上必然误报失败,而误报又被上层抹平。三段代码连起来:robot_interface.cpp::setup_motors()(第 101 行)把 motor_zero_offset_[count] 传进 create_motor(),DM 驱动构造函数(第 22 行)存进 motor_zero_offset_;can_rx_cbk()(第 214 行)解码反馈帧时写的是 motor_pos_ = range_map(pos_int, 0, bitmax<uint16_t>(16), −PosMax, +PosMax) + motor_zero_offset_——offset 是加在回读位置上的;而 set_motor_zero()(第 185 行)的判据用的是 get_motor_pos(),它返回的正是这个已经加过 offset 的 motor_pos_。
后果很直接:对腰部 yaw(motor_zero_offset = 2.093 rad)来说,零点写得再成功,回读位置也是 2.093 rad,远超 $10^{-2}$ 门限,日志里必然出现 set zero error、函数必然返回 false。判据检查的是「URDF 关节角是否为零」,零点命令写的是「电机原始位置为零」——两个坐标系之间隔着 2.093 rad 的装配偏置时,判据检查的对象就错了。这也解释了 set_zero.yaml 与 robot.yaml 那两份配置的一处关键差别:set_zero.yaml 的 motor_zero_offset 23 项全是 0.0,而 robot.yaml 第 13 项是 2.093。它们不是冗余副本——标定路径刻意用一份 offset 全零的配置,正是为了让判据在标定那一刻成立;推理路径才需要那个 2.093。走 set_zero.py 判据有效,走 /set_zeros 服务(读 robot.yaml)则腰部那一项必然误报。
要说清楚这次误报的实际影响:零点本身是写进去了的(电机内部 raw 零点已置 0),坏掉的只是那道验收判据,而且偏置反正在软件里,姿态不会因此错。真正的损害是判据失去了作为判据的资格——日志里每次标零都稳定出现一条 set zero error,操作者很快就会学会忽略它;等到某个关节真的没标进去、同样打这条 warn 时,它已经淹没在预期噪声里了。一个恒定误报的告警等于没有告警,这与 7.6 节失败表里「标定值断电丢失」那条的成因是同一类问题:不是硬件坏,是验收链上某一环把信号丢了。
而这次误报没有任何人会看到。RobotInterface::set_zeros()(第 322–330 行)的 lambda 里只有一行 motor->set_motor_zero();——返回值被直接丢弃,不汇总也不抛出;InferenceNode::set_zeros_srv()(第 547–562 行)只在推理正在跑时拒绝,其余情况一律 response->success = true; response->message = "Zeros set successfully";。于是 ros2 service call /set_zeros 给出的回答,对 23 个电机是无条件的成功,哪怕其中若干个真的没写进去。
这是本节里同一个失效模式的第二次出现:上面 write_motor_flash() 是「实现为空却返回 true」,这里 set_zeros() 是「判据失败却不返回失败」。两者都不是会崩溃的 bug,而是把一条真实的验收信息在传递链上抹平——它比崩溃更难发现,因为每一层都在报告成功。对策与上一条相同:不要相信服务返回值,去数日志里的 set zero success / set zero error;或者标定时统一走 offset 全零的 set_zero.py 路径,让判据本身成立。
门限 $10^{-2}$ rad 的物理含义可以从量化分辨率反推。dm_motor_driver.cpp 第 7–10 行的 dm_limit_param[] 给两种 DM 电机各一组上限:DM4340P_48V 是 {PosMax 12.5, SpdMax 20, TauMax 28, OKpMax 500, OKdMax 5},DM10010L_48V 是 {12.5, 25, 200, 500, 5};位置按 16 bit 编码,速度与扭矩按 12 bit。于是位置分辨率是 $2\times 12.5/65535 = 3.81\times 10^{-4}$ rad = 0.0219°,门限 $10^{-2}$ rad 相当于 26.2 个量化步。这说明门限防的不是编码器噪声(那只有 1 个 LSB),而是「人摆歪了、标定件没卡到位」这类装配级误差。把三层判据按精度排开:机械层「肉眼判垂直」≈$1.75\times10^{-2}$ rad、驱动层 $1\times10^{-2}$ rad、量化层 $3.8\times10^{-4}$ rad——量化精度比验收门限细 26 倍,硬件本来能做得更好,门限是工程取舍不是能力上限;也正因为它松,下面那张表里 $10^{-2}$ rad 就能把 ATOM01 的踝裕度从 2.27× 吃到 1.94×。
对照:BHL 断电即丢零,还多一层电气零点
BHL 是一个极有价值的对照,因为它的标定方法完全不同。lowlevel 仓库 README 写得毫不含糊:「Because the joint actuators only have single encoder on the motor shaft, we need to calibrate the zero position of the joints after each power cycle.」单编码器装在电机轴上,经过 15:1 减速,输出轴的绝对位置在断电瞬间就丢了——上电后编码器只知道自己在那 4096 cpr 一圈内的哪个相位,而减速器输出轴转了多少圈、关节现在究竟在哪个角,这两个信息全丢了。所以 BHL 没有 ATOM01 那种「摆到零位写零」的选项,它的 calibrate_joints.py 反过来做:先记录 12 个腿关节的初始读数,要求人把每个关节推到机械硬限位,脚本按一张预设的轴向符号表把 12 个关节的读数归一化,然后逐个持续跟踪硬限位极值(5 个关节取 max、7 个取 min),最后 offsets = limit_readings − ideal_values 写进 calibration.yaml。而 ideal_values 是 URDF 声明的限位端:左腿 $[-10, +33.75, +56.25, 0, -45, -15]$°、右腿 $[+10, -33.75, +56.25, 0, -45, +15]$°。
它用「机械限位」这个断电也不会丢的物理事实,代替了「零位」这个会丢的物理事实——代价是每次上电都要重标一遍(12 个关节逐个推到限位),换来的是标定值不依赖任何持久化存储。脚本里另有一张 joint_axis_directions 符号表(12 项,其中 5 个 $-1$),与 ATOM01 的 motor_sign 是同一件事的同一位置。
BHL 还有第二层零点,是电气的。motor_configuration.json 里 encoder.flux_offset = 37.8376(配 pole_pairs = 14、cpr = 4096、gear_ratio = -15.0、phase_order = -1),由 calibrate_electrical_offset.py 把电机切到 Mode.CALIBRATION、等 20 s 让驱动器自己跑完标定序列得到。这一层标的是「编码器读数与转子磁链的相位关系」:它错了不会让姿态偏移,而会让扭矩方向错、电流飙升、电机发烫。两层零点必须分开排查——机械层零点错的症状是姿态恒定偏移(第 7.6 节第十行),电气层零点错的症状是低速扭矩准高速扭矩塌、或者一使能就过流(第 1.3 / 6.1 节)。把它们混成一个「标定」,是排查时最容易走错的一步。
代价:踝裕度、躯干雅可比与域随机化区间
算代价先要有分母,分母就是扭矩声明。effort 腿与腰一律 120 Nm,踝与手臂一律 27 Nm;velocity 腿腰 25.0、踝与手臂 8.0 rad/s。把这份 URDF 与 robot.yaml 的 motor_model 对起来读,能看出哪些关节是贴着驱动器的量化满量程设计的。motor_model 23 项里 9 项是 1(DM10010L_48V,TauMax 200 Nm)、14 项是 0(DM4340P_48V,TauMax 28 Nm),按 urdf2motor 恒等映射,每侧大腿 yaw/roll/pitch 与膝这 4 个关节(两侧共 8 个)加腰部 yaw 合计 9 个用 200 Nm 的大电机;左右踝 pitch/roll 共 4 个加全部 10 个手臂关节合计 14 个用 28 Nm 的小电机。
TauMax 不是标称峰值,它是 range_map() 给 12 bit 扭矩字段定的满量程,同时也是 motor_mit_cmd() 里 limit(f_t, −TauMax, +TauMax) 的指令钳位上限——所以 URDF 声明值与它的比值就是「这个关节在协议上还有多少可让的余量」:大腿与腰声明 120 Nm,占 200 Nm 满量程的 60%;踝与手臂声明 27 Nm,占 28 Nm 满量程的 96.4%。踝和手臂是贴着天花板配的,驱动层几乎没有再往上让的空间,这正是下面那张踝裕度表的两个分母来源:27 Nm 声明值 ÷ 第 1.2 节蹲姿需求 11.91 Nm = 2.27×,再叠加零点偏置那 2.03 Nm 就是 27 ÷ (11.91 + 2.03) = 1.94×。同一份 dm_limit_param[] 里还有 OKpMax = 500、OKdMax = 5:膝与腰的 kd 配的就是 5.0,正好顶在量化上限,再想要更大的阻尼会被 limit() 静默钳掉。
零点偏移的代价可以算出来,而且它不是二阶小量。机理是:踝 pitch 零点偏 $\delta$,会让整机质心相对踝轴产生一个恒定水平偏距 $\approx h_{com}\cdot\delta$(小角近似),单腿支撑时地面反力 $W = mg$ 对这个偏距产生一个恒定静力矩
$$\Delta\tau_{ankle} = W \cdot h_{com} \cdot \delta = m g h_{com} \delta$$
它不是噪声(不会随时间平均掉),是偏置(每一帧都在),方向还和 CoP 前移的方向叠加。把第 1.2 节的蹲姿踝需求加上这一项,裕度就变成 $\tau^{declared}/(\tau^{demand} + \Delta\tau)$:
| 机器人 | 踝轴→整机质心高度 $h_{com}$ (m) | 整机重 $W$ (N) | $\delta = 10^{-2}$ rad(驱动门限):$\Delta\tau$ (Nm) | 踝裕度变化 | $\delta = 0.05$ rad:$\Delta\tau$ (Nm) | 踝裕度变化 |
|---|---|---|---|---|---|---|
| ATOM01 | 0.6125 | 331.1 | 2.03 | 2.27× → 1.94× | 10.14 | 2.27× → 1.22× |
| AgiBot X1 | 0.6362 | 346.4 | 2.20 | 7.98× → 6.54× | 11.02 | 7.98× → 3.80× |
| Unitree H1 | 0.9351 | 506.5 | 4.74 | 5.62× → 3.37× | 23.68 | 5.62× → 1.30× |
| Unitree G1 | 0.6681 | 344.4 | 2.30 | 8.63× → 6.18× | 11.51 | 8.63× → 2.89× |
| BHL | 0.3423 | 160.1 | 0.55 | 10.20× → 7.97× | 2.74 | 10.20× → 4.25× |
读这张表要抓住三件事。第一,$\delta$ 只要到驱动层允许的最大值($10^{-2}$ rad,0.57°),ATOM01 的踝裕度就从 2.27× 掉到 1.94×,H1 从 5.62× 掉到 3.37×。「标定达标」和「裕度不受影响」是两回事:门限保证的是零点写进去了,不保证误差在力学上可忽略。第二,代价随 $h_{com}$ 线性放大,所以腿长、质心高的机器对零点最敏感——H1 的 $h_{com}$ 是 0.935 m,同样 $10^{-2}$ rad 产生 4.74 Nm 的恒定偏置,是 BHL(0.342 m、0.55 Nm)的 8.6 倍;$\delta$ 到 0.05 rad 时 H1 的偏置 23.68 Nm 已经吃掉 40 Nm 踝 effort 的 59%,裕度只剩 1.30×。第三,这个偏置和第 1.2 节的结论直接挂钩:H1 本来就是「CoP 只能到脚尖力臂 39%」的平踩式机器,踝裕度被零点偏置吃掉一半,等于把它仅剩的姿态调整能力又削掉一层。
另一条代价路径是躯干位姿。在零位姿态下对 URDF 做数值雅可比($\partial \vec p_{torso}/\partial \delta_j$,逐关节给单位扰动、算躯干原点位移),ATOM01 最敏感的三项是踝 roll 779.7 mm/rad、踝 pitch 776.3 mm/rad、膝 476.3 mm/rad;在 $10^{-2}$ rad 门限下分别对应 7.80 / 7.76 / 4.76 mm 的躯干位置误差,12 个腿关节同号偏移累积 50.9 mm,按 RSS(独立同分布、无相关性假设)合成是 17.5 mm。BHL 的 12 个腿关节 RSS 是 8.78 mm。单关节 0.57° 看着很松,叠到躯干上是厘米级——而躯干位置与姿态正是策略观测里 base 状态的来源,也是第 2 层 IMU 杆臂补偿的基准点。零点偏 1° 与 IMU 装偏 1° 在观测里产生的是同一类误差,这也是为什么两者要在同一张排查表里。
反过来看,这个数字解释了域随机化里一个看起来奇怪的取值。BHL 的 env_cfg.py 里有一条 randomize_joint_default_pos,pos_distribution_params = (-0.05, 0.05)、mode = "startup":每次 reset 给所有关节的默认位置加一个 ±0.05 rad(±2.86°)的随机偏移。这个幅度是它自己驱动层 $10^{-2}$ rad 验收门限的 5 倍。换句话说,BHL 在训练时假定的零点不确定性,比它硬件能保证的零点精度松了一档。这是域随机化的正确用法:随机化范围要覆盖「真机上标不出来、或者标了也会漂」的那部分误差,宁可宽不可窄;代价是策略变保守(它必须学会容忍 ±2.86° 的默认位置漂移)。推论也很直接:如果你把零点标定做到远优于 $10^{-2}$ rad 并且能证明它不漂,这个随机化范围就可以收窄,策略的性能上限随之提高——标定精度是可以直接换成运动性能的。这大概是整篇文章里「硬件工艺 → 训练配置」最短的一条因果链。
上电回位与灵巧手 homing:同一个工程结构
最后一块拼图是上电后怎么从任意姿态回到默认姿态。reset_joints() 用 4 s、20 ms 周期(200 步)做平滑过渡,混合函数是 smoothstep $s(p) = p^2(3 - 2p)$,并且把 $K_p$ 临时降到配置值的 1/2.5,走完再恢复——因为零点标定完成后机器人可能停在任意姿态,硬切到 joint_default_angle 会甩。闭链踝在过渡路径里同样过 get_decoupleQVT()。refresh_joints() 里还有一个 sleep_for(1000 ms):刷新完所有电机状态后等 1 s 再读关节,给 CAN 反馈帧留时间。这两个「等」和上面那个 500 ms 的 setup_sleep_time 是同一类东西:零点标定是一条时序敏感的流程,所有等待都是为了让「命令已生效」这件事在总线上可观测。
灵巧手是同一个问题,只是换了个名字叫回零(homing)。RoboParty 的 dexhand 驱动里 init_hand(enable_motors, home_motors, home_wait_time) 默认 (True, True, 5.0):广播回零命令后,以 20 ms(kHomePollPeriod)轮询新鲜的 0x50 位置反馈与 0x5A 状态反馈,要求六个轴全部满足「已停止(status == kStoppedStatus)+ 接近零位(|position| ≤ kHomePositionTolerance = 400 counts)+ 无报警」,并且位置与状态反馈的 generation 计数都比发命令前更新过,才算 homed;超时则 record_fault_("home_motors_timeout", kHomeTimeout = -6) 并禁用电机。
SDK 头文件 LHandProLib.h 里有一个专门的错误码 C_LER_NOT_HOME = 11 // 没回零错误,外加 set_move_no_home(0/1) 开关和 set_home_current(handle, %)——「没回零就运动」在协议层是被显式拦截的,不是靠调用方自觉。README 强调 home_wait_time = 5.0 是最大超时不是固定等待,且只有「软件进程重启、手本体没掉电、零位可信」时才能用 init_hand(True, False, 0.0) 跳过回零;VALIDATION.md 的 4.2 验收项就是「初始化 + 回零 → 六轴运动到 1200 → 回到 0 并读回位置」。从 23 个关节到 6 根手指,零点标定是同一个工程结构:一个由装配决定的常量,加一道有机器判据的验收门限,加一条「未标定则拒绝运动」的硬拦截。
所以零点标定在整条链路里的位置是确定的:排在 7.4 的 sim2sim 之后、真机策略上机之前。sim2sim 验的是「策略学到的是可迁移的物理,不是 PhysX 的接触模型」;零点标定验的是「这台机器的关节坐标系和仿真里的关节坐标系是不是同一个」。两个都过了,「策略原样上真机」这句话才成立。它是整条 sim-to-real 链路上唯一一个不做就一定错、做了就能验、而且验证成本只有几秒的环节——这也是它值得单独成节的原因。
7.6 sim-to-real 的真实失败模式清单
把前面六层的约束汇总成一张排查表(每一条的验证口径都对应上文引用的公开文档:电机表征、关节 ID 映射、sim2sim 验收)。这张表是本文最想留下的东西——真机上机出问题时,按这个顺序查,能覆盖绝大多数情况:
| 症状 | 最可能的原因 | 对应本文哪一层 | 验证方法 |
|---|---|---|---|
| 静止站立正常,一转身就发散 | IMU 杆臂未补偿,或 URDF 的 imu 帧坐标与真机安装位置不符 | 第 2 层 | 录 $\dot\omega$ 与加速度计读数,检查 $|\vec a_P - \vec a_O|$ 是否随 $\dot\omega$ 线性增长 |
| 左右动作镜像反了 / 某关节反向 | 关节 ID ↔ CAN ID ↔ 方向符号映射表错 | 第 6.2 节 | 逐关节单点给小位置指令,肉眼确认方向;核对映射表的 sign 列 |
| 低频抖动 / 指令卡顿 | CAN 总线超载或丢帧(节点数 >6 或频率过高) | 第 6.2 节 | 算负载率 $N f L / R$,应 <70%;用 CAN 分析仪看 error frame 计数 |
| 低速扭矩准、高速扭矩塌 | 反电动势前馈用了标称 $K_V$(实测偏低 15%) | 第 1.3 / 6.1 节 | 做电机表征,把实测 R/L/$K_t$ 写进固件 |
| 位置环频率上不去 | 编码器接口瓶颈(I²C 78 µs → 12.8 kHz 上限) | 第 1.3 节 | 示波器量单次读取耗时,换 SPI 或增量式 |
| 跑 10 分钟正常、30 分钟无力 | 电池热失控前的 SoP 降额,电压未到保护线 | 第 5.3 节 | 记录电芯温度与 SoP 上报值,看是否随时间下降 |
| 跌倒后爬不起来(BMS 锁死) | 瞬时压降被误判为欠压,未用 SoP 判据 | 第 5.3 节 | 检查保护逻辑是走硬件比较器还是 MCU 的 SoP 判断 |
| 策略把自己手臂撞进躯干 | 仿真模型缺碰撞体(X1 是 3/58) | 第 3.3 节 | 数 URDF 里的 collision 元素数量,补齐连杆包络 |
| 深蹲姿态下膝关节堵转/报警 | 静力矩需求超出声明 effort(BHL 膝裕度 1.51×) | 第 1.2 节 | 用 CoP/静力平衡模型复算,在奖励里加 base_height 惩罚 |
| 站立姿态整体偏移 / 同一策略在 sim 与真机上姿态差一个恒定角度 | 关节零点偏移未标定,或标定值断电丢失(ATOM01 腰部 yaw 就有 2.093 rad 的装机偏置) | 第 7.5 节 | 摆到机械标定件定义的零位,重跑 set_zero.py / /set_zeros,逐电机回读位置确认 |q| < 1e-2 rad |
| 仿真指标很好但真机剧烈甩腿 | dof_acc 惩罚为 0 或过小 | 第 7.3 节 | 看真机电流峰值是否触碰限幅/BMS 过流线 |
| 上电即垮塌 | 缺多圈绝对值编码器或电磁抱闸 | 第 1.5 节 | 断电后手动检查关节是否保持;核对编码器位数 |
| 偶发 CAN 丢帧,与关节运动同步 | 动力线与信号线同束走线,PWM 共模干扰 | 第 3.3 节 | 示波器看 CAN 差分对上的共模噪声;分开走线 |
这张表里 13 个症状,只有 3 个(第二、八、十一条)是「策略/软件」问题,其余 10 个全部是硬件标定或物理约束问题。这就是为什么「sim-to-real gap」这个词在工程上是有误导性的——绝大多数所谓的 gap 不是仿真不够真,是硬件没标定准。而硬件标定准的前提,是你在设计阶段就把上面六层的约束算清楚了。
收口:从一句话需求到一台机器的约束传递
把七层串起来看,一台人形机器人的设计不是七个独立决策,是一条单向的约束传递链。用一个具体例子走完全程:
| 步骤 | 决策 | 由什么决定 | 决定了什么下游 | 本文对应数字 |
|---|---|---|---|---|
| 1 | 步态类型:平踩式 or 脚尖蹬地 | 任务需求 | 踝扭矩下限 | CoP 力臂需 ≥ 脚掌长:H1 只到 39%、ATOM01 78%、G1 102%、X1 220% |
| 2 | 踝 effort = 27 / 50 / 80 Nm | 步骤 1 + 整机质量 + 脚掌长 | 踝模组选型(电机 + 减速比) | $d_{cop}^{max} = \tau/(mg)$ |
| 3 | 减速比 n 与电机 $K_t$ | 踝扭矩 + 反驱性要求 | 反射惯量 $n^2J_r$、所需电流 | M6C12 ×15 → 0.0224 kg·m²、1.378 Nm/A → 20 Nm 需 14.5 A |
| 4 | 关节数量与分布 | 任务(腿 12 + 臂 10) | CAN 总线拓扑 | 22 节点单总线 130.9% → 必须拆成 4 路,每路 5–6 节点 |
| 5 | 控制频率 500 Hz | 总线负载率 + 编码器接口 | 驱动器 MCU 与接口选型 | I²C 78 µs → 12.8 kHz 位置环上限 |
| 6 | 单关节峰值功率 | effort × velocity | 电池包 C 率下限 | G1 膝 2,780 W = 6.6 C(单关节) |
| 7 | 电池包 Wh 与串数 | 平均功率 × 目标续航 + 模组电压档 | 躯干容积与质量分布 | G1 421 Wh = 1.747 L = 241 Wh/L;13S/46.8 V 匹配 48 V 模组 |
| 8 | BMS 双路径架构 | 步骤 6 的脉冲电流 + 安全合规 | 可交付峰值(SoP)与故障行为 | 硬件比较器路径独立于 MCU;IEC 62133 / UL 2054 |
| 9 | IMU 安装位置 | 躯干容积(电池占了大头)+ 杆臂最小化 | 状态估计误差量级 | r = 0.683 m → 34.15 m/s² = 噪声底的 21,752× |
| 10 | 域随机化范围 | 步骤 9 的估计误差 + 摩擦 + 负载不确定 | 策略鲁棒性与保守度 | 摩擦 [0.1,1.25]、质量 [−1,+3] kg、每 5 s 推 1.5 m/s |
| 11 | 奖励权重 | 步骤 2/3/6 的硬件上限 | 真机电流峰值是否触限 | dof_acc −2.5e-7 直接压峰值功率需求 |
| 12 | 装机零点标定 | 步骤 2/3 选定的关节模组 + 结构件上能加工出的机械零位 | 策略能否原样上真机(第 7.5 节) | 机械标定件定义零位 → 驱动验收门限 |q| < 1e-2 rad;ATOM01 腰部 yaw 装机偏置 2.093 rad |
这条链是单向的,所以设计顺序也是单向的。(本文的逐步复算以 BHL 论文 arXiv:2504.17249 为样本,因为它同时公开了硬件 BOM、固件与训练配置。)先定步态和质量分布,再定关节扭矩,再定电机和减速比,再定总线拓扑,再定电池,再定 IMU 位置,最后才是训练配置。绝大多数失败的自研项目是反着来的:先跑通了一个仿真策略(网上下载的开源配置),再去买电机,然后发现扭矩不够;先买了便宜的 IMU 装在头部(好装配),然后发现转身就发散;先定了小电池包(躯干塞不下),然后发现蹬地时 BMS 过流保护。每一次「回头改」的成本都比顺着做高一个数量级,因为改关节扭矩意味着改结构件、改结构件意味着改质量分布、改质量分布意味着改 IMU 杆臂、改杆臂意味着重训策略。
而这一切的最上游,是那个最不技术的决策:这台机器要做什么样的步态。踝关节能不能压到脚尖,决定了它能不能起步蹬地;能不能蹬地,决定了它能不能跑;能不能跑,决定了它需不需要 1000 W/kg 的功率密度;而这又决定了电池、总线、散热和成本。X1 把髋 pitch 配到 8 rad/s、ATOM01 把踝配到 27 Nm,都不是工程失误,是它们各自把「稳定行走的操作平台」而不是「动态运动」定为目标后的正确取舍。读一台机器的 URDF,本质上是在读它的产品定位。
数据来源与解析口径
本文所有数字都可复算,来源分五类:
| 类别 | 具体来源 | 解析方式 | 覆盖的本文数字 |
|---|---|---|---|
| 模型文件 | Berkeley Humanoid Lite URDF(22 revolute,另有 MJCF)、Unitree G1 URDF(29 dof)、Unitree H1 URDF(10 dof)、AgiBot 灵犀 X1 URDF + MJCF(xyber_x1_serial.xml)、RoboParty ATOM01 URDF(23 revolute) | xml.etree.ElementTree 逐 joint/link 解析 effort、velocity、mass、inertia、origin xyz;反射惯量按平行轴定理沿运动链累加 | 第 1.1–1.4、2.1、3.1、7.5–7.6 节全部扭矩/惯量/质量数字;图 2、3、5、7 |
| BOM 文件 | X1 三版 BOM(xlsx,仓库按日期分目录:首版 2024-10-24 / v3 2025-03-07)、ATOM01 散件清单 xlsx(内部 D-BOM 与量产 ZZEBOM 两份价格口径) | openpyxl 逐单元格读取,材质与单价逐行归类 | 第 3.1–3.2 节材质迁移、成本结构;图 10、11、12 |
| 厂商官方规格 | Unitree G1 电池包(9000 mAh / 421.2 Wh / 46.8 V / 54.6 V / 120×80×182 mm / ~2h)、Unitree H1 电池包(15 Ah / 864 Wh / 标称 57.6 V / 最高 67.2 V,由 864÷15 与 67.2÷4.2 反推 16S)、BHL 官方文档(电机表征、CAN 通信、FOC、IMU 对比、关节 ID 映射、固件执行时序、3D 打印与装配、训练与sim2sim、机载计算机) | 逐项抄录,串数由充电上限电压 ÷ 4.200 V/cell 反推 | 第 1.3、2.3、5.1–5.2、6.1–6.2、7.1–7.4、7.6 节;图 4、13–17 |
| 标定与部署代码 | RoboParty 部署文档 README_CN(电机标零章节) + set_zero.py + set_zero.yaml;roboparty_motors/include/motor_driver.hpp 与 dm / evo / lro / xyn 四家驱动器实现;roboparty_inference/robots/rpo/robot.yaml + robot_interface.cpp + decouple_rpo.cpp;ATOM01 V2.0 总3d打印标定件(8 类 STEP) + 《3D打印件标定说明.pptx》24 页(python-pptx 逐页取 <a:t> 文本)+ ATOM01 V1.0 01总标定件;dexhand hand_driver.hpp + lhandpro_driver.cpp + LHandProLib.h + VALIDATION.md;BHL calibrate_joints.py + calibrate_electrical_offset.py + lowlevel README(Joint Calibration 章节);BHL motor_configuration.json + humanoid/env_cfg.py | 逐行读源码取常量与协议字段(门限、sleep、CAN 帧、FLASH 存根、闭链五杆参数、随机化区间),yaml/json 直接解析 | 第 7.5–7.6 节全部零点标定数字(2.093 rad、1e-2 rad、500 ms、flux_offset 37.8376、±0.05 rad、400 counts、五杆 20/42.35/180/110 mm) |
| 行业公开资料 | TI / ADI / Renesas 人形机器人 BMS 参考设计(块图、器件清单与 24 V / 48 V 选型)、人形机器人 BOM 成本行业数据(约 3.5 万美元/台 → 2030–2035 年 1.3–1.7 万美元;执行器占 40–60%、减速器占单执行器 30–50%;绿的谐波占国内谐波市场 30–40%)、RobotWorld 知识库已收录的 873 条零部件档案(关节模组 166 / 执行器 140 / 减速器 65 / 传感器 200 / 丝杠 202 / 灵巧手 64 条) | 器件型号与规格逐条核对,零部件参数取知识库 specs 字段 | 第 1.5、3.2、4.2、5.2–5.3 节的全部型号与规格;图 16 |
计算口径:重力加速度取 $g = 9.80665$ m/s²;准静态 CoP 模型假设单腿支撑、整机重量由该腿承担、CoP 沿脚掌纵向移动;40° 屈膝姿态的膝/髋静力矩按 URDF 的 link mass 与质心位置做二维静力平衡逐段累加;峰值功率按 $\sum_i \tau_i^{peak} \cdot \omega_i^{peak}$ 计算,这是「全关节同时峰值」的理论上限,不是实际工况(第 5.1 节已证明它在物理上不可达);CAN 负载率按 $\rho = N f L / R$,帧长取 119 bit,波特率 1 Mbps,工程可接受上限取 70%;电池续航按 $t = E/P$ 理想放电,未计入 Peukert 效应与温度降额;能量密度按包级(含结构与 BMS)而非电芯级计算;第 7.5 节的零点偏置力矩按 $\Delta\tau = m g h_{com}\delta$(小角近似,$h_{com}$ 为零位姿态下整机质心到踝轴的高度,由 URDF link mass 与质心累加得到),躯干位移敏感度按零位姿态下对 URDF 做数值雅可比 $\partial \vec p_{torso}/\partial \delta_j$ 得到,「同号累积」与「RSS 合成」是同一个偏移分布的两种极端假设(完全相关 / 完全独立),实际值落在两者之间。
本文的局限,也说清楚。一,五台机器中只有 BHL 公开了完整的电机表征、固件与训练配置,因此第 1.3、6.1、7.1–7.5 节的实测数字全部来自 BHL,其他机器只能从 URDF 声明值反推——声明值与实测值的系统性偏差(如 $K_V$ 低 15%)在其他机器上未经验证。二,G1 / H1 的电池包参数来自厂商官方规格,但 BMS 的具体器件型号未公开,第 5.3 节的器件栈是行业通用选型而非这两台机器的实际用料。三,准静态 CoP 模型不含动态项($J\dot\omega$、科氏力、地面反力分布),因此「裕度倍数」是静力口径,动态工况下膝关节需求会显著上升——第 1.2 节的结论三正是从这个矛盾反推出来的。四,本文不对任何机器的设计做优劣判定:所有「紧配」(如 BHL 膝 1.51×、H1 髋 roll 冠状面 3.42×)都是在各自目标定位下的合理取舍,不是缺陷。
相关站内资料:谐波 / 行星 / RV / 摆线四类减速器的详细选型对比,见站内已有的《机器人关节模组选型》一文,本文不重复该部分;873 条零部件档案(含各型号完整规格与厂商链接)在站内零部件库中可按类别检索。


