项目是一个小型双足机器人 Microduck,分为两个主要部分:
microduck/- 在 Raspberry Pi Zero 2 W 上运行的部署代码mjlab_microduck/- 基于 MuJoCo/MjLab 的强化学习训练环境
下面是对 Microduck 项目的完整解读。
Microduck 项目解读
一、项目概览
Microduck 是一个小型双足机器人项目,核心思路是:在仿真中用强化学习训练行走策略 → 导出为 ONNX 模型 → 部署到真实硬件(树莓派 + Dynamixel 舵机)上运行。
项目分为两大相对独立的子工程:
| 子工程 | 作用 | 运行平台 |
|---|---|---|
microduck/ |
部署运行时:读 IMU、读手柄/键盘、驱动舵机、加载 ONNX 策略 | Raspberry Pi Zero 2 W |
mjlab_microduck/ |
强化学习训练环境:训练并导出 walk.onnx |
开发机(GPU) |
两者通过 walk.onnx 模型文件衔接。
二、整体目录结构
Microduck/
├── README.md # 中英文双语装机/运行文档
├── bno08x_calibrate_ui.py # BNO08x IMU 校准工具
├── scripts/
│ └── imu_mujoco_orientation_viewer.py # IMU 方向可视化调试
│
├── microduck/ # ===== 部署代码(运行在 Pi 上)=====
│ ├── Makefile # rsync 同步 + SSH 运行/调试
│ ├── pyproject.toml # 依赖:numpy, onnxruntime, placo, rustypot, imu库
│ ├── systemd/
│ │ ├── microduck-gamepad.service # 无头手柄开机服务
│ │ └── install-gamepad-daemon.sh
│ └── src/
│ ├── main.py # 入口:开扭矩→回中位→启动调度器
│ ├── scheduler.py # 50Hz 控制主循环(核心)
│ ├── controller.py # ControllerProtocol 接口定义
│ ├── robot_controller.py # 真实硬件:Xl330PyController + IMU 封装
│ ├── observer.py # 每 tick 读取电机/IMU 状态 → RobotState
│ ├── imu_reader.py # BNO08x I2C 线程读取 + 四元数坐标变换
│ ├── constants.py # 舵机ID映射、中位、KP、过流保护参数
│ ├── scheduler.py
│ ├── observer.py
│ ├── voltage.py / imu.py / stop.py # 调试小工具
│ ├── agents/
│ │ └── walk.onnx # 训练好的行走策略(ONNX)
│ ├── moves/ # 动作策略(状态机:INACTIVE→STARTING→ACTIVE→STOPPING)
│ │ ├── move.py # Move 抽象基类 + MotorCommand
│ │ ├── walk.py # 行走:ONNX 推理 + 启动平滑过渡 + 摔倒检测
│ │ ├── squat.py # 下蹲
│ │ └── rotate_head.py # 转头
│ ├── input/ # 输入源抽象
│ │ ├── input_source.py # UserInput 数据结构 + 速度限幅
│ │ ├── keyboard_input.py
│ │ └── gamepad_input.py
│ ├── sim/ # 本地 MuJoCo 仿真(不部署)
│ │ ├── sim_main.py # 仿真入口,可注入延迟/CoM偏移
│ │ ├── mujoco_controller.py # 实现 ControllerProtocol 的仿真控制器
│ │ └── placo_controller.py
│ ├── debug/ # 绘图/方向校验脚本
│ └── model/
│ ├── mjcf/robot.xml # MuJoCo 机器人模型(含 STL 资产)
│ └── urdf/robot.urdf
│
└── mjlab_microduck/ # ===== 强化学习训练环境 =====
├── pyproject.toml # 依赖:mjlab, warp-lang, placo
└── src/mjlab_microduck/
├── agents/velocity.pt # 训练好的 PyTorch 策略权重
├── scripts/export_onnx.py # 导出 ONNX
└── tasks/
├── __init__.py
├── mdp.py # 自定义奖励/观测/课程函数
└── microduck_velocity_env_cfg.py # 完整训练配置(PPO)
三、部署端核心架构(microduck/src)
1. 控制主循环:Scheduler(50Hz)
scheduler.py 是整个系统的心脏,每个 tick(20ms)做:
- 读状态 :
Observer.read_state()→ 电机位置/速度/电流 + IMU(加速度/角速度/四元数) - 读输入 :键盘或手柄 →
UserInput(激活哪些动作 + 速度指令) - 动作状态机 :遍历已注册的 Move(walk/squat/head),按
INACTIVE → STARTING → ACTIVE → STOPPING状态调用,每个 Move 向同一个MotorCommand.target_angles字典写入目标角 - 过流保护:用 BAM 电机模型估算总电流,超过阈值(15A)连续 2 tick 就断扭矩
- 下发指令 :
sync_write_goal_position批量写入目标位置 - 频率保持:sleep 补足剩余时间
2. 控制器抽象:ControllerProtocol
controller.py 定义了一个 Protocol 接口,真实硬件 (RobotController)和仿真器 (MuJoCoController)都实现同一套接口,所以 scheduler.py 和 moves/* 的代码在真机和仿真中完全复用。
RobotController 内部:
- 用
rustypot.Xl330PyController通过 OpenRB-150 与 14 个 XL330 舵机通信 - 自动查找
/dev/serial/by-id/usb-ROBOTIS_OpenRB-150_* - 用
MOTOR_SIGN/MOTOR_OFFSET做模型坐标→硬件坐标转换(膝盖有 ±45° offset) - 启动
ThreadedIMUReader后台线程以 50Hz 读 BNO08x I2C
3. 行走策略:WalkMove
moves/walk.py 的核心逻辑:
- 加载 :
onnxruntime加载walk.onnx,从模型 metadata 读取joint_names和default_joint_pos(参考中位) - 启动过渡 :
on_start用 smoothstep 把关节从当前位置平滑插值到策略默认位姿(1.5s),同时把 KP 从 400 降到 125(更柔顺) - 每步推理 :构造观测向量 → ONNX 推理 → 输出动作 =
default_pose + action * scale- 观测 =
gyro(3) + projected_gravity(3) + 关节位置偏差(N) + 关节速度(N) + 上一步动作(N) + 速度指令(3) [+ 参考相位(2)]
- 观测 =
- 摔倒检测:body 系下 projected_gravity 的 z 分量 > -0.5 时跳过推理(机器人已倒地)
- 停止:恢复 KP 到 400,回到 INACTIVE
4. 输入系统
input_source.py 抽象出 InputSource 接口,键盘和手柄各自实现。速度统一在 scale_velocity() 中限幅(前进 0.7 m/s、后退 0.5、侧移 0.3、原地转向 3 rad/s、行进转向 1.5 rad/s)。
5. 安全机制
- PID 文件锁 :
/tmp/microduck_scheduler.pid防止两个控制进程抢占总线 - 过流保护:BAM 模型估算 + 实测 current 双路,断扭矩
- 摔倒检测:IMU 重力投影判断
- 停止标志 :
/tmp/microduck_scheduler.stop可由stop.py远程触发
四、训练端(mjlab_microduck)
基于 MjLab (MuJoCo 强化学习框架),用 PPO 训练速度跟踪行走策略:
- 场景 :平地,
timestep=0.005s,num_envs=1 - 观测:关节位置/速度、IMU 安装帧下的 projected_gravity 和角速度(带噪声和 0~1 步延迟,模拟真实 BNO080)
- 动作:关节位置指令(scale=1.0)
- 奖励:线/角速度跟踪、姿态 upright、空气时间(迈步)、脚滑惩罚、自碰撞惩罚、双脚距离惩罚、动作平滑
- 课程学习:3000 步后解除"禁止迈步"惩罚并扩大速度范围
- 网络 :Actor/Critic 均为
(512, 256, 128),ELU 激活,obs 归一化 - 导出 :
scripts/export_onnx.py把velocity.pt转成walk.onnx,并把joint_names、default_joint_pos写入模型 metadata
训练时的 IMU 安装四元数 BNO080_MOUNT_QUAT = (0.7071, 0, 0.7071, 0) 与部署端 constants.py 的 IMU_MOUNT_QUAT 完全一致,保证 sim-to-real 对齐。
五、关键设计亮点
- ControllerProtocol 解耦 :真机/仿真共用调度器和动作代码,
sim_main.py只换 controller 和 input source - ONNX metadata 传递参考位姿:训练时的默认关节角直接编码进模型,部署端无需硬编码
- 延迟建模 :训练中对 IMU/关节观测加延迟,仿真中也可注入
--delay-*参数,提高 sim-to-real 鲁棒性 - BAM 电机模型过流保护:在不增加总线读取的情况下,用位置误差+速度反推电流,提前于 BMS 跳闸切断
- 无头手柄服务:systemd 服务让机器人开机后可直接用手柄启动,手柄连接时自动关 Wi-Fi 提升 2.4GHz 蓝牙稳定性
六、数据流总结
IMU(BNO08x) ──I2C──► ThreadedIMUReader ─┐
├─► Observer.read_state() ─► RobotState
舵机(XL330) ──OpenRB-150──► RobotController ─┘ │
▼
键盘/手柄 ──► InputSource.read() ─► UserInput ──────► Observation
│
▼
Move.step() ─► ONNX(walk.onnx) ─► action
│
▼
MotorCommand.target_angles ─► sync_write_goal_position ─► 舵机