做这个微小型双足鸭形机器人之前,我其实先问了自己一个问题:市面上开源的四足、双足平台已经不少了,为什么还要折腾一只“鸭子”?后来做完整套系统我算是想明白了——双足机器人最难的不是“能走”,而是“在极小体积、极低成本约束下,依然走得稳、走得自然”。鸭形外观天然自带低重心和宽脚掌的仿生优势,加上强化学习来学习步态,整条技术链路压缩到桌面级别之后,反而比大型机器人更有挑战也更有意思。这个项目本质上是把强化学习、嵌入式控制、开源硬件揉在一起,做成一个能稳定行走、能抗扰动、还能二次开发的小家伙。无论你是想入门双足控制、想研究学习运动策略,还是单纯想找一个能动手玩起来的开源机器人平台,这套东西都挺值得拆开看一看。
我在做这个项目时走了不少弯路,从硬件构型到仿真训练再到真机迁移,几乎每个环节都有坑。下面把这些经验和细节完整写出来,基本按我实际推进的顺序来,方便你直接复用。
1. 项目整体设计与思路拆解
1.1 一个“鸭子”为什么要用强化学习?
先解决一个看起来有点蠢的问题:做双足机器人,用传统控制不好吗?ZMP、倒立摆、步态规划这些经典方法在大型人形机器人上已经很成熟了,为什么非要用强化学习?
答案是:传统控制方法依赖精确动力学模型。微小双足系统体积小、关节间隙大、结构柔性明显,你建出来的模型和实物之间的偏差可能超过模型本身的精度。我在早期尝试过用基于线性倒立摆的步态规划,仿真里走得还行,一上实物就原地抖动——因为模型里没算进去那些细微的齿轮间隙和线缆阻力。强化学习的好处是它不显式建模这些物理量,而是通过大量交互数据让策略隐式学到系统的真实动力学,策略可以直接输出关节角度序列,天然容错。
再说为什么是鸭形。双足机器人的稳定性和质心高度直接相关,鸭子矮胖、脚掌宽、重心低,这种构型天然降低了平衡难度,让学习更容易收敛。同时鸭形外壳给了传感器和电池足够的布置空间,外观也更有亲和力,适合放在桌面或者展台上跑。这个定位决定了整个项目的调性:不是为了追热门,而是每一项设计都有物理依据。
1.2 系统整体架构与模块划分
整个系统拆成五层来设计,每一层都可以独立替换和调试:
| 层级 | 内容 | 职责 |
|---|---|---|
| 仿真层 | 基于MuJoCo搭建鸭形机器人模型 | 提供强化学习训练环境,输出物理反馈 |
| 算法层 | PPO + 域随机化训练策略 | 产出关节角度控制策略 |
| 部署层 | NCNN/TFLite量化模型,Python推理 | 在板端运行策略网络 |
| 执行层 | STM32双闭环关节控制 | 接收角度指令,稳定跟踪目标角度 |
| 交互层 | 手机App/PC上位机,遥控与调参 | 实时监控状态、下发模式切换指令 |
这套分层里最关键的一个设计决策是:强化学习策略不直接输出PWM,而是输出目标关节角,交给底层PID去跟踪。这么做的好处是把高频振动交给传统控制器处理,策略只负责低频步态决策——一个典型的双层控制架构。
2. 硬件平台解析与选型心路
2.1 五连杆腿结构与小体积构型
鸭形机器人每条腿我采用了经典的五连杆机构,也就是两个主动自由度加上侧面连杆形成的闭环结构。这种结构的优势在于:电机可以直接装在躯干附近,腿部末端(脚掌)只承受力不承受电机重量,大幅降低腿部转动惯量。惯量小意味着关节反转速度快,策略网络输出的高频摆动指令能够被有效执行,而不是被机械滞后吃掉。
具体构型参数我调了三版才定下来:
- 大腿长度55mm,小腿长度60mm,脚掌长70mm、宽40mm;
- 两条腿在髋关节处间距为90mm,保证站立时底支撑面足够大;
- 每个髋关节拥有横滚和俯仰两个自由度,共4自由度。
这条腿构型的关键是闭环连杆的比例。大腿连杆和小腿连杆比例如果小于0.7,脚掌轨迹会出现明显尖角,导致步态卡顿。实测下来55:60这个比例最顺,脚掌能走出平滑的摆线轨迹。如果比例再大,又会牺牲抬起高度,越障能力变差。
2.2 驱动与感知硬件的实际选型
硬件选型上,我踩过的最大坑就是盲目追求扭矩数字。第一版电机我选了大扭矩金属齿轮舵机,扭矩确实足够,但重量直接超标——整机超过800g,机械结构自己就把电机压死了。后来换成了微型数字舵机,单关节峰值扭矩约1.8kg・cm,重量11g,整机控制在420g才跑通。
实际选型建议参考这张表:
| 部件 | 选型建议 | 说明 |
|---|---|---|
| 关节电机 | 9g级微型数字舵机,金属输出齿轮 | 金属齿轮耐磨,舵机内部带轴承更好 |
| 主控 | ESP32-S3或树莓派Pico W | 需要有足够的定时器资源控制多路舵机 |
| 感知模块 | MPU6050六轴IMU | 用于姿态估计和摔倒检测 |
| 电源 | 2S 300mAh锂电池加5V稳压 | 动态电流大,稳压模块必须有余量 |
| 外壳 | PETG 3D打印,壁厚2mm | 兼顾轻量与抗摔 |
IMU要不要装这件事我犹豫过。纯强化学习策略理论上可以做到用关节编码器信息行走,但真机运行时,策略需要知道自己摔没摔、歪没歪,IMU提供的姿态信息可以让训练里的状态空间和实物状态空间对齐。我最终加了MPU6050,跑DMP库读四元数,效果稳定。
2.3 装配与重心分布的经验
硬件装完只是第一步,重心分布才是真正决定成败的部分。鸭形机器人最忌讳“头重脚轻”——因为外壳做成鸭头后,很多人习惯把电池放在头部,结果重心前移,训练出来的策略一上真机就不断前倾摔倒。
我最后的配重方案是:电池平放在腹部偏后位置,IMU置于质心附近,鸭头外壳尽量做成空心轻质结构。这样静态站立时,重心投影点落在两脚掌支撑多边形中心偏前约5mm处。这个位置经过实测,既不会在前倾时难以纠正,也能保证起步时重心能自然前移。
有个细节值得特别说:舵机线在关节内的走向会影响转动阻力。如果线卡在关节运动轴附近,会形成周期性阻力,这种阻力的频率和步态频率一旦凑巧,策略会被迫输出奇怪的补偿动作,直接导致步态变形。所有线束必须沿固定臂走线并用扎带固定,给足活动余量。
3. 仿真训练与强化学习算法细节
3.1 仿真环境搭建:MuJoCo还是Gazebo?
仿真环境的选择直接影响训练效率和迁移效果。Gazebo配合ROS是机器人的经典组合,物理精度尚可且生态完善,但渲染和物理计算开销大,同一时间只能跑少量并行环境。强化学习动辄需要上千小时的交互数据,在Gazebo上跑PPO可能要几天,而MuJoCo只需要几个小时。
我最终选了MuJoCo,理由有三个:一是计算效率高,单核单环境能跑到实时速率的几十倍;二是MJX支持GPU批量并行,一次性可以跑上千个并行环境;三是接触模型和关节驱动模型比较精细,适合足式机器人。
MuJoCo里建模倒不复杂,把URDF或MJCF格式导入,设置好关节摩擦、电机驱动类型和接触参数就能开跑。但有一个参数务必调准:关节阻尼(damping)。这个参数定义了关节被动阻力,我最初保持默认值,训练出来的步态看起来没问题,一上真机就软绵绵,因为真机舵机内部齿轮箱阻尼远大于MuJoCo默认值。后来我把每个关节的damping设置为0.85,训练出的策略更“紧绷”,迁移效果明显改善。
3.2 状态空间、动作空间与奖励函数设计
状态空间的设计决定了策略到底能“看到”什么。我的状态空间中包含:
- 11维本体状态:躯干三轴角速度、三轴姿态、两条腿各关节当前角度;
- 8维目标动作:策略刚输出的上一步动作,形成动作平滑性约束;
- 4维扩展状态:脚底接触传感器、行走速度指令。
动作空间则是4个目标关节角增量,输出范围限制在-0.4到0.4弧度之间,并经过低通滤波平滑后送入底层PID。
奖励函数是训练里最值得花时间的部分。我第一版只给了前进速度奖励和生存奖励,结果训练出来的步态变成了“鸭子滑行”——机器人直接保持站立姿势然后往前溜冰,明显是利用了物理引擎的bug。后来参考了开源双足项目 common practice,把奖励拆成四部分:
- 前进速度奖励:跟随目标速度的方向投影,鼓励往前走;
- 动作变化惩罚:前后两步动作差值平方,防止抖动;
- 身体朝向惩罚:躯干横滚角和俯仰角偏离期望值越小越好;
- 能量惩罚:关节速度平方加权,抑制无效摆动。
这三项惩罚的本质是给策略施加“行为规范”,否则稀疏的速度奖励会让策略找到一堆投机取巧的解法。能量惩罚尤其重要,我亲眼见过不设这项时,策略学会了用极高频抖动脚掌来换取微小的前进量,真机上舵机直接过热保护。
3.3 PPO训练的参数调整与收敛判断
算法我用了PPO(Proximal Policy Optimization),不是因为它是效果最好的,而是因为它实现成熟、调参经验多、开源代码多,对于开源项目是最好的起点。像SAC、TD3这类基于价值的算法在连续控制上也很好,但训练不稳定时你很难判断是代码问题还是算法问题。PPO至少有个清晰稳定的训练曲线可以参考。
关键参数我记录如下:
- 并行环境数:4096(MuJoCo MJX)
- 每个环境 rollout 长度:24步控制周期(每步50ms)
- PPO clip ratio:0.2
- value loss 系数:1.0
- entropy 系数:0.01
- 学习率:3e-4,使用线性衰减
判断收敛不以奖励值高低为准,而是看两个指标:策略熵值是否稳定在预期范围,以及仿真里随机重置姿态后的恢复成功率。如果策略训练完只会从固定初始姿态走路,说明泛化不足。我的标准是:至少80次随机重置(包括随机推倒、随机初始偏移)中有75次能恢复行走。
4. 开源架构设计与代码实现
4.1 整体代码结构与通信方式
这套系统的开源架构分为三大部分:仿真训练仓库、嵌入式控制仓库和通信协议库。训练仓库基于Python和MuJoCo,嵌入式控制仓库基于C++和ESP-IDF/Arduino框架,两者之间通过一套自定义的轻量二进制协议交互。
核心代码结构如下:
duck-bot/ ├── sim/ # MuJoCo 环境与训练代码 │ ├── env.py # 环境封装,Gymnasium 接口 │ ├── model.xml # 鸭形机器人模型描述 │ ├── train_ppo.py # PPO 训练入口 │ └── rsl_rl/ # 依赖的强化学习库 ├── firmware/ │ ├── main.cpp # ESP32 或树莓派 Pico 控制逻辑 │ ├── pid.c # 关节双闭环控制器 │ ├── motion.c # 运动指令解析与插值 │ └── imu.c # MPU6050 数据读取与姿态解算 ├── bridge/ # 上位机与通信协议 │ ├── protocol.h # 数据包定义 │ └── wifi_udp.py # 板端 Wi-Fi/UDP 透传脚本 └── docs/ # 搭建文档与硬件图纸这样划分的好处是:做算法的人不需要碰嵌入式代码,做嵌入式的人不需要理解神经网络细节,只需要遵循通信协议就能接上新策略。
4.2 双层控制:强化学习输出与底层PID
这一层是整套系统里最容易被低估的部分。很多人以为策略输出关节角之后直接给舵机就行,实际上舵机内部控制频率本来就不高,如果直接给目标角,每一拍之间的角度跳变会非常大,舵机执行时会产生“咔咔”声甚至堵转。
我的方案是在主控里跑一个50Hz的插值循环,把策略输出的目标关节角按10等份逐步过渡,每份5ms生成一个中间目标角度,作为舵机角度环的输入。舵机自身的控制器负责高频跟踪,这样形成:**强化学习(低频步态规划)→ 主控插值(中频轨迹平滑)→ 舵机内环(高频关节跟踪)**的三级链路。
底层PID参数我调成了P=1.2、I=0.05、D=0.15,输出限幅±0.6V。调PID时有个经验:不要用阶跃响应调,而是直接回放训练时的关节角轨迹,观察跟踪误差曲线。因为实际运行中关节轨迹是连续变化的,阶跃响应调出来的参数在跟踪连续轨迹时往往会过冲。
4.3 sim-to-real迁移的几个关键点
仿真训练完的策略直接搬到真机,几乎注定失败。我花了三周时间才把迁移成功率从30%提到稳定运行,落地的关键就三点:域随机化、动作平滑和反馈滤波。
域随机化是指在训练时随机改变仿真参数,让策略见过足够多“歪”的情况。我在训练中做了这些随机化:
- 关节阻尼随机范围0.6~1.2倍;
- 电机驱动延迟随机0~5ms;
- 质心位置随机偏移±3mm;
- 地面摩擦系数随机0.4~1.2;
- 外部推力的方向和大小随机施加。
动作平滑则是在策略输出后加了一个一阶低通滤波器,截止频率10Hz。这个滤波器在仿真里会稍微降低性能,但实物上价值极大——它能滤掉策略因状态噪声产生的抖动输出,保护舵机不被高频磨损。
反馈滤波主要针对IMU数据和关节编码器数据。IMU原始数据有高频噪声,直接进入状态空间会让策略产生误判。我用了滑动窗口平均,窗口长度恰好对应一个控制周期(50ms),这比复杂的卡尔曼滤波更可靠,因为卡尔曼滤波对模型误差敏感。但这里要说清楚,滑动窗口会带来约一个控制周期的相位滞后,对快速实时反馈不太友好。硬件算力允许的话可以试一下互补滤波,效果更均衡。
5. 常见问题与排查实录
5.1 机器人原地转圈或单脚抖动
这是新手上路最常见的问题。原地转圈通常是两侧腿的舵机零点不一致导致的,也就是同样一个目标角度,左脚实际转的角度和右脚不一样。排查方式是把机器人悬空,给头部标同角度指令,观察脚掌朝向是否一致。不一致就手动校准舵机零点偏移。
单脚抖动则大概率是步态频率和机械结构的共振点重合了。我的鸭形机器人步频如果在2.2Hz附近,腿部会明显抖动,整个躯干开始共振。解决办法是微调策略输出的步频目标,或者给脚掌加硅胶垫缓冲——我用了厚度2mm的硅胶脚垫,抖动明显抑制。
5.2 训练发散与奖励“Hack”
训练过程中奖励不升反降,或者一直不涨,先别急着调网络结构,按这个顺序排查:
- 检查状态空间中是否有NaN或Inf,通常是仿真参数异常导致;
- 检查动作线性响应到仿真中被正确应用,是否乘了缩放因子导致动作太小;
- 降低学习率至1e-4,排掉参数更新过大的问题;
- 检查reward scale是否过大或过小,能量惩罚系数过大直接让策略躺平。
奖励“Hack”是另一个坑。前面提的“溜冰步态”就是典型的,此外还有策略学会故意摔倒然后用躯干往前蹭的。对付这种问题,我的经验是不要一味加惩罚项,而是先提升仿真接触模型和摩擦系数的真实性——很多投机策略是利用了仿真摩擦系数过低的缺陷,你修正物理模型后,同类手段自然失效。
5.3 实物续航与发热问题
微型舵机的效率和发热是个大问题。整套系统在正常行走时平均功耗大约6W,300mAh 2S电池持续行走续航只有大约8分钟,这在演示场合非常尴尬。
后来我做了一个优化:在站立或等待模式下调低舵机掉电频率,把姿态保持任务交给四个关节的锁存力矩,主控只在姿态偏差超过阈值时才重新执行闭环校正。这样静态等待功耗降到了1.2W,续航提升到25分钟以上。这个方案实现起来很简单,代码里加一个状态判断就行。
这里有个要特别提示的:舵机的死区参数不要设得太大。死区大了虽然省电,但会让关节产生一定“游隙”,做精确步态时会表现为脚掌随机漂移,策略又得输出修正量,反而增加能耗。我把死区精度调高到约0.9度,功耗轻微增加但整体步态稳定性提高很多。
5.4 策略迁移失败的系统性排查
如果训练策略直接在真机上跑不动,先不要怀疑算法,按照这个表格从机械到软件逐项排查:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 启动后立即摔倒 | 初始姿态与训练不一致 | 调整起始姿态角,或增加初始随机化训练 |
| 行走时越走越歪 | 舵机零点漂移 | 校准零点偏移,应用软件补偿 |
| 步态迟缓无力 | 舵机供电不足 | 测量动态电压跌落,更换大C数电池或加电容 |
| 高频振荡 | 反馈滤波滞后 | 减小滤波窗口,或改用互补滤波 |
| 某个关节不动 | 舵机掉线或线缆断路 | 检查线缆,尤其是关节附近的折弯位置 |
写在最后的小心思
做这个鸭形机器人的过程中,我最大的体会有两点。第一,强化学习项目里,仿真物理参数的准确度比算法创新重要得多——你花几个星期优化一个新算法,不如花几天把仿真里的摩擦、阻尼调准,后者对迁移效果的提升是立竿见影的。第二,开源架构的意义不在于代码全部开源,而在于每一层接口都干净清晰,让其他人能在不重写整个系统的情况下替换掉任何一部分。我的协议只要字段不变,别人换一个更好的训练算法、换一套机械结构,整个软件栈都能直接跑通。
最后再分享一个我实测好用的小技巧:调试强化学习策略时,不要直接看总奖励曲线,把每个奖励分量曲线单独打印出来。前进速度奖励、动作变化惩罚、能量惩罚分开看,一眼就能定位是速度没提上去,还是动作在乱抖。这一步能帮你省下至少一半的调参时间。
这个项目后续扩展的方向也很多,比如给策略加上视觉输入做避障、用因果强化学习里的干预机制去引导策略关注关键足底接触特征、或者引入基于模型的规划和强化学习结合。希望这篇解析能给想动手做双足或者对足式机器人感兴趣的朋友一些实质性的帮助。