你有没有见过一个人形机器人站立时,脚底平稳,上身却时不时微微颤动?那个瞬间你会意识到,它身上真正撑着平衡的不是某一个电机,而是整套控制系统在背后不断"修正错误"。人形机器人控制的特殊挑战,恰恰是大多数人一开始低估的部分:以为把关节电机驱动起来就能走路,结果一放开就是原地倒下。
这篇文章是我在控制系统入门系列里特意单独拎出来讲的一篇。目标读者是有一定自动控制原理或机器人学基础、但刚接触足式/人形平台的同学,也适合想系统了解"人形机器人为什么难控制"的工程师。我会从控制系统的基础语言讲起,再逐条拆解人形机器人独有的特殊挑战,最后给一个简化的腿部力控设计实例,配合可直接复现的思路,帮你在进入复杂框架之前先把地基夯实。
1. 人形机器人的控制问题:为什么它和机械臂完全不是一回事
1.1 从"固定基座"到"浮动基座":复杂度的一次质变
工业机械臂之所以好控制,核心原因是它的基座被螺栓死死固定在地面或底座上。整个机械臂的运动学链条从固定的基座开始,末端的位置、速度可以直接用关节角表达,动力学模型里也不需要考虑基座自身的运动反馈。换句话说,机械臂的控制问题是一个经典的"串联刚性连杆在重力场中的轨迹跟踪"问题,自由度少则6个、多则14个,模型相对干净。
人形机器人则完全不同。它没有物理上固定在地面的基座,整个身体是一个"浮动基座"系统。这意味着躯干、头部、手臂的位置姿态不是由地面直接支撑的,而是在每个瞬间由腿部与地面的接触力、身体惯性、关节力矩共同决定。这个过程用教科书语言说,是从欧拉-拉格朗日方程建立的浮动基座动力学:
M(q) q̈ + C(q, q̇) q̇ + G(q) = τ + J_c^T F_c
其中 q 包含浮动基座的6个广义坐标(3个位置+3个姿态)以及所有关节角;F_c 是地面接触点施加的外力。机械臂动力学里不需要 F_c 这一项,因为基座是固定的、接触点在基座上;而人形机器人恰恰是靠这一项维持生命——没有足底接触力,没有平衡。
从控制角度,这个差异不是"多几个自由度"那么简单,而是本质性的:你控制的是漂浮在空间中、受重力支配、只通过支撑脚与地面产生非完整约束的刚体系统。自由度数从6跳到30多,外部接触从"确定"变成"事件驱动",动态耦合从"弱"变成"强"。所有教科书里的线性定常假设,在这里基本全部失效。
1.2 三个常见任务,三个完全不同的控制目标
控制系统的任务,决定了控制器的设计方向。人形机器人最常见的任务是站立、行走、操作,它们对应的目标差异非常大。
站立平衡:目标是保持质心投影落在支撑多边形内,同时在受到外部扰动时能快速回到平衡点。这里控制器的核心是"刚度"+ "阻尼"——既要抵抗重力带来的倒下趋势,又要避免刚性过度导致振荡。工程上常用膝关节力矩补偿、踝关节阻抗调节,控制频率至少1kHz。
行走平衡:目标和站立有很大区别。行走过程中,机器人大部分时间实际上处于"受控失稳"状态——质心投影会在支撑多边形边界附近甚至之外移动,但通过下一步落点不断重建新的支撑多边形,从而形成动态稳定。这时控制的目标不是"保持在原地",而是"保持有界的不稳定运动",需要规划层和稳定层协同工作。
操作与移动结合:在搬东西、推门、握持物体时,机器人末端与外界交换力,身体受外部力的影响更复杂。比如推一扇门,力的反作用会通过手臂传到躯干再传到腿部,控制目标变成了"在接触力变化时维持身体平衡并平滑执行运动"。这个难度的提升,是很多人第一次意识到"手和脚不能分开控制"。
我们做控制系统的人喜欢把问题转化为数学形式,但前提是选对目标函数。人形机器人每换一种任务,控制问题的约束集、代价函数甚至状态定义都不同,这是它区别于单一机械臂应用的第一道坎。
2. 控制系统的基础语言:把机器人的身体翻译成数学
2.1 状态、输入、输出:状态空间视角其实是"记账系统"
控制理论入门必提状态空间,但在机器人领域,很多人只停留在"用矩阵写微分方程"的层面,没有理解它在身体上的物理对应。简单来说,状态就是把机器人当前"在干什么"完整记下来所需的全部信息:每个关节的角度和角速度、躯干的位置和速度、姿态和角速度。输入是关节力矩,或者你设计的虚拟力,输出则是你关心的物理量——质心位置、足底力、躯干姿态、末端速度等等。
写成数学形式,就是状态方程 ẋ = f(x, u),其中 x 是几十维的状态向量,u 是关节力矩向量。人形机器人的 f 是非线性的、时变的,而且取决于地面接触情况。有些人习惯把这当作"理论书上的抽象符号",实际上它就是源代码里每个控制周期都在更新的那个向量。
用生活类比来讲,状态空间就像给机器人身体开了个"总账本"。每个控制周期,你需要知道身体每部分在哪、以什么速度运动,才能决定下一步该施加多大力量。账本记录得越准,控制越可靠。所以状态估计(从传感器数据推算全身状态)是人形机器人控制里的基本功,它本质上是在给账本记账,而控制器只是用这本账做决策。
2.2 为什么人形机器人几乎不可能开环控制:三个残酷前提
开环控制的思路是:不给反馈,只按预计算的输入序列运行。机械臂在某些高精度场景都很少这么做,因为哪怕建模再准,执行器摩擦、负载变化、温度漂移都会让末端偏出去。而人形机器人,开环控制从原理上就注定失败,原因有三:
第一个前提是模型必须百分之百准确。人形机器人每个人的质量分布、关节阻尼、执行器响应都不一样,而且身体在运动时质量和惯量分布是动态变化的,你根本不可能离线算出一组固定的力矩轨迹让机器人稳稳站住。
第二个前提是扰动必须不存在。地面上一个小石子、一阵风、腿部驱动器一个细微的电流波动,都会导致机器人偏离预定轨迹。而没有反馈的系统,偏离之后只会越来越大。
第三个前提是执行器必须是无误差的理想力矩源。现实中无论是电驱还是液压,都有响应延迟、力矩纹波和摩擦,你期望的力矩和实际输出的力矩之间有动态差异。
这三个前提,机械臂都很难满足,人形机器人更是全盘崩溃。所以人形机器人控制领域中,几乎所有的控制器都是闭环的,而且通常是"内层高速闭环 + 外层慢速规划"的结合。
2.3 由内到外:绝大多数机器人控制器的分层骨架
理解了"必须闭环",再看具体怎么组织反馈链。人形机器人的控制器几乎都是分层结构,从外到内大体是:
最外层是任务级规划器:决定"下一步走到哪里、手臂怎么摆",频率通常在30-100Hz,不需要每毫秒都做出反应。下一层是全身运动生成器:把任务目标转换成全身关节期望轨迹、质心轨迹、足底力分配,频率在200-1kHz。再往内是力/力矩控制器:根据期望轨迹和当前状态,输出每个关节的力矩指令,频率1kHz左右。最里层是关节执行器伺服环:通常由驱动器完成,频率可能到4-20kHz,负责把力矩指令快速转化为电机电流。
分层的目的很朴素:高层任务不需要快速响应,低层动力学需要极快响应;如果所有控制逻辑都挤在1kHz的大循环里,状态估计、优化求解、动力学计算都会超时。把低频决策与高频伺服分开,每个层次都能用最合适频率和算法。这也是我后来调试四足和双足平台时最深的体会之一:上来先搞清楚我的控制循环里,哪些模块该跑高频、哪些该跑低频,否则后面全是时序灾难。
3. 人形机器人的五个"特殊挑战":教科书很少细讲的细节
3.1 高维状态与实时算力压力
人形机器人的自由度通常在20到40之间。每个自由度至少带来角度和角速度两个状态,加上浮动基座的12个状态,全身状态向量轻轻松松超过80维。在这个状态下做全身动力学控制,每一步都要解一个高维优化问题:任务空间是几十维的,接触约束是多个接触点的不等式,还有关节力矩限幅、加速度限幅,目标函数里还包含任务优先级。
麻烦在于,这一切必须在1毫秒到几毫秒内完成。1kHz控制频率意味着留给控制器的时间窗口是1ms,你需要在里面完成状态估计的更新、期望轨迹的查表或者插值、全身动力学方程的计算、优化问题的求解,最后把力矩指令发给执行器。常规做法有几种:
- 不搞全身优化,而用"简化模型 + 补偿":把全身动力学简化为倒立摆模型或线性倒立摆模型(LIP),然后把简化模型的输出作为高层参考,再用全身动力学做反馈补偿。
- 采用分层优化:先解任务优先级高的(比如质心轨迹)、再解任务优先级低的(比如姿态跟踪),每层只求一个低维QP(二次规划)。
- 离线预计算 + 在线查表:把很多最优策略离线算好,线上只做插值。
这个算力压力很多人一开始感受不到,真要动手做实时控制才会发现,你的代码写得再漂亮,偶尔一个malloc都会让控制周期抖动一毫秒。所以做人形机器人控制的第一课其实是工程纪律:避免动态内存分配、避免复杂分支、对每个循环的耗时做严格审计。
3.2 欠驱动:你是靠"脚"而不是靠"腰"站稳的
"欠驱动"这词听上去抽象,落在人形机器人上非常直观:身体只有脚下踩地的地方能产生外部力,躯干本身没有被任何东西直接撑住。你想想,人在站立时,肚子、后背没有一个电机推着你;你之所以没倒,是因为脚掌和地面的摩擦力、以及腿部关节力矩传递到地面的反作用力,间接支撑了整个身体。
用倒立摆类比:把人体等效为一个倒立摆,支点是脚踝或脚底接触点,躯干是摆杆,腿部关节通过改变支点位置、施加地面反力来稳定摆杆。经典倒立摆模型的状态只有四五维,但能揭示一个重要结论:你没法直接驱动躯干姿态,只能通过控制支点的加速度来间接影响它。
所以人形机器人身上,控制输入的一部分对象其实是"不能直接驱动"的物理量。这在数学上表现为:浮动基座6个自由度中没有直接的执行器输入,只能通过接触力间接控制;系统方程中部分广义坐标对应的广义力恒为零。控制系统要对这一点有清醒的认识——你想控制躯干姿态,不能直接对躯干加力矩,只能靠脚底力的阶跃、重心的移动间接实现。这也是为什么"质心轨迹规划"和"足底力分配"是人形控制里绕不开的两块内容。
3.3 非最小相位特性:"先往反方向动"其实是正常现象
这个东西看起来最理论,却直接影响实际调参。非最小相位系统的典型特征是:阶跃响应一开始会往期望方向的反向走,然后再回调。著名现象是飞机升降舵偏转时,飞机会先稍微低头、然后再抬头——被控制的俯仰角存在一个"反向瞬态"。
人形机器人的腰、腿部系统里也存在类似特性:当你往某个方向快速移动质心时,由于动力学耦合,身体某部位会先出现反向偏移,或者加速度对力的响应具有瞬态反向。如果控制器不考虑这一点,用的增益很高,系统很容易在瞬态反向阶段产生振荡甚至发散。
更实际的影响是:非最小相位特性限制了反馈控制系统能达到的性能上限。教科书里的Bode积分定理告诉我们,在某些频段降低灵敏度的同时,必然在其他频段增加灵敏度——"水床效应"。人形机器人动力学里的右半平面零点决定了你的闭环带宽高到一定程度就无法再稳定,盲目加增益只会让系统咔咔作响甚至倒下。这也是为什么纯位置环在双足系统上很难把频率调高:刚性关节位置环会把非最小相位动态的相位滞后变成激励,导致控制回路发振。
3.4 接触不确定:地面不是你的朋友,至少不完全可控
机械臂末端操作时,接触是工具,是可控的;人形机器人行走时,接触是生命线,却也是最大的不确定性来源。你永远不知道下一步踩到的地面有多硬、摩擦系数多大、表面有没有凹凸。而且地面接触是离散事件:脚落下之前没有接触约束,落下之后突然出现大量不等式约束;脚抬起腾空阶段又是完全自由的浮动基座动力学。
这类"离散-连续混合"动态最难系统化处理。控制器的每个控制周期都要根据当前接触状态切换模型:单支撑期、双支撑期、腾空期、冲击阶段,模型各不相同。足底冲击力可能在几十毫秒内产生非常大的峰值,对状态估计和控制器都是高难度考验。
从控制理论看,处理接触不确定有几个方向:一是把地形信息加入估计器,在线预测接触位置和法向;二是设计鲁棒控制器,让控制系统对一定范围的地面参数变化不敏感;三是用全身动力学优化,把接触力约束显式建入QP中。每种方法都有代价:鲁棒控制可能牺牲一定精度,全身优化对模型精度和算力要求都很高。我们遇到的真实情况通常是:仿真里的接触参数和真机差别很大,调好的控制器,换一块地面就明显退步。
4. 从公式到代码:一个简化的腿部力控设计实例
前面讲了很多"难",这部分我们动手做一个非常简化的单腿站立控制,体会力控的基本思想。目标不是做成能扛住真机的完整控制器,而是把原理吃透:用虚拟弹簧阻尼控制腿部,让机器人腿部对外力有柔顺性,同时保持目标位置。
4.1 建模:两连杆腿、雅可比与动力学
先用最简单的平面二连杆模型模拟一条腿。大腿长度 l1、小腿长度 l2,髋关节角 θ1、膝关节角 θ2。足端位置(相对于髋关节)的正运动学是:
x = l1 sin(θ1) + l2 sin(θ1+θ2) z = l1 cos(θ1) + l2 cos(θ1+θ2)
控制腿部运动,我们更关心足端速度与关节角速度的关系,这就是雅可比矩阵 J:
v = J * q̇
J = [[l1 cosθ1 + l2 cos(θ1+θ2), l2 cos(θ1+θ2)], [-(l1 sinθ1 + l2 sin(θ1+θ2)), -(l2 sin(θ1+θ2))]]
这套式子虽然简单,却是全身控制里每个模块都会反复用到的基础。你要是能自己推导并写出 J,后续理解重心雅可比、接触雅可比就容易很多。动力学完整表达式比较复杂,这里不做完整展开,但有一条核心关系必须强调:实际要施加给关节的力矩 τ,与期望在足端产生的笛卡尔空间力 F 之间,满足 τ = J^T F。
4.2 虚拟弹簧阻尼:用最朴素的力控思想
人体站立时,腿部肌肉不是僵硬地"钉死"关节,而是兼具弹性和阻尼:弹性让身体能被扰动后回到原位置,阻尼让回弹过程不振荡。控制器也要模仿这个特征。虚拟模型控制的核心思想就是:设置一个从期望足端位置到当前足端位置的"虚拟弹簧-阻尼器",按偏差生成笛卡尔空间期望力:
F_des = Kp * (p_des - p) - Kd * (v_des - v)
然后把 F_des 映射成关节力矩 τ = J^T F_des。这就是最简单的虚拟模型控制/阻抗控制雏形。它和位置控制的区别在于:位置控制直接给定关节角目标,然后用力矩死死把关节拉过去;虚拟模型控制给定的是力的期望,关节被表现得像弹簧,容许一定的顺应——遇到地面冲击时腿会自动"让"一点,而不是硬碰硬。
4.3 伪代码级实现框架
下面给一个示意级Python框架,重点是逻辑结构:
import numpy as np # 简单二连杆腿参数 l1, l2 = 0.4, 0.4 # 大腿、小腿长度(m) g = 9.81 def forward_kinematics(theta1, theta2): """关节角 -> 足端位置(相对髋关节)""" x = l1 * np.sin(theta1) + l2 * np.sin(theta1 + theta2) z = l1 * np.cos(theta1) + l2 * np.cos(theta1 + theta2) return np.array([x, z]) def jacobian(theta1, theta2): """关节角速度 -> 足端速度的雅可比矩阵""" J = np.array([ [l1*np.cos(theta1) + l2*np.cos(theta1+theta2), l2*np.cos(theta1+theta2)], [-(l1*np.sin(theta1) + l2*np.sin(theta1+theta2)), -l2*np.sin(theta1+theta2)] ]) return J def virtual_force(p, v, p_des, v_des): """虚拟弹簧阻尼: 笛卡尔空间期望力""" Kp = 800.0 Kd = 60.0 return Kp * (p_des - p) - Kd * (v - v_des) def control_step(theta1, theta2, dtheta1, dtheta2): """一个控制周期: 关节角状态 -> 关节力矩""" p = forward_kinematics(theta1, theta2) J = jacobian(theta1, theta2) qd = np.array([dtheta1, dtheta2]) v = J @ qd p_des = np.array([0.0, 0.85]) v_des = np.array([0.0, 0.0]) F = virtual_force(p, v, p_des, v_des) tau = J.T @ F return tau在真实系统中,你需要在1kHz循环里反复调用类似逻辑,并且把上面省略的模块补上:状态估计器提供躯干姿态和足端位置,动力学前馈提供重力/科氏力补偿,力矩限幅保护执行器,接触检测决定控制模式切换。
4.4 调参与验证的注意点:先看力矩再看姿态
用这套代码做仿真实验时,有几个经验值得分享:
第一,Kp、Kd的比例要按物理体系来选。Kp的单位是N/m,Kd是N·s/m,它们的量级取决于机器人质量。一个20kg的单腿系统,Kp取几百上千是合理的;Kp太小腿会绵软,Kp太大会振荡,Kd的作用则是在临界附近抑制振铃。调参时先用固定Kp加小的Kd,对阶跃扰动做仿真,观察足端轨迹的振荡次数和阻尼比。
第二,把J^T算错是最常见的bug。雅可比矩阵的符号、转置方向直接决定力矩正负,稍不留神腿就往相反方向蹬。我的习惯是先在仿真里做一次"期望力到关节加速度"的开环验证,确认力的方向正确,再接入闭环。
第三,关节力矩限幅一定要放在控制器输出之后、发给执行器之前。人形机器人关节能承受的力矩有限,虚拟模型计算出的力矩可能远超限幅,如果不砍,下一次动力学计算就会用"实际没有发生"的假想力矩,整个控制状态会慢慢失真。先看输出力矩有没有频繁触限,再看姿态稳不稳,这是我调试腿部控制器的固定顺序。
5. 给刚入门者的学习路径与避坑建议
5.1 一条建议的学习路径
很多同学一上来就想跑最新的全身控制器,但我建议路径还是按部就班:
第一,数学基础:线性代数、刚体运动学、刚体动力学。特别是雅可比、惯性矩阵、李群李代数,后面都是这些工具在反复使用。第二,经典控制:传递函数、PID、根轨迹、频域分析,理解反馈的基本逻辑。第三,现代控制:状态空间、LQR、状态观测器/卡尔曼滤波,建立"状态估计+状态反馈"的框架思维。第四,机器人专项:正逆运动学、动力学、ZMP/质心动力学、足式机器人简化的倒立摆模型。第五,优化入门:二次规划、线性规划、简单约束优化,能读懂代价函数和约束在优化问题里的写法。第六,动手仿真:拿一个开源物理仿真环境,把单腿站立控制跑通,再扩展到四条腿、两条腿。
这条路径走下来,你对控制系统的理解会非常扎实。我的体会是,人形机器人控制最难的不是某一个方程的推导,而是把这些模块串成一个能在实时循环中稳定运行的整体。你走得慢一点,每层都用手推一遍代码,后面排查问题会轻松很多。
5.2 几个新人特别容易踩的误区
第一个误区是"PID能解决一切"。PID对单输入单输出、弱耦合系统很好用,但人形机器人是多变量强耦合系统,单纯给每个关节一套独立PID,往往会在动态任务中互相打架。不是说PID没用,而是需要把它放在一个合理的框架里:用动力学模型处理耦合,用PID处理残留误差。
第二个误区是"仿真跑通了就能上真机"。仿真和真机之间至少有四个差异:传感器噪声与延迟、执行器响应延迟与非线性、模型参数误差(质量、惯量、摩擦)、地面接触特性差异。仿真调好的参数,真机上轻则抖动重则摔机。我自己在调试腿部控制器时,真机第一次起脚下的期望,跟仿真完全不是一回事。所以一定要在仿真里刻意加入噪声、延迟、参数不确定性,提前压榨控制器的鲁棒性。
第三个误区是"主控频率越高越好"。高频控制确实能让伺服响应更快,但频率越高,对状态估计的质量、传感器数据延迟、通信时序的要求也越高。如果状态估计有5ms延迟,你就算跑10kHz的控制环也没用,甚至会因为增益太高而放大噪声。频率的选择要和状态估计精度、执行器响应速度匹配,不是无脑拉高。
第四个误区是"强化学习可以取代经典控制"。近些年强化学习在双足运动生成上进步很快,能产生非常自然的动作,但它对仿真环境与真实世界差距的敏感性依然很高,训练安全问题也更难保证。我的态度是:经典控制提供了稳定的基准和理解框架,强化学习提供了复杂运动生成的思路,两者是互补关系,不是替代关系。
5.3 工具选型与仿真到实机的鸿沟
工具选型方面,我建议初学者优先选择支持刚体动力学、接触模型的开源物理仿真引擎,并且确认它能方便地设置关节力矩控制接口、读取接触力,不然你连一条腿都控不稳。物理引擎的参数校准很重要:碰撞摩擦系数、弹性恢复系数、仿真步长都直接影响控制效果,默认参数经常不适合足式机器人场景。
更实际的问题是把控制代码迁移到实机。仿真环境里,你可以随时暂停查看状态;实机上,每个控制周期只有一个死板的循环。代码里任何一个耗时不确定的函数都会导致控制周期抖动,可能直接反映为关节振荡。我的建议是:从一开始就让你所有的控制代码满足实时约束——不用动态内存分配、不用系统调用、所有中间量预分配好。听起来太抠门,但在1kHz控制周期下,这些细节确实是生死线。
至于工具链,先用最简单的方式把你的雅可比推导和虚拟模型控制写出来,手撸一遍以后再用现成库优化。很多人一开始就依赖框架,遇到问题都不知道是自己的模型错了还是库的用法错了。先小再大,先简后繁,这条路径最适合人形机器人这个复杂度极高的领域。
最后再分享一点个人体会。做过几轮足式平台之后,我的感受是人形机器人控制的门槛不在于某一个理论分支有多深,而在于所有环节必须无缝衔接:建模、状态估计、控制器、执行器、传感器、实时系统,每一项掉链子都会把你打回原点。所以如果你刚开始做,不要贪多求快,先把一条腿的虚拟模型控制在仿真里调到"推一下能回来、不振荡",再谈全身。稳扎稳打,比什么都有用。