5. 步态控制
站立控制只需维持一组关节目标,行走则要持续回答三个问题:哪条腿着地、摆动腿向哪里落、执行器如何跟踪关节目标。一条可解释的模型驱动管线可以把它们拆成:
步态时钟 -> 足端轨迹 -> 逆运动学 -> 关节目标角 -> 关节 PD 伺服
本章使用 §4 的真实 Pupper v3 mesh 模型,同一个脚本支持 walk、trot、pace、bound 和 gallop。它是 开环步态生成 + 关节闭环 PD 跟踪:步态层不使用足端接触、机身姿态或速度反馈,关节层会根据实际关节角和角速度修正跟踪误差。
本章目标
- 用周期、占空比和相位偏移描述四足步态
- 生成连续的支撑相与摆动相足端轨迹
- 用数值 IK 将足端目标转为十二个关节目标角
- 在 MuJoCo viewer 中对比 walk、trot、pace、bound 和 gallop
- 区分步态层开环控制与关节层闭环 PD
前置阅读
5.1 识别常见步态
四足步态的核心区别是落足顺序和支撑时长。walk 通常保留三足支撑,trot 使用两组对角腿,pace 改为两组同侧腿,bound 让前腿对与后腿对交替,gallop 用非对称四拍顺序留出腾空相。walk、trot、bound 和 gallop 的典型节奏如图 1 所示,pace 可以与 trot 对照理解。

| 步态 | 落足关系 | 占空比 | 稳定性 | 典型用途 |
|---|---|---|---|---|
| walk | RL → FL → RR → FR | 0.75 | 高 | 低速、谨慎落足 |
| trot | FL + RR,FR + RL | 0.50 | 中 | 常规巡航 |
| pace | FL + RL,FR + RR | 0.50 | 中低 | 相位对照、侧倾分析 |
| bound | RL + RR,FL + FR | 0.42 | 低 | 高速直行、跳跃 |
| gallop | RR → RL → FL → FR | 0.28 | 低 | 高速奔跑 |
表中的占空比来自本章 demo 预设,是用于比较相位关系的起点,不是所有机器人通用的唯一参数。高速步态还需要自由基座动力学、落地缓冲和姿态稳定;固定基座 demo 只检查相位和足端轨迹。
5.2 编排步态相位
步态图把一轮周期

步态时钟由三类参数决定:
- 周期
:完成一轮步态的时间,周期越短,步频越高。 - 占空比
:单腿在一个周期内处于支撑相的比例。 - 相位偏移
:第 条腿进入支撑相的时刻,用周期比例表示。
本章脚本把
当
def leg_phase(t: float, leg: str, t_cycle: float, gait: Gait):
t_local = ((t / t_cycle) - gait.phase_offsets[leg]) % 1.0
if t_local < gait.duty:
return True, t_local / gait.duty
return False, (t_local - gait.duty) / (1.0 - gait.duty)
返回值中的 s 是当前支撑段或摆动段内的归一化进度。下游足端轨迹只需处理 in_stance 和 s,不需要知道当前是哪种步态。
5.3 生成足端轨迹
足端目标在髋关节局部坐标系中运动。支撑相内,脚相对地面保持不动,从机身看就是由前向后扫;摆动相内,脚抬起并从后方回到前方。两段轨迹如图 3 所示。

设步长为
摆动相的足端目标为
对应实现直接返回三维目标:
def foot_trajectory(s, in_stance, step_length, step_height, stand_height):
if in_stance:
x = step_length * (0.5 - s)
z = -stand_height
else:
x = step_length * (s - 0.5)
z = -stand_height + step_height * np.sin(np.pi * s)
return np.array((x, 0.0, z), dtype=float)
sin(\pi s) 保证摆动相起点和终点回到地面高度,但它不保证端点垂向速度为零。自由基座和真机落地需要用摆线、五次多项式或 Bezier 曲线进一步限制端点速度和加速度。
5.4 串联 IK 与 PD
足端轨迹输出的是几何位置,MuJoCo actuator 接收的是关节目标。整条控制链如图 4 所示。

PupperLegIK 使用 mj_jacSite 读取真实模型的足端雅可比,通过 DLS 迭代求解 HAA、HFE 和 KFE 三个关节角。四条腿的髋偏置、左右镜像和关节限位全部来自 pupper_v3.xml,避免了解析玩具 IK 与 mesh 几何不一致。
def gait_step(kin, t, step_length, seed, *, t_cycle,
step_height, stand_height, gait):
target = {}
for leg in LEG_ORDER:
in_stance, s = leg_phase(t, leg, t_cycle, gait)
foot_base = HIP_OFFSETS[leg] + foot_trajectory(
s, in_stance, step_length, step_height, stand_height
)
q = kin.ik(foot_base, leg=leg, q_seed=seed[leg])
seed[leg] = q
target[leg] = q
return target
seed 保留每条腿上一帧的 IK 结果,使数值迭代从附近解开始,减少关节角在不同解支之间跳变。
关节目标角写入 data.ctrl 后,真实模型的 affine actuator 等价于
demo 在编译模型时使用
5.5 配置五种步态
run_gait_control.py 用同一个 Gait 数据结构保存相位偏移、占空比、周期、步长、步高和前进速度。步态切换不会改变足端轨迹、IK 或 PD 代码。
| 步态 | 相位偏移 | 周期 | 步高 | 前进步长 |
|---|---|---|---|---|
| walk | RL 0.00、FL 0.25、RR 0.50、FR 0.75 | 0.90 s | 3.0 cm | 3.5 cm |
| trot | FL/RR 0.00、FR/RL 0.50 | 0.50 s | 3.5 cm | 5.0 cm |
| pace | FL/RL 0.00、FR/RR 0.50 | 0.50 s | 3.5 cm | 5.0 cm |
| bound | RL/RR 0.00、FL/FR 0.50 | 0.45 s | 4.5 cm | 6.5 cm |
| gallop | RR 0.00、RL 0.12、FL 0.45、FR 0.57 | 0.50 s | 5.0 cm | 8.0 cm |
表中周期和步高对应 forward 预设;inplace 将步长置零,并对部分步态使用稍低的步高。每种步态都有两个实验方向:
inplace:保持 weld 位置不变,用于检查抬腿顺序。forward:按预设速度平移 weld,用于观察足端的前后扫动。
5.6 检查原地步态
在 codes/practices/quadruped/cs123 目录运行:
# macOS 的交互 viewer 必须用 mjpython
uv run mjpython 5.gait-control/run_gait_control.py \
--gait trot --viewer inplace
# Linux 和 Windows 使用 python
uv run python 5.gait-control/run_gait_control.py \
--gait trot --viewer inplace
原地 trot 如图 5 所示。step_length=0 使足端只做垂向抬落,适合检查 FL + RR 与 FR + RL 的对角同步关系。

检查顺序为:
- 确认机身和 mesh 位置没有错位。
- 确认应同相的腿同时抬起和落下。
- 确认摆动腿的足端有明显离地间隙。
- 确认关节连续运动,没有 IK 解支跳变。
机身被 weld 固定,因此 base z std 接近零是约束的结果,不能当作真实地面稳定性证据。
5.7 检查前进轨迹
uv run mjpython 5.gait-control/run_gait_control.py \
--gait trot --viewer forward
forward 预设给足端一个非零步长,并按预设速度平移 base weld。从跟随机身的相机中可以看到支撑相向后扫、摆动相向前回收,如图 6 所示。

forward是轨迹可视化,不是自由基座行走证明。机身位移由 weld 直接给定,不是足地接触力积分出的运动;因此不能用它评估实际速度误差、能耗、打滑或抗扰性。
不带 --viewer 时,脚本会为指定步态同时生成 inplace 和 forward 两段 GIF:
uv run python 5.gait-control/run_gait_control.py --gait walk
uv run python 5.gait-control/run_gait_control.py --gait trot
uv run python 5.gait-control/run_gait_control.py --gait pace
uv run python 5.gait-control/run_gait_control.py --gait bound
uv run python 5.gait-control/run_gait_control.py --gait gallop
剩余四种前进步态的轨迹对比如图 7 所示。walk 依次落足,pace 两条同侧腿同步,bound 前后腿对交替,gallop 按非对称四拍运动。 组合图可以用以下命令重新生成:
uv run python 5.gait-control/run_gait_control.py --comparison

输出文件遵循 lab5_<direction>_<gait>.gif 命名,例如 lab5_inplace_trot.gif 和 lab5_forward_gallop.gif。如果需要先生成 GIF 再打开 viewer,可以组合 --gif 与 --viewer。
5.8 划定控制边界
“开环”和“有 PD”可以同时成立,因为它们描述的是不同控制层。
| 控制层 | 输入 | 输出 | 是否闭环 |
|---|---|---|---|
| 步态时钟 | 仿真时间、预设相位 | stance/swing 和段内进度 | 否 |
| 足端轨迹 | 步长、步高、站高 | 足端位置 | 否 |
| 数值 IK | 足端目标、上一帧解 | 关节目标角 | 几何迭代,不是运动反馈 |
| 关节 PD | 目标角、实际角、角速度 | 关节力矩 | 是 |
| base weld | 预设位置 | 机身约束力 | 强制约束 |
当前步态层没有使用以下反馈:
- 足端接触检测和接触时刻的相位重置
- IMU 测得的 roll、pitch 和 yaw
- 期望速度与实际速度的误差
- 足端打滑、法向力和落地冲击
解除 weld 后,仅靠时钟驱动的 walk/trot/pace/bound/gallop 很容易走偏、打滑或翻倒。一条完整的模型驱动运动控制器通常还需要机身姿态稳定、落足点调整、接触力分配或 MPC。RL 步态 则将这些修正能力融入策略学习。
5.9 对比全局时钟与 CPG
本章控制器使用统一的全局相位,四条腿通过固定偏移读取局部相位。CPG 通常为每条腿配置振荡器,通过振荡器间的耦合保持相位关系,如图 8 所示。

全局时钟参数少、易于调试,适合建立 baseline。CPG 能通过耦合调整节奏,但只有在接触或姿态信号真正反馈到振荡器时,才能比固定时钟更好地恢复协调。把全局相位作为强化学习观测也很常见,因为它为策略提供了明确的周期结构。
参考资料
- CS123 Lecture 5: Model-based Gait Control
- Marc Raibert, Legged Robots That Balance