Go1四足机器人运动控制入门:从速度指令到闭环与低层关节控制 四足机器人这个东西第一次上手最容易踩的坑不是代码写不出来而是你明明把指令发出去了机器狗却趴在地上一动不动或者猛地一窜把你吓一跳。这篇内容围绕消费级四足平台 Go1 的运动控制开发展开属于这个系列的第二篇重点讲“怎么让它真的动起来、动得听话、动得安全”。我会把高层运动接口的速度指令、模式状态机、步态与身姿参数、状态反馈解析、低层关节控制这几块拆开讲中间穿插我自己调试时的踩坑记录。适合已经能把 PC 和机器狗网络打通、但还没真正让机器狗走起来的开发者也适合想把机器狗做成移动底盘、做巡检或做算法验证的同学。看完你应该能独立写出一套“站立—行走—转向—急停”的完整控制脚本并且知道每一步为什么这么设参数。1. 先想清楚操控机器狗到底在操控什么刚接触这套平台的人脑子里通常有两种极端想法。一种是觉得它就是个“会走路的玩具”发个前进指令就应该傻乎乎地走过去另一种是觉得必须从十二个电机的力矩开始一点点算才算“真正的控制”。这两种想法都会让人在第一天就卡住。真实情况介于两者之间厂商已经把“怎么抬腿、怎么维持平衡”这层最难的活封在底层控制器里了露给你的是一套运动学与步态的参数化接口你要做的是在这套接口之上表达清楚“我想让它以什么姿态、什么速度、朝哪个方向走”。1.1 从“发速度指令”到“十二条腿关节出力”的控制链路把控制链路拆开看大致是四层。第一层是你写的应用代码产生一个期望速度向量包含前后速度、左右速度、转向角速度三个分量有时再加一个身高的期望值。第二层是机器狗机载的主控它拿到这个向量后结合当前 IMU 姿态、足端触地信息、关节编码器读数跑一遍步态规划算出每条腿在当前这个步态周期里应该落在哪个落足点、抬多高、支撑多久。第三层是单腿逆运动学把落足点换算成髋、大腿、小腿三个关节的角度。第四层才是电机驱动把角度指令变成电流通过关节里的行星减速器输出力矩。这条链路意味着一个很重要的事实你发的是“意图”不是“动作”。你发“向前 0.4 米每秒”机器狗内部会自己决定先迈哪条腿、抬多高、身体前倾多少。所以当你看到机器狗走得不直、走着走着歪了、或者原地踏步不走问题往往不在“速度给错了”而在模式没切对、姿态估计漂了、或者地面摩擦系数和默认步态不匹配。理解这一点之后调试的排查顺序就顺了先确认模式对不对再确认指令有没有被接收然后才去看步态参数最后才怀疑硬件。1.2 为什么优先选高层运动接口而不是直接拧关节几乎每个新手都会问我同一个问题能不能不用高层接口直接给十二个关节角度答案是能但强烈不建议作为第一个上手方案。原因有三个。第一是安全。高层接口内部有平衡控制器兜底你给一个略微超范围的速度它会限幅你给一个突然的姿态变化它会做平滑过渡。低层关节控制没有这层兜底你给错一个角度机器狗会直接把腿硬掰到那个位置轻则关节过载保护停机重则扭伤结构件、摔机。第二是效率。用高层接口二十行代码就能让机器狗走起来用低层接口你得先标定零点、测出各关节的角度限位、设计站立姿态、写插值、调 PD 参数没有几天时间跑不通。第三是可维护性。高层接口的输入是物理量速度单位是米每秒转向单位是弧度每秒换一台机器狗、换一个场地参数大体还能用。低层接口的输入是关节角度一旦换了机器型号、换了腿部长度全部要重新标定。所以我的建议很明确先把高层接口玩透能稳定地走、稳定地转、稳定地停再去碰低层。低层接口的定位不是“更高级的控制”而是“做特殊动作、做算法研究、做故障诊断”时才需要动用的工具。2. 落地前的环境准备与网络打通运动控制的调试八成的时间浪费在通信上。我见过太多人代码写得没问题就是网线插错了口、本机网卡没配对地址、防火墙把 UDP 拦了然后对着屏幕怀疑人生。2.1 有线直连的地址规划与网卡配置消费级四足平台通常留了一个以太网口机载主控自己有一个固定网段。常见的做法是机器狗侧占一个网段你的开发机在同一网段里配一个不同尾号的地址直连网线或者通过一个小交换机接进来。以常见的地址规划为例机器狗主控可能是192.168.123.161这类地址你的开发机配成192.168.123.100子网掩码255.255.255.0。配置方式在 Linux 下用命令行最直接# 查看网卡名一般是有线口 ip link show # 临时配置地址重启失效 sudo ip addr add 192.168.123.100/24 dev eth0 sudo ip link set eth0 up # 确认路由和地址 ip addr show eth0要提醒一句如果你的开发机同时还连着无线网两个网卡的默认路由可能打架。稳妥的做法是把无线网卡的默认路由权重调低或者干脆调试期间只留有线和本地回环。配完地址第一件事是ping。ping 得通说明链路层和网络层都通了后面才谈得上应用层。ping -c 4 192.168.123.161注意ping 不通先别改代码。按“网线是否插对端口—本机地址是否同网段—防火墙是否放行—机器狗是否已经上电启动完成”这个顺序查四步之内基本都能定位。2.2 指令端口与状态端口的分工这套平台的通信设计是上下行分离的你往一个端口发指令从另一个端口收状态。指令走的是“高层运动控制端口”状态上报走的是“状态查询端口”两者端口号不同一条 UDP 连接管不了两件事。为什么要这么设计因为上行是低频的、小包的、要求低时延下行是高频的、大包的、可以容忍少量丢包。分开之后状态包再大也不会堵住指令通道指令通道再忙也不影响状态刷新。理解了这个设计意图你就不会犯“把状态包发到指令端口上”这种低级错误。实际写代码的时候我会开两个 socket一个绑定本地任意端口用于发送指令另一个绑定状态端口用于接收。如果状态端口是 8007 这类需要独占的端口注意别让别的进程占着。import socket CMD_ADDR (192.168.123.161, 8080) # 指令下发目标 STATE_PORT 8007 # 本地监听状态端口 cmd_sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) state_sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) state_sock.bind((0.0.0.0, STATE_PORT)) state_sock.settimeout(1.0)端口号的具体数值请以你手上这版官方头文件和示例代码为准不同固件版本可能有差异我这里给的是便于理解的结构。写代码前先去翻一遍官方的通信说明比事后猜要省时间。2.3 通信链路自检的三步法我自己固化的自检流程是三步每次换机器、换电脑、换系统都跑一遍。第一步看状态包有没有进来。只收不发跑十秒看能不能收到稳定的状态数据。如果一包都收不到问题在链路层或者端口占用别往下走。第二步看状态包内容是否合理。打印 IMU 的俯仰、横滚机器狗平放在地上时这两个值应该接近零打印电池电压应该在正常区间。如果打印出来全是零或者全是乱码说明结构体解析的字节序或者字段偏移错了。第三步发一条“最安全”的指令。什么是安全指令就是让它趴下或者保持阻尼状态的那条。确认这条指令能让机器狗从站立切到趴下说明你的上行通道是通的且解析是对的。只有做完这三步我才会去发运动指令。实操心得很多人跳过第三步直接发前进指令结果机器狗状态不对一下窜出去撞到桌腿。用一条“趴下”指令做终点验证成本最低、风险最小。3. 高层运动模式用一套速度指令把机器狗“赶”起来高层运动接口的核心是一个状态机不是一个连续可调的旋钮。这一点必须先说清楚否则你会一直在“为什么我发了速度它不动”这个问题上打转。3.1 模式编号与状态机的正确切换顺序机器狗内部维护着一个当前模式你每发一包指令里面都带着一个模式字段。机载控制器看到模式变了会做相应的过渡动作。切换是有顺序要求的不能从“阻尼趴地”直接跳到“高速行走”。常见的模式分类大致是这几类模式类别典型编号范围行为描述使用场景阻尼/被动0 附近电机卸力腿可以手动摆动搬运、收纳、故障后安全状态站立/平衡1 至 3撑起身体保持水平上电后的第一个稳定状态低速行走3 至 5小步幅、高步频、稳室内、狭窄空间、精细对位高速行走6 至 8大步幅、有腾空相平整开阔场地特殊动作10 以上坐下、爬起、上台阶等演示、特定任务正确顺序是上电 → 阻尼状态 → 站立模式等身体完全撑起→ 低速行走模式 → 给速度 → 减速到零 → 回站立 → 阻尼。每一步之间留出足够的过渡时间站立到行走我一般留 1 到 2 秒行走回站立也留 1 秒以上。为什么不给过渡时间会出问题因为站立时四条腿是同时着地的四足支撑行走是两两交替的步态。如果模式一切换你立刻给一个大速度控制器还没完成步态初始化就会出现“腿不知道该往哪落”的瞬间表现为抽搐或者趔趄。def switch_mode(mode, hold1.5): 切换模式并按指定时间保持期间持续以 20Hz 发同一条指令 t0 time.time() while time.time() - t0 hold: send_cmd(modemode, vx0.0, vy0.0, vyaw0.0) time.sleep(0.05)这段代码里的 20 赫兹不是随便写的。指令下发频率太低控制器会认为你掉线了自动降级到安全状态频率太高也没意义反而占带宽。实测 10 到 50 赫兹之间都比较稳我习惯用 20 赫兹。3.2 速度指令的四个分量与量纲换算一条运动指令里最关键的几个量是前后速度、左右速度、转向角速度有时再加一个侧向偏移或者身高的微调。它们的量纲必须搞清楚这是新手最容易翻车的地方。参数物理含义常用量纲消费级平台典型上限备注前后速度沿机体纵轴平移米/秒1.0 至 1.5超过上限会被限幅或降级左右速度沿机体横轴平移米/秒0.5 左右侧移能力弱于前后转向角速度绕垂直轴偏航弧度/秒1.5 至 2.5正负号决定左右需实测确认身高机身离地高度米0.15 至 0.35调高更灵活调低更稳关于正负号我必须强调不同固件版本、不同坐标定义符号可能是反的。别照抄别人的代码就上电。我的做法是先把所有速度设成很小的值比如前后速度给 0.1观察机器狗是往前还是往后转向角速度给 0.2看是左转还是右转。确认方向之后再写进常量里。速度给多少合适我的经验值是这样室内木地板或者瓷砖地面前后速度 0.3 到 0.6 米每秒比较舒服转向 0.5 到 1.0 弧度每秒室外平整水泥地可以提到 0.8 到 1.0地毯这种软地面要往下降因为足端会陷进去实际步幅会被吃掉。还有一个细节速度指令是渐变的不是跳变的。你要从 0 加速到 0.5中间应该有几个中间值。直接跳变会让控制器内部的参考轨迹出现阶跃表现为机器狗猛地一顿。我一般写一个斜坡函数用 0.3 到 0.5 秒完成加速。def ramp_to(target, current, step0.05): 简单的线性斜坡避免速度阶跃 if abs(target - current) step: return target return current step * (1 if target current else -1)3.3 步态与身姿参数的调法除了速度高层接口一般还暴露几个“手感”参数调对了机器狗走起来像模像样调不对就像喝多了。抬腿高度决定越障能力和稳定性。抬高一点过门槛、过线缆不容易绊抬太高落足冲击变大机身晃动明显能耗也上去了。室内平地我一般用偏低的档位需要过障碍时临时提高。步频决定单位时间迈几步。步频高、步幅小走起来稳但慢适合精细对位步频低、步幅大速度快但对地面平整度要求高。有个经验关系步幅乘以步频约等于前进速度所以你想要更快要么加步频要么加步幅但两者都有上限。机身高度影响重心和腿部余量。降低身高重心低抗侧向扰动能力强但腿部伸展余量小过坎容易顶到升高身高越障余量大但重心高急转弯时更容易侧倾。姿态角包括俯仰和横滚的期望值。这两个参数在爬坡和走斜面时特别有用。我做过一个斜坡测试纯靠速度指令上坡机器狗会一直低头把俯仰角设一个小的抬头补偿值走起来就顺多了。注意这个补偿值别设太大超过十几度控制器会认为姿态异常。注意这些参数不是调一次就完事。换地面、换负载、换电池电量最优值都会变。我的做法是把常用的几套参数写成配置字典按场景切换而不是每次现场手调。4. 闭环控制与状态反馈的实际用法只会发速度不叫开发叫遥控。真正让机器狗“听话”的关键是把状态反馈用起来形成一个闭环。4.1 从状态包里挖出可用的观测量状态包里的信息量比大多数人以为的多。常用的几类观测量是姿态类横滚、俯仰、偏航三个角以及对应的角速度。这是做航向保持和防倾倒的基础。偏航角是累积的会漂但短时间内的变化量是可信的。速度类机体坐标系下的三轴线速度以及足端的估计速度。这个值是估出来的不是直接测的会有延迟和噪声做控制时要滤波。位置类机身位置和足端位置。机身位置是积分出来的长时间会漂足端位置相对可靠可以用来判断是否打滑。状态类电池电压、电流、各电机温度、足端接触力。这几项是做安全保护的关键尤其是电机温度和电池电压我建议每次调试都打印出来。状态机类当前模式、当前步态、是否有故障标志位。故障标志位一定要读有些异常控制器会自己降级但如果你不读标志位就会以为是自己的代码有问题。解析状态包时最容易出的错是结构体对齐和字节序。我建议先用官方的解析示例跑通确认字段值合理再自己写。自己从头写解析一不小心少算一个填充字节后面所有字段全错位。4.2 用偏航角做航向保持的简易闭环这是最实用、收益最高的一个小闭环。机器狗走直线时因为地面摩擦不均、腿部差异、IMU 零偏走十几米就会偏出好几度。加一个航向闭环直行精度立刻上一个档次。思路很朴素记下起步时的偏航角作为目标运行中读当前偏航角算误差用比例控制输出转向角速度。Kp_yaw 1.2 yaw_target None def heading_hold(current_yaw, base_yaw): err wrap_to_pi(base_yaw - current_yaw) # 处理跨 ±pi 的情况 vyaw Kp_yaw * err return max(-1.0, min(1.0, vyaw)) # 限幅这里有两个坑。第一个坑是角度环绕偏航角从接近正 pi 跳到负 pi如果不做归一化误差会瞬间变成两倍 pi机器狗会突然原地转一圈。所以一定要写wrap_to_pi。第二个坑是增益。比例系数给大了机器狗会左右摇摆像蛇一样给小了纠偏太慢走完十米还是歪的。我的经验是从 0.8 开始试逐步加到出现轻微摆动再回调 30%。这个系数和速度有关速度越快同样的增益越容易振荡所以高速时要把增益降下来。同样的思路可以用在横滚保持上比如走斜坡时防止侧倾。但横滚闭环要谨慎因为横滚的可用调节手段少调过头容易翻。4.3 频率、时延与平滑处理闭环控制的性能一半取决于增益一半取决于时延。这条链路里至少有四个时延源你的采集周期、网络传输、机载控制器的处理周期、执行机构的响应。实测下来从你发指令到机器狗真正动起来端到端延时有几十毫秒量级。这个时延决定了你的闭环带宽上限也就决定了增益不能给太大。另外状态包和指令包的频率最好对齐。我习惯统一在 20 到 50 赫兹。如果你用 100 赫兹发指令但状态只刷新 20 赫兹那中间四帧你用的是旧数据闭环效果反而差。还有一点容易被忽略发送线程和接收线程要分开。如果用单线程“发一包、收一包、睡一会”一旦收到一个超大状态包或者发生一次丢包重传指令节奏立刻乱掉。用两个线程发送线程只管按固定周期发接收线程只管收和解析中间用共享变量交换最新状态。实操心得我在接收线程里加了一个“最后一包时间戳”主控线程每次读状态前检查这个时间戳超过 200 毫秒没更新就触发安全逻辑把速度归零并切回站立。这个机制救过我好几次——网线被扯掉的时候机器狗不会一直按着最后的指令往前冲。5. 低层关节控制什么时候需要怎么不炸机前面说了不建议新手直接上低层但有些场景绕不开做一个特定的蹲起动作、做降落缓冲、做单腿支撑的算法验证、或者排查某个关节是否异常。这时候就必须用低层接口。5.1 低层模式的准入条件与安全前置进入低层模式之前我强制自己做四件事一件都不能省。第一件把机器狗吊起来或者放在软垫上。用支架把机身架空四条腿悬空这样即使控制量给错腿也只是在空中乱蹬不会带着机身摔倒。第二件确认关节编号和正方向。十二个关节的编号顺序、每个关节的正方向必须实测确认。方法是给某一个关节一个很小的正角度指令观察实际转动方向。正负号搞反是低层控制最常见的事故来源。第三件设置保守的限幅。角度限幅、角速度限幅、力矩限幅三个都要设。角度限幅防止超出机械限位角速度限幅防止突然抽动力矩限幅防止过载。我的习惯是第一次测试把力矩限幅设到额定值的 30% 以内。第四件准备好随时断电。手放在电源开关旁边或者用一个能立刻执行的急停脚本。这一条听起来很土但真的有用。低层控制还有一个前提电机必须先使能。使能之后电机会保持当前位置这时候腿是硬的推不动。这个状态下你才能开始发角度指令。如果不使能直接发角度指令会被忽略。5.2 KP/KD 与力矩限幅的取值逻辑低层控制的每个关节都要配两个增益位置增益和速度增益有人叫阻尼增益。它们的物理意义是位置增益越大关节越“硬”越努力回到目标角度但太大会振荡速度增益越大关节运动越“黏”起到阻尼作用抑制过冲。这里有个生活化的类比位置增益像弹簧的劲度系数速度增益像减震器的阻尼。弹簧太硬一碰就弹阻尼太小弹起来停不下来。场景位置增益取向速度增益取向说明站立保持中等偏高中等要撑得住但不能硬碰硬慢速摆动中等中等偏高摆动平滑优先快速摆动偏低偏高减少冲击和过冲触地缓冲偏低高靠阻尼吸收冲击悬空测试从低往高试从低往高试逐步加别一步到位取值没有通用答案因为和机器狗自重、腿部惯量、关节减速比都有关系。我的实操路径是先在悬空状态给一个很小的增益命令一个十度的小角度看响应慢慢加位置增益直到出现轻微过冲再回退然后加速度增益把过冲压下去。整个过程可能调几十组参数做好记录。这里有个反直觉的点增益不是越高性能越好。太高的位置增益会让电机持续高频修正电流波动大、发热快长时间跑下来电机温度会顶到保护阈值。我吃过这个亏一次连续测试二十分钟后机器狗自动趴下查了半天代码最后发现是电机过热保护。5.3 从站立姿态插值开始的第一个动作低层控制的第一个动作我建议做“从站立姿态小幅摆动一条腿”而不是“让机器狗走”。走涉及步态规划、重心转移、触地检测变量太多。具体做法是读当前十二个关节的角度作为起始姿态定义一组目标角度比如只改右前腿的髋关节五度然后做插值过渡。def interpolate(start, target, duration, rate100): 从 start 到 target 平滑插值duration 单位秒 steps int(duration * rate) for i in range(steps 1): alpha i / steps # 用余弦插值两端速度为零过渡更柔和 s 0.5 - 0.5 * math.cos(math.pi * alpha) q [a (b - a) * s for a, b in zip(start, target)] send_joint_cmd(q) time.sleep(1.0 / rate)这段代码里用余弦插值而不是线性插值原因是线性插值在起点和终点速度不为零会有“一顿”的感觉余弦插值两端速度为零过渡更柔和对机械结构更友好。这个技巧在低层控制里非常常用几乎所有平滑过渡都能用。插值完成后保持在目标位置几百毫秒观察有没有振荡、有没有异响再决定是否继续下一步。整套动作确认无误后才考虑把它串成一个序列。6. 常见问题与排查实录这一节是我实际调试中积累的按“症状—可能原因—排查动作”整理成表遇到问题可以直接对照。6.1 指令发了没反应这是最高频的问题。九成的情况不是通信问题而是模式没切对或者指令格式不对。我遇到过一次特别典型的状态包收得好好的指令也发出去了机器狗就是不动。折腾了半小时最后发现我发的是站立模式站立模式下速度字段是被忽略的。也就是说你在站立模式下给前后速度机器狗会老老实实站着。还有一种情况是模式切对了但指令包里少算了一个校验字段。有些固件版本会对指令做校验校验不通过直接丢弃不报错。这种最难查因为抓包看数据是发出去了。解决办法是拿官方的示例代码原样跑一遍确认能动能走再往里面加自己的逻辑。6.2 抖动、抽搐与“走路发飘”抖动分三种。第一种是高频小幅抖动通常是指令频率和状态频率不同步或者你的控制循环有抖动。检查方法很简单把控制循环改成固定周期看抖动是否消失。第二种是周期性大幅度摆动一般是增益给大了。把转向或者横滚的比例系数降下来立竿见影。第三种是偶发抽搐很可能是丢包或者时延尖峰。用抓包工具看 UDP 的间隔如果有超过几百毫秒的空洞那就是网络问题。检查网线质量、交换机、以及你的接收线程有没有被阻塞。“走路发飘”是另一种表现机器狗走起来脚底像踩棉花轨迹不直落脚点乱。这种情况先看地面。地毯、草地、松散砂石这些软地面会让默认步态失效需要提高抬腿高度、降低速度、加大步频。如果地面没问题再看 IMU 零偏把机器狗静置几秒看姿态角是否稳定如果静止时姿态角还在缓慢变化说明需要重新标定。6.3 安全与硬件保护清单运动控制这块安全不是一句口号是要写成代码的。我的脚本里固定有这几条保护保护项触发条件动作状态超时超过 200 毫秒未收到状态包速度归零切站立电压低低于阈值按机型定减速提示返航或停机电机过温任一电机温度超阈值降速持续超温则趴下姿态异常横滚或俯仰超过限值立即停止运动回站立指令超范围速度超上限限幅记录日志心跳丢失上位机停止发指令超过 1 秒控制器自身降级到安全状态注意这些保护逻辑要在发送线程之外实现不要写在发送循环里。发送循环一旦卡住保护逻辑也跟着卡住等于没有保护。另外一个容易被忽略的点是电池。四足机器狗在高速运动时电流很大电压会瞬间被拉低。如果你设的电压保护阈值太高稍微一加速就触发保护停机设太低又起不到保护作用。我的做法是看空载电压和满载电压的差值把阈值设在两者之间偏下的位置并且加一个持续时间的判断避免瞬时压降误触发。最后说个小技巧调试阶段我强烈建议在机器狗周围清出一片空地至少留出两米的余量并且先把速度上限钉死在 0.3 米每秒以内。等所有逻辑跑通、所有保护验证过再往上调。我见过太多人一上手就给满速结果机器狗一头撞上墙修一次腿的钱够买好几块开发板。慢一点稳一点后面调参的时候你会感谢当初的自己。