☰
从单腿弹跳到四足平衡:SLIP虚拟腿与三段式控制律实战
2026/10/31 4:33:51 网站建设 项目流程

简介:这份PDF文档对应Marc H. Raibert所著《Legged Robots that Balance》,面向足式机器人、运动控制与人工智能方向的研究者及高年级学生,聚焦机器人如何通过主动平衡与动态控制实现稳定站立、行走和奔跑。内容从早期弹性腿实验、单腿弹跳杆原型与Leg实验室研究展开,系统讨论主动平衡、运动控制分解、动力学建模、传感器融合和闭环反馈等关键问题。文档还呈现研究动机、腿式机械与主动平衡研究史、奔跑机器人控制分解、三维空间运动及多腿机器人奔跑等章节线索,兼具经典理论脉络与实验案例参考价值。压缩包仅含1个PDF文件,大小约7.13MB,便于集中阅读和检索;目前已有395人学习,适合作为足式机器人平衡算法的入门梳理或课程专题延伸材料。

1. 单腿蹦跳为什么能撑起四足平衡:从“Legged Robots that Balance”的极简模型说起

把一台四足机器人的十二个关节全部锁死,只留一条能伸缩的腿,它还能站住吗?这个听起来像自废武功的问题,恰恰是《Legged Robots that Balance》这份文档反复在回答的事。足式机器人平衡算法的核心不在于关节数量,而在于能不能把整机压缩成一条会弹的虚拟腿,再用三条互相解耦的规则分别处理高度、姿态和速度。适合谁读:正在做四足或双足样机、被“仿真稳、实机晃”折磨过的控制工程师,以及想从零搭一台单腿弹跳平台验证控制律的学生。读完之后你该能自己列出参数表、写出控制律、并且知道哪几个数字一动就会翻车。

2. 把十二个关节压成一条虚拟腿:SLIP 模型的解耦假设与三个关键参数

2.1 凭什么敢用三个状态量描述一台四足机

SLIP(弹簧负载倒立摆)模型的全部内容是:把整机质量集中到一个质点上,把腿等效成一根没有质量、只有刚度和阻尼的弹簧,触地期间只沿腿方向出力,不产生绕髋的力矩。这样一来,触地阶段的动力学方程从“十二自由度刚体加接触约束”塌缩成平面内的两自由度加一个姿态角,三个状态量就能描述。

代价也很清楚:虚拟腿只出径向力,机身俯仰就没有任何回复力矩,姿态必须由另一套执行器单独处理。这正是三段式控制律要拆成三段的原因——它们本来就是被解耦假设切开的三个独立子系统,并非为了好看才拆。

这个假设成立有前提,我一般会在选型阶段就核对三条:

  • 单腿等效质量不超过整机质量的 15%。超过这个比例,摆腿产生的反作用力矩会直接推动机身俯仰,虚拟腿假设从第一步就破了。
  • 触地时间要短。支撑相占整个步态周期的比例建议在 0.35 到 0.5 之间,触地时间越长,姿态耦合累积得越多。
  • 足端不打滑,触地瞬间切向冲击小。橡胶足垫在光滑瓷砖上的摩擦系数不足 0.5 时,落脚点偏移的控制效果会被打对折。

有一个无量纲数很好用:Froude 数 Fr = v²/(g·L0),其中 v 是前向速度、L0 是标称腿长。工程上 Fr 落在 0.2 到 0.5 之间时,单位距离的运输成本最低,控制也最稳;低于 0.1 会退化成准静态迈步,高于 1.0 就开始出现明显的腾空相和落地冲击,参数窗口急剧收窄。整定之前先算一遍 Fr,能省掉很多瞎调的时间。

顺便说清它和 ZMP 那条路的区别,避免选错方向:

维度准静态 ZMP / 支撑多边形动态弹跳 SLIP
质心位置约束投影必须落在支撑多边形内允许飞出支撑区,靠动量交换
典型速度< 0.3 m/s0.5–2.0 m/s
对腿质量敏感度低高(腿部惯量直接破坏解耦)
参数整定难度低,但速度上不去中,窗口窄但性能上限高
适合场景平地慢走、楼梯、狭窄空间奔跑、越障、抗扰动

两条路并非互斥,我见过比较务实的做法是低速段用 ZMP 兜底、高速段切到弹跳控制,切换点就在 Fr≈0.15 附近。

2.2 等效刚度、标称腿长、触地角:三个参数怎么定

SLIP 落地要定下来的参数其实只有三个,剩下的都是它们的派生量。这一节把取值区间和调错的症状一次说清,后面整定的时候可以直接查。

参数符号常见取值区间物理含义调错的典型表现
等效刚度k8–25 kN/m(按 15–30 kg 整机折算)支撑相把重力势能转成弹性势能的速率偏硬:触地冲击大、关节电流尖峰、足端打滑;偏软:下沉过深、抬腿高度不够、越障失败
标称腿长L00.25–0.45 m空载时髋关节到足端的距离偏短:行程不足、摆动相来不及收腿;偏长:摆动惯量大、步频上不去
触地角 / 落脚点纵向偏移x_f±0.02–0.10 m支撑相水平反力的方向与大小偏移不足:速度跟不上指令;偏移过度:前后来回振荡,越走越晃

刚度不要凭感觉给,用固有频率反推更靠谱:ω = sqrt(k/m),弹跳周期 T = 2π/ω。控制器的执行周期至少要压到 T/20 以内,否则支撑相只有几次采样,力控根本来不及响应。一台 20 kg 的机器、k 取 15 kN/m,ω 约 27 rad/s,T 约 0.23 s,控制周期就得做到 11 ms 以下,基本对应 100 Hz 以上的主控频率。

触地角在单腿平台上是一个可以直接给定值的量,到了四足机上它变成摆动相末端的落脚点位置,由逆运动学算出来。这两件事本质上是一回事,只是执行方式不同。

提示:刚度和标称腿长一旦确定,最好写进配置文件并锁死,整定其他增益期间不要动它们。刚度变了,触地动力学的时间尺度就变了,之前调好的姿态增益会全部失效。

3. 三段式控制律写进代码:高度、姿态、速度各管一段

3.1 跳跃高度:用落地腿长做能量闭环

高度控制的本质是能量控制。机器人每次触地,弹簧压缩储存弹性势能,支撑相后半段伸腿把能量还回去,还给多少决定了下一次弹多高。想跳得更高就多伸一点,想跳得低就少伸一点——但伸腿这件事不能用位置指令硬怼,必须做成力或者力矩的闭环,否则触地瞬间的冲击会被位置环放大成电流尖峰。

我一般用“期望腿长轨迹 + 径向力 PD”的方式实现:支撑相前半段给一条收缩的腿长曲线,后半段让它回弹到略超过标称腿长的位置,PD 环负责跟踪。收缩的深度对应能量注入的多少,用一个 0 到 1 的系数调节,这个系数就是高度指令的直接映射。

高度指令的来源有两种。定高模式用机载 IMU 积分出竖直速度,在腾空相顶点估计跳跃高度,做慢速外环修正。速度模式就用固定系数,简单可靠。前者精度高但对 IMU 零偏敏感,后者皮实但抗负载变化差。样机阶段建议先用后者跑通,再切前者。

3.2 机身姿态:髋力矩差动与俯仰 PD 的边界

姿态环的存在完全是为了补虚拟腿假设的窟窿。腿只出径向力,机身俯仰没有任何回复力矩,所以必须让髋关节额外输出一个力矩,这个力矩不参与支撑,只管姿态。

控制律本身平淡无奇:俯仰角 PD,输出髋关节力矩。真正难的是边界条件。

第一,力矩不能太大。髋关节力矩会通过腿的反作用传递到足端,改变实际的支撑力方向,一旦超过径向力的 20% 左右,虚拟腿假设就开始失真,落脚点控制的效果会莫名其妙地变差。我一般给髋力矩加一个软限幅,取当前径向力乘以腿长再乘 0.2。

第二,角度反馈必须滤波但不能滤太多。触地冲击会直接打进 IMU,30 到 50 Hz 的低通是常规操作,但滤波带来的相位滞后会让阻尼项失效。经验做法是:角度用较低截止频率(30 Hz)保证干净,角速度用较高截止频率(80 Hz)保证相位,两者分开滤波。

第三,期望俯仰角不是零。四足机在加速时故意给一点抬头或低头的前倾角,能让支撑力的水平分量自然对齐加速度方向,减小落脚点偏移的负担。这个前倾角一般取 0.03 到 0.08 rad,随加速度线性变化。

3.3 前向速度:落脚点偏移量的那条线性公式

速度控制靠落脚点。落脚点放得比“速度中点”靠前一点,支撑相的水平反力就向后,机身减速;放得靠后一点就加速。这条线性关系是整个框架里最像“玄学”的部分——公式只有一行,但增益给错就振荡。

常用的形式是:x_f = 0.5·T_st·v_des + k_v·(v_x − v_des)。前一项是让落脚点落在支撑相中点对应的位置,保证匀速时水平力积分为零;后一项是速度误差的线性反馈,k_v 的量纲是秒。

k_v 的取值有物理上界。粗略地说,它不能超过支撑相时长的一半,否则相当于把一个周期的误差在一次支撑内全部纠正,必然过冲。0.22 s 的支撑相,k_v 上限就在 0.11 s 左右,实际取 0.08 到 0.12 比较稳。速度指令变化剧烈时要给指令本身加一阶低通,时间常数取 0.15 到 0.3 s,否则落脚点会在前后极限之间来回甩。

下面这段代码把三段控制律写在一个类里,平面单腿,状态是 (x, z, vx, vz, theta, omega),可以直接接到一个简单的仿真循环里跑。

import numpy as np class VirtualLegController: """单腿平面弹跳的三段式控制律:高度、姿态、速度各用一条独立规则""" def __init__(self, m=18.0, g=9.81, L0=0.34): self.m, self.g, self.L0 = m, g, L0 # 高度环:径向力 PD,刚度按固有频率反推,k=15kN/m -> kp 取 2200 N/m 足够 self.kp_height = 2200.0 # N/m self.kd_height = 46.0 # N·s/m,阻尼比约 0.6 self.depth_gain = 0.35 # 收缩深度系数,直接映射跳跃高度指令 # 姿态环:髋力矩 PD,输出会被下面限幅到径向力的 20% self.kp_theta = 85.0 # N·m/rad self.kd_theta = 6.0 # N·m·s/rad # 速度环:落脚点偏移,k_v 不超过支撑相时长的一半 self.k_v = 0.10 # s self.T_st = 0.22 # 标称支撑相时长 s self.vx_des = 0.60 # 期望前向速度 m/s self.vx_cmd = 0.60 # 一阶低通后的指令 def update_command(self, vx_cmd, dt, tau=0.2): alpha = dt / (tau + dt) self.vx_cmd += alpha * (vx_cmd - self.vx_cmd) def stance(self, state, phase_t): """phase_t 从 0 到 T_st,返回径向力、髋力矩、当前期望腿长""" x, z, vx, vz, th, om = state L_meas = np.hypot(x, z) # 前半段收缩、后半段回弹,深度由 depth_gain 决定能量注入量 L_des = self.L0 * (1.0 - self.depth_gain * np.sin(np.pi * phase_t / self.T_st)) F_leg = self.kp_height * (L_des - L_meas) - self.kd_height * vz # 腿只能推不能拉,同时给一个 6 倍自重的上限防炸机 F_leg = float(np.clip(F_leg, 0.0, 6.0 * self.m * self.g)) # 髋力矩软限幅:不超过径向力产生的等效力矩的 20% tau_max = 0.2 * F_leg * self.L0 tau_hip = -self.kp_theta * th - self.kd_theta * om tau_hip = float(np.clip(tau_hip, -tau_max, tau_max)) return F_leg, tau_hip, L_des def swing(self, state, phase_t, T_sw): """摆动相:算落脚点,给一条带向下预速度的落腿轨迹""" x, z, vx, vz, th, om = state # 落脚点相对髋的纵向偏移:速度中点 + 速度误差反馈 x_f = 0.5 * self.T_st * self.vx_cmd + self.k_v * (vx - self.vx_cmd) # 限制在腿长行程的 40% 以内,超了说明指令速度已经不可达 lim = 0.4 * self.L0 x_f = float(np.clip(x_f, -lim, lim)) s = phase_t / T_sw h_swing = 0.08 + 0.05 * abs(self.vx_cmd) # 抬腿高度随速度微调 x_tgt = x_f * s z_tgt = self.L0 + h_swing * np.sin(np.pi * s) - 0.15 * self.L0 * s return x_tgt, z_tgt, x_f

逻辑说明:stance里的L_des用一条正弦曲线描述支撑相的收放过程,depth_gain越大,收缩越深,回弹时注入的能量越多,跳得越高,这就是高度指令的物理入口。F_leg的上下限不是可选项,下限为零是因为腿不能拉,上限 6 倍自重是防止参数整错时把关节打坏。tau_hip的动态限幅跟着F_leg走,保证姿态力矩永远不会喧宾夺主。

swing里的x_f就是 3.3 节那条线性公式,lim那一行是很多人漏掉的:指令速度超出可达范围时,落脚点偏移会被行程截断,实际行为与控制器以为的行为不一致,表现就是“速度指令加上去但机身不提速”,后面的排查章节还会提到这一点。z_tgt里那个-0.15 * L0 * s是给落腿一个向下的预速度,让触地干脆,避免足端“探地”式的软接触。

参数怎么改:先固定depth_gain让机器能连续弹跳,再把kp_height从固有频率算出的值往下调 30% 起步,逐渐加回到能稳住高度为止;kp_theta和kd_theta按阻尼比 0.6 到 0.8 配;k_v从 0.05 起,每次加 0.02,出现前后振荡就退回上一档。

4. 从单腿到四足:对角小跑的相位调度与整定顺序

4.1 相位状态机与双支撑窗口的取舍

把三条控制律从一条腿搬到四条腿,中间的桥是相位调度。最常用的是对角小跑:左前配右后为一组,右前配左后为一组,两组相位差 0.5。占空比 β(支撑相时长除以步态周期)决定了步态性质:β = 0.5 时两组正好首尾相接,没有腾空相也没有双支撑相;β > 0.5 出现双支撑窗口,稳定性好但速度上限低;β < 0.5 出现腾空相,速度快但对控制精度要求高。

四足机的起飞阶段我建议从 β = 0.55 开始,留一个 10% 周期的双支撑窗口做兜底。这个窗口的价值在于:当某条腿触地检测失败或者力控异常时,另外两条腿还在支撑,机器不会直接趴下。代价是速度上不去,Fr 大概只能到 0.3。等参数稳了、触地检测可靠了,再把 β 降到 0.45 换取速度。

状态机本身不要做成纯定时的。定时器在仿真里永远准,实机上电池电压一掉、地面一软,相位就漂。我一般用“定时器给期望相位 + 事件检测给实际相位”的混合结构:摆动相开始时启动定时器,触地事件可以提前结束摆动相,超时未触地则强制切换并记录一次异常。事件优先级高于定时器,但连续三次异常就降速保护。

触地检测用什么信号,取决于硬件。足端有力和力矩传感器最直接,阈值取 5% 到 8% 自重,加 10 ms 的持续窗口防抖。没有传感器就用关节电流:触地瞬间电流会因为支撑力突增而出现尖峰,但摆动相加速段的电流尖峰是干扰项,必须用“抬腿后 30 ms 内屏蔽”这条互锁把它排掉。编码器差分算足端竖直速度也能判触地,速度由负转正的那个过零点就是,延迟比力传感器大 5 到 10 ms,精度够用。

4.2 摆动相轨迹的三个硬约束

摆动相看起来只是把腿抬起来放到前面,实际上有三个约束不能破,破了就是各种诡异现象。

抬腿高度不能省。平地低速时 0.06 m 够用,速度超过 1 m/s 之后建议给到 0.09 到 0.12 m,因为机身本身的上下起伏就有 0.03 到 0.05 m,余量不足会拖地。抬腿高度还要和摆动时间挂钩:T_sw 越短,轨迹竖直方向的加速度越大,需要的力矩越大,超过关节能力就会出现“抬到一半掉下来”的情况。

落点必须有向下的预速度。这条前面代码里已经体现。零速落地的轨迹意味着足端在触地那一刻竖直速度为零,力控会先感受到一个缓慢增长的接触力,触地检测的阈值判定被拉长,相位延迟随之产生。经验值给 -0.3 到 -0.8 m/s,速度越快给得越大。

落地点的横向位置要留裕量。四足机对角小跑时,足端横向落点如果贴着机身中线,侧向抗扰能力几乎为零,一个侧向推力就能让它横着趔趄。横向落点一般取髋关节横向间距的 0.9 到 1.1 倍,稍微外扩一点,形成自然的支撑宽度。

4.3 整定顺序:先刚度、后增益、最后落脚点

参数整定最怕的是一起调。四足机的参数耦合很强,同时动三个增益,你根本分不清是哪个起了作用。我的一般顺序是这样:

第一步,锁死所有增益,只调刚度。把机器吊起来,让腿在空中小幅收放,用示波器看关节力矩和足端位移,估算实际的等效刚度。仿真里设的 15 kN/m,实机因为减速器柔性和连杆变形可能只有 10 到 12 kN/m,这个偏差不修,后面所有基于固有频率算出来的增益都是错的。

第二步,调姿态增益。让机器站在地上,人在侧面推一下机身,看俯仰角能不能在两次振荡内收敛。振荡超过两次就降 kp 或加 kd,收敛太慢就反过来。

第三步,调高度环。给一个固定的跳跃高度指令,看连续弹跳二十次的高度是否漂移。高度单调上升说明能量注入过多,把 depth_gain 往下调 10%;单调下降就反过来。

第四步,最后调落脚点增益。给阶跃速度指令,看速度响应有没有过冲和振荡。这一步必须在前面三步都稳的前提下做,因为落脚点偏移产生的水平力会扰动俯仰,如果姿态环本来就在临界状态,你永远调不出稳定的速度响应。

整个流程跑一遍大概两到三天,每换一次地面材质(水泥、地毯、橡胶垫)都要重新核对第一步和第三步,因为等效刚度里的接触刚度变了。

5. 仿真跑得漂亮、实机却翻车:5 个高频排查点

现象一:实机一上电就前后点头,俯仰角在 ±8° 之间持续振荡,仿真里完全没有。原因:虚拟腿假设里腿只出径向力,但真实腿有质量、有转动惯量,髋关节出力的同时会把反作用力矩直接加到机身上;再加上触地冲击打进 IMU,俯仰角测量本身带着高频噪声,阻尼项被噪声喂成了正反馈。 解决:先把姿态增益砍一半,看振荡是否减弱,如果减弱就说明是增益过高;同时把角速度通道的低通截止频率从 80 Hz 降到 50 Hz。根治要靠两件事:腿部轻量化(把单腿质量压到整机的 12% 以内),以及在髋关节力矩上叠加一个基于腿摆动的反作用前馈项。

现象二:速度指令给 0.8 m/s,实际稳定在 0.55 到 0.6 之间,怎么加增益都上不去。原因:落脚点偏移被腿长行程截断了。按公式算出来的 x_f 已经接近 0.15 m,而腿在标称腿长下的纵向可达行程只有 0.12 m,实际落脚点被截在极限位置,控制器以为给的是大偏移,实际给的是小偏移,误差积分永远消不掉。 解决:先打印实际落脚点和指令落脚点,两条曲线一对比就清楚了。短期把指令速度限制在可达范围,长期要么提高步频(缩短支撑相时长,同样的偏移量能支撑更高速度),要么加大腿长行程。

现象三:摆动腿刚抬起来就报触地,相位机乱跳,机器原地抽搐。原因:触地判定用的是关节电流阈值,摆动相起始的加速段电流尖峰超过了阈值;或者足端力传感器阈值贴着噪声底,振动就能触发。 解决:加互锁,抬腿后 30 ms 内屏蔽触地事件;阈值改成“超过阈值并持续 10 ms 以上”才算有效;如果两个信号都有,用“力传感器为主、电流为辅”的与逻辑,误触发率能降一个数量级。

现象四:跑直线时航向缓慢漂移,十米之后偏出半米。原因:两组对角腿的地面反力不对称,可能来自足垫磨损、关节摩擦差异,或者 IMU 的零偏没标定。偏航角的积分误差在长距离上会被放大。 解决:每次上电静止两秒做 IMU 零偏标定,这一步省不得;在控制律里加一个偏航角速度的慢速外环,输出叠加到左右两侧落脚点的横向偏移上,用差动的方式把航向纠回来,增益给得很小,时间常数取 1 到 2 s。

现象五:满电时参数刚好稳,跑十分钟后开始发飘。原因:高度环和姿态环的输出是力矩指令,力矩指令经过电流环转换成实际力矩,电池电压下降后同样的电流对应的力矩下降,等效于所有增益同时变小,系统从过阻尼滑向欠阻尼。 解决:确认关节驱动器工作在力矩模式而不是电压模式;如果只能给电压,就在指令上乘一个电池电压的补偿系数(标称电压除以实测电压),并且把低压保护阈值设在标称电压的 85%。

这五条里有三条我在不同平台上都遇到过,血泪经验是:先怀疑假设,再怀疑参数。虚拟腿假设破了,参数怎么调都是在错误的模型上做优化。

6. 用机械能漂移率做验收:把“看着稳”变成一条可测曲线

调完之后最难回答的问题是:它到底算不算稳?靠眼睛看视频不算数,靠“跑了五分钟没摔”也不算数,因为有些机器是在缓慢发散,只是还没到临界点。

我的做法是算每个弹跳周期的机械能漂移率。取支撑相中段到下一个支撑相中段的机械能 E = 0.5·m·(vx² + vz²) + m·g·z + 0.5·k·(L0 − L)²,如果控制律是能量自洽的,相邻周期的 E 应该只在一个小范围里波动;持续为正说明每次触地都在多注入能量,迟早越跳越高直到失控;持续为负说明在耗散,早晚会衰减到停。

import numpy as np def energy_drift(log, m, g, L0, k, fps=200): """log 每行: [t, x, z, vx, vz, L],按触地事件切分成若干周期""" t = log[:, 0] E = 0.5 * m * (log[:, 3]**2 + log[:, 4]**2) + m * g * log[:, 2] \ + 0.5 * k * (L0 - log[:, 5])**2 # 用 z 的局部极小值作为触地时刻,简化版峰值检测 idx = [i for i in range(1, len(z) - 1) if log[i, 2] < log[i-1, 2] and log[i, 2] < log[i+1, 2]] if len(idx) < 3: return None cycle_E = np.array([E[i] for i in idx]) drift = np.diff(cycle_E) / cycle_E[:-1] # 相邻周期的相对能量变化 return {"cycles": len(idx), "mean_drift": float(drift.mean()), "std_drift": float(drift.std()), "series": drift}

这段代码做的事情很直白:从日志里还原每个时刻的机械能,用机身高度 z 的局部极小值定位触地时刻,把能量按周期切片,算相邻周期的相对变化。fps是日志采样率,低于 200 Hz 时峰值检测会漏掉触地点,建议日志至少按 500 Hz 记录。k要填实测的等效刚度,不是配置文件里那个名义值,两者差 20% 以上就会算出错误的能量。

对应的验收指标我一般这么定:

指标计算方式合格线超标说明什么
机械能漂移率相邻周期能量相对变化的均值|mean| < 5%正值偏大是能量注入过多,负值偏大是耗散过强
漂移离散度相对变化的标准差std < 8%触地条件不一致,重点查地面和足垫
落脚点误差收敛比第 n+1 周期落脚点误差 / 第 n 周期< 0.6大于 0.9 说明速度环基本没起作用
俯仰角 RMS支撑相内俯仰角均方根< 3°超过 5° 说明姿态环在硬扛,髋力矩大概率被限幅削平了
触地检测延迟实际触地到状态机切换的时间< 15 ms超过 25 ms 相位机会出现明显滞后,高速时直接失稳

这套指标的好处是可以横向比较。换了新足垫、改了腿长、动了刚度,跑一遍同样的日志分析,五个数字摆在一起,改动是变好还是变坏一目了然,不用再靠“感觉比昨天顺一点”来判断。我现在的习惯是每次上机前先跑三十秒的定速巡航,把这五个数存成基线,之后任何参数改动都必须让这五个数不劣化才允许继续往下试。

回过头看,这套框架最难的地方在于:它把一个看起来无限复杂的问题,硬生生砍成了三条各管一段的规则。一旦你相信了这个砍法,剩下的就是耐心核对每个假设还剩多少成立。我刚接触的时候总想着一上来就把四足调稳,后来才明白应该先把一条单腿弹到能量漂移率 3% 以内,那之后四足的很多问题会自己消失。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询