• 欢迎访问少将全栈,学会感恩,乐于付出,珍惜缘份,成就彼此、推荐使用最新版火狐浏览器和Chrome浏览器访问本网站。
  • 吐槽,投稿,删稿,交个朋友
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏少将全栈吧

Microduck 的背隙仿真:1° 齿轮间隙怎么用被动铰链建模才能不毁掉 sim2real

Build in Public admin 20小时前 20次浏览 已收录 扫描二维码

做过舵机机器人的人都知道一个让人崩溃的场景:仿真里策略跑得完美,部署到真机上步态全散了。原因不是模型不对,不是奖励函数有问题,是齿轮间隙。Microduck 用 14 个 Dynamixel XL330 舵机驱动全身关节,每个舵机减速箱里都有齿轮背隙——输出轴和电机轴之间不是刚性连接的。pollen-robotics/microduck_rl 仓库里有一整套背隙建模方案,核心思路简单粗暴:给每个舵机关节塞一个被动铰链,让仿真里的位置编码器读到的值和真机一样”穿过”背隙。

背隙是什么,为什么 XL330 躲不掉

齿轮背隙(backlash)就是齿轮啮合时的机械间隙。你转动输入轴,输出轴不会立刻跟着动,得等间隙吃掉之后才开始转。XL330 是 Dynamixel 系列里最小的舵机之一,用在 800g 的双足机器人上,扭矩余量本来就紧张,减速比不能太大,齿轮也不能做太精密。结果就是每个关节大约 ±1° 的背隙,总共 2° 的死区。

2° 听起来不大。但 Microduck 站起来才 25cm,脚踝关节 2° 的误差传导到身体重心就是几个毫米的偏移。50Hz 控制循环里,每一步都带着这个误差,策略网络在仿真里学到的是”我发出指令 A,关节就到位置 A”,到了真机变成了”我发出指令 A,关节先空转一会儿,再晃到位置 A 附近”。这个差异足以让 sim2real 彻底失败。

被动铰链方案:不改变维度,只插入死区

microduck_rl 的背隙建模方案在 src/mjlab_microduck/tasks/backlash.py 里,核心是一个 make_backlash_variant() 函数。它做的事情是:在 MJCF 模型里,给每个舵机关节后面串联一个被动关节(passive__backlash)。舵机主动关节接受控制指令,被动铰链模拟齿轮间隙——它没有驱动力,只能自由转动 ±1°,通过 MuJoCo 的关节约束实现。

这样做有几个好处。首先是物理上合理。真实齿轮间隙就是一个弹簧死区:在死区内,输出端不受输入端约束;出了死区,刚性传递。MuJoCo 的 passive joint 配合 position constraint 可以精确模拟这个行为。

其次是观测维度不变。这一点至关重要——Microduck 的所有策略共享一个 61 维观测向量(48 维本体感知 + 13 维指令块),部署时通过 ONNX 推理。如果在观测里加上背隙铰链的位置,维度就变了,所有已训练的策略和 ONNX 模型全废。用被动铰链方案,背隙铰链的状态被”藏”在舵机关节的读数里——qpos[servo] + qpos[backlash] 合并成一个新的”编码器读数”,维度仍然是 14 个关节位置,和没有背隙的模型完全一样。

编码器穿过背隙:观测和奖励必须对齐

这里有一个关键设计决策,AGENTS.md 里用粗体标注了:如果某个观测量被重映射到传感器视角(背隙编码器),那么同一物理量的跟踪奖励也必须测量同一个视角——否则策略会因为”纠正了自己看到的偏差”而被惩罚。

具体来说,BacklashEncoderBamActuator 这个类在 friction_dr_bam.py 里做了两件事:一是模拟 BAM 执行器的电压控制物理,二是把位置编码器的读数改成 qpos[servo] + qpos[backlash]。joint_pos 和 joint_vel 观测项也跟着改,读出来的都是穿过背隙后的值。

这意味着什么?策略网络看到的关节位置不是”舵机想去的位置”,而是”舵机输出端实际到的位置”。这和真机完全一致——XL330 的编码器装在输出端,读到的本身就是穿过齿轮间隙后的值。如果在训练时观测用的是 qpos[servo](背隙前),但奖励函数惩罚的是 qpos[servo] + qpos[backlash](背隙后),策略就会试图纠正一个它根本看不见的偏差,结果就是在真机上疯狂抖动。

一键生成背隙变体:A/B 对比的工程保障

microduck_rl 不是只有一个背隙模型,而是给每个任务都准备了背隙变体。tasks/__init__.py 里有一个 _BACKLASH_TASKS 表,注册时自动在每个基础任务后面追加一个 -Backlash- 版本。比如 Mjlab-Velocity-Flat-MicroDuck 对应 Mjlab-Velocity-Flat-Backlash-MicroDuck。

背隙变体的生成完全自动化。add_backlash.py 脚本读取基础 MJCF 文件,在每个舵机关节后插入被动铰链,参数化背隙角度(默认 2.0°),输出 robot_*_backlash.xml。脚本的命令行是 add_backlash.py --backlash-deg 2.0。

这个设计让 A/B 对比变得非常干净。同一个任务、同一套奖励函数、同一个训练配置,唯一区别是有没有背隙。训练完两个变体,把 ONNX 都导出来,用 scripts/infer_policy.py 在同一个仿真环境里跑,直接对比步态质量、能耗、稳定性指标。如果背隙变体的性能显著下降,说明策略对齿轮间隙敏感,需要在训练时加入背隙随机化;如果两个变体表现接近,说明策略本身鲁棒,可以直接部署。

背隙和 BAM 执行器的耦合问题

背隙建模不是独立模块,它和 BAM 执行器模型深度耦合。BAM(BAM Actuator Model)是 Rhoban 实验室开发的执行器物理模型,模拟 XL330 的电压控制律、反电动势、库仑摩擦和负载相关摩擦。microduck_rl 在 BAM 之上叠了一层摩擦域随机化(FrictionDRBamActuator),再叠加背隙编码器反馈。

三层模型嵌套的执行顺序是:策略发出电压指令 → BAM 计算电机输出扭矩(含摩擦 DR)→ 扭矩通过背隙铰链传导到输出端 → 编码器读到输出端位置 → 位置反馈给策略。每一层都有独立的域随机化参数,训练时在 4096 个并行环境里随机采样,让策略学到的是对整个参数空间鲁棒的策略,而不是对单一标称参数过拟合。

这套方案的代价是训练时间。背隙变体的收敛速度通常比基础任务慢 20-30%,因为策略需要额外学会在齿轮死区内做控制——死区内输出端不受控,策略必须学会”预判”间隙方向,在换向前主动补偿。这和真机上 experienced 工程师调舵机的手感是一样的:你得提前打方向,等间隙吃掉再发力。

从仿真到真机:背隙是特性不是 bug

Microduck 的背隙建模方案揭示了一个 sim2real 的核心原则:不要试图在仿真里消除真实硬件的不完美,而是要忠实地建模它。1° 的齿轮间隙在 XL330 这种小舵机上是物理现实,与其假装它不存在然后在真机上踩坑,不如在训练时就把这个”特性”喂给策略网络,让策略自己学会怎么和背隙共处。

Pollen Robotics 选择把背隙建模做成了标配——每个任务都有背隙变体,每个发布到 Hugging Face Hub 的策略都可以选择用背隙模型训练还是用基础模型训练。这种工程化的处理方式比”调大域随机化范围祈祷覆盖”靠谱得多,因为你在训练的不是一个”希望鲁棒”的策略,而是一个”已经见过背隙并学会应对”的策略。

喜欢 (0)赏
[🍬谢谢你请我吃糖果🍬🍬~]
分享 (0)
关于作者:
少将,关注Web全栈开发、项目管理,持续不断的学习、努力成为一个更棒的开发,做最好的自己,让世界因你不同。