Microduck 训练参数调整与新动作开发指南
给第一次接触机器人强化学习的人:先学会运行和观察,再改一个参数;先让仿真可靠,再谈新动作;先辨识一只舵机,再训练整机。
本文按本地源码逐项核对。训练仓基线为
microduck_rl的develop@29e887e。当前工作区另有一处本地实验改动:走路任务的air_time.threshold_min从上游值0.125 s改为0.04 s。本文会同时标出二者,不把本地实验值冒充上游默认值。
1. 新手应该分哪些阶段
不要从“写一个跳舞奖励函数”开始。推荐按下面六级推进。每一级都以前一级的产物为输入。
| 阶段 | 建议时间 | 只做什么 | 交付物 | 通过标准 | 暂时不要碰 |
|---|---|---|---|---|---|
| 0. 建立地图 | 半天 | 看懂三个仓库、任务列表和训练命令 | 一张自己的文件地图 | 能说清训练、部署、舵机模型各在哪 | 奖励权重、PPO 参数 |
| 1. 跑通现有策略 | 1 天 | 列任务、5 次迭代冒烟、回放已有 checkpoint | 命令和回放录像 | 能稳定复现,不出现 NaN/维度错误 | 新动作、真机上电 |
| 2. 看懂一次决策 | 1~2 天 | 理解 61 维观测、14 维动作、50 Hz 控制 | 手写一张输入输出表 | 能解释一个动作怎样变成关节目标 | 改观测维度 |
| 3. 单变量实验 | 2~4 天 | 每次只改一个命令、奖励或随机化参数 | A/B 实验记录 | 能从曲线和回放解释因果 | 同时改五六个参数 |
| 4. 新动作 | 1~2 周 | 从最接近的现有任务复制,先做走快,再做舞蹈 | 独立任务、测试、checkpoint | 冒烟、训练、回放、导出均通过 | 直接上真机 |
| 5. 换舵机 | 1~3 周 | 单舵机台架、BAM 拟合、协议适配、重训 | 参数 JSON、总线后端、新 ONNX | 仿真与台架响应接近 | 买齐 15 只后才第一次测 |
| 6. 真机闭环 | 持续 | 吊装、软垫、低增益、逐技能验收 | 测试记录与安全边界 | 温度、电流、时序、动作均达标 | 无保护直接落地跑 |
1.1 你现在最适合做什么
你已经有 Radxa Zero 3W,但还不需要让它承担训练。近期按这个顺序最稳:
- 在 Mac 上跑通任务列表、短训练和浏览器回放;
- 用现有走路任务做一次“只改一个参数”的 A/B;
- 学会读 observation、reward、curriculum 三张表;
- 做一个仿真专用的“走快”任务;
- 再做有节拍的摇摆/踏步舞蹈;
- Radxa 先用于 Linux、串口和单舵机台架,最后才加载 ONNX 控制整机。
阶段 3 以前,完全不需要买齐舵机,也不需要修改 Rust 总线代码。
2. 三个代码层分别负责什么
本项目不是一个仓库包办所有事情。
microduck_rl(训练)
机器人 MJCF + BAM 执行器 + 任务/奖励/PPO
│ 导出 ONNX
▼
microduck(运行时)
61 维观测 → ONNX → 14 维动作 → 安全处理 → 舵机总线
▲
│ 自定义舵机参数 JSON
bam(执行器辨识)
单舵机实测日志 → 拟合电机、摩擦、惯量、回差相关参数
2.1 训练仓 upstream/microduck_rl
| 位置 | 中文含义 | 什么时候看 |
|---|---|---|
src/mjlab_microduck/tasks/microduck_*_env_cfg.py |
每个技能的“实验配方” | 改命令、奖励、随机化、课程、PPO |
src/mjlab_microduck/tasks/mdp.py |
自定义观测、命令、奖励、事件、课程函数 | 新增行为逻辑 |
src/mjlab_microduck/tasks/__init__.py |
任务注册表 | 新增任务 ID |
src/mjlab_microduck/robot/microduck_constants.py |
HOME 姿态、BAM 舵机、机器人变体 | 换舵机、改站姿、选模型 |
src/mjlab_microduck/robot/microduck/ |
MJCF、网格、关节和碰撞 | 改机械结构、质量、惯量、限位 |
src/mjlab_microduck/actuator/friction_dr_bam.py |
BAM 摩擦随机化和回差编码器 | 理解执行器,不是普通 PD |
tests/test_*_cfg.py |
配方不变量测试 | 每次新增任务都应照着写 |
scripts/export.py |
checkpoint → ONNX | 正式导出,带上观测归一化 |
scripts/infer_policy.py |
CPU MuJoCo 部署预演 | 上真机前复核 |
2.2 真机运行时仓库根目录
| 位置 | 中文含义 | 换舵机时的作用 |
|---|---|---|
duck-control/src/io.rs |
硬件抽象接口 RobotIo |
新协议后端应实现这个接口 |
duck-control/src/bus.rs |
当前 Dynamixel XL330 总线实现 | 飞特等协议需要替换/新增实现 |
duck-control/src/model.rs |
15 个 ID、HOME、波特率、寄存器 | ID、零位、协议参数来源 |
duck-control/src/obs.rs |
61 维观测和 14→15 动作映射 | 训练与真机的最高风险契约 |
robotd/src/control.rs |
推理、动作缩放、低通、技能切换 | 部署动作处理必须与训练匹配 |
deploy/robotd.toml |
每台机器的运行参数模板 | 增益、动作尺度、安全阈值 |
2.3 BAM 仓库 bam
BAM 不是“给 MuJoCo 填一个最大扭矩”。它从位置误差经过固件位置环、电压、电流、反电动势,再计算力矩和摩擦。换舵机时,真正应该替换的是实测拟合出的 BAM 参数。
3. 先理解这些强化学习术语
| 源码名 | 新手解释 | 本项目例子 |
|---|---|---|
Environment / EnvCfg |
训练世界的完整规则 | 地面、机器人、传感器、奖励、终止 |
| Observation | 策略能看到的信息 | IMU、关节角、关节速度、上一动作、命令 |
| Action | 策略每 20 ms 输出的数字 | 14 个相对 HOME 的关节位置偏移 |
| Command | 人希望机器人完成什么 | 前进速度、转速、头部姿态、身体姿态 |
| Reward | 告诉策略什么行为更好 | 速度跟踪、直立、抬脚、平滑 |
| Penalty | 对坏行为扣分 | 滑脚、碰撞、动作突变 |
| Termination | 何时结束并重置当前 episode | NaN、超时、摔倒或动作完成 |
| Event | 启动、重置或定时发生的变化 | 随机质心、推一下机器人 |
| Domain randomization / DR | 让每个仿真世界略有不同 | 电压、摩擦、质量、编码器偏差 |
| Curriculum | 先易后难地调整训练规则 | 逐渐加大扰动、扩大头部命令 |
| PPO | 更新策略网络的方法 | 当前所有任务使用的算法 |
| Checkpoint | 可继续训练的 .pt 快照 |
model_12500.pt |
| ONNX | 真机只负责推理的模型文件 | 输入 61,输出 14 |
| Sim-to-real | 仿真策略迁移到真机 | 参数、时序、接口必须尽量一致 |
奖励不是动作脚本。你没有告诉鸭子“先抬左脚再抬右脚”,而是告诉它“跟上目标速度、别倒、脚别拖地、动作别太抖”,PPO 自己搜索出动作。
4. 61 维输入与 14 维输出是不能随便动的契约
当前所有 alpha 策略使用同一个布局:
| 索引 | 宽度 | 内容 |
|---|---|---|
| 0~2 | 3 | 躯干坐标系角速度 |
| 3~5 | 3 | 躯干坐标系投影重力 |
| 6~19 | 14 | 相对 HOME 的关节角,嘴巴除外 |
| 20~33 | 14 | 关节速度,嘴巴除外 |
| 34~47 | 14 | 上一次策略原始动作 |
| 48~50 | 3 | vx, vy, yaw_rate |
| 51~54 | 4 | neck_pitch, head_pitch, head_yaw, head_roll |
| 55~60 | 6 | 身体 x, y, z, roll, pitch, yaw 命令槽 |
动作顺序是:
0..4 左腿:hip_yaw, hip_roll, hip_pitch, knee, ankle
5..8 颈头:neck_pitch, head_pitch, head_yaw, head_roll
9..13 右腿:hip_yaw, hip_roll, hip_pitch, knee, ankle
真机有 15 只舵机;第 10 个物理槽位是嘴巴,策略不会输出它。duck-control/src/obs.rs 使用同一个映射完成读取和回填。
新技能如果暂时用不到某个命令槽,应保留并填零或极小“存活范围”,不要删除它。否则新 ONNX 不能与现有运行时热切换。
5. 第一次跑通:Mac 上只做这四件事
进入训练仓:
cd /Volumes/dev/dev/microduck/microduck-replica/upstream/microduck_rl
uv sync
uv run list-envs | rg MicroDuck
先做 5 次迭代的配置冒烟:
CUDA_VISIBLE_DEVICES="" uv run train Mjlab-Velocity-Flat-MicroDuck \
--env.scene.num-envs 64 \
--agent.max_iterations 5
已有 checkpoint 可用浏览器 viewer 回放:
CUDA_VISIBLE_DEVICES="" uv run play Mjlab-Velocity-Flat-MicroDuck \
--checkpoint-file logs/rsl_rl/velocity/2026-09-08_22-55-20_expE-gait-ramp/model_12500.pt \
--viewer viser \
--num-envs 1
macOS 上优先用 --viewer viser。正式的 4096 并行环境训练应使用 NVIDIA CUDA 机器;Radxa Zero 3W 用来推理,不适合承担 PPO 正式训练。
当前注册表里最值得新手先认识的任务:
| Task ID | 它实际训练什么 | 适合什么时候学 |
|---|---|---|
Mjlab-Velocity-Flat-MicroDuck |
平地速度跟踪和站立 | 第一个任务 |
Mjlab-Velocity-Rough-MicroDuck |
小台阶、网格和缓坡行走 | 平地稳定以后 |
Mjlab-VelStand-Flat-MicroDuck |
行走加跌倒恢复 | 想做统一走/起身策略时 |
Mjlab-StandUp-Flat-MicroDuck |
从坐姿温和站起,并控制身体姿态 | 学单次目标动作 |
Mjlab-SitStand-Flat-MicroDuck |
一个策略在坐姿和站姿间切换 | 学离散命令 |
Mjlab-GroundPick-Flat-MicroDuck |
分阶段低头触地再恢复 | 学周期相位动作 |
Mjlab-BallKick-Flat-MicroDuck |
保持支撑并踢球 | 学物体交互和不对称动作 |
Mjlab-Roulade-Flat-MicroDuck |
前滚、落地、再站稳 | 学动态动作和状态门控 |
Mjlab-Spin-Flat-MicroDuck |
穿轮滑鞋按相位旋转 | 学角速度目标和相位包络 |
带 -Backlash- 的任务用于测试串联齿轮间隙;-Rollers 和 Swizzle 属于轮滑模型。它们不是更高级的“升级版走路”,而是不同物理结构或鲁棒性变体。
阶段 1 验收
list-envs能看到Mjlab-Velocity-Flat-MicroDuck;- 5 次迭代没有维度、配置、NaN 错误;
- 回放能看到鸭子,能改变速度命令;
- 知道新日志写到
logs/rsl_rl/<experiment_name>/...。
6. 参数在哪里改:按目标查文件
6.1 想改站立姿态或零位
文件:src/mjlab_microduck/robot/microduck_constants.py
搜索:HOME_FRAME
当前关键值包括左右腿镜像的髋、膝、踝,以及颈部和头部。真机侧必须同步核对 duck-control/src/model.rs 的 DEFAULT_POSITION。两边不一致时,关节位置观测会永久带一个偏差,表现很像舵机不准或策略不稳。
原则:
- 只改仿真 HOME:策略会针对新的零位训练,但旧运行时会错误解释;
- 只改真机 HOME:ONNX 看到的 14 个关节角整体偏移;
- 换舵机花键方向或装配零位后,应先测零位,再同时维护两端;
- 不要用 HOME 去掩盖错误的舵机方向,方向应在总线/模型映射层处理清楚。
6.2 想改关节行程、质量、惯量、碰撞或脚底摩擦
文件:
robot/microduck/joints_properties.xml:关节默认属性、限位、原生 PD 备用配置;robot/microduck/robot_walk.xml:走路模型的 body、geom、mass、inertia、mesh;robot/microduck/robot_groundcontact.xml:允许身体接地的动作;robot/microduck/config_mjcf_*.json:Onshape 导出配置;microduck_constants.py:soft_joint_pos_limit_factor=0.9。
注意三种参数不要混淆:
机械关节 limit = 结构允许到哪里
soft limit factor 0.9 = 训练提前保留安全余量
action scale = 网络输出换算成多大角度偏移
换外形和质量后,应重新导出/计算质量与惯量。只换 STL 视觉网格但保留旧质量,画面会像新机器,动力学仍是旧机器。
6.3 想改舵机动力学
文件:src/mjlab_microduck/robot/microduck_constants.py
搜索:_BAM_ACTUATOR_KWARGS
当前训练配置:
| 参数 | 当前值 | 含义 |
|---|---|---|
motor_name |
xl330 |
使用 BAM 内置 XL330 参数族 |
model |
m6 |
使用含复杂摩擦项的 M6 模型 |
kp_fw |
200 | 模拟舵机固件位置 P 增益 |
vin_range |
6.5~8.2 V | 每个环境的电池电压 |
vin_drop_gain_range |
0.0~0.2 | 多舵机出力造成的压降 |
vin_min |
6.0 V | 压降后的下限 |
delay_min_lag/max_lag |
3~6 仿真步 | 命令延迟范围 |
target_names_expr |
排除 passive_* |
只驱动 14 个主动关节 |
kp_fw=200 与 XML 里的 kp=0.55 不是同一层,不能直接互换。当前实体由 BAM 接管;XML 中历史原生 PD 参数主要用于无 BAM 对照。
6.4 想改走路/跑步目标速度
文件:tasks/microduck_velocity_env_cfg.py
搜索:command.ranges
当前范围:
lin_vel_x = (-0.4, 0.4) # 前后 m/s
lin_vel_y = (-0.3, 0.3) # 横向 m/s
ang_vel_z = (-1.0, 1.0) # 转向 rad/s
TURN_IN_PLACE_FRACTION=0.15 会专门抽取原地转向样本。只扩大最大速度并不自动得到“跑步”:策略可能用更快的小碎步、拖脚或倾倒来换速度分。
推荐一次只把前进上限从 0.4 提到 0.5,保留后退范围不变,观察速度误差、摔倒率、腾空时间和脚高。确认稳定后再到 0.6。超过真实执行器能力的命令只会训练出奖励妥协,不会创造额外力矩。
6.5 想让脚抬高或步子更明显
同一走路配置中搜索:
air_time.weight:离地时间奖励强度;threshold_min/max:有效离地时间窗口;foot_clearance.target_height:摆动脚净空目标;foot_swing_height.target_height:摆动阶段脚高目标;pose.std_walking:偏离 HOME 的容许程度;action_rate_l2:动作变化惩罚。
当前 checkout 的脚高目标均为 0.02 m。air_time.threshold_min 的上游值是 0.125 s,你当前工作区的实验值是 0.04 s。后者意图是先奖励任何真实抬脚,等 Metrics/air_time_mean > 0.10 后再逐步收紧。
典型因果关系:
| 调整 | 可能得到 | 常见副作用 |
|---|---|---|
增大 air_time.weight |
更愿意迈步 | 跳步、站立时乱抬脚 |
降低 threshold_min |
更早获得学习信号 | 小碎步也能得分 |
增大 target_height |
脚抬得更高 | 耗能、落地冲击、步频下降 |
放宽腿部 std_walking |
更大动作空间 | 姿态怪、关节撞限位 |
强化 action_rate_l2 |
更平滑 | 太早强化会压死探索 |
6.6 想让动作更稳、更平滑
搜索:
upright.weight/std:躯干直立;body_ang_vel.weight:抑制 roll/pitch 晃动,不惩罚 yaw;angular_momentum.weight:抑制整体角动量;action_rate_l2:相邻动作差;joint_torque_rate_l2:相邻力矩差;cfg.curriculum["action_rate_weight"]:平滑惩罚何时增强。
走路任务不是一开始就把动作变化惩罚设为 -1.0,而是从 -0.1 分阶段收紧,到约 1500 次 PPO 迭代达到 -1.0。原因是早期需要探索,过早抑制动作会学成“站着最安全”。
6.7 想提高 sim-to-real 鲁棒性
走路配置顶部的开关与范围是第一入口:
| 随机项 | 当前状态 | 当前起始范围/设置 |
|---|---|---|
| 躯干质心 | 开 | ±3 mm,课程扩到 ±15 mm |
| 头部组件质心 | 开 | ±3 mm,课程扩到 ±10 mm |
| 质量+惯量 | 开 | 0.95~1.05 倍,一起缩放 |
| BAM 摩擦 | 开 | 0.9~1.1 倍 |
| armature | 开 | 0.9~1.1 倍 |
| 速度冲击 | 开 | 每 3~6 s,±0.3 m/s |
| IMU 安装误差 | 开 | 随机轴最大 6° |
| 编码器偏置 | 开 | ±0.015 rad |
| kP / kD | 关 | 范围存在但目前不生效 |
| 关节 damping | 关 | BAM 下这条路径本来也不是主要入口 |
| 初始机身倾斜 | 关 | pitch ±10°、roll ±5° 仅为预设 |
源码顶部 COM_RANDOMIZATION_RANGE 后的旧注释仍写“课程扩到 ±8 mm”,但本版本实际 com_range 课程的最后一档是 0.015 m,即 ±15 mm。训练行为由课程表决定,本文采用实际执行值;读参数时应检查定义和后续覆盖,不能只读顶部注释。
原则不是“随机越大越好”。DR 应覆盖实测误差,而不是覆盖想象中的最坏情况。随机范围过大时,策略会为了所有虚构机器人都不倒,学成紧张、保守、难以行走的折中动作。
6.8 想改传感器噪声和延迟
在走路配置的 Observations 段搜索 Unoise 和 delay_*:
| 观测 | 当前噪声 | 当前延迟 |
|---|---|---|
| 躯干角速度 | ±0.03 | 0~1 控制步 |
| 投影重力 | ±0.01 | 0~1 控制步 |
| 关节位置 | ±0.001 rad | 编码器偏置另算 |
| 关节速度 | ±0.25 rad/s | 固定 1 控制步 |
固定 IMU 安装偏角应在运行时标定并纠正;训练中的随机轴 6° 用来增强容忍度,不能代替已知偏差的校准。
6.9 想改课程学习节奏
文件还是任务配置,搜索 cfg.curriculum。
课程的 step 是环境步,不是 PPO 迭代号。当前每次迭代每环境采样 24 步,因此:
第 N 次 PPO 迭代对应 step = N × 24
课程函数位于 tasks/mdp.py,常用入口:
reward_weight:分阶段改奖励权重;standing_envs_curriculum:增加零速度站立样本;pose_command_range_curriculum:扩大姿态命令;com_range_curriculum:扩大质心随机化;- 各任务自己的 phase、reset mixture 和 disturbance 课程。
不要从 env.cfg 临时读写当前课程状态;项目约定由 manager 和事件参数维护。
6.10 想改 PPO 网络和训练超参数
每个任务文件最底部都有独立的 *RlCfg。走路是 MicroduckRlCfg:
| 参数 | 当前值 | 新手建议 |
|---|---|---|
| actor/critic 隐层 | 512, 256, 128 | 前四个阶段不要改 |
| 激活 | ELU | 不改 |
| observation normalization | 开 | 必须保留并正确导出 |
| PPO clip | 0.2 | 不改 |
| entropy | 0.01 | 探索不足/过强时才研究 |
| epochs / mini-batches | 5 / 4 | 不改 |
| learning rate | 1e-3 adaptive | 不改 |
| gamma / lambda | 0.99 / 0.95 | 不改 |
| desired KL | 0.01 | 不改 |
| rollout steps | 24 | 课程换算依赖它 |
| save interval | 250 | 可按磁盘和观察频率改 |
| max iterations | 50,000 | 是上限,不代表一定要跑满 |
新手最常见误区是行为不好就先调学习率。多数情况下,问题其实在目标不可达、奖励漏洞、重置分布或仿真物理。
7. 怎样做第一个单变量 A/B 实验
以“鸭子只会贴地碎步”为例。
7.1 写下假设
现象:air_time_mean 长期约 0.04 s,air_time reward 近零。
假设:0.125 s 的奖励门槛太高,策略从未收到抬脚正反馈。
只改:threshold_min 0.125 → 0.04。
不改:速度范围、脚高目标、PPO、DR、随机种子策略。
7.2 每个实验必须记录
复制 rl-series/experiments/TEMPLATE.md,至少填:
- git commit 与
git diff; - task ID、环境数、最大迭代、随机种子;
- 唯一变量;
- baseline 和 candidate 的 checkpoint;
- 同一组命令下的回放;
- 结论、是否回滚、下一步。
7.3 优先观察的指标
| 指标 | 说明 | 期望趋势 |
|---|---|---|
| episode length | 是否经常早早摔倒 | 上升后稳定 |
| velocity tracking reward/error | 是否真跟上命令 | reward 上升、error 下降 |
air_time_mean |
是否真的迈步 | 脱离零附近,但不是越大越好 |
| foot height/clearance | 是否拖脚 | 接近目标,不持续过冲 |
| upright / tilt | 躯干是否稳定 | 稳定,不靠大幅前倾作弊 |
| action std / entropy | 是否仍在探索 | 逐步下降,不能突然塌缩 |
| penalty reward | 惩罚符号是否正确 | 所有 penalty 贡献应 ≤ 0 |
最后一条非常重要:项目里有两类惩罚函数。基础 cost 常返回正数,需要负权重;名字含 *_penalty、*_l1 的自取负函数可能返回负数,需要正权重。最可靠的检查不是看名字,而是看日志中每个惩罚贡献是否始终不大于零。
8. 从走路一步一步做出跑步
当前任务列表没有独立的 Running 环境。microduck_velocity_env_cfg.py 虽然给 std_running 赋值,但它目前等于 std_walking,不能把这理解为已有成熟跑步策略。
8.1 第一阶段:先做“走快”而不是“跑”
以 make_microduck_velocity_env_cfg 为模板,建立独立任务,保持 61/14 契约和全部 DR/观测设置。
只调整:
lin_vel_x正向上限:0.4 → 0.5 m/s;- 训练样本增加中高速 bucket,避免均匀采样导致高速样本稀少;
- 回放固定测 0、0.2、0.4、0.5 m/s;
- 保持脚高、air time、平滑项不变,先测策略自然反应。
通过标准:0.5 m/s 跟踪误差下降,同时低速和站立能力没有明显退化。
8.2 第二阶段:让“跑态”有可辨认的物理定义
跑步不只是速度更大。需要先决定你接受哪一种定义:
- 快速步行:始终至少一只脚接地;
- 跑步:允许短暂双脚离地;
- 跳跃式跑步:有明显腾空和更高冲击。
Microduck 尺寸小、舵机和落地冲击敏感,建议先做快速步行,再谨慎开放短腾空。
可能需要的奖励变化:
- 速度高时提高有效 air-time 窗口,低速时仍保持站立/走路规则;
- 使用速度条件化奖励,不能让零命令环境也通过跳跃刷分;
- 适量提高摆动脚高度;
- 检查支撑脚滑动和落地冲击;
- 平滑和力矩变化惩罚在探索后期逐渐增强。
不要直接删除 upright 或大幅减弱动作正则。先在小幅变化中确认机器的可实现上限。
8.3 第三阶段:做课程
推荐课程:
0~500 iter 主要训练 0~0.4 m/s,复制已验证步态
500~1200 加入 0.4~0.5 m/s
1200~2500 加入 0.5~0.6 m/s,并加强脚高/离地目标
2500 以后 加强摩擦、电压、推扰动与动作平滑
数字只是实验起点,不是已验证的官方跑步配方。是否推进下一阶段,应由速度误差、摔倒率和回放共同决定。
8.4 为什么建议单独任务
对新手,先用独立 microduck_running_env_cfg.py 更容易解释因果:日志目录独立、速度分布明确、不会破坏已经能走的基线。等走和跑都稳定后,再研究一个由速度命令自动切换步态的统一策略。
9. 从简单舞蹈一步一步做复杂动作
舞蹈有两条路线。
9.1 路线 A:关键帧播放,不用 RL
如果动作完全固定、地面和负载也固定,先用关键帧插值。这是验证机械限位、舵机方向和节拍的最快方法。强化学习只在需要平衡、抗扰、落地或适应误差时增加价值。
9.2 路线 B:节拍/相位命令 + RL 平衡
这是推荐路线:人定义舞蹈节奏和目标姿态,RL 学习怎样在真实动力学下跟随目标而不摔倒。
现有项目已有可借用的结构:
SitStandCommandCfg:离散姿态命令;GroundPickPhaseCommandCfg:用周期相位驱动动作;spin_phase_from_command、spin_rate_by_phase:相位包络;phase_pose_track/phase_pose_track_l1:按相位跟踪目标姿态;microduck_roulade_env_cfg.py:动态动作的连续进度、落地门控和课程。
9.3 第一个舞蹈只做四拍
建议动作:站稳 → 左摆 → 右摆 → 回中,同时轻微点头。
- 用相位
φ∈[0,1)表示一个周期; - 使用
sin(2πφ), cos(2πφ)两个槽表达相位,避免0.99→0.00的不连续; - 定义 4~8 个关键帧,再做平滑插值;
- 奖励目标关节姿态、直立和双脚接地;
- 惩罚滑脚、自碰撞、动作突变和过大力矩;
- 先固定慢节拍,再随机节拍;
- 最后再加外力和物理随机化。
若要保持现有 61 维接口,可像 ground-pick/spin 一样复用某两个命令槽编码相位,但真机运行时必须用完全相同的编码。不要在仿真里用真实 phase,部署时却只给一个按钮状态。
9.4 舞蹈课程建议
阶段 A 固定 6 s 周期,只跟踪腿部小幅摆动
阶段 B 加头部关键帧,保持双脚着地
阶段 C 周期随机到 4~8 s
阶段 D 扩大摆幅,加入踏步
阶段 E 加电压、摩擦、质量和轻微推扰动
阶段 F 随机起始相位,测试中途接管和循环衔接
9.5 舞蹈的常见奖励漏洞
- 只有姿态奖励:策略可能倒在地上摆关节;
- 只有节拍奖励:策略可能用抖动高频穿过目标;
- 完成奖励每步都发:越早到终点越能持续刷分;
- 相位在 1→0 跳变:观测和目标突然不连续;
- 强制所有关节精确跟踪:没有余量用于保持平衡;
- 一开始就加很强平滑惩罚:策略不敢探索动作。
解决方式是连续相位、状态门控、一次性或限速的成功奖励,以及给平衡修正保留自由度。
10. 新动作应该复制哪个模板
| 新动作 | 最接近模板 | 原因 |
|---|---|---|
| 走快、跑、横移 | microduck_velocity_env_cfg.py |
连续速度命令和成熟步态 DR |
| 蹲下再站起、两种舞姿切换 | microduck_sitstand_env_cfg.py |
一个策略处理两种离散状态 |
| 固定起点到固定终点的鞠躬 | microduck_standup_env_cfg.py |
单次动作、终点姿态清晰 |
| 踢球 | microduck_ball_kick_env_cfg.py |
物体交互、支撑脚与目标速度 |
| 连续旋转 | microduck_spin_env_cfg.py |
相位包络和角速度目标 |
| 翻滚、跳起、落地动作 | microduck_roulade_env_cfg.py |
连续进度、阶段门控、落地奖励 |
| 轮滑动作 | microduck_velocity_rollers_env_cfg.py |
被动轮关节和轮地摩擦 |
新增任务的源码工作流是:
- 复制最接近的 cfg,而不是从空文件写;
- 在文件顶部集中放
ENABLE_*和行为常量; - 保留 61 维观测、14 维动作、BAM 和 NaN 防护;
- 只留下与动作目标不冲突的基础奖励;
- 在
tasks/mdp.py添加必要的纯函数和有状态门控; - 在
tasks/__init__.py注册新的 task ID; - 给新任务单独的
experiment_name; - 在
tests/添加配置和函数测试; - 64 环境 × 5 迭代冒烟;
- 回放确认物理和奖励,再跑正式训练。
对不对称动作,例如只踢右脚、只向右旋转,默认不要启用左右镜像 symmetry。对称动作也应先关闭,行为稳定后再做有对照的开启实验。
11. 换成其他厂家舵机:完整参数传播链
其他厂家和类型当然可以用来学习和调试,但分两种目标:
- 学控制/GPIO/PWM:SG90、串口舵机都可以,适合单舵机练习;
- 复刻整只 Microduck:必须同时满足重量、尺寸、力矩、速度、回差、协议、供电和散热,不能只看堵转力矩。
换舵机应按四层处理。
11.1 机械层
记录:尺寸、重量、安装孔、输出轴、舵盘、可用角度、零位方向。
要影响:
- CAD/打印件舵机仓;
- MJCF mesh 和关节 axis/position;
- 各 body 的 mass、inertia、CoM;
- 关节 range 和碰撞几何;
- HOME/真机默认姿态。
11.2 电气层
记录:额定/允许电压、空载/堵转电流、线序、总线电平、温度保护。
要影响:
- 电池和降压方案;
- BAM 的
vin_range、vin_min、压降范围; - 真机电压保护和温度阈值;
- 动作峰值、电流预算和同时启动策略。
11.3 通信层
当前 duck-control/src/bus.rs 明确使用 rustypot::servo::dynamixel::xl330::Xl330Controller,协议 V2、1 Mbps,并读取 XL330 的寄存器地址。换成飞特等协议时,不能只改型号字符串。
推荐保留上层 RobotIo:
RobotIo::read() → positions / velocities / currents / IMU
RobotIo::write() → 15 个位置目标
RobotIo::set_gain() → 全关节增益
RobotIo::set_torque() → 扭矩开关
RobotIo::slow_sensors() → 电压与每舵机温度
为新协议写一个新的后端,实现同样的单位和顺序。需要逐项解决:
- sync read/write 是否支持;
- 位置、速度、电流的原始单位换算;
- 方向和零点;
- ID 与广播规则;
- 寄存器地址、返回延迟、超时;
- IMU 板是否还能与舵机共用一条总线。
若新舵机协议无法与当前 Dynamixel IMU 板共总线,就要分总线或改 imu_to_dxl 固件。这是系统结构变化,不是一个配置项。
11.4 动力学层:先辨识一只,再买齐
当前 BAM 参数库有 XL330 和若干其他型号,但没有本项目计划使用的 HL-1910 参数。正确路径是实测拟合一个自定义 JSON。
台架至少记录:
- 多个电压;
- 多个固件 P 增益;
- 空载和带已知臂长/负载;
- 正向与反向;
- 慢速摩擦、阶跃、正弦和抬落轨迹;
- 温度从冷机到热机的变化;
- 指令到运动的延迟;
- 正反向趋近同一点的回差。
BAM 仓库给出的流程是:
# 1. 用对应厂家的 record 模块采集原始日志
uv run python -m bam.<manufacturer>.all_record \
--port <串口> --motor <型号> \
--mass <负载kg> --arm-mass <摆臂kg> --length <臂长m> \
--vin <电压> --logdir data_raw
# 2. 重采样/清洗为 5 ms 时间步
uv run python -m bam.process --raw data_raw --logdir data_processed --dt 0.005
# 3. 拟合;建议 m1~m6 都做并留一个 kP 作验证集
uv run python -m bam.fit \
--actuator <型号名> --model m6 \
--logdir data_processed --validation_kp <留出的kP> \
--output params/<型号名>/m6.json
# 4. 对比实测与仿真
uv run python -m bam.plot \
--actuator <型号名> --logdir data_processed --sim \
--params params/<型号名>/m6.json
若 BAM 没有该厂家的采集模块,要先照已有 manufacturer 包增加协议读写和轨迹记录器。不要拿另一款舵机的 JSON 改个文件名。
11.5 把自定义舵机模型接到 Microduck
固定版本的 BamActuatorCfg 支持 json_path。未来修改训练配置时,概念上从:
motor_name="xl330",
model="m6",
换成:
json_path="/绝对路径/params/hl1910/m6.json",
json_path 与 motor_name + model 二选一,不能同时填写。其余这些仍需按实测决定:
kp_fw:新舵机真实运行增益;vin_range/vin_min:真实供电窗口;vin_drop_gain_range:电池、线束和总电流造成的压降;delay_min_lag/max_lag:实测端到端延迟;- BAM friction DR:多只舵机、冷热状态的离散度;
- backlash 变体:实测总死区,不是厂商宣传值。
11.6 为什么换舵机后要重训
策略学到的是“给出一个关节偏移后,这台具体机器会怎样动”。速度、摩擦、延迟、回差和电压响应一变,同一动作会产生不同的落脚时间和姿态。旧 ONNX 可以用作仿真退化基线,但不能默认直接上新硬件。
推荐顺序:走路 flat → 站立/恢复 → 坐站 → 其他技能 → rough/backlash。没有必要第一天重训全部策略。
12. 仿真参数、运行时参数必须成套记录
运行时 deploy/robotd.toml 的关键默认/说明值:
| 参数 | 当前值/默认 | 用途 |
|---|---|---|
bus.port |
/dev/ttyS2 |
Radxa 舵机/IMU 总线 |
control.hz |
50 Hz | 必须与训练时间尺度一致 |
policy.action_scale |
walk 0.9 | 原始动作 → 关节偏移 |
policy.standing_action_scale |
1.0 | 站立策略动作尺度 |
policy.gain |
200 | 真机位置 P 增益 |
standing_gain_ratio |
0.8 | 站立相对增益 |
head_lowpass |
0.5 | 头部目标低通默认 |
legs_lowpass |
0.7 | 腿部目标低通默认 |
voltage_adapt |
false | 可选电压动作补偿 |
deadman_ms |
500 ms | 命令失联后停止速度 |
gain_limp |
50 | 摔倒软化增益 |
battery_empty_shutdown |
true | 低电自动坐下/关机 |
12.1 当前低通配置存在版本冲突
这是不能省略的事实:
- 当前训练仓
AGENTS.md说策略训练时不加动作低通,除非训练和运行时成套验证; - 当前运行时配置仍默认头部
0.5、腿部0.7,并称这些是 alpha 策略训练时的值; - 当前
microduck_velocity_env_cfg.py中没有对应的ACTION_LOW_PASS_*实现。
因此不能笼统写“必须开”或“必须关”。正确做法是把以下三件东西视为一个版本集合:
训练 commit + checkpoint/ONNX + runtime 动作处理配置
对自己新训练的策略,先在 CPU 部署预演和吊装真机上做四组 A/B:无低通、仅头部、仅腿部、0.5/0.7;记录速度误差、相位延迟、跌倒、峰值电流和抖动,再锁定配置。
13. 训练、导出和部署的质量门
13.1 每次配置改动
uv run --with pytest pytest tests/
CUDA_VISIBLE_DEVICES="" uv run train <TASK_ID> \
--env.scene.num-envs 64 \
--agent.max_iterations 5
检查:task 能创建、61/14 不变、奖励均为有限值、无 NaN、课程键名存在。
13.2 正式训练
uv run train <TASK_ID> --env.scene.num-envs 4096
简单单次动作通常先预留约 1000 次迭代看能否发现;步态和恢复通常需要 4000~6000 甚至更多。max_iterations 是停止上限,不能代替收敛判断。
13.3 回放
uv run play <TASK_ID> \
--checkpoint-file <model_N.pt> \
--viewer viser --num-envs 1
不要只挑最好看的命令。固定测试矩阵至少包括:零命令、低速、最高训练速度、转向、命令突变、不同随机种子、低电压、摩擦上下界。
13.4 正式导出
uv run scripts/export.py <TASK_ID> \
--checkpoint-file <model_N.pt> \
--onnx-file <out.onnx> \
--viewer viser
必须走项目导出脚本,因为 actor 开启了 observation normalization,导出脚本会把归一化器烘进 ONNX。手工只导网络时,仿真 play 可能正常,真机却会收到未归一化输入。
13.5 CPU 部署预演
uv run scripts/infer_policy.py --walking <out.onnx>
它用 50 Hz 控制、BAM 执行器复演部署路径。还应分别测试 --no-bam、电压、增益、延迟和 action scale 参数,确认模型不是只在单一设定下碰巧工作。
13.6 真机测试金字塔
L1 单舵机台架
L2 15 舵机总线但不装负载
L3 整机悬空、低增益、人工急停
L4 吊装带少量负载
L5 软垫地面、零命令站立
L6 低速直行和停止
L7 转向、最大训练速度
L8 单次技能和连续动作
任何一级出现方向错误、撞限位、过流、过热、通信丢包或异常振荡,都回到前一级,不靠安全代码硬扛。
14. 出现问题时先查哪里
| 现象 | 优先检查 | 不要先做什么 |
|---|---|---|
| 训练完全不动 | 目标是否可达、正奖励是否有梯度、平滑惩罚是否过强 | 调大学习率 |
| 站着刷分不走 | 速度奖励/命令采样、高速样本占比、奖励门控 | 再加强站立奖励 |
| 只贴地碎步 | air time 门槛、脚高、摆动奖励是否能收到 | 同时改所有步态项 |
| 越训越抖 | action/torque rate 课程、奖励 jackpot、观测延迟 | 只在部署端加很重滤波 |
| 仿真很好,真机摔 | HOME、关节顺序、方向、动作尺度、BAM、电压、时延、低通 | 继续训练更多迭代 |
| 一换电池状态就变 | vin_range、压降、运行时电压补偿 |
固定成满电仿真 |
| 个别舵机总跑偏 | 零点、方向、编码器偏置、回差、ID 映射 | 放大整体 DR 掩盖坏舵机 |
| 动作撞限位 | XML range、soft limit、HOME、action scale | 只靠真机裁剪 |
| reward 数值很好但动作怪 | 奖励漏洞、重复发成功奖、缺少状态门控 | 相信总 reward 一个数 |
| 导出后才坏 | ONNX 是否含 normalizer、输入 61/输出 14 | 手写 checkpoint 转 ONNX |
15. 推荐的新手七次实验
每次只改一个变量,并保留 baseline。
- 原配置 64 环境 × 5 iter:学会启动和找日志;
- 原配置 64 环境 × 100 iter:看 reward、entropy 和 episode length;
- 固定 checkpoint 回放多组速度:学会行为评估;
- A/B 一个
air_time.threshold_min:学会从现象提出假设; - A/B 一个前进速度上限:理解命令分布与能力边界;
- 建立“走快”独立任务:学会复制模板、注册和写测试;
- 建立四拍左右摇摆任务:学会相位、姿态跟踪和稳定门控。
完成第 7 项以后,再开始 HL-1910 单舵机辨识。这样你已经知道拟合出的电压、延迟、摩擦和回差最终会进入哪里,不会把台架实验做成一堆无法用于训练的数据。
16. 一页参数索引
| 我想改…… | 文件 | 搜索词 |
|---|---|---|
| 初始站姿/零位 | robot/microduck_constants.py |
HOME_FRAME |
| 真机零位与 ID | duck-control/src/model.rs |
DEFAULT_POSITION, JOINT_IDS |
| 舵机模型 | robot/microduck_constants.py |
_BAM_ACTUATOR_KWARGS |
| 自定义舵机 JSON | 同上 | json_path(替代 motor_name/model) |
| 关节限位/原生 PD | robot/microduck/joints_properties.xml |
range, chosen_actuator |
| 质量、惯量、几何 | robot/microduck/robot_*.xml |
mass, inertia, geom |
| 走路速度 | tasks/microduck_velocity_env_cfg.py |
command.ranges |
| 抬脚 | 同上 | air_time, foot_clearance, foot_swing_height |
| 稳定和平滑 | 同上 | upright, action_rate_l2, angular_momentum |
| DR 开关与范围 | 同上顶部和 Events 段 | ENABLE_, RANDOMIZATION_RANGE |
| 传感器噪声 | 同上 Observations 段 | Unoise, delay_min_lag |
| 课程 | 同上末段 | cfg.curriculum |
| PPO | 任务文件最底部 | *RlCfg |
| 奖励/事件实现 | tasks/mdp.py |
函数名 |
| 新任务注册 | tasks/__init__.py |
register_mjlab_task |
| 真机协议 | duck-control/src/bus.rs |
Xl330Controller, RobotIo |
| 61/14 布局 | duck-control/src/obs.rs |
OBS_LEN, ACTION_LEN |
| 真机动作尺度/低通 | deploy/robotd.toml |
action_scale, lowpass, gain |
| 真机安全 | 同上 | [safety] |
常用搜索命令:
rg -n 'HOME_FRAME|_BAM_ACTUATOR_KWARGS' src/mjlab_microduck/robot
rg -n 'command.ranges|cfg.rewards|cfg.curriculum' src/mjlab_microduck/tasks
rg -n 'OBS_LEN|ACTION_LEN|DEFAULT_POSITION|JOINT_IDS' /Volumes/dev/dev/microduck/duck-control/src
rg -n 'action_scale|lowpass|gain|deadman' /Volumes/dev/dev/microduck/deploy/robotd.toml
git diff -- src/mjlab_microduck/tasks
17. 最重要的五条原则
- 先复现,再改动;一次只改一个变量。
- 先验证物理目标可达,再开始长训练。
- 奖励好看不等于动作正确,曲线、状态指标和回放缺一不可。
- 训练 commit、ONNX 和运行时处理必须作为同一个版本集合。
- 换舵机先测一只,真机先吊装;任何安全保护都不能替代逐级验证。
配套阅读: