MicroDuck这名字听起来像个实验室恶作剧,但当你真把一只巴掌大的双足机器鸭放在实验台上,让它以50Hz的神经控制闭环摇摇晃晃走出稳定步态时,你才会意识到,鸭子外壳下面装的其实是一整套控制理论的浓缩实验课。
我在MicroDuck项目上前前后后投入了大概三个月,从MuJoCo里建模仿真,到把控制代码真正烧进主控芯片,中间踩过的坑足够写满一整本调试笔记。这篇文章不打算按教科书的方式讲闭环控制原理,而是以MicroDuck为线索,把双足机器人神经控制闭环从原理到落地完整过一遍:50Hz控制频率为什么够用、CPG节律生成与PID关节闭环之间怎么配合、50Hz陷波器在真实系统里到底救了多少次场,以及MuJoCo viewer重放这类让你少掉头发的调试技巧。
1. 为什么做一只双足机器鸭:四足稳定之后,双足才是真门槛
1.1 MicroDuck到底是个什么东西
MicroDuck本体是一个约20厘米高的小型双足机器人,外形参考了家鸭的简化结构:两条腿、两个脚掌、一个质心偏高的身体。整机重量控制在900克左右,两条腿各有3个主动自由度,髋关节两个、膝关节一个,脚掌是被动结构,依靠橡胶垫阻尼提供接地缓冲。主控用的是STM32H750,IMU是BMI088,关节执行器采用42步进电机加行星减速箱的方案,整套系统以50Hz的固定频率运行控制循环。
很多人第一反应是:双足已经这么难了,为什么还要做成鸭子?鸭子的行走姿态其实是研究双足动态稳定的天然样本。鸭子在陆地上行走时身体左右摇摆,这种摇摆不是缺陷,而是一种质心转移策略——通过躯干的侧向摆动把重心压到支撑腿上,从而降低对脚掌落地精度的要求。MicroDuck在结构上刻意复现了这种高质心、窄支撑面的不稳定特征,因为只有这样,控制器才是真正在解决双足平衡问题,而不是靠宽脚掌硬撑。
1.2 双足结构的动力学难点
四足机器人哪怕一条腿意外抬起,剩下三条腿仍然构成一个静态稳定多边形,控制压力小很多。双足就完全不客气了:单腿支撑相里,系统只剩下一个支撑点,质心在地面的投影必须持续落在支撑脚掌范围内之外,还要控制身体的姿态角速度不失控。换言之,这是一个高维、强耦合、非线性的欠驱动系统。
MicroDuck的连杆参数并不复杂,每条腿只有3个关节,但从动力学角度看,髋关节的两个旋转轴在步行中会互相耦合——内摆腿和外摆腿同时涉及髋关节偏航和滚转,膝关节的屈伸又直接影响身体的俯仰力矩。建模时如果忽略科氏力和离心力项,静态站立还能凑合,一旦开始迈步,跟踪误差会迅速累积,最终表现为鸭子越走越歪,最后侧翻。所以这个项目的控制框架一开始就必须走闭环,不能靠开环前馈硬扛。
1.3 50Hz控制频率是怎么定下来的
控制器设计里的一个重要问题是:一个控制周期到底要多快?MicroDuck最终选择了50Hz,也就是20毫秒完成一次“采集IMU→更新状态估计→计算CPG相位→生成关节参考轨迹→执行PID→输出力矩指令”的全链路。这里需要解释几个容易被忽略的制约因素。
第一是执行器带宽。42步进电机加减速箱后,即使在细分驱动模式下,关节端的响应带宽也只有5到8Hz左右,实际的输出力矩响应更慢。一个50Hz的控制循环已经是执行器带宽的6到10倍,再提高控制频率对关节跟踪效果几乎没有任何帮助,只会白白增加主控的算力负担。
第二是传感器噪声。BMI088的加速度计和陀螺仪在100Hz以上的频段噪声明显增大,姿态解算如果跑在100Hz,反而需要更强的低通滤波,引入的相位延迟比50Hz下更严重。50Hz刚好能兼顾角度估计的平滑度和实时性。
第三是CPG神经网络的更新约束。MicroDuck使用的中枢模式发生器(Central Pattern Generator)本质上是一组耦合非线性振荡器,需要每步求解微分方程。在50Hz下,20毫秒内完成振荡器状态更新、输出映射和关节控制是足够的,而且50Hz也正好能匹配电机编码器的读取和驱动器指令刷新周期。
有意思的是,50Hz恰好也是市电工频。这意味着电源传导进来的纹波噪声、PWM驱动产生的电磁串扰,都会直接落在控制带宽内——如果控制回路里没有针对性的滤波处理,50Hz的干扰会以周期振荡的形式混进关节力矩指令里。这个话题我后面单独用一节来讲,因为陷波器参数整定是整个项目里最折磨人的环节之一。
2. 神经控制闭环的核心拆解:CPG节律发生器与关节PID的配合
2.1 说“神经控制”,其实指的是CPG
MicroDuck的“神经控制”并不是很多人理解的那种端到端深度强化学习。强化学习路线需要大量仿真数据和非常细致的reward shaping,在实物上迁移时还会遇到sim-to-real的种种问题。MicroDuck采用的中枢模式发生器(CPG)是一种更接近生物脊髓机制的神经控制模型:它不依赖外部环境感知来产生完整的步态轨迹,而是通过一组相互耦合的振荡器自激产生节律性的关节运动信号。
CPG的数学模型我用了耦合Hopf振荡器。每个关节分配一个振荡器,动规如下:
[ \dot{x_i} = \alpha(\mu - r_i^2)(x_i - o_i) - \omega_i y_i + \sum_{j} k_{ij}(x_j - x_i) ] [ \dot{y_i} = \alpha(\mu - r_i^2)y_i + \omega_i (x_i - o_i) ]
其中( r_i^2 = x_i^2 + y_i^2 ),(\omega_i) 是第(i)个振荡器的固有频率,(o_i) 是偏移量,(k_{ij}) 是振荡器之间的耦合系数。振荡器输出的(x_i)经过线性映射,就变成对应关节的目标角度或力矩偏置。
Hopf振荡器最大的好处是极限环稳定。不管初始状态如何,振荡器都会收敛到以(\sqrt{\mu})为半径的稳定极限环上,这意味着CPG天然具备对扰动的恢复能力——当外部冲击把当前相位推偏了,振荡器会在几个周期内自动回到正常相位关系。这就是步态生成的“神经”属性:不是查表,不是轨迹插值,而是微分方程自主振荡。
2.2 关节层的PD/PID闭环
CPG输出的只是参考轨迹,真正让鸭腿执行动作的是关节层的PID闭环。MicroDuck每个主动关节跑的是一个位置环PID,输出是力矩指令,再通过驱动器的电流环转化为电机扭矩。
这里有一个容易被新手搞混的层级关系。双足机器人的控制结构通常分三层:最内层是电机电流环(扭矩环),中间是关节位置环,最外层是身体姿态环。CPG处于最上层,负责生成步态参考;姿态环根据IMU反馈修正CPG输出的偏移量;位置环跟踪修正后的关节目标;电流环保证输出力矩可控。
MicroDuck的PID参数因为关节而异,髋关节主要负责支撑身体,惯性负载大,所以比例增益偏高;膝关节在摆动相主要克服小腿和脚的转动惯量,微分增益需要大一些来抑制超调。调试时参数大致范围如下:
| 关节 | Kp | Ki | Kd | 备注 |
|---|---|---|---|---|
| 髋关节滚转 | 8.0 | 0.2 | 0.6 | 支撑相主导,需较大刚度 |
| 髋关节偏航 | 6.0 | 0.1 | 0.4 | 摆动相主导,阻尼适中 |
| 膝关节 | 10.0 | 0.3 | 0.8 | 需要快速响应,加强微分 |
PID参数整定我建议先从比例环节入手。先把Kp调到一个足够让关节在重力下不塌陷的值,再加一点Kd消掉振荡,Ki只在出现稳态误差时少量加。MicroDuck实测中Ki加多了反而坏事——积分饱和会让关节在一个方向持续发力,表现为鸭腿缓慢“漂移”,最终打乱CPG的相位同步。
2.3 50Hz频率下能跑完的计算链路
我最初在PC仿真时控制循环用到了500Hz,但转到STM32后资源受限,被迫压缩到50Hz。一开始担心频率降低会让姿态控制失效,实测结果却没有预想中糟糕,原因在于控制链路的每段计算时间都被压得很窄。
STM32H750主频480MHz,FPU全开。每个20毫秒控制周期的计算分配大致是:IMU读取和姿态解算占3.5毫秒,用的是Mahony互补滤波加四元数转欧拉角;CPG振荡器求解占2毫秒,用的是固定步长四阶龙格库塔;关节PID占1.5毫秒;剩下的时间用于与上位机通信和电机驱动器刷新指令。整个链路跑完不到8毫秒,留了超过一半的裕量。
这里有个工程直觉:控制频率不是越高越好,而是够用就好。在50Hz控制频率以下,系统延迟为20毫秒,加上驱动器响应延迟和步进电机本身的机械滞后,总延迟约30毫秒。对于步态周期1秒左右的鸭子来说,30毫秒只占3%,姿态环完全来得及修正。如果强行跑200Hz,延迟降到7毫秒,但噪声和计算抖动反而增加,系统表现不一定更好。
3. 50Hz工频干扰与机械共振:陷波器是第一个救命稻草
3.1 噪声到底从哪来
MicroDuck整机用锂电池供电,控制板上的DCDC开关电源和电机驱动器的PWM波形是全系统最大的噪声源。电机驱动器工作在20kHz PWM频率下,虽然基波远高于控制带宽,但电源线上的纹波和布线寄生电感会产生振铃,这些振铃经过非线性和地弹效应后,会折叠出包括50Hz在内的低频分量,混进IMU的供电和模拟信号里。
另一个50Hz噪声来源更隐蔽:结构共振。MicroDuck的打印件支架和电路板之间通过铜柱刚性连接,整个机体的第一阶共振频率恰好落在48到53Hz附近。当步态频率接近1Hz时,机体振动的高次谐波激励了这个共振,振动信号又通过IMU的安装面反馈到姿态解算里,形成一条机械-电子-算法的闭环振荡。
也就是说,50Hz干扰并不全是电源的问题,还可能来自结构本身的机械固有频率。排查时需要区分噪声是电磁耦合还是机械振动,方法很简单:用手按住机体,让结构共振消失,观察IMU原始输出中50Hz分量是否大幅衰减。按住后衰减明显,说明问题在机械路径;没有变化,则是电磁路径。
3.2 Notch Filter参数整定:Q值的权衡
陷波器(Notch Filter)是一种专门衰减某个窄带频率的数字滤波器。它的传递函数标准形式是:
[ H(s) = \frac{s^2 + \omega_0^2}{s^2 + \frac{\omega_0}{Q}s + \omega_0^2} ]
其中(\omega_0)是陷波中心频率,Q影响陷波带宽和深度。Q值越大,陷波带宽越窄,对相邻频段的损伤越小,但对频率漂移越敏感;Q值越小,陷波宽度越大,滤波后信号越平滑,但可能吃掉有用的低频成分。
MicroDuck的IMU数据在学校实验室实测,50Hz干扰主要来自市电谐波和附近设备辐射。经过频谱分析,干扰峰集中在49.8Hz附近且比较稳定,所以陷波器中心频率设为50Hz。Q值我最初设为30,但实际效果不理想——电机发热导致电容容值漂移和主时钟微小偏差后,陷波中心频率偏离了几个赫兹,50Hz干扰不仅没滤掉,还连带了有用的低频姿态信号一起衰减。
后来我把Q值降到8,带宽拉宽到大约6Hz,陷波范围覆盖47到53Hz。代价是10Hz以下的姿态有效信号损失了大约5%,但对控制来说完全够用。Q值从30到8的调整过程让我意识到:陷波器的参数不能只靠理想滤波器的数学推演,必须结合真实频谱漂移来定。工业标准做法是先录制实际噪声数据做频谱分析,再根据峰形决定带宽。
3.3 滤波前后的实测对比
接入陷波器之后,最直观的变化在关节力矩指令上。我把驱动器回读的电流值换算成力矩后记录曲线,未加陷波器时,力矩指令上叠加了一个幅度大约12%的50Hz周期振荡,这个振荡被CPG步态本身1Hz的节律调制,形成一种让人摸不着头脑的“拍频”效果。加上陷波器后,力矩曲线变得干净,50Hz分量从12%降到了0.8%左右。
随后我还发现一个连锁反应:因为IMU输出里的50Hz干扰被滤掉,姿态角微分项不再剧烈抖动,关节PID里的微分项增益可以稍微调大而不担心放大噪声,控制回路的整体阻尼改善了。陷波器看似只是一个前级信号处理模块,实际效果却让你后续整个控制器的调参空间都变大了。
这里必须提醒一点:陷波器这种东西能加在IMU信号上,就不要加在控制输出上。把控制器输出的指令信号过陷波器会引入额外的相位延迟,直接影响闭环稳定性。MicroDuck最初踩过这个坑,把陷波器加在PWM输出端,结果鸭子走路像喝醉了一样前后摇摆,原因就是相位延迟让本应阻尼振动的微分项反而激起了振荡。
4. MuJoCo仿真与实物联调:viewer重放是最被低估的调试工具
4.1 为什么选MuJoCo而不是其它物理引擎
MicroDuck的仿真环境用的是MuJoCo,相比PyBullet和Gazebo,MuJoCo在双足动态仿真上有一个显著优势:它的求解器对刚体接触的处理非常稳定,摩擦锥和接触点补偿算法成熟,仿真步长可以压得很小,即使步态中存在瞬间的脚掌拍地,也不会出现频繁的穿透或弹跳。这对复现双足支撑相和摆动相的切换非常重要。
另一个重要原因是MuJoCo原生提供了数据录制与回放能力。通过mj_saveState或者mjr_captureFrame,可以把仿真过程中的关节位置、速度、控制量、传感器原始数据完整记录成文件,再用mjv_updateScene和mjv_pasteTexture渲染回放。对于MicroDuck这种需要反复调参的项目,能按帧回放上次的步态是极其重要的功能。
4.2 viewer重放机制怎么用
使用MuJoCo的simulate工具配合load一个录制好的jnt状态文件,可以对仿真过程进行逐帧回放。回放时能看到每一次脚掌接触地面时关节力矩的实际输出模式,还能检查是否存在脚掌滑动、膝关节反弯之类的异常现象。
我把每次步态实验的状态保存下来,命名规则是exp_日期_时间_步态类型_参数版本。这样每次调整CPG参数或PID增益后,我可以保存一组完整的状态记录。第二天回头复盘时,直接加载对应的状态文件,旋转视角,放大某个关键帧,观察髋关节在单腿支撑期的力矩曲线。这种工作流比单纯保存几个数字指标要直观得多——数据显示鸭子步态正常,但视频里其实可以看到外八字越来越严重,这类细节数字里不会体现。
4.3 仿真到实物的迁移陷阱
MicroDuck在MuJoCo里走得很顺,一旦换到实物就原地摔跤。最典型的几个问题:
仿真里脚掌刚度很大,接触几乎无变形,但实物脚掌的橡胶垫有非线性阻尼,落地时吸收了一部分冲击力量,导致支撑相的质心轨迹和仿真预测不一致。
仿真模型里电机是一个理想的力矩源或速度源,没有考虑步进电机的齿槽转矩和摩擦力矩。MicroDuck步行时膝关节在小角度范围内往复运动,齿槽转矩造成的非线性力矩干扰在仿真中完全不存在。
CPG振荡器的参数在仿真里无论如何都能稳定收敛,但在实物上由于电机响应滞后,髋关节的实际角度跟目标角度之间有一个稳定相位差,这个相位差在小幅度扰动下会被CPG的自然动力学放大,造成身体扭转。
解决仿真到实物的迁移问题,我没有试图把模型建得更精确,而是反过来:把实物当作一个“更严格的环境”,重新辨识了关节的摩擦系数、脚掌的接地阻尼和质心偏移,把这些参数反写回MuJoCo仿真。经过两三轮“仿真-实物”的参数对齐,MuJoCo里那个鸭子的行为才和实物比较接近。这之后再把新的步态参数先在仿真里跑通,再搬到实物,成功率从最初的不到30%提升到了80%以上。
5. 步进闭环与调速系统:底层执行器是控制链路上最难糊弄的部分
5.1 张大头42步进闭环方案
MicroDuck的关节执行器选用了市面常见的“张大头42步进闭环”方案,也就是42步进电机加霍尔编码器加闭环驱动器。很多人对步进电机的印象还停留在开环控制:给多少脉冲转多少角度,失步了也不知道。实际上步进电机一旦负载超过保持力矩,或者加速度过大,就会失步——后面的所有位置控制都建立在错误的位置假设上,整个步态自然就乱了。
闭环步进驱动器通过编码器实时监测电机转子的实际位置,一旦发现与指令位置偏差超过设定阈值,立即通过内部PID调节电流进行补偿,把转子拉回期望位置。这对双足步行是关键的:膝关节在摆动相末端需要精确停在指定的屈曲角度,如果失步导致膝关节伸直过度,脚会拖地,直接引发绊倒。
MicroDuck在部署闭环步进之后,关节位置跟踪误差从开环状态下的平均5到8度降到1度以内。这个提升几乎感觉不到“额外成本”——闭环驱动器只是比开环多了编码器读取和补偿算法,但可靠性完全不在一个数量级。
5.2 从单闭环到双闭环:调速系统的演进逻辑
MicroDuck控制链路里的电流环和位置环之间的关系,其实可以类比经典的双闭环直流调速系统。健身房里常见的跑步机电机调速就是一个双闭环系统:内环是电流环,外环是速度环。电流环负责限制电枢电流不超过安全值,速度环根据目标速度和实际速度的偏差调整电流指令。
MicroDuck的关节执行器虽然用的是步进电机而不是直流电机,但控制结构类似。我的实践是:外环是位置环PID,输出期望角速度;中间的速度环对期望角速度做比例控制,生成电流指令;内环的驱动器电流环响应电流指令,输出实际力矩。只是步进驱动器通常把内环都封装好了,留给用户的是位置模式和速度模式两种接口。
在调试的早期我犯过只做位置环、忽略速度环限制的错误。结果是位置环的Kp一旦调高,位置误差对应的角速度指令过大,驱动器电流瞬间饱和,力矩跟不上,关节反而出现抖动。后来我给位置环输出做速率限幅,每个控制周期允许的关节角速度变化不超过每秒0.5弧度,把过大的阶跃请求平滑掉,抖动问题基本消失。这相当于在单闭环系统中引入了速度环的限幅功能,虽然形式上简单,但效果等同于双闭环。
5.3 温度变化对执行器的影响
项目后期MicroDuck连续行走测试时,步进电机温度开始上升。让电机持续满力矩运行15分钟,外壳温度能从25度升到60度。温度升高带来的问题是双重的:绕组电阻增大导致同样的电流产生的热量更多,实际力矩输出下降;编码器读数的零点会随温度轻微漂移,关节位置反馈值出现缓慢的偏移。
为了验证温度的影响,我做了一组对照测试:刚上电时记录一组PID参数下的跟踪误差,连续运行20分钟后,在同样的PID参数下重新测量。结果显示膝关节的位置跟踪误差从0.8度增大到了1.5度,髋关节更明显,从1.2度扩大到2.2度。这不是控制器失效,而是执行器性能参数在温度变化后发生了漂移。
解决方式不是增加更复杂的温度补偿模型,而是做热管理,让系统工作在稳定的温度区间。我在电机外壳贴了导热硅胶垫,通过铝合金支架把热量传导到外露的散热片上,同时降低了持续高负载工况下的PWM电流上限。这样连续运行30分钟后,电机温度稳定在45度左右,关节跟踪误差变化控制在0.2度以内,PID参数不需要针对温度做在线调整。
6. 踩坑实录与调参顺序:让鸭子稳起来的几件小事
6.1 调试中遇到的三个诡异现象
第一个现象是鸭子站着不动时,膝关节会每隔几秒轻微哆嗦一下。查了半天发现是CPG里两个振荡器之间的耦合系数(k_{ij})设为了正值,导致振荡器的偏移量(o_i)被互相牵拉,形成低频拍振。解决方案是把同一个腿上的髋关节和膝关节之间的耦合设为正向锁相,左腿和右腿之间的耦合设为反相锁相。CPG的耦合符号千万不能靠直觉猜,最好先在仿真里用相图验证一遍相位关系。
第二个现象是IMU数据在电池电量降到3.6V以下时出现明显的直流偏置漂移。早期误以为是DCDC电源的负载调整率问题,查了电路之后发现是BMI088的供电端在低压时进入了不规范的掉电区间,模拟前端偏置发生了跳变。解决方式是在降压器输出端加了一个足够大的储能电容和一个低电压复位阈值点。
第三个现象最具迷惑性:鸭子步速越慢越不稳。按直觉应该越慢越容易控制,但MicroDuck在步态周期从0.8秒延长到1.4秒后,侧向摇摆幅度反而增大了几乎一倍。原因在于慢速行走时,CPG振荡器的固有频率(\omega_i)被调低,而机体侧向动态的自然频率是固定的(由质心高度和支撑面决定),两者之间失去频率分离,形成了参量共振。解决方式是当步速低于某阈值时,提高髋关节滚转通道的刚度,把机体侧向自然频率移到CPG频率之外。
6.2 一套实用的参数整定顺序
控制系统的参数彼此牵连,如果乱改参数,永远无法判断某个改动到底是改善还是恶化。MicroDuck调试到后期,我总结出一套固定顺序,分享出来供参考:
先锁CPG参数,再用仿真标定CPG输出映射到关节目标角度的系数。目标是让理想轨迹在MuJoCo里跑出稳定步态,期间完全不接实物。
然后是关节PID。在实物上让鸭子做静态站立测试,调整髋关节和膝关节的Kp、Kd,直到角度误差小于1度且无振荡。静态站立稳定后,再让CPG低速运行(只做踏步不做前进),调整动态跟踪表现。
接着调姿态环。姿态环的作用是在CPG轨迹上叠加修正偏移量,修正系数很小,一般不超过CPG输出的10%。如果姿态环参数影响过大,会跟CPG争夺控制权,表现为步态变形。
最后调状态机的切换逻辑。MicroDuck在“站立-起步-行走-停止”四个状态之间切换,切换时机如果和CPG相位没有对齐,落地瞬间姿态环会突然收到一个较大的偏差冲击,很容易摔倒。解决方式是只在CPG相位特定的窗口内允许状态切换。
6.3 MicroDuck的后续扩展方向
项目目前处于一个能稳定行走、能转向的阶段,下一步我打算在CPG里加入感知反馈通路,让陀螺仪的角速度信号作为振荡器的额外耦合输入。这样当鸭子受到侧向冲击时,CPG能主动调整步态相位而不是全靠姿态环事后纠正,这也是生物中枢模式发生器更接近真实的方式。
另外,MicroDuck的控制架构可以平滑迁移到四足、六足甚至一些工业机器人的节律任务上。CPG加闭环的思想不局限于步行,它本质上是一种把动态系统行为“编排”成稳定节律的模式生成方法,像工业机械臂的重复搬运任务、仿生扑翼机构的驱动,都能套用同一套框架。
到最后我还是想说,MicroDuck这种项目真正的产出其实不是那只鸭子本身,而是让你在50Hz这种限制条件下,亲手把神经控制、闭环反馈和物理执行器焊接成一个能工作的整体。这个过程很难靠读论文获得,只有把参数写在代码里,看着它跌跌撞撞站起来,才能真切理解每一步取舍的分量。