高层控制和低层控制的边界在哪?两套指令体系彻底讲清
- 分清高层控制和低层控制各自在解决什么问题,以及边界为什么划在那里
- 看懂位置、速度、力矩三种低层控制模式的差异,知道足式机器人为什么离不开力矩控制
- 判断自己的项目该接哪一层,以及贸然用低层接口要承担什么风险
第一次翻宇树 SDK 的示例目录,很多人会卡在同一个地方:为什么 g1、h1、h2、r1 这些型号目录下面,都整整齐齐地分了 high_level/ 和 low_level/ 两个子文件夹?
同一台机器人,给两套接口,还非要用目录隔开——这不是冗余,这是整套系统里最重要的一条设计边界。理解了它,你才知道自己的项目该往哪一层接;理解不了,轻则绕远路,重则把硬件弄坏。
这篇就专门讲这条线。照例说明:本文基于公开仓库的文档与代码结构,我没有真机可以验证,涉及运行行为的部分以官方文档的说法为准。
两套接口,两个抽象层次
先用一句话把差异说清楚:
- 高层控制:你说「往前走,速度 0.5」,机器人自己决定四条腿怎么迈。
- 低层控制:你说「1 号关节转到这个角度、施加这么大力矩」,一共十几个关节,每个都由你来说。
这不是「简单版」和「专业版」的区别,而是你在这套系统里承担的责任完全不同。
高层控制下,机器人内部跑着厂商写好的运动控制器——它管步态、管平衡、管落脚点、管摔倒保护。你只是给它下达意图。
低层控制下,那个控制器被你替换掉了。平衡是你的责任,步态是你的责任,摔了也是你的责任。
从消息定义上能直接看到这两条路径。在 unitree_dds_wrapper 生成的消息清单里,两组名字并列存在:
LowCmd_ / LowState_ —— 低层:关节级
SportModeCmd_ / SportModeState_ —— 高层:运动模式级
而 LowCmd 里装的是什么,看相邻的消息类型就知道了:
MotorCmd_ / MotorCmds_ —— 单个 / 一组电机指令
MotorState_ / MotorStates_ —— 电机状态
IMUState_ —— 姿态
低层这条路上,你面对的是一个个电机。
低层的三种控制模式
老一代的 unitree_legged_sdk 把这件事暴露得更直白。它的示例目录是这样的:
example/example_position.cpp —— 位置控制
example/example_velocity.cpp —— 速度控制
example/example_torque.cpp —— 力矩控制
example/example_walk.cpp —— 行走(高层)
example/example_joystick.cpp —— 手柄
前三个是低层的三种模式,第四个是高层。这个目录本身就是一份教学大纲。
位置控制:告诉关节转到某个角度,它自己想办法过去。最直观,也最像我们熟悉的舵机——之前讲舵机驱动时用的就是这个模式。缺点是「硬」:它会不惜代价到达目标角度,撞到障碍物也会硬顶。
速度控制:告诉关节以某个角速度转。适合需要匀速运动的场合。
力矩控制:直接告诉关节输出多大力矩。这是足式机器人绕不开的模式,原因下面细说。
为什么足式机器人离不开力矩控制
想象你端着一杯水下楼梯。你的腿不是"转到某个精确角度",而是在控制着腿部的软硬程度——脚落地的瞬间要软一点吸收冲击,支撑身体时要硬一点。
纯位置控制做不到这件事。设想一台纯位置控制的四足机器人走到一块石头上:它的腿"认为"自己应该在某个角度,但石头把脚顶高了,位置环发现有偏差,于是拼命输出更大电流去纠正——结果是把自己弹起来,或者把电机烧了。
力矩控制换了个思路:不规定位置,规定"你用多大力气推地面"。地面高一点低一点,腿会自然顺应,因为你要求的是力,不是位置。
实际的关节控制往往是几种模式的混合——同时给位置目标、速度目标和力矩前馈,再配上刚度和阻尼参数。这就是常说的阻抗控制:让关节表现得像一根可以调节软硬的弹簧。刚度调高,腿就硬;调低,腿就软。
这也解释了足式机器人为什么要用那种特殊的关节电机:普通带大减速比的伺服电机因为齿轮箱摩擦大、反向驱动困难,很难做精确的力矩控制。足式机器人普遍用低减速比的准直驱方案,就是为了让力矩能被精确地控制和感知。
边界为什么划在这里
有人会问:既然低层这么强,为什么还要有高层?直接全用低层不好吗?
因为让一台机器人稳定行走,本身就是一个巨大的工程。
高层接口背后,是完整的运动控制栈:步态生成、质心轨迹规划、落脚点选择、平衡恢复、状态估计、摔倒保护。这套东西可能是一个团队做了几年的成果。你调用高层接口,等于免费用上了这一整套。
反过来,低层接口的价值在于它是研究和创新的入口。你想验证一个新的控制算法、想训练一个强化学习策略、想让机器人做一个厂商没想过的动作——这些都必须绕过内置控制器,从关节级接管。
我们在强化学习那篇里讲的整条训练链路,最终产物就是一个直接输出关节指令的策略网络。它走的正是低层这条路。
所以这条边界的本质是:厂商把"让机器人正常工作"和"让机器人做新东西"这两件事,用接口层次分开了。 大多数应用开发者停在高层,研究者和算法工程师才下到低层。
从目录结构看能力分布
回到 unitree_sdk2 的 include/unitree/robot/ 目录,各型号的模块构成可以横向对比:
go2:sport、video、vui、config、robot_state、obstacles_avoid、utrackb2:sport、front_video、back_video、config、robot_state、motion_switcherg1:loco、arm、audio、agv、common/terminationsh1:locoh2:loco、arm、common/terminations
这些都是高层能力模块——sport(四足运动)、loco(人形运动)、arm(手臂动作)都是"下达意图"级别的接口。低层控制不在这里,它走的是前面说的 LowCmd 话题,是另一条通道。
b2 那个 motion_switcher(运动模式切换)值得单独说一句。既然有"切换",就说明机器人内部存在多个运动模式,而模式之间的切换需要一个受控的过程——你不能在机器人正跑着的时候突然换掉它的控制器。这类接口的存在,本身就暗示了低层接管也不是随时随地可以发生的,需要先把机器人置于正确的状态。
unitree_rl_gym 的真机部署文档里也强调了这一点:部署前要确保机器人处于调试模式。这不是走过场。
安全:低层控制真正的门槛
老 SDK 的头文件清单里有一个文件很显眼:
include/unitree_legged_sdk/safety.h
在一个总共只有十来个头文件的 SDK 里,安全被单独拎出来做成一个模块。这个信号很明确:低层控制是危险的。
危险在哪?
你的程序会成为控制回路的一部分。 高层控制下,你的程序卡住一秒,机器人顶多站着不动。低层控制下,控制指令每毫秒都要按时送达——你的程序卡住一秒,意味着一秒内没有新指令,机器人可能直接失控倒地。
这也是为什么老 SDK 的 README 提到示例要用 sudo 运行以进行内存锁定:锁住内存页防止被换出,是实时程序的标准做法,为的就是消除不可预测的延迟尖峰。
指令的量纲直接对应物理输出。 位置指令写错一个数量级,关节会以最大能力冲向一个荒谬的角度。力矩指令写错,输出的是真实的力。这里没有"运行报错",只有硬件的物理反应。
必须有物理层面的兜底。 这就是为什么 unitree_sdk2 的各个示例目录里 gamepad.hpp 反复出现,unitree_rl_gym 的真机部署代码里有专门的 remote_controller.py——真机调试时,永远要有一个人握着遥控器,能在任何时刻切断你的程序输出。
如果你要走低层这条路,这几条是底线:先在仿真里跑通、在真机上从最保守的参数起步、始终有人在旁边握着急停、控制程序做好超时保护。
你的项目该接哪一层
接高层,如果你做的是应用:巡检、导航、跟随、遥操作、和大模型结合做交互——这些场景关心的是"机器人去哪、做什么",不关心每条腿怎么迈。用高层接口,把精力放在你的业务逻辑上。
接低层,如果你做的是控制或学习研究:验证新的控制算法、部署强化学习策略、复现论文、研究新步态。这时候内置控制器反而是障碍。
中间还有一条路容易被忽略:先用高层把整个系统跑通,验证你的应用逻辑没问题,再针对某个具体环节下沉到低层。绝大多数项目并不需要从第一天起就自己写平衡控制。
如果你还没接触过真实的足式机器人,最划算的路径是先在机器人与具身智能卷里用 ESP32 做一台自平衡车——自平衡的原理和PID 控制在小车上跑通之后,你对"控制回路不能卡"这件事会有切身体会,再看这套工业级 SDK 的安全设计,就不会觉得是小题大做了。
三句话总结
高层是"说意图",低层是"给关节下命令",中间隔着一整套运动控制栈。
力矩控制不是可选项,它是足式机器人能适应地形的物理基础。
低层接口把控制回路的责任交给了你,包括实时性和安全性——这是能力,也是负担。
关于这两代 SDK 在通信机制上的演进,以及为什么从 UDP 换到了 DDS,我们在这一卷的另一篇里对着读。