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

Microduck 的齿隙模拟:±1° 齿轮间隙怎么建模才不会摔鸭子

Build in Public admin 59分钟前 7次浏览 已收录 扫描二维码

做 sim2real 的人大概都有过这种体验:策略在仿真里走得稳稳的,上了真机第一步就歪。你调了地面摩擦、加了传感器噪声、域随机化也铺了一堆参数,但鸭子还是摔。问题可能出在一个你没想到的地方——齿轮间隙。

Microduck 用的是 Dynamixel XL330 舵机,14 个关节驱动整个身体。这种舵机内部有齿轮箱,齿轮之间不是严密咬合的,存在一定量的机械间隙。Pollen Robotics 在训练仓库里专门给每个任务做了一个 Backlash 变体,把这个间隙建模到了仿真里。今天拆开看这套方案到底在干什么。

什么是齿隙,为什么它对小型机器人影响这么大

齿隙(backlash)就是齿轮啮合时的机械游隙。你用手拧舵机输出轴,能感觉到一小段空转——那就是齿隙。工业级谐波减速器可以把齿隙控制在弧分级别,但 XL330 这种小型舵机用的是普通齿轮箱,间隙大约在 ±1° 左右,总游隙 2°。

2° 听起来不大。但对一个 25 厘米、800 克的双足机器人来说,这个量级的关节误差会累积。脚踝偏 2°,身体重心偏移可能就有 1-2 厘米,对于窄支撑面的双足行走来说,这足以让一步踩空。

Pollen Robotics 的做法不是在真机上去消除齿隙,而是在仿真里忠实地建模它,让策略在训练时就学会应对。

仿真里怎么建模一个被动间隙关节

Microduck 的 RL 仓库(microduck_rl)用了一个很巧妙的方法。它不是在控制器层面加个死区或者噪声就完事,而是在 MuJoCo 的物理模型里插了一个真实的被动关节。

具体来说,每个舵机关节旁边多了一个 passive_<joint>_backlash 铰链。这个铰链是未被驱动的(unactuated),它串联在舵机输出和实际连杆之间。间隙范围被限制在 ±1°,也就是说这个被动关节的角度被约束在 -1° 到 +1° 之间。

在 MuJoCo 的 MJCF 模型里,这意味着每个关节从”一个驱动自由度”变成了”一个驱动自由度 + 一个被动自由度”。14 个舵机就多了 14 个被动关节。这些被动关节没有电机控制它们,完全由物理仿真决定状态——齿轮的间隙就是这样的,你控制不了那段空转,它自己滑到哪算哪。

关键在于,这个被动关节的生成是自动化的。仓库里的 add_backlash.py 脚本可以给任意一个基础机器人模型加上齿隙变体,生成对应的 robot_walk_backlash.xml 或 robot_allcollisions_backlash.xml。

编码器视角:为什么观测要”穿透”间隙

这一步是最容易被忽略、也是最精妙的部分。

真实的 Dynamixel 舵机里,编码器(位置传感器)安装在齿轮箱的输出端——也就是间隙的后面。舵机控制器做 PD 控制时,它读到的是间隙之后的实际输出位置,而不是电机端的原始角度。如果你在仿真里只看电机端的位置,策略拿到的信息和真机上的就不一样。

Microduck 的解决方案是修改观测函数。在 Backlash 变体里,joint_pos 观测被替换成了 joint_pos_rel_backlash,它返回的是 qpos[servo] + qpos[backlash]——电机角度加上间隙角度,正好等于输出端的实际位置。

同样的,joint_vel 也做了对应替换。这意味着策略看到的关节状态和真机编码器读到的完全一致。

还有一个细节:BacklashEncoderBamActuator 这个类模拟了固件层面的 PD 控制。真实舵机的 PD 控制器也是基于编码器读数来做的,所以仿真里的执行器必须也基于”穿透间隙后的”位置来做 PD,而不是基于电机端的原始位置。

这个设计保证了从训练到部署的一致性。策略在训练时看到的就是部署时能看到的,不会有信息差。

奖励函数的坑:被动关节不能进惩罚

齿隙关节有个特殊行为:它几乎一辈子都贴在 ±1° 的限位上。因为齿轮的间隙就那么大,被动关节不是被电机推到这边就是那边,中间停留的时间极短。

如果奖励函数里有一个”关节超出软限位”的惩罚项,齿隙关节会持续触发这个惩罚。策略就会收到一个永久性的惩罚信号,训练根本没法正常进行。

Microduck 的 backlash.py 里专门处理了这个问题。它把 dof_pos_limits 奖励项的 asset_cfg 限定为只匹配 ^(?!passive_).* 的关节——也就是排除了所有 passive 开头的关节。齿隙关节被允许贴着硬限位运行,不会被惩罚。

类似地,姿态奖励(pose reward)也需要调整。variable_posture 奖励会根据关节名匹配标准差字典,齿隙关节的名字像 passive_left_hip_yaw_backlash 会同时匹配多个模式,导致匹配歧义报错。解决方法是给正则表达式加一个 backlash 排除前缀。

这些看起来都是小细节,但每一个都可能导致训练失败或者策略质量下降。Pollen Robotics 在 CLAUDE.md 里专门写了一条不变量:-Backlash- 变体必须镜像对应基础任务的机器人模型(walk / allcollisions / rollers),这样 A/B 比较才不会混淆变量。

观测维度不变:部署链路零改动

这套设计有一个很强的约束:观测和动作维度不能变。

基础任务和 Backlash 变体的 actor 观测都是 61 维。48 维本体感知 + 13 维指令块。Backlash 变体只是把其中两个观测项的函数换了(从 joint_pos 换成 joint_pos_rel_backlash),但维度完全一样。

这意味着 ONNX 导出和运行时加载不需要任何修改。你可以训练一个 Backlash 变体的策略,导出成 ONNX,直接扔给 Microduck 的运行时去跑。运行时不知道也不关心策略是在有齿隙还是无齿隙的环境里训练的。

这个设计选择背后的逻辑是:策略家族共享同一个 61 维观测契约,所有策略可以热切换。走路策略、跌倒恢复策略、坐下策略,都可以在任何时刻接管机器人。如果齿隙变体改了维度,这个热切换机制就断了。

训练效果:为什么要做 Backlash 变体

你可能会问,既然齿隙只有 ±1°,直接在域随机化里加个关节位置噪声不就行了?

不一样。域随机化加的噪声是随机的、无相关性的。但齿隙不是随机噪声——它是一个确定性的、方向相关的、负载相关的物理现象。电机正转时间隙在一个方向,反转时要先走过间隙才能驱动负载。这个特性会让关节在换方向时出现短暂的”失控”——电机在转,但负载没动。

这种动态行为用随机噪声模拟不了。只有物理建模才能让策略真正学会”换方向时先吃掉间隙再发力”的行为模式。

从训练成本看,Backlash 变体和基础任务用同样的训练配置——4096 个并行环境,PPO 算法,大约 1-2 小时出一个能用的步态策略。齿隙变体不需要额外训练时间,因为被动关节的计算开销很小。

Pollen Robotics 给每个主要任务都做了 Backlash 版本:Velocity、VelStand、StandUp、SitStand、GroundPick、BallKick、Roulade。这基本上是一个完整的 A/B 实验矩阵——你可以对比同一个任务在有无齿隙下的策略表现,看齿隙到底让 sim2real 差了多少。

对复刻者的实际建议

如果你在复刻 Microduck 或者做类似的小型双足机器人,齿隙建模是一个值得投入的环节。几条经验:

第一,先量清楚你的舵机齿隙有多大。XL330 的标称值是 ±1°,但实际值可能因批次和磨损有差异。用手指拧输出轴,用量角器粗测一下就行。

第二,齿隙建模的成本很低。MuJoCo 里加一个被动铰链,约束在 ±1° 范围内,改两个观测函数。代码量不大,但效果差异明显。

第三,如果你用的是其他型号舵机(比如飞特 HL 系列),齿隙特性可能不同。飞特舵机用的是金属齿轮,间隙通常比 XL330 小一些,但仍然存在。建模时用实测值。

第四,齿隙变体的训练不需要单独的观测维度或部署适配。这是一套”训练时多吃苦,部署时无缝用”的方案。

齿隙这件事看起来是个小参数,但它是 sim2real gap 里最容易被忽略的确定性误差源之一。Microduck 的做法给了一个很清晰的模板:物理建模 + 编码器视角观测 + 奖励函数适配,三步搞定。

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