BAM 执行器建模与辨识

本章是本书最重要的技术章节之一。目标:理解 BAM(Bandwidth-Aware Motor)模型为什么是 Sim2Real 成败的核心,学会用台架实测数据拟合 HD1910 的 BAM 参数,并正确实现齿隙模型。

10.1 为什么要给舵机"建模型"

真机用的是位置控制舵机,不是理想力矩源。舵机有扭矩上限、摩擦、带宽、齿隙、响应延迟。如果在仿真里把舵机当成"指哪打哪"的理想执行器,训练出的策略在真机上会:

  • 跟不上(带宽不足);
  • 抖(摩擦/齿隙);
  • 摔倒(扭矩不够)。

BAM 模型就是用来在仿真里精确模拟这些特性的执行器模型,是 Pollen 原版 Sim2Real 成功的核心。

10.2 BAM 模型的物理成分

BAM 模型包含五个关键参数:

参数物理含义影响
最大扭矩 torque_max舵机最大输出扭矩动作是否"没劲"
转子惯量 J_rotor电机+齿轮的转动惯量响应快慢
粘性摩擦 b_viscous与速度成正比阻尼、平滑
库仑摩擦 tau_coulomb与速度无关的恒定摩擦抖动、死区
闭环带宽 bandwidth舵机跟随指令的速度上限跟不上指令

此外还有齿隙 backlash——齿轮间隙造成的滞后。

10.3 原版 XL330 与飞特 HD1910 的差异

原版 microduck_rl 内置的是 Dynamixel XL330 专属参数(反电动势、电压-扭矩曲线、摩擦、齿隙、响应延迟经过台架标定)。而飞特 HD1910:

  • 扭矩常数、转子惯量不同;
  • 摩擦、带宽、齿隙特性不同;
  • 电压特性不同。

**直接用原版预训练模型跑飞特舵机,步态必然失效。**必须重新标定 BAM 参数并重训。

10.4 HD1910 参数拟合(实测 vs 估算)

第 8 章做了台架数据采集。这里用 scripts/calibration/hd1910_bench_data_process.py 拟合。

10.4.1 拟合脚本代码详解

#!/usr/bin/env python3
"""
scripts/calibration/hd1910_bench_data_process.py
从台架CSV拟合BAM模型参数(带宽、粘性摩擦、库仑摩擦、转子惯量)
"""
import argparse, json
import pandas as pd, numpy as np
from scipy.optimize import minimize

def bam_model_step(t, params, target):
    """简化一阶带宽模型"""
    w, b, tau_c, J = params
    return target * (1 - np.exp(-w * t))   # 一阶响应近似

def loss_fn(params, t, target, measured):
    pred = bam_model_step(t, params, target[0])
    return np.mean((pred - measured)**2)

def fit_bam(csv_path, out_json):
    df = pd.read_csv(csv_path)
    t = df["t"].values
    target = df["target_pos"].values
    actual = df["actual_pos"].values
    x0 = [22.0, 0.003, 0.012, 1.2e-6]     # 经验初值
    bounds = [(1,60), (1e-6,0.1), (1e-6,0.1), (1e-8,1e-4)]
    res = minimize(loss_fn, x0, args=(t, target, actual),
                   bounds=bounds, method="L-BFGS-B")
    bam_result = {
        "bandwidth_hz": float(res.x[0]),
        "b_viscous": float(res.x[1]),
        "tau_coulomb": float(res.x[2]),
        "J_rotor": float(res.x[3]),
        "servo_model": "HD1910", "voltage": 7.4,
        "unit": "SI(kg,m,s,rad)"
    }
    print(json.dumps(bam_result, indent=2))
    with open(out_json, "w", encoding="utf-8") as f:
        json.dump(bam_result, f, indent=2, ensure_ascii=False)

if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument("--csv", required=True)
    p.add_argument("--out", required=True)
    a = p.parse_args()
    fit_bam(a.csv, a.out)

10.4.2 拟合得到的参考参数(实测后替换)

hd1910_params = {
    "torque_max": 12.0,      # N·cm,HD1910 标称堵转扭矩@12V
    "rotor_inertia": 1.2e-6, # kg·m²
    "damping": 0.0018,       # 粘性摩擦
    "coulomb_friction": 0.009, # 库仑摩擦
    "bandwidth": 22,         # Hz,约20~25Hz
    "backlash": 0.0025,      # 齿隙(rad)
    "voltage": 12.0
}

10.4.3 修改 bam_actuator.py

在 software/training/mjlab/actuators/bam_actuator.py 的 Feetech 分支加入:

class FeetechHD1910BAMActuator(FrictionDRBamActuator):
    def __init__(self, **kwargs):
        super().__init__(
            torque_max=12.0, rotor_inertia=1.2e-6,
            damping=0.0018, coulomb_friction=0.009,
            bandwidth=22, backlash=0.0025, voltage=12.0,
            **kwargs
        )

10.5 齿隙模型:最容易出错的地方

关键设计:Pollen 原版的齿隙放在舵机输出端(编码器在齿隙之后)。这意味着:

  • 编码器读到的位置是"输出轴"位置,而不是电机轴位置;
  • 齿隙造成的位置误差会进入观测(反馈),影响策略。

原版实现了 BacklashEncoderBamActuator,这是 Sim2Real 成功最核心的设计。复刻时必须完整实现,否则来回摆动、抖动、跟随误差都会失配。

⚠️ 齿隙 backlash 是 Sim2Real 最大影响项之一。真机上来回摆动基本就是齿隙不准导致的。拟合后建议在域随机化里给它加扰动(见第 11 章)。

10.6 消融实验:估算 vs 实测

为证明实测拟合的价值,设计对照实验(写入书稿附录数据):

  • 方案 A:经验估算 BAM 参数训练;
  • 方案 B:台架实测拟合 BAM 参数训练;
  • 指标:真机站立稳定时长、行走成功率、跌倒恢复成功率。

预期结论:实测拟合参数显著提升真机迁移成功率,尤其在行走与粗糙地面场景。

10.7 工程踩坑实录

  • 齿隙放错位置:齿隙放在电机轴而非输出端,导致仿真观测与真机编码器读数不一致,真机来回摆。
  • 带宽拟合失真:台架采样率不足,阶跃响应太快,拟合带宽偏小,仿真里舵机"跟不上"。提高采样率(>500Hz)后重采重拟合。

10.8 本章小结

你现在能:

  • 理解 BAM 模型的五个核心参数;
  • 用台架数据拟合 HD1910 的 BAM 参数;
  • 正确实现齿隙模型(编码器在输出侧)。

下一步,给训练加上域随机化,让策略对参数误差鲁棒,进一步缩小 Sim2Real gap。