简介:面向机器人研究者、工程师与学生的四足机器人步态控制入门资源,围绕步态周期、运动学模型和稳定性控制展开,配有可运行的Python实现代码。文档从四足行走基本思路切入,系统讲解支撑阶段与摆动阶段的交替、交替步态与对角步态的协调规则,并针对重心与支撑三角形的关系说明如何利用逆向运动学调整腿部位置,避免倾倒。随后以字典存储四条腿坐标,通过move_leg函数控制前进后退、walk函数按对角步态循环模拟行走,并附上代码运行结果与逐段解析,帮助读者将理论映射为实际逻辑。资源包为单个docx文档,容量约17KB,内容紧凑便于通读或按节检索。已有592名学习者查看,适合作为步态控制快速入门资料,也为进一步探索复杂地形下的四足机器人控制打下基础。
1. 四足机器人步态控制:四条腿怎么走出稳定节拍
四条腿的机器人难的不是站住,而是走起来。站住只需要静态稳定,走起来每时每刻都要打破平衡再找回平衡,支撑、摆动、落脚必须被编排成一套可以循环的节拍,这正是四足机器人步态控制在解决的问题。这篇笔记以一个可运行的 Python 实现为线索,拆解机器人控制里的运动学模型、步态周期、对角步态相位分配,以及代码背后的参数与坑。核心就一句话:四条腿的行走问题,大半是时序问题,小半是稳定性判断问题,先算清节拍再谈代码,才不会跑起来就翻车。
2. 步态周期与运动学模型:先把腿的“节拍”算清楚
四足机器人行走时,四条腿不是各走各的。所有步态控制算法都在回答两个问题:一条腿什么时候支撑、什么时候摆动;四条腿之间如何错开相位。本章先把这两个问题的底子打牢。
2.1 支撑相与摆动相:步态周期的最小单位
任何一条腿在一个完整步态周期里都会经历两个阶段。支撑相(stance phase)里,腿踩在地面上,承受机器人的部分甚至全部重量,为身体提供向上的力和向前的推进力;摆动相(swing phase)里,腿离开地面,向前摆动并准备落到新的落脚点。两个相位合起来,就是一个步态周期 T。
四条腿的相位组合方式直接决定了步态类型。如果任意时刻都有三条腿支撑,这种步态叫行走步态,特点是稳定但速度慢,适合在不平整地面或教学验证场景中使用;如果只有对角方向的两条腿同时支撑、另外两条腿同时摆动,就是原文采用的对角步态。对角步态速度更快,但支撑腿从三条变成两条,稳定性从“静态稳定”退化为“动态稳定”,控制难度随之上升。
这里引入一个容易被新手忽略的指标:占空比,即支撑相占整个周期的比例。占空比大于 0.5 时,任意时刻至少有三条腿支撑,重心位置有较大容错空间;占空比小于 0.5 时,会出现两条腿同时腾空的窗口期,对实时控制频率和姿态反馈的要求明显提高。原文代码里的对角步态,占空比约为 0.5,正好卡在两条腿支撑的动态稳定区间。
提示:占空比是步态控制里最值得先定的参数。它决定了你后面所有稳定性判断用静态判据还是动态判据,也决定了控制循环最低要跑多快。
2.2 腿的坐标模型:用二维坐标描述四腿状态
原文代码里,四条腿没有直接用关节角度表达,而是用二维平面坐标(x,y)表示落脚点的位置。这个简化看起来“土”,但对理解步态控制的核心逻辑反而更清晰:控制算法只负责决定“每条腿在哪个时刻移动到哪个位置”,至于每个关节该转多少度,交给逆运动学去算。
我先定义一个腿部状态字典,这是整套代码的数据基础:
legs = { 'front_left': {'x': 0, 'y': 0}, 'front_right': {'x': 1, 'y': 0}, 'back_left': {'x': 0, 'y': 1}, 'back_right': {'x': 1, 'y': 1}, }这个字典把四条腿的落脚点坐标集中管理,键名直接对应身体四角。x 表示前进方向坐标,y 表示横向坐标。初始值里的 0 和 1 不是真实物理距离,而是归一化位置——你可以理解为机器人站在一块 1×1 的方格地面上,四条腿分别位于方格四个角。实际项目里这个值会替换成从正运动学模型算出来的初始足端位置。
选择字典而不是列表,是我一直保留的习惯。列表用位置索引,代码写长了容易把“第几条腿是哪个元素”记混;字典用语义化键名,legs['front_left']一眼就能看出在操作哪条腿。后面接逆运动学时,这个字典结构也能直接作为输入,不增加额外的类型转换成本。
到这里,运动学模型就有了初始形态:一条腿的运动被抽象为它在机体坐标系中的落脚点坐标随时间的变化。后续加关节角,不需要改步态控制主体,只需要在足端位置的基础上做一次逆运动学换算,得到三个关节角发给舵机或电机即可。
2.3 重心与支撑三角形:稳定性判断的核心约束
原文里反复提到一个判断标准:机器人的重心必须始终落在支撑三角形内。支撑三角形,是指当前所有着地的腿的落点围成的多边形。三条腿支撑时它是一个三角形,判断相对简单;两条腿支撑时它退化成一条线段,重心投影必须落在支撑线段附近很小的范围内,判断难度陡增。
我在阅读原文时注意到一处矛盾的表述:前面说“每一时刻至少三条腿支撑”,后面对角步态又说“每次都有两条腿支撑”。这两句话不能同时成立。实际含义是:“至少三条腿支撑”是静态稳定行走(walk 步态)的前提,适合缓慢、高稳定性的场景;而对角步态(trot)确实只有两条对角腿着地,它的稳定性来自重心位置和运动速度的共同作用,不能再拿“重心在支撑三角形内”当唯一判据。
解决这个矛盾的标准做法,是引入零力矩点(ZMP)概念:在两条支撑腿着地的瞬间,重心投影落在这条支撑线段附近,并且在下一步落脚前,重心始终处于支撑多边形与惯性力的综合平衡点上。说得直白一点,对角步态不是“站得住”,而是“倒向下一只脚的速度刚好够快”。代码示例里没有做这个动态判断,但理解这一点能帮你明白为什么同一套算法,放平地上能走,放到斜坡上就翻。
在真实项目中,我一般会在步态循环里加一个简化检查函数,先算支撑多边形,再判断重心投影是否落在多边形内,超出阈值就减小步距。后面第 5 章会详细展开这个检查最容易在哪个环节失效。
3. 步态协调与相位分配:对角步态为什么稳
有了步态周期和坐标模型,下一步是把四条腿的运动排进同一个时间表里,让它们按顺序动作而不是乱作一团。这一步就是步态协调。
3.1 对角步态 vs 同侧步态:一张对比表
四足步态并非只有一种。常见做法是几种基础步态轮着来:速度慢用 walk,中等速度用对角步态 trot,高速冲刺用弹跳步态。把几种步态放在一起对比,选型逻辑就很清楚了:
| 步态类型 | 同时支撑的腿 | 稳定性来源 | 速度潜力 | 典型场景 |
|---|---|---|---|---|
| 行走步态 walk | 3 条 | 静态稳定,重心在支撑三角形内 | 低 | 不平整地面、教学验证 |
| 对角步态 trot | 2 条(对角) | 动态稳定,靠重心位置与速度维持 | 中 | 平坦地面、中速移动 |
| 同侧步态 pace | 2 条(同侧) | 动态稳定,机身横向晃动较大 | 中 | 个别场合,较少采用 |
| 弹跳步态 bound | 前后两组交替 | 动态稳定,存在腾空期 | 高 | 高速奔跑、跨越障碍 |
原文选择的对角步态,兼顾了稳定性和速度,且实现复杂度低,特别适合作为第一个跑通的步态。同侧步态这里多提一句:它让同侧的前后两条腿同时摆动,机身左右晃动明显,在实机上体验很差,我在某原理样机上试过一次就放弃了,属于踩过坑才知道的教训。
3.2 相位偏移:把四条腿安排进同一个周期
对角步态的命名来自它的相位分配。四条腿被分成两组:前左与后右为对角组一,前右与后左为对角组二。同一组内的两条腿相位相同,组与组之间相位相差半个周期。用相位偏移量表示就是这样:
| 腿 | 相位偏移 | 一个周期内的动作 |
|---|---|---|
| front_left | 0 | 前半周期摆动,后半周期支撑 |
| back_right | 0 | 前半周期摆动,后半周期支撑 |
| front_right | 0.5 | 后半周期摆动,前半周期支撑 |
| back_left | 0.5 | 后半周期摆动,前半周期支撑 |
相位偏移 0.5,意味着第二组腿的动作比第一组晚半个周期。第一组腿进入支撑相时,第二组腿正在摆动;反过来也一样。这样排下来,任意时刻都有一组对角腿着地,机身获得持续的支撑。这也是对角步态在四条腿只有两条着地时依然能稳定的核心原因——支撑状态永远存在,没有完全腾空的瞬间。
3.3 主循环:初始化、更新、检查三步走
控制算法的主流程可以抽象成三步:初始化、更新、检查。初始化设置四条腿的初始位置坐标;更新按相位表推进腿的位置;检查判断重心是否还在安全区域,不在就修正步幅或步距。这个循环在每个控制周期内重复执行,频率通常要高于步态频率的十倍以上。
我习惯在写完整代码前,先把这个三步主循环画成文字结构,再填充函数调用,避免一上来就写代码把时序逻辑搞乱。下面是三步的伪代码结构,实际函数实现放在第 4 章:
# 初始化:设定四条腿初始位置 legs = init_legs() # 更新:按相位偏移推进各组腿 for phase in range(2): if phase == 0: move_group1(legs) else: move_group2(legs) # 检查:判断重心是否在支撑多边形内 if not is_stable(legs): reduce_step_distance(legs)这里的move_group1和move_group2分别操作对角组一和对角组二;is_stable做支撑多边形判定;reduce_step_distance在不稳定时收窄步距。这个结构的价值在于,把“步态逻辑”和“运动学计算”解耦,后面替换成真实机器人时,只需要重写move_leg内部实现,不用动相位分配逻辑。
4. Python 实现:让四条腿按节拍走起来
前面的原理落在代码上,就是一组字典、两个函数和一个主循环。本段我直接给出完整实现,并逐段说明参数含义和容易出错的位置。
4.1 数据结构:用字典描述腿部状态
第一步仍是定义legs字典。初始坐标选择 0 和 1 只是为了演示,真实项目中这个值来自机器人的初始位姿,通过正运动学计算得到:给定初始关节角,算出足端在机体坐标系下的位置,再填入字典。
legs = { 'front_left': {'x': 0.0, 'y': 0.0}, 'front_right': {'x': 1.0, 'y': 0.0}, 'back_left': {'x': 0.0, 'y': 1.0}, 'back_right': {'x': 1.0, 'y': 1.0}, }注意我把坐标值写成浮点数,这是为了后续做步距累加时不会因为整数运算产生舍入偏差。0.1 米连续走十步,浮点累加会得到 1.0,如果初始值是整数 0,运算结果会自动转成浮点,但习惯上我还是会明确写0.0,避免在移植到数值计算库时出现类型不一致的问题。
4.2 核心函数 move_leg 与 walk:位置推进与相位切换
下面是完整的步态控制代码。我修复了原文档里两处对不上号的逻辑:一处是代码排版导致的语法问题,另一处是一次循环内四条腿连续前进但输出只显示两条腿动了的相位问题。
import time legs = { 'front_left': {'x': 0.0, 'y': 0.0}, 'front_right': {'x': 1.0, 'y': 0.0}, 'back_left': {'x': 0.0, 'y': 1.0}, 'back_right': {'x': 1.0, 'y': 1.0}, } step_time = 1 # 单个相位持续秒数,完整周期为 2 秒 step_distance = 0.1 # 每步前进距离,单位米 def move_leg(leg_name, direction, distance): """移动指定腿:direction=1 前进,-1 后退""" if direction == 1: legs[leg_name]['x'] += distance elif direction == -1: legs[leg_name]['x'] -= distance def walk(steps=5): """对角步态:两个相位交替推进两组对角腿""" print("机器人开始行走!") for i in range(steps): # 相位 1:前左 + 后右 同时前进 move_leg('front_left', 1, step_distance) move_leg('back_right', 1, step_distance) print(f"步伐 {i+1} 相位1: {legs}") time.sleep(step_time) # 相位 2:前右 + 后左 同时前进 move_leg('front_right', 1, step_distance) move_leg('back_left', 1, step_distance) print(f"步伐 {i+1} 相位2: {legs}") time.sleep(step_time) if __name__ == '__main__': walk()这段代码的逻辑分三层。legs字典是全局状态,保存四条腿的当前位置;move_leg只做一件事,按方向和距离更新指定腿的 x 坐标;walk是协调者,决定哪个相位让哪组腿前进。重点在walk函数:一次循环代表一个完整步态周期,周期内部再分成两个相位,相位 1 操作对角组一,相位 2 操作对角组二,每次操作后打印当前状态并延时step_time秒。
参数说明:step_time是单个相位的持续秒数,所以一个完整周期是 2 秒;step_distance是每次前进的距离,也就是步幅;steps是行走的步态周期数,对应“走几步”。这三个参数一起决定行走总距离与平均速度:平均速度 =step_distance / (2 * step_time),按当前值就是 0.05 米/秒。
4.3 参数怎么调:步距、周期、步数与速度的关系
参数调整是步态控制里最像“玄学”的部分,但其实有一条明确的物理约束:步距增大,重心偏移范围跟着增大,支撑多边形必须能包住偏移后的重心;周期缩短,腿部摆动加速度增大,对执行器的响应速度要求提高。新手最容易犯的错误,是让step_distance直接决定速度而不考虑支撑范围,结果步距调大后机器人连走三步就摔倒。
一张参数速查表,按我的调试习惯整理:
| 参数 | 作用 | 调大 | 调小 |
|---|---|---|---|
step_distance | 单步前进距离 | 速度快,稳定性下降 | 速度慢,更稳定 |
step_time | 单相位时长 | 节奏慢,控制压力小 | 节奏快,控制压力大 |
steps | 周期数量 | 走得远 | 走得近 |
原则是:先固定step_time,从较小的step_distance起步,比如 0.05,确认支撑稳定后再逐步增大到 0.1、0.15。每次调整只动一个参数,记录它触发失稳的临界值,这套数据会成为你后续切换步态类型的重要参考。某实验室的四足样机调试记录里,我发现它的对角步速上限恰好出现在步距超过支撑三角形内切圆的半径时,阈值非常清晰。
5. 四足步态避坑指南:五个常见翻车点
代码能跑和机器人能走是两回事。这一段我把拆过项目、调过实机过程中反复遇到的坑整理出来,按现象、原因、解决的顺序写,方便你对照排查。
5.1 方向搞反:第一轮就走歪
现象:启动代码后,预期向前,但实际机器人横着移动,或者一组腿前进一组腿后退。
原因:坐标轴定义不一致。示例代码把 x 方向定义为前进方向,但实际机器人的机体坐标系里,前向可能被定义成 y 轴或 -x 方向。还有一个隐蔽点:direction=1表示前进,但如果你在初始化时把后腿的 y 坐标设成和前腿一致,所有腿都在一条直线上,支撑多边形退化为线段,横移现象会被误判为控制器问题。
解决:写代码前先画一张坐标轴定义图,明确前向是哪个轴的正方向。代码里我建议把方向参数和坐标系定义放到常量里,比如FORWARD_DIRECTION = 1,不要直接在调用处写魔法数字。从那次以后,我每换一台新样机,第一件事就是验证move_leg('front_left', 1, 0.1)之后 x 坐标是否真的增大且与机械前向一致。
5.2 只有摆动没有抬腿:腿在地上拖
现象:代码能输出位置变化,但机器人实机走起来发出刺耳的摩擦声,前进阻力很大。
原因:二维坐标模型里,腿只在 x 方向做水平位移,缺少 z 方向的抬腿动作。真实机器人每条腿的落脚点轨迹应该是一段弧线或梯形曲线:先抬起来,前移,再落下。缺少抬腿分量,腿就像在地上拖行。
解决:给落脚轨迹加上 z 方向的高度控制。常见做法是让 z 随相位变化,在摆动相中间时刻抬到最高,两端落回地面。可以直接在move_leg内部增加一个高度字段:
# 给腿部状态加 z 坐标,摆动相中段抬到 0.05 米 legs[leg_name]['z'] = 0.05 * math.sin(math.pi * phase_progress)这里的phase_progress表示当前相位进度,0 到 1 之间。用正弦函数的好处是抬腿高度在起落两端连续,不会产生突变。实际项目中,这个 z 轨迹再经逆运动学映射到髋关节和膝关节角度,比单纯改 x 坐标复杂一个量级,但这是从“模拟行走”走向“真实行走”的必经一步。
5.3 用 time.sleep 当实时控制:步子越走越乱
现象:代码里用time.sleep(step_time)控制节奏,在 PC 上运行正常,跑到控制器上后步态周期明显漂移,有时还会出现某一组腿多移动一次的现象。
原因:time.sleep只能保证最少阻塞这么多秒,不能保证精确的周期。遇上系统调度延迟、其他线程抢占,下一个相位实际开始时间会滞后。控制周期一旦抖动,四条腿的相位关系就会破坏,步态自然乱套。
解决:不要用sleep做实时调度。我一般把主循环改成固定周期,比如 100Hz,用累计时间判断当前相位。框架如下:
start_time = time.time() while True: now = time.time() if now - start_time >= phase_duration: advance_phase() start_time = now这个循环在每个控制周期里检查是否到了相位切换点,而不是让线程傻等。对入门示例来说sleep足够,但如果你要往实机上移植,这一步必须在设计初期就替换掉,否则后面每一步调试都会被时间抖动干扰。
5.4 静态重心检查当成动态判据:跑起来必翻
现象:静止状态检查重心都在支撑三角形内,一旦开始走动,第二条腿还没落地重心就已经偏出去了,机器人踉跄甚至摔倒。
原因:对角步态本身只有两条腿支撑,支撑区域是一条线段,重心投影不可能持续“在三角形里”。如果你还用三条腿支撑的静态判据检查对角步态,任何时刻都会判定为不稳定,或者说,只有你把步距缩到极小才能通过检查,而这会让机器人几乎原地踏步。
解决:对角步态改用动态稳定判据。入门阶段最简单的替身方案是限制步距上限,保证重心偏移速度不超过支撑切换速度;进阶方案是接入零力矩点,用加速度计估计重力方向,实时计算重心投影,控制周期内不断微调步伐。记住一个判断标准:静态判据用于 walk 步态,动态判据用于 trot 及更快步态,混用是调试器也救不回来的。
5.5 步距突然调大:支撑三角形瞬间失效
现象:调参时把step_distance从 0.1 改成 0.3,第一轮步态就能看到重心明显偏移,第三轮直接翻倒。
原因:步距增大后,身体重心在支撑腿切换瞬间的水平偏移超出支撑多边形的覆盖范围。四条腿支撑时,支撑多边形是一个较大的矩形;两条支撑腿时,它退化成线段。步距越大,重心在切换瞬间越接近支撑边界,一旦越过边界就没有任何静态恢复力矩,只能靠动态步法找回平衡。
解决:步距调整遵循“先验支撑边界,再动参数”的流程。我一般会在代码里临时打印每一时刻的重心坐标和支撑腿坐标,手动比较两者距离。如果发现重心投影距离支撑线段边缘小于某个阈值(比如 0.02 米),就把步距调回上一档,同时缩短相位时间提高切换频率。这个阈值在实机上需要用机身姿态数据标定,但在仿真阶段用固定值足够。
6. 进阶方向:把位置步态接进仿真器和闭环控制
跑通这份代码后,下一步不是急着上实机,而是先把它接进仿真环境验证步态周期与稳定性判断。我的习惯是先在仿真器里搭一个简化的四足模型,把legs字典里的坐标作为足端目标位置,逆运动学求解后再发给关节,运行速度和前面 Python 示例一致。
仿真验证重点关注三点:第一,相位切换时机与实际关节响应是否同步;第二,增加 z 方向抬腿轨迹后,足端轨迹是否连续光滑;第三,在支撑腿切换的瞬间,机身姿态是否有异常抖动。仿真里这些问题暴露得越早,实机上花的时间越少。
从位置控制走向闭环控制,核心是接入传感器反馈。最经济的做法是先接入虚拟 IMU,用它给出的机身倾角修正重心坐标,再调整相位切换时机。更接近落地产品的方案是用力矩控制替代位置控制,把每条腿的支撑力纳入优化目标,这会把问题引向动力学建模,复杂度明显上升。对入门者,我建议先完成两步:一是给代码加上 z 轴抬腿轨迹,二是把sleep换成固定周期循环,这两步做完,示例代码就具备了移植到真机的骨架。
回看我自己的调试记录,最容易出问题的地方永远是相位切换和坐标系定义,而不是算法本身。从那以后,我每次搭新的步态控制,都强制自己先画坐标轴定义和相位偏移表,再写循环,最后才考虑参数优化,这套流程帮我挡掉了至少三四次实机上的低水平翻车。希望这篇笔记能帮你少走同样的弯路。
本文还有配套的精品资源,点击获取