Microduck 仿真里的舵机不是 PD:BAM 怎么把 XL330 摩擦建模到电压级
做过 sim2real 的人都知道,策略在仿真里跑得再好看,上了真机该摔还是摔。问题出在哪?大部分人第一反应是接触模型不对、地面摩擦不对、传感器噪声没加对。但 Microduck 这个量级的机器人——25 厘米、800 克、14 个 XL330 舵机——sim2real gap 的大头根本不在这些地方。它在于舵机本身。
Microduck 的训练仓库用了一套叫 BAM 的执行器模型,全称 Better Actuator Models,由 Rhoban 实验室开发。这不是 MuJoCo 自带的 PD 控制器加个摩擦系数那么简单。它从电压控制律开始建模,一路覆盖反电动势、库仑摩擦、Stribeck 效应、负载相关摩擦,最后还套了一层域随机化。今天拆开看这套方案到底在做什么。
为什么 PD 模型不够用
MuJoCo 默认的执行器模型是位置控制 + 内置 PD + 库仑粘性摩擦。你给一个目标角度,PD 控制器算出力矩,加上一个跟速度成正比的粘性摩擦项和一个常数库仑摩擦项。对于大扭矩工业机械臂来说这套够用了——那些关节的摩擦相比输出力矩可以忽略。
但 XL330 不行。这是 Dynamixel 的微型舵机,行星齿轮减速,最大扭矩标称 1.5 N·m。在 Microduck 上它驱动的是一条 800 克的腿,每个关节的负载跟舵机本身的摩擦在同一数量级。你用 MuJoCo 的 PD 模型去仿真,策略学到的是”我给个目标角度,舵机就会精确到达”。上了真机,舵机说我到不了——因为电压在负载下掉了,齿轮摩擦吃掉了一部分力矩,Stribeck 效应让低速段摩擦比高速段更大。策略没见过这种情况,直接摔。
BAM 的出发点就是这个问题。它不假设舵机是一个理想的力矩源,而是把舵机当作一个电机加齿轮箱的物理系统来建模。
BAM M6:从电压到力矩的完整链路
BAM 为 XL330 提供了一个 M6 级别的模型。这里的”M6″指的是模型的精细程度,从最简单的 M1(理想 PD)到最复杂的 M6(电压控制 + 全摩擦模型)。Microduck 用的就是这个最高级别。
建模链路是这样的:
输入是电压,不是目标角度。BAM 接收的指令是固件层面的电压值,就像真舵机的 PWM 控制一样。电压乘以电机常数得到电机力矩,电机力矩经过齿轮箱减速放大后输出到负载端。
反电动势(back-EMF) 在这个链路里不可忽略。电机转动时会产生一个反向电压,削弱实际加在电机上的有效电压。转速越高反电动势越大,高速段的有效驱动力矩会下降。这在 MuJoCo 的 PD 模型里完全没有,但对 XL330 这种小电机影响明显——舵机快速摆动时你测到的力矩比静止时小,很大一部分原因是反电动势在吃电压。
摩擦模型是分层的。 BAM 不用一个常数摩擦系数,而是把摩擦拆成三个分量:
- 库仑摩擦:常数,跟运动方向有关。这是最基本的摩擦项。
- Stribeck 摩擦:低速段摩擦比高速段大。具体来说,在速度接近零时摩擦有一个峰值,随着速度增加摩擦先下降再趋于稳定。这个效应叫做 Stribeck,它导致舵机在低速段出现”粘滞”行为——你想让它微微动一下,它卡住不动,然后突然跳一下。
- 负载相关摩擦:齿轮箱的摩擦跟输出端受力有关。腿伸直时齿轮受力小,摩擦小;腿弯曲承重时齿轮受力大,摩擦大。BAM 把这个关系也建模了。
这三层加在一起,BAM 的 compute() 方法每个仿真步都在重新算实际输出力矩,而不是给一个目标角度然后假设到达。
FrictionDRBamActuator:在 BAM 上面加域随机化
BAM 给了一个精确的摩擦模型,但仿真到真机之间还是有参数不确定性。同一型号的 XL330,因为批次、润滑脂状态、磨损程度不同,摩擦特性会有差异。Microduck 的 FrictionDRBamActuator 就是解决这个问题的。
看代码结构。FrictionDRBamActuator 继承自 BAM 的 BamActuator,重写了 _compute_friction_budget 方法。关键就几行:
def _compute_friction_budget(self, motor_torque, external_torque, stribeck_coeff):
base = super()._compute_friction_budget(motor_torque, external_torque, stribeck_coeff)
fs = getattr(self, "friction_scale", None)
return base if fs is None else base * fs
friction_scale 是一个 (num_envs, 1) 的张量,每个并行环境有自己的缩放系数。每个 episode 开始时,randomize_bam_friction 事件给每个环境采一个新值。这意味着 4096 个环境里,每个环境的舵机摩擦都略有不同。策略必须在一个摩擦参数的分布上都有效,而不是过拟合到一组特定参数。
注意一个细节:BAM 的设计里 dof_frictionloss 被置零了,因为 BAM 自己在 compute() 里算摩擦。所以如果你用 mjlab 标准的 dr.dof_frictionloss 去做摩擦域随机化,在 BAM 模式下是个空操作。必须通过 friction_scale 这条路走。Microduck 的 AGENTS.md 里专门把这条标为不变量——搞错了就是静默失败,训练照跑但域随机化没生效。
电压域随机化:电池不是恒压源
除了摩擦,BAM 模型还允许对电压做域随机化。Microduck 的配置里有三个参数:
vin_range:每个环境的初始电池电压。锂电池满电和快没电时输出电压差不少,直接影响舵机最大力矩。vin_drop_gain_range:负载下的电压跌落。这个参数模拟的是电池内阻和线路压降——所有舵机同时发力时总线电压会掉。跌落量等于gain * sum(|tau|),力矩越大跌得越多。vin_min:电压跌落后的硬下限。防止仿真里出现负电压这种非物理情况。
这三个参数组合起来,模拟了 Microduck 上真实的供电行为。15 个舵机同时运动的瞬间,总线电压会瞬时下跌,每个舵机能拿到的实际电压比静态时低。策略如果在训练里没见过这种情况,上了真机做大幅动作时就会因为电压跌落导致力矩不足而摔倒。
BacklashEncoderBamActuator:齿隙里读编码器
Microduck 还有一个更精细的变体:BacklashEncoderBamActuator。这个类在 FrictionDRBamActuator 基础上又加了一层——让固件 PD 反馈读到的位置经过齿隙关节。
真舵机的磁性编码器装在输出端,在齿轮间隙的后面。当齿轮在死区里滑动时,编码器读到的位置不变——因为负载端没动。固件的 PD 控制环以为位置没变,不补偿。直到齿轮碰到另一侧,编码器突然跳变。
仿真里怎么复刻这个?BacklashEncoderBamActuator 的 get_command 方法把 cmd.pos 替换成 qpos[main] + qpos[backlash]。主关节角度加上齿隙关节角度,等于负载端实际角度。BAM 的电压控制律收到的是这个”看过齿隙”的位置,跟真机固件行为一致。
cmd.vel 故意不加齿隙偏移,因为速度在 BAM 里驱动反电动势和摩擦计算,这些是转子侧的物理量,不该经过齿隙。这个区分很关键——搞反了就会让摩擦模型看到虚假的速度信号。
这套方案的实际意义
Microduck 的 AGENTS.md 里有一句话说得很直白:”at this scale — tiny servos driving a ~800 g biped — actuator fidelity is most of the sim2real gap”。舵机建模精度占了 sim2real gap 的大部分。
这跟大型仿人机器人的情况完全不同。波士顿动力那种量级的机器人,关节力矩几十牛米,摩擦相比之下可以忽略,PD 模型就够用。但 Microduck 这个尺寸,舵机摩擦和负载力矩在同一量级,你不把摩擦建模到 Stribeck 和负载相关这个级别,策略学到的动作在真机上就是会变形。
BAM 的论文发表在 2025 年 ICRA 上,作者来自 Rhoban 实验室和波尔多大学。论文标题是”Extended Friction Models for the Physics Simulation of Servo Actuators”,核心贡献就是提出了一套从实测轨迹辨识摩擦模型的方法,并提供了常见舵机的已辨识参数库。Microduck 用的 XL330-M288-T 就在这个库里面,不需要自己跑辨识流程。
BAM 库目前支持 MuJoCo CPU 和 MuJoCo Warp 两种后端。Microduck 训练用 Warp(GPU 并行 4096 环境),部署推理时用 CPU。两套后端跑同一套摩擦模型,保证训练和推理的执行器物理一致。
小结
Microduck 的 sim2real 方案里,BAM 执行器模型是地基。不是域随机化、不是观测噪声、不是接触模型——是舵机本身。在 800 克的双足机器人上,XL330 的电压控制律、Stribeck 摩擦、负载相关摩擦、齿隙编码器反馈,每一项都对策略迁移有实质影响。BAM 把这些物理细节建模到仿真里,FrictionDRBamActuator 再在上面加一层参数不确定性,让策略不依赖特定的摩擦系数。这套方案的代价是计算量更大——每个仿真步都要算摩擦预算——但在 4096 并行环境的规模下完全可以接受。
对于做小型机器人 sim2real 的人来说,BAM 的思路值得参考:先把执行器建模到物理级,再谈域随机化。顺序反了就是在错误的基础上调参数,怎么调都调不到对。
数据来源
- GitHub: pollen-robotics/microduck_rl 仓库 README 及源码
- GitHub: Rhoban/bam 仓库 README
- BAM 论文: Duclusaud et al., “Extended Friction Models for the Physics Simulation of Servo Actuators”, ICRA 2025
- 源码: src/mjlab_microduck/actuator/friction_dr_bam.py
- 源码: src/mjlab_microduck/robot/microduck_constants.py
- AGENTS.md: pollen-robotics/microduck_rl 仓库
