
1. 项目缘起为什么我要做一只双足机器鸭1.1 从“玩具”到“平台”的定位转变最早动这个念头其实特别朴素——我想做一个能在地上走来走去、看起来又不太像传统双足机器人的东西。市面上双足机器人不是没有但要么是动辄几万块的科研平台要么是只能做几个预设动作的玩具。我想要的是一台能跑控制算法、能实时闭环、结构还足够简单的小型双足平台成本控制在千元级别最好还能塞进一个看起来有点意思的外壳里。MicroDuck 这个名字就是这么来的。鸭子的身体结构其实特别适合做双足实验重心靠后、腿短、步幅小天然就是一个低动态的行走模型。相比人形机器人那种高重心、大惯量的系统机器鸭的容错率高得多摔了也不心疼调参的时候心理压力小很多。这一点很关键我见过太多人做双足项目第一步就被“怕摔”劝退了。这个项目的核心目标有三个第一50Hz 的神经控制闭环也就是控制频率稳定在每秒 50 次这是整个系统的节拍器第二完整的感知-决策-执行链路从 IMU 姿态解算到关节力矩输出中间不能有断点第三可复现、可拆解所有资料开源别人拿到能自己搭一台出来。说白了这不是一个炫技项目是一个教学和实验平台。1.2 50Hz 这个数字是怎么定下来的很多人第一反应会问为什么是 50Hz不是 100Hz 或者 200Hz这个问题我当初也纠结了很久后来是这么算的。双足行走的步态周期一般在 0.4 到 0.8 秒之间取中间值 0.6 秒。一个步态周期里关键的相位切换点大概有 4 到 6 个抬腿、落地、重心转移、支撑切换。如果控制频率是 50Hz那么每个步态周期有 30 个控制节拍平均每个相位切换点能分到 5 到 7 个节拍。这个分辨率足够捕捉相位变化又不会让计算量爆炸。再往上算100Hz 意味着每个周期 60 个节拍理论上更平滑但对 MCU 的算力要求翻倍而且 IMU 的噪声在更高频率下反而更容易被放大。50Hz 配合一个设计良好的50Hz 陷波器刚好能把电机振动和机械共振压住又不至于引入太大的相位滞后。实测下来50Hz 的控制周期是 20ms这个时间窗口里完成一次完整的“读传感器-算姿态-跑控制器-写 PWM”绰绰有余留有余量给调试和日志输出。提示控制频率不是越高越好。频率越高对传感器噪声越敏感对滤波器设计要求越高。50Hz 是一个在响应速度和鲁棒性之间比较平衡的点尤其适合这种小尺寸、低惯量的双足平台。1.3 适合谁来参考这个项目如果你是完全零基础的小白这个项目会让你有点吃力但也不是不能碰。我建议你先从理解“控制闭环”这个概念开始把整个系统拆成感知、决策、执行三块一块一块啃。如果你有单片机开发经验或者玩过舵机云台、平衡车之类的项目那上手会快很多。对于做机器人方向的学生和工程师MicroDuck 的价值在于它是一个完整的、可运行的闭环系统不是那种只跑仿真的玩具。你可以拿它验证自己的步态算法、滤波器设计、状态估计方案改坏了重新烧录就行。对于爱好者它是一台能让你真正理解“控制周期”和“实时性”的实体设备比看一百篇论文都管用。2. 系统架构拆解一只机器鸭的神经骨架2.1 硬件选型为什么不用现成的舵机方案市面上大部分小型双足机器人用的是舵机便宜、简单、扭矩还行。但我一开始就排除了舵机方案原因有三个。第一舵机的控制接口是位置指令内部自带一个闭环你没法直接控制力矩。而双足行走的核心恰恰是力矩控制尤其是在支撑相和摆动相切换的时候力矩的平滑过渡决定了步态稳不稳。第二舵机的响应带宽有限50Hz 的控制频率下舵机内部的 PID 往往跟不上会出现明显的相位滞后。第三舵机的反馈信息很少你只知道它转到了哪个位置不知道它用了多大力、电流多大、温度多高。所以我选的是无刷电机加行星减速器的方案。具体来说是 2208 无刷电机配 1:10 的行星减速箱额定扭矩在 0.8 N·m 左右峰值能到 1.5 N·m。这个扭矩对于一只体重 1.2kg 左右的机器鸭来说做静态站立和低速行走完全够用。电机驱动器用的是支持 FOC 的板子可以跑力矩模式也能读回电流和位置。IMU 选的是 MPU6050 的升级版六轴I2C 接口输出频率能到 1kHz。虽然 50Hz 的控制周期只需要 20ms 读一次但高频率采样配合滑动平均滤波能把姿态解算的噪声压得很低。主控用的是 STM32F405168MHz 主频带 FPU跑姿态解算和控制器绰绰有余。部件型号/规格关键参数选型理由主控STM32F405168MHz, FPU, 1MB Flash算力足够生态成熟IMUMPU6050 升级版六轴, 1kHz 输出成本低资料多电机2208 无刷1:10 减速, 0.8N·m力矩可控反馈丰富驱动器FOC 驱动板支持力矩/速度/位置模式闭环带宽高电源3S 锂电11.1V, 1500mAh续航约 40 分钟2.2 控制闭环的四个环节整个控制闭环可以拆成四个环节我用一个循环图来描述传感器采集 → 状态估计 → 控制器计算 → 执行器输出然后回到传感器采集。这个循环每 20ms 跑一次雷打不动。传感器采集环节我读的是 IMU 的加速度和角速度以及两个电机的编码器位置和电流。这里有个细节IMU 的 I2C 读取不能阻塞太久否则会打乱整个 20ms 的节拍。我的做法是把 I2C 配置成 DMA 模式数据搬完了触发中断主循环去取。这样读取时间从原来的 3ms 降到了 0.5ms 以内。状态估计环节用的是互补滤波。加速度计算倾角陀螺仪积分算角速度两者加权融合。互补滤波的系数我调了很久最后定在 0.98 对陀螺仪、0.02 对加速度计。这个系数意味着我更信任陀螺仪的短期积分但用加速度计来修正长期漂移。50Hz 下这个滤波器跑起来很稳姿态角波动在 ±0.5 度以内。控制器计算环节我用的是任务空间 PD 控制加前馈。简单说就是先根据步态相位生成期望的足端轨迹然后通过雅可比矩阵把足端力映射到关节力矩。PD 参数是分开调的支撑相和摆动相用两套参数。支撑相要硬一点保证承重摆动相要软一点保证落地不弹跳。执行器输出环节就是把算出来的力矩值通过 CAN 总线发给电机驱动器。这里要注意力矩指令要做限幅防止瞬间过流。我设的限幅是额定扭矩的 1.5 倍超过就截断。2.3 50Hz 陷波器的设计考量电机转动的时候减速箱的齿轮啮合会产生周期性的振动频率大概在 45 到 55Hz 之间。这个振动会通过机身传到 IMU 上如果不处理姿态解算出来的角度会一直抖。我试过用低通滤波但截止频率设低了会引入太大滞后设高了又滤不干净。最后用的是50Hz 双 T 型陷波滤波器。这个滤波器的特点是在 50Hz 附近有一个很深的陷波但对其他频率的信号影响很小。设计的时候我先用频谱分析仪测了电机振动的实际频率发现峰值在 48Hz 左右所以把陷波中心频率定在 48Hz带宽设了 6Hz。这样既能压住振动又不会把有用的步态信号滤掉。双 T 型陷波器的传递函数是这样的# 双T型陷波滤波器系数计算 import numpy as np def notch_coeffs(f0, fs, Q): w0 2 * np.pi * f0 / fs alpha np.sin(w0) / (2 * Q) b0 1 b1 -2 * np.cos(w0) b2 1 a0 1 alpha a1 -2 * np.cos(w0) a2 1 - alpha # 归一化 b [b0/a0, b1/a0, b2/a0] a [1, a1/a0, a2/a0] return b, a b, a notch_coeffs(48, 50, 5) print(b:, b) print(a:, a)这段代码算出来的系数直接烧进 MCU每个控制周期对 IMU 的角速度做一次滤波。实测下来电机全速运转时姿态角的抖动从 ±3 度降到了 ±0.8 度效果非常明显。注意陷波器的中心频率一定要根据实际测量来定不能拍脑袋。不同批次的电机、不同的装配松紧度振动频率都会有偏差。我建议先用手机上的频谱分析 App 粗测一下再精调。3. 实操过程从零搭建一台 MicroDuck3.1 机械结构的装配顺序与要点装配顺序很重要顺序错了后面要拆了重来。我的建议是先装腿再装身体最后装外壳。装腿的时候先把髋关节的电机固定到骨盆板上注意电机的出线方向要朝内不然会跟另一条腿干涉。然后装大腿连杆这里用的是碳纤维板轻而且刚性够。膝关节的电机装在大腿末端同样注意出线方向。最后装小腿和脚掌脚掌上贴了一层硅胶垫增加摩擦力的同时也能吸收落地冲击。装身体的时候先把主控板和驱动器用尼龙柱固定到底板上然后装 IMU。IMU 的安装位置很关键要尽量靠近机器人的重心而且要用减震胶垫隔离不然电机振动会直接传上去。我试过用硬连接结果姿态噪声大了三倍换成减震胶垫之后立刻好转。装外壳的时候鸭子的头和尾巴是 3D 打印的用卡扣固定方便拆装。外壳不只是为了好看它还能保护内部的线缆防止行走的时候被勾住。3.2 固件烧录与参数配置固件分两部分主控固件和电机驱动器固件。主控固件用 Keil 或者 STM32CubeIDE 编译烧录的时候注意选对芯片型号。电机驱动器固件一般厂家会提供用它们的上位机工具烧录就行。烧录完之后第一步是校准 IMU。把机器鸭放在水平桌面上静止不动运行校准程序采集 1000 个样本求平均把零偏存到 Flash 里。这一步不做姿态解算会一直漂。第二步是配置电机参数。包括极对数、减速比、编码器分辨率、电流环 PID。这些参数厂家一般会给参考值但最好自己再调一遍。我的做法是先用速度模式跑一下看波形稳不稳然后再切到力矩模式。第三步是设置控制参数。包括控制频率50Hz、陷波器系数、互补滤波系数、PD 参数。这些参数我整理成了一个配置表存在主控的 Flash 里可以通过串口在线修改不用每次重新烧录。// 控制参数配置结构体 typedef struct { float control_freq; // 50.0 Hz float notch_f0; // 48.0 Hz float notch_Q; // 5.0 float comp_alpha; // 0.98 float kp_stance; // 支撑相Kp float kd_stance; // 支撑相Kd float kp_swing; // 摆动相Kp float kd_swing; // 摆动相Kd float torque_limit; // 1.5倍额定 } ControlConfig;3.3 步态生成与相位切换逻辑步态生成我用的是有限状态机加轨迹插值。整个步态周期分成四个状态左腿支撑、左腿摆动、右腿支撑、右腿摆动。每个状态持续 0.3 秒加起来 0.6 秒一个完整周期。状态切换的触发条件是时间不是传感器。为什么因为时间触发更稳定不会因为传感器噪声导致误切换。但我会用足端接触传感器做一个安全校验如果到了该切换的时间但足端还没接触地面就延迟 20ms 再切防止踏空。足端轨迹用的是贝塞尔曲线摆动相的起点和终点都是支撑相的位置中间抬高 3cm。这样生成的轨迹平滑速度和加速度都连续不会对电机造成冲击。# 足端轨迹生成简化版 def bezier_traj(p0, p1, p2, p3, t): # 三次贝塞尔曲线 x (1-t)**3 * p0[0] 3*(1-t)**2*t * p1[0] 3*(1-t)*t**2 * p2[0] t**3 * p3[0] y (1-t)**3 * p0[1] 3*(1-t)**2*t * p1[1] 3*(1-t)*t**2 * p2[1] t**3 * p3[1] return x, y # 摆动相轨迹起点(0,0)控制点(0.02,0.04)控制点(0.06,0.04)终点(0.08,0) for i in range(30): # 50Hz下0.6秒周期有30个点 t i / 29.0 x, y bezier_traj((0,0), (0.02,0.04), (0.06,0.04), (0.08,0), t) print(ft{t:.2f}, x{x:.4f}, y{y:.4f})3.4 实时调试与数据记录调试的时候我最依赖的是串口波形。主控每 20ms 发一帧数据出来包括姿态角、关节角度、力矩指令、电流值。上位机用 Python 的 matplotlib 实时画出来一眼就能看出哪里不对。数据记录也很重要。我会把每次调试的数据存成 CSV方便事后分析。有一次我发现机器鸭走几步就往左偏查了数据才发现是左腿的电机零位偏了 2 度重新校准之后就好了。如果没有数据记录这种问题很难定位。实操心得调试的时候一定要一次只改一个参数。我见过有人一口气改五个参数结果机器鸭行为变了但不知道是哪个参数起的作用。改一个测一次记一次这是最快的路径。4. 常见问题与排查技巧实录4.1 姿态解算漂移与振动问题姿态漂移是最常见的问题表现是机器鸭站着不动但姿态角一直在缓慢变化。原因通常是 IMU 零偏没校准好或者互补滤波系数不合适。我的排查步骤是先看静止时的角速度输出如果零偏超过 0.5 度/秒就重新校准如果零偏正常但角度还是漂就把互补滤波的 alpha 调大一点更信任陀螺仪。振动问题更麻烦表现是姿态角高频抖动。前面说的 50Hz 陷波器就是解决这个的。但如果陷波器调了还是抖就要检查 IMU 的机械安装。我遇到过 IMU 没贴紧、螺丝松动的情况重新固定之后抖动就消失了。问题现象可能原因排查方法解决方案姿态角缓慢漂移IMU零偏未校准静止时看角速度输出重新校准零偏姿态角高频抖动电机振动传导频谱分析找振动频率调整陷波器参数抖动依旧IMU安装松动手动晃动IMU看输出重新固定加减震垫步态不稳PD参数不匹配看关节力矩波形分支撑/摆动相调参电机发热力矩限幅过高测电流有效值降低限幅检查摩擦4.2 电机力矩控制异常排查电机力矩控制异常的表现是指令发下去但电机不转或者转得很奇怪。我遇到过几种情况。一种是CAN 总线通信失败。CAN 的终端电阻一定要接120 欧姆两端各一个。我一开始没接通信时好时坏查了半天才发现是这个问题。另一种是电机零位不对。无刷电机的零位是电角度零点如果校准不准力矩输出会有一个正弦误差表现为电机转起来一顿一顿的。解决办法是重新跑一遍零位校准确保编码器读数和电角度对齐。还有一种是电流环带宽不够。FOC 驱动器的电流环 PID 如果调得太软力矩响应会滞后50Hz 的控制周期下就会出问题。我的做法是把电流环带宽调到 1kHz 以上这样力矩指令基本能实时跟踪。4.3 步态相位切换失败的处理相位切换失败的表现是机器鸭走着走着突然停住或者两条腿同时摆动导致摔倒。原因通常是状态机的切换条件没写好或者足端接触检测不可靠。我的处理方法是加超时保护。每个状态都有一个最大持续时间比如支撑相最多 0.5 秒摆动相最多 0.4 秒。如果超时了还没切换就强制切换到下一个状态同时记录一个错误标志。这样即使传感器出问题机器鸭也不会卡死。另外足端接触检测我用了双重判断一个是足底的压力传感器一个是电机的电流。如果压力传感器有信号或者电机电流超过阈值就认为足端接触了。两个条件满足一个就行提高了可靠性。4.4 电源管理与续航优化续航是个容易被忽视的问题。3S 1500mAh 的电池理论上能跑 40 分钟但实际用下来只有 25 分钟左右。原因是电机在支撑相的时候电流很大峰值能到 3A。优化方法有几个一是降低站立高度减少膝关节的力矩需求二是优化步态缩短摆动相时间减少电机加速的次数三是加一个大电容在电机峰值电流的时候由电容供电减轻电池负担。我加了一个 1000uF 的低 ESR 电容续航提升到了 32 分钟。提示电池不要用到完全没电再充锂电过放会永久损坏。我设的截止电压是 9.6V3.2V每节到了就报警强制关机。5. 仿真与实物联调MicroDuck 在 MuJoCo 中的复现5.1 为什么要在 MuJoCo 里先跑一遍实物调试的成本很高摔一次可能就要修半天。所以我在实物装配之前先在 MuJoCo 里把整个控制链路跑通了。MuJoCo 的好处是物理仿真精度高接触模型比较真实而且支持实时查看器能看到机器鸭在虚拟环境里走。我的流程是先在 MuJoCo 里建一个和实物尺寸一致的模型包括质量、惯量、关节限位、摩擦系数。然后把控制代码移植过去用 MuJoCo 的传感器接口替代真实的 IMU 和编码器。这样控制器代码几乎不用改就能在仿真里跑。仿真里调参比实物快得多因为可以加速运行也可以随时暂停、回放。我一般会在仿真里把 PD 参数调到差不多然后再上实物微调。这样实物调试的时间能缩短一半以上。5.2 MuJoCo 模型的关键参数配置MuJoCo 模型的核心是 XML 文件里面定义了机器鸭的几何、关节、执行器和传感器。我重点说几个容易出错的参数。质量与惯量一定要根据实物测量来填不能随便估。我用厨房秤称了每个部件的质量用游标卡尺量了尺寸然后算的惯量。惯量填错了仿真里的步态会和实物差很多。关节阻尼这个参数影响很大。填太小仿真里关节会一直晃填太大步态会变得很僵硬。我试了好几次最后定在 0.01 N·m·s/rad和实物的减速箱阻尼比较接近。接触参数包括摩擦系数、接触刚度、接触阻尼。摩擦系数我设的 0.8和硅胶垫的实际摩擦差不多。接触刚度设的 10000 N/m阻尼设的 100 N·s/m。这些参数需要根据仿真表现微调目标是让足端落地时不弹跳、不穿透。!-- MuJoCo 关节配置示例 -- joint namehip_left typehinge axis0 1 0 range-1.2 1.2 damping0.01 armature0.001/ joint nameknee_left typehinge axis0 1 0 range-2.0 0 damping0.01 armature0.001/ !-- 接触参数 -- geom namefoot_left typebox size0.04 0.02 0.01 friction0.8 0.1 0.1 solref0.01 1 solimp0.9 0.95 0.001/5.3 仿真与实物的差异处理仿真再准和实物也有差异。我总结了几点主要的差异和应对方法。传感器噪声仿真里的 IMU 是理想的没有噪声。实物有噪声而且有振动。所以我在仿真里给 IMU 加了高斯噪声模拟实物的噪声水平。这样调出来的滤波器参数上实物才能用。电机动态仿真里的电机是理想力矩源实物有电流环带宽限制、有摩擦、有齿隙。我在仿真里给力矩指令加了一阶低通模拟电流环的滞后。齿隙比较难模拟我是在实物调试的时候单独处理的。通信延迟仿真里没有通信延迟实物有 CAN 总线的传输延迟大概 1ms 左右。这个延迟在 50Hz 下影响不大但如果控制频率提到 100Hz 就要考虑了。5.4 仿真回放与数据对比MuJoCo 的查看器支持重新播放这个功能特别有用。我可以把一次仿真跑下来的数据存成轨迹文件然后重新加载一帧一帧地看机器鸭的动作。哪里不对暂停下来看关节角度、力矩、接触力一目了然。我还会把仿真数据和实物数据放在一起对比。比如关节角度曲线仿真里是平滑的实物里可能有抖动。抖动的来源就是前面说的振动和噪声。通过对比我能判断是控制器的问题还是机械的问题。实操心得仿真里跑得再好上实物也要做好摔的准备。我第一次上实物的时候机器鸭走了三步就往前扑倒了。后来发现是重心位置和仿真里差了 5mm重新调整电池位置就好了。仿真和实物的差距往往就在这些细节上。6. 拆解资料与二次开发建议6.1 完整拆解清单与物料表我把 MicroDuck 的拆解资料整理成了一份完整的物料表包括每个部件的名称、规格、数量、采购渠道和参考价格。这份表我放在项目仓库里别人可以直接照着买。类别部件规格数量备注结构碳纤维板2mm厚1套含骨盆、大腿、小腿结构3D打印件PLA1套鸭头、鸭尾、外壳驱动无刷电机22082个带1:10减速箱驱动FOC驱动器支持CAN2个力矩模式控制主控板STM32F4051个带FPU传感IMU六轴1个I2C接口电源锂电池3S 1500mAh1个带保护板紧固螺丝/尼龙柱M2/M3若干含减震垫6.2 二次开发的几个方向MicroDuck 作为一个平台可以往几个方向扩展。步态算法验证你可以把现在的有限状态机换成 CPG中枢模式发生器或者用强化学习训练一个步态策略。50Hz 的控制频率对强化学习来说有点低但可以跑一些简单的策略网络。状态估计升级现在的互补滤波比较简单你可以换成卡尔曼滤波或者粒子滤波提高姿态估计的精度。IMU 也可以换成更高精度的型号比如带磁力计的九轴 IMU。感知扩展加一个摄像头或者激光测距让机器鸭能感知前方障碍物做简单的避障。主控的算力有限可以考虑加一个树莓派或者 Jetson Nano 做上层决策主控只负责底层控制。通信升级现在的 CAN 总线带宽有限如果要传图像或者大量数据可以换成以太网或者 WiFi。但要注意通信延迟会增加控制闭环的实时性会受影响。6.3 开源资料与社区协作我把 MicroDuck 的所有资料都开源了包括机械图纸、PCB 文件、固件源码、仿真模型、调试工具。仓库地址在项目主页上有兴趣的可以自己去拿。开源的好处是别人可以帮你发现问题、改进设计。我收到过好几个有价值的反馈比如有人发现膝关节的限位角度不够有人建议把 IMU 的安装位置再降低一点。这些反馈我都采纳了现在的版本比第一版稳定了很多。如果你也想参与我建议从复现开始。先照着资料搭一台出来跑通基本步态然后再改。改的时候把你遇到的问题和解决方案记录下来提交到仓库里。这样整个项目才能越做越好。提示复现的时候不要一上来就改设计。先按原版做跑通了再改。我见过太多人一上来就改结果出了问题不知道是原版的问题还是自己改的问题浪费很多时间。7. 个人实操体会与后续扩展思路做 MicroDuck 这个项目最大的体会是控制闭环的稳定性比算法的先进性重要得多。我试过很多花哨的算法最后发现只要 50Hz 的节拍稳、陷波器调好、PD 参数匹配简单的控制律也能走出很稳的步态。反过来算法再先进如果控制周期抖动、传感器噪声大照样走不好。另一个体会是仿真和实物要交替迭代。纯仿真调参快但容易忽略实物的非线性纯实物调试慢而且容易摔。我的做法是仿真里调个大概实物上微调然后再回到仿真里验证。这样来回几次参数就收敛了。后续我打算做几个扩展一是加一个简单的视觉模块让机器鸭能跟着人走二是把控制频率提到 100Hz看看步态会不会更平滑三是做一个多机版本让两只机器鸭能互相配合。这些都在计划中做好了会继续开源。最后分享一个小技巧调试的时候把控制周期的时间戳打出来。我一开始没打后来发现偶尔会有周期抖动查了半天发现是串口打印阻塞了主循环。把打印改成 DMA 之后周期抖动从 ±2ms 降到了 ±0.1ms。这个细节很小但对闭环稳定性影响很大。