开源 | legged_control 开源项目详细解析
基于 OCS2(Optimal Control for Switched Systems 2) 与 ros-control 的足式机器人 NMPC-WBC 控制框架
项目地址:https://github.com/qiayuanl/legged_control
目录
1. 项目概述
legged_control 是一个基于 OCS2(Optimal Control for Switched Systems 2)和 ros-control 构建的足式机器人控制栈与框架,核心采用 NMPC(非线性模型预测控制)+ WBC(全身控制) 的分层控制架构。
该框架的主要优势:
- 据作者所知,这可能是性能最佳的开源足式机器人 MPC 控制框架;
- 可在数小时内部署到 Unitree A1 真机;
- 得益于 ros-control 接口,可方便地移植到自定义机器人。
支持机器人类型:Unitree A1、Aliengo、Go1(可扩展至 Go2 等)。
2. 项目含义与背景
2.1 问题背景
足式机器人(四足、双足)的控制是一个复杂的非线性最优控制问题,难点在于:
- 浮动基座:基座不受直接驱动,需通过接触力间接控制;
- 接触切换:足端与地面接触状态离散切换(stance/swing),形成混合动力学系统;
- 欠驱动:关节数量少于自由度,需协调多任务;
- 实时性:控制频率需达到数百 Hz 才能保证稳定。
2.2 解决思路
legged_control 采用分层控制策略:
- 上层 NMPC:在 1 秒时间窗内求解最优轨迹(位置、速度、接触力),频率约 100~200Hz;
- 下层 WBC:基于当前状态求解关节力矩,考虑完整刚体动力学,频率 500Hz~1kHz;
- 状态估计:卡尔曼滤波融合 IMU、关节编码器、接触信息估计基座位姿与速度。
2.3 学术背景
该框架发表于 IROS 2023,主要参考 ETH Zurich 的 ANYmal 控制方案,并在 OCS2 框架下实现。


3. 系统框架
系统整体数据流如下图所示:
┌─────────────────────────────────────────────────────────────────┐
│ 用户指令 (cmd_vel / goal) │
│ ↓ │
│ TargetTrajectoriesPublisher │
│ (将速度/位置目标转换为状态轨迹参考) │
│ ↓ │
│ ┌────────────────────────┐ │
│ │ NMPC (SQP 求解器) │ ← Gait Schedule (步态) │
│ │ - 质心动力学模型 │ ← Swing Trajectory │
│ │ - 摩擦锥/零速度约束 │ │
│ │ - 二次跟踪代价 │ │
│ └────────────┬───────────┘ │
│ ↓ │
│ 优化状态 x* 与输入 u* (未来 1s 轨迹) │
│ ↓ │
│ ┌────────────────────────┐ │
│ │ WBC (QP 求解器) │ ← 测量状态 (RBD State) │
│ │ - 浮动基座动力学方程 │ │
│ │ - 摆动腿跟踪 │ │
│ │ - 基座加速度跟踪 │ │
│ │ - 接触力跟踪 │ │
│ │ - 力矩/摩擦锥约束 │ │
│ └────────────┬───────────┘ │
│ ↓ │
│ 关节力矩 τ + 关节位置/速度 PD 前馈 │
│ ↓ │
│ ┌────────────────────────┐ │
│ │ 硬件接口 (HybridJoint)│ │
│ │ τ_ff + kp·(q_d-q) │ │
│ │ + kd·(v_d-v) │ │
│ └────────────┬───────────┘ │
│ ↓ │
│ 机器人电机 / Gazebo │
│ ↓ │
│ ┌────────────────────────┐ │
│ │ 状态估计 (Kalman Filter)│ ← IMU + 关节状态 + 接触 │
│ │ 估计基座位姿与速度 │ │
│ └────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
控制循环时序
- 硬件控制循环:500 Hz(
LeggedHWLoop),执行read() → update() → write(); - WBC 循环:与硬件循环同频(500 Hz),在
LeggedController::update()中调用; - MPC 循环:独立线程,100 Hz 求解一次完整 SQP 优化;
- MRT 接口:1000 Hz 评估当前时刻的策略(插值)。
4. 项目结构
legged_control-master/
├── legged_common/ # 公共接口定义
│ └── include/legged_common/hardware_interface/
│ ├── ContactSensorInterface.h # 接触传感器接口
│ └── HybridJointInterface.h # 混合关节接口(力矩+PD)
│
├── legged_hw/ # 硬件接口基类
│ ├── include/legged_hw/
│ │ ├── LeggedHW.h # 硬件接口抽象基类
│ │ └── LeggedHWLoop.h # 控制循环(500Hz)
│ └── src/
│ ├── LeggedHW.cpp # URDF加载/接口注册
│ └── LeggedHWLoop.cpp # 定时循环实现
│
├── legged_interface/ # NMPC 最优控制问题建模 ★
│ ├── include/legged_interface/
│ │ ├── LeggedInterface.h # OCP问题构建主类
│ │ ├── SwitchedModelReferenceManager.h # 步态/参考管理
│ │ ├── LeggedRobotPreComputation.h # 预计算
│ │ ├── constraint/ # 约束
│ │ │ ├── FrictionConeConstraint.h # 摩擦锥
│ │ │ ├── ZeroVelocityConstraintCppAd.h # 零速度(支撑相)
│ │ │ ├── ZeroForceConstraint.h # 零力(摆动相)
│ │ │ ├── NormalVelocityConstraintCppAd.h# 法向速度
│ │ │ ├── SwingTrajectoryPlanner.h # 摆动轨迹规划
│ │ │ └── LeggedSelfCollisionConstraint.h# 自碰撞
│ │ ├── cost/
│ │ │ └── LeggedRobotQuadraticTrackingCost.h # 二次跟踪代价
│ │ └── initialization/
│ │ └── LeggedRobotInitializer.h # 初始化器
│ └── src/ # 对应实现
│
├── legged_wbc/ # 全身控制器 ★
│ ├── include/legged_wbc/
│ │ ├── WbcBase.h # WBC基类(任务构造)
│ │ ├── WeightedWbc.h # 加权WBC(单QP)
│ │ ├── HierarchicalWbc.h # 分层WBC(HoQp)
│ │ ├── HoQp.h # 分层QP求解
│ │ └── Task.h # 任务(等式/不等式)
│ └── src/
│ ├── WbcBase.cpp # 7个任务构造
│ ├── WeightedWbc.cpp # qpOASES 求解
│ ├── HierarchicalWbc.cpp # 零空间分层求解
│ └── HoQp.cpp # 零空间投影
│
├── legged_estimation/ # 状态估计 ★
│ ├── include/legged_estimation/
│ │ ├── StateEstimateBase.h # 估计基类
│ │ ├── LinearKalmanFilter.h # 卡尔曼滤波
│ │ └── FromTopiceEstimate.h # 从话题读取(cheater)
│ └── src/
│ ├── StateEstimateBase.cpp
│ ├── LinearKalmanFilter.cpp # 18维状态KF
│ └── FromTopicEstimate.cpp
│
├── legged_controllers/ # 控制器主入口 ★
│ ├── include/legged_controllers/
│ │ ├── LeggedController.h # 主控制器
│ │ ├── SafetyChecker.h # 安全检查(姿态)
│ │ └── TargetTrajectoriesPublisher.h
│ ├── src/
│ │ ├── LeggedController.cpp # init/update/启动MPC线程
│ │ └── TargetTrajectoriesPublisher.cpp # cmd_vel→轨迹
│ ├── config/
│ │ ├── controllers.yaml # 控制器类型注册
│ │ ├── joy.yaml # 手柄映射
│ │ └── {a1,aliengo,go1}/ # 各机器人配置
│ │ ├── task.info # 任务/代价/约束参数
│ │ ├── gait.info # 步态定义
│ │ └── reference.info # 参考轨迹参数
│ └── launch/
│ └── load_controller.launch # 加载控制器
│
├── legged_gazebo/ # Gazebo 仿真
│ ├── include/legged_gazebo/LeggedHWSim.h # 仿真硬件接口
│ ├── config/default.yaml # 仿真参数
│ └── worlds/empty_world.world
│
├── legged_examples/legged_unitree/ # Unitree 机器人实现
│ ├── legged_unitree_description/ # URDF 描述
│ │ ├── urdf/
│ │ │ ├── robot.xacro # 机器人主文件
│ │ │ ├── common/leg.xacro # 单腿建模
│ │ │ ├── common/transmission.xacro# 传动
│ │ │ └── {a1,aliengo,go1}/const.xacro # 机器人常量
│ │ ├── meshes/ # 3D 模型
│ │ └── launch/empty_world.launch # Gazebo 启动
│ └── legged_unitree_hw/ # 真机硬件接口
│ ├── include/legged_unitree_hw/UnitreeHW.h
│ ├── src/UnitreeHW.cpp # UDP 通信
│ ├── config/a1.yaml # 500Hz/功率限制
│ └── lib/ # Unitree SDK
│
├── legged_control/ # 元包
└── qpoases_catkin/ # qpOASES 依赖
5. 算法原理与公式
5.1 非线性模型预测控制(NMPC)
5.1.1 最优控制问题建模
NMPC 在每个控制周期求解如下连续时间最优控制问题(OCP):
{ min u ( ⋅ ) ϕ ( x ( t I ) ) + ∫ t 0 t I l ( x ( t ) , u ( t ) , t ) d t s.t. x ( t 0 ) = x 0 (初始状态) x ˙ ( t ) = f ( x ( t ) , u ( t ) , t ) (系统动力学) g 1 ( x ( t ) , u ( t ) , t ) = 0 (状态-输入等式约束) g 2 ( x ( t ) , t ) = 0 (纯状态等式约束) h ( x ( t ) , u ( t ) , t ) ≥ 0 (不等式约束) \begin{cases} \underset{\mathbf{u}(\cdot)}{\min} \ \ \phi(\mathbf{x}(t_I)) + \displaystyle\int_{t_0}^{t_I} l(\mathbf{x}(t), \mathbf{u}(t), t)\, dt \\ \text{s.t.} \ \ \mathbf{x}(t_0) = \mathbf{x}_0 \quad \text{(初始状态)} \\ \quad\ \ \dot{\mathbf{x}}(t) = \mathbf{f}(\mathbf{x}(t), \mathbf{u}(t), t) \quad \text{(系统动力学)} \\ \quad\ \ \mathbf{g}_1(\mathbf{x}(t), \mathbf{u}(t), t) = \mathbf{0} \quad \text{(状态-输入等式约束)} \\ \quad\ \ \mathbf{g}_2(\mathbf{x}(t), t) = \mathbf{0} \quad \text{(纯状态等式约束)} \\ \quad\ \ \mathbf{h}(\mathbf{x}(t), \mathbf{u}(t), t) \geq \mathbf{0} \quad \text{(不等式约束)} \end{cases} ⎩ ⎨ ⎧u(⋅)min ϕ(x(tI))+∫t0tIl(x(t),u(t),t)dts.t. x(t0)=x0(初始状态) x˙(t)=f(x(t),u(t),t)(系统动力学) g1(x(t),u(t),t)=0(状态-输入等式约束) g2(x(t),t)=0(纯状态等式约束) h(x(t),u(t),t)≥0(不等式约束)
5.1.2 状态与输入定义
系统状态 x ∈ R 24 \mathbf{x} \in \mathbb{R}^{24} x∈R24 与输入 u ∈ R 24 \mathbf{u} \in \mathbb{R}^{24} u∈R24 定义为:
x = [ h c o m q b q j ] , u = [ f c v j ] \mathbf{x} = \begin{bmatrix} \mathbf{h}_{com} \\ \mathbf{q}_b \\ \mathbf{q}_j \end{bmatrix}, \quad \mathbf{u} = \begin{bmatrix} \mathbf{f}_c \\ \mathbf{v}_j \end{bmatrix} x= hcomqbqj ,u=[fcvj]
其中:
- h c o m ∈ R 6 \mathbf{h}_{com} \in \mathbb{R}^{6} hcom∈R6:归一化质心动量 [ v c o m , x , v c o m , y , v c o m , z , L x / m , L y / m , L z / m ] T [v_{com,x}, v_{com,y}, v_{com,z}, L_x/m, L_y/m, L_z/m]^T [vcom,x,vcom,y,vcom,z,Lx/m,Ly/m,Lz/m]T;
- q b ∈ R 6 \mathbf{q}_b \in \mathbb{R}^{6} qb∈R6:基座位姿 [ p x , p y , p z , θ z , θ y , θ x ] T [p_x, p_y, p_z, \theta_z, \theta_y, \theta_x]^T [px,py,pz,θz,θy,θx]T(ZYX 欧拉角);
- q j ∈ R 12 \mathbf{q}_j \in \mathbb{R}^{12} qj∈R12:12 个关节角(4 腿 × 3 关节 HAA/HFE/KFE);
- f c ∈ R 12 \mathbf{f}_c \in \mathbb{R}^{12} fc∈R12:4 个足端接触力(每个 3 维);
- v j ∈ R 12 \mathbf{v}_j \in \mathbb{R}^{12} vj∈R12:12 个关节速度。
5.1.3 质心动力学模型
系统动力学采用 归一化质心动力学:
h ˙ c o m = [ v ˙ c o m L ˙ / m ] = [ 1 m ∑ i = 1 4 f c , i + g ∑ i = 1 4 ( r c , i − r c o m ) × f c , i ] \dot{\mathbf{h}}_{com} = \begin{bmatrix} \dot{\mathbf{v}}_{com} \\ \dot{\mathbf{L}}/m \end{bmatrix} = \begin{bmatrix} \frac{1}{m}\sum_{i=1}^{4}\mathbf{f}_{c,i} + \mathbf{g} \\ \sum_{i=1}^{4}(\mathbf{r}_{c,i} - \mathbf{r}_{com}) \times \mathbf{f}_{c,i} \end{bmatrix} h˙com=[v˙comL˙/m]=[m1∑i=14fc,i+g∑i=14(rc,i−rcom)×fc,i]
基座运动学:
q ˙ b = J b − 1 ( q ) ⋅ v b a s e \dot{\mathbf{q}}_b = \mathbf{J}_b^{-1}(\mathbf{q}) \cdot \mathbf{v}_{base} q˙b=Jb−1(q)⋅vbase
关节运动学:
q ˙ j = v j \dot{\mathbf{q}}_j = \mathbf{v}_j q˙j=vj
动力学模型通过 CppAd 自动微分 生成(LeggedRobotDynamicsAD),避免手写雅可比。
5.1.4 代价函数
采用二次跟踪代价:
l ( x , u , t ) = ( x − x r e f ( t ) ) T Q ( x − x r e f ( t ) ) + ( u − u r e f ( t ) ) T R ( u − u r e f ( t ) ) l(\mathbf{x}, \mathbf{u}, t) = (\mathbf{x} - \mathbf{x}_{ref}(t))^T \mathbf{Q} (\mathbf{x} - \mathbf{x}_{ref}(t)) + (\mathbf{u} - \mathbf{u}_{ref}(t))^T \mathbf{R} (\mathbf{u} - \mathbf{u}_{ref}(t)) l(x,u,t)=(x−xref(t))TQ(x−xref(t))+(u−uref(t))TR(u−uref(t))
权重矩阵 Q \mathbf{Q} Q(24×24)与 R \mathbf{R} R(24×24)在 task.info 中配置。输入代价中关节速度项通过雅可比转换到任务空间:
R j o i n t = J b a s e → f e e t T R t a s k J b a s e → f e e t \mathbf{R}_{joint} = \mathbf{J}_{base\to feet}^T \mathbf{R}_{task} \mathbf{J}_{base\to feet} Rjoint=Jbase→feetTRtaskJbase→feet
5.1.5 约束
(1) 摩擦锥约束(支撑腿,软约束形式):
μ ⋅ f z − f x 2 + f y 2 + ϵ ≥ 0 \mu \cdot f_z - \sqrt{f_x^2 + f_y^2 + \epsilon} \geq 0 μ⋅fz−fx2+fy2+ϵ≥0
其中 μ \mu μ 为摩擦系数(A1 默认 0.3), ϵ \epsilon ϵ 为正则化项避免梯度奇异。采用 Relaxed Barrier 惩罚函数转化为软约束:
penalty ( h ) = { − μ ln ( δ − h ) h ≤ δ − ϵ 二次平滑 otherwise \text{penalty}(h) = \begin{cases} -\mu \ln(\delta - h) & h \leq \delta - \epsilon \\ \text{二次平滑} & \text{otherwise} \end{cases} penalty(h)={−μln(δ−h)二次平滑h≤δ−ϵotherwise
(2) 零速度约束(支撑腿足端):
v f o o t , i = J i ( q ) q ˙ = 0 , i ∈ stance \mathbf{v}_{foot,i} = \mathbf{J}_i(\mathbf{q}) \dot{\mathbf{q}} = \mathbf{0}, \quad i \in \text{stance} vfoot,i=Ji(q)q˙=0,i∈stance
(3) 零力约束(摆动腿):
f c , i = 0 , i ∈ swing \mathbf{f}_{c,i} = \mathbf{0}, \quad i \in \text{swing} fc,i=0,i∈swing
(4) 摆动轨迹约束(摆动腿 z 轴位置):
足端高度跟踪三次样条曲线:
z f o o t ( t ) = CubicSpline ( t ; z l i f t o f f , z m i d , z t o u c h d o w n ) z_{foot}(t) = \text{CubicSpline}(t; z_{liftoff}, z_{mid}, z_{touchdown}) zfoot(t)=CubicSpline(t;zliftoff,zmid,ztouchdown)
中点高度 z m i d = max ( z l i f t o f f , z t o u c h d o w n ) + h s w i n g z_{mid} = \max(z_{liftoff}, z_{touchdown}) + h_{swing} zmid=max(zliftoff,ztouchdown)+hswing,摆动高度 h s w i n g = 0.08 h_{swing}=0.08 hswing=0.08 m。
(5) 自碰撞约束(软约束):指定 link 对(如 LF_calf, RF_calf)间最小距离 d m i n = 0.05 d_{min}=0.05 dmin=0.05 m。
5.1.6 求解方法
采用 多重打靶(Multiple Shooting) 将连续 OCP 离散化为 NLP,再用 SQP(序列二次规划) 求解:
- 时间窗 [ t 0 , t I ] [t_0, t_I] [t0,tI] 离散为 N N N 段,每段 Δ t = 0.015 \Delta t = 0.015 Δt=0.015 s;
- 每个时间点设状态 x k \mathbf{x}_k xk 与输入 u k \mathbf{u}_k uk 为决策变量;
- 动力学约束通过 RK2 积分器耦合相邻节点;
- SQP 每次迭代求解一个 QP 子问题,QOASES/HPIPM 求解;
- 默认
sqpIteration=1(每次 MPC 只迭代一次 SQP,依赖热启动)。
配置参数(task.info 中 sqp 段):
dt = 0.015:离散时间步;sqpIteration = 1:SQP 迭代次数;nThreads = 3:并行线程数;integratorType = RK2:二阶龙格库塔积分。
5.2 全身控制器(WBC)
5.2.1 决策变量
WBC 仅考虑当前时刻,决策变量为:
x w b c = [ q ¨ f c τ ] ∈ R 18 + 12 + 12 = 42 \mathbf{x}_{wbc} = \begin{bmatrix} \ddot{\mathbf{q}} \\ \mathbf{f}_c \\ \boldsymbol{\tau} \end{bmatrix} \in \mathbb{R}^{18+12+12=42} xwbc= q¨fcτ ∈R18+12+12=42
其中:
- q ¨ ∈ R 18 \ddot{\mathbf{q}} \in \mathbb{R}^{18} q¨∈R18:广义坐标加速度(6 基座 + 12 关节);
- f c ∈ R 12 \mathbf{f}_c \in \mathbb{R}^{12} fc∈R12:4 足端接触力;
- τ ∈ R 12 \boldsymbol{\tau} \in \mathbb{R}^{12} τ∈R12:12 关节力矩。
5.2.2 任务定义
WBC 定义了 7 个任务,分为等式约束 A x = b \mathbf{A}\mathbf{x}=\mathbf{b} Ax=b 与不等式约束 D x ≤ f \mathbf{D}\mathbf{x}\leq\mathbf{f} Dx≤f:
| 任务 | 类型 | 公式 | 优先级 |
|---|---|---|---|
| 浮动基座动力学方程 | 等式(约束) | M q ¨ + h ( q , q ˙ ) = J T f c + S T τ \mathbf{M}\ddot{\mathbf{q}} + \mathbf{h}(\mathbf{q},\dot{\mathbf{q}}) = \mathbf{J}^T\mathbf{f}_c + \mathbf{S}^T\boldsymbol{\tau} Mq¨+h(q,q˙)=JTfc+STτ | 约束 |
| 力矩限制 | 不等式(约束) | $ | \tau_i |
| 摩擦锥 | 不等式(约束) | μ f z ≥ f x 2 + f y 2 \mu f_z \geq \sqrt{f_x^2+f_y^2} μfz≥fx2+fy2 | 约束 |
| 支撑腿零运动 | 等式(约束) | J i q ¨ + J ˙ i q ˙ = 0 \mathbf{J}_i\ddot{\mathbf{q}} + \dot{\mathbf{J}}_i\dot{\mathbf{q}} = \mathbf{0} Jiq¨+J˙iq˙=0 | 约束 |
| 摆动腿跟踪 | 等式(任务) | J i q ¨ = p ¨ d e s − J ˙ i q ˙ \mathbf{J}_i\ddot{\mathbf{q}} = \ddot{\mathbf{p}}_{des} - \dot{\mathbf{J}}_i\dot{\mathbf{q}} Jiq¨=p¨des−J˙iq˙ | 高 |
| 基座加速度跟踪 | 等式(任务) | q ¨ b a s e = q ¨ b a s e , d e s \ddot{\mathbf{q}}_{base} = \ddot{\mathbf{q}}_{base,des} q¨base=q¨base,des | 中 |
| 接触力跟踪 | 等式(任务) | f c = f c , d e s \mathbf{f}_c = \mathbf{f}_{c,des} fc=fc,des | 低 |
其中摆动腿期望加速度采用 PD 反馈:
p ¨ d e s = k p ( p d e s − p m e a s ) + k d ( p ˙ d e s − p ˙ m e a s ) \ddot{\mathbf{p}}_{des} = k_p(\mathbf{p}_{des} - \mathbf{p}_{meas}) + k_d(\dot{\mathbf{p}}_{des} - \dot{\mathbf{p}}_{meas}) p¨des=kp(pdes−pmeas)+kd(p˙des−p˙meas)
k p = 350 , k d = 37 k_p=350, k_d=37 kp=350,kd=37(A1 配置)。
5.2.3 浮动基座动力学方程
基于 Pinocchio 的 CRBA 计算质量矩阵 M \mathbf{M} M,RNEA 计算非线性项 h = C q ˙ + g \mathbf{h} = \mathbf{C}\dot{\mathbf{q}} + \mathbf{g} h=Cq˙+g:
[ M − J T − S T ] ⏟ A [ q ¨ f c τ ] = − h ⏟ b \underbrace{\begin{bmatrix}\mathbf{M} & -\mathbf{J}^T & -\mathbf{S}^T\end{bmatrix}}_{\mathbf{A}} \begin{bmatrix}\ddot{\mathbf{q}} \\ \mathbf{f}_c \\ \boldsymbol{\tau}\end{bmatrix} = \underbrace{-\mathbf{h}}_{\mathbf{b}} A [M−JT−ST] q¨fcτ =b −h
其中 S = [ 0 12 × 6 , I 12 ] \mathbf{S} = [\mathbf{0}_{12\times6}, \mathbf{I}_{12}] S=[012×6,I12] 为关节选择矩阵, J \mathbf{J} J 为足端雅可比堆叠。
5.2.4 求解方法
项目提供两种 WBC 实现:
(1) WeightedWbc(加权法,默认):将所有任务合并为单个 QP:
min x ∑ i w i ∥ A i x − b i ∥ 2 s.t. A c o n x = b c o n , D c o n x ≤ f c o n \min_{\mathbf{x}} \sum_i w_i \|\mathbf{A}_i \mathbf{x} - \mathbf{b}_i\|^2 \quad \text{s.t.} \quad \mathbf{A}_{con}\mathbf{x}=\mathbf{b}_{con},\ \mathbf{D}_{con}\mathbf{x}\leq\mathbf{f}_{con} xmini∑wi∥Aix−bi∥2s.t.Aconx=bcon, Dconx≤fcon
权重(A1 配置): w s w i n g = 100 , w b a s e = 1 , w f o r c e = 0.01 w_{swing}=100,\ w_{base}=1,\ w_{force}=0.01 wswing=100, wbase=1, wforce=0.01。使用 qpOASES 求解。
(2) HierarchicalWbc(分层法):严格优先级,通过零空间投影(HoQp):
Task 0 (约束): 浮动基座方程 + 力矩限制 + 摩擦锥 + 零运动 Task 1: 基座加速度 + 摆动腿 Task 2: 接触力跟踪 \begin{aligned} &\text{Task 0 (约束): 浮动基座方程 + 力矩限制 + 摩擦锥 + 零运动} \\ &\text{Task 1: 基座加速度 + 摆动腿} \\ &\text{Task 2: 接触力跟踪} \end{aligned} Task 0 (约束): 浮动基座方程 + 力矩限制 + 摩擦锥 + 零运动Task 1: 基座加速度 + 摆动腿Task 2: 接触力跟踪
高优先级任务的解投影到低优先级任务的零空间:
x = x p r e v + Z p r e v x c u r r \mathbf{x} = \mathbf{x}_{prev} + \mathbf{Z}_{prev} \mathbf{x}_{curr} x=xprev+Zprevxcurr
其中 Z p r e v \mathbf{Z}_{prev} Zprev 为前序任务雅可比的零空间基。
5.2.5 输出与电机命令
WBC 解出 τ \boldsymbol{\tau} τ 后,与 NMPC 给出的期望关节位置 q d e s \mathbf{q}_{des} qdes、速度 q ˙ d e s \dot{\mathbf{q}}_{des} q˙des 组合为 混合命令:
τ m o t o r = τ w b c + k p ( q d e s − q ) + k d ( q ˙ d e s − q ˙ ) \tau_{motor} = \tau_{wbc} + k_p(q_{des} - q) + k_d(\dot{q}_{des} - \dot{q}) τmotor=τwbc+kp(qdes−q)+kd(q˙des−q˙)
其中 k p = 3 , k d = 0 k_p=3, k_d=0 kp=3,kd=0(低增益,仅作接触缓冲), τ w b c \tau_{wbc} τwbc 为前馈力矩。该接口由 HybridJointInterface 实现。
5.3 状态估计(卡尔曼滤波)
5.3.1 状态与观测
采用线性卡尔曼滤波估计基座位置与速度(参考 Flayols et al. 2017)。
状态向量 x ^ ∈ R 18 \mathbf{\hat{x}} \in \mathbb{R}^{18} x^∈R18:
x ^ = [ p b a s e v b a s e p f o o t , 1 p f o o t , 2 p f o o t , 3 p f o o t , 4 ] ∈ R 18 \mathbf{\hat{x}} = \begin{bmatrix} \mathbf{p}_{base} \\ \mathbf{v}_{base} \\ \mathbf{p}_{foot,1} \\ \mathbf{p}_{foot,2} \\ \mathbf{p}_{foot,3} \\ \mathbf{p}_{foot,4} \end{bmatrix} \in \mathbb{R}^{18} x^= pbasevbasepfoot,1pfoot,2pfoot,3pfoot,4 ∈R18
观测向量 y ∈ R 28 \mathbf{y} \in \mathbb{R}^{28} y∈R28:
y = [ p f o o t , 1 − p b a s e ⋮ p f o o t , 4 − p b a s e v f o o t , 1 − v b a s e ⋮ v f o o t , 4 − v b a s e z f o o t , 1 ⋮ z f o o t , 4 ] \mathbf{y} = \begin{bmatrix} \mathbf{p}_{foot,1} - \mathbf{p}_{base} \\ \vdots \\ \mathbf{p}_{foot,4} - \mathbf{p}_{base} \\ \mathbf{v}_{foot,1} - \mathbf{v}_{base} \\ \vdots \\ \mathbf{v}_{foot,4} - \mathbf{v}_{base} \\ z_{foot,1} \\ \vdots \\ z_{foot,4} \end{bmatrix} y= pfoot,1−pbase⋮pfoot,4−pbasevfoot,1−vbase⋮vfoot,4−vbasezfoot,1⋮zfoot,4
足端位置/速度由正运动学从关节编码器计算,足端高度假设接触时为 0。
5.3.2 状态转移方程
x ^ k ∣ k − 1 = A x ^ k − 1 + B a i m u \mathbf{\hat{x}}_{k|k-1} = \mathbf{A}\mathbf{\hat{x}}_{k-1} + \mathbf{B}\mathbf{a}_{imu} x^k∣k−1=Ax^k−1+Baimu
其中:
A = [ I 3 Δ t I 3 0 0 I 3 0 0 0 I 12 ] , B = [ 1 2 Δ t 2 I 3 Δ t I 3 0 ] \mathbf{A} = \begin{bmatrix} \mathbf{I}_3 & \Delta t \mathbf{I}_3 & \mathbf{0} \\ \mathbf{0} & \mathbf{I}_3 & \mathbf{0} \\ \mathbf{0} & \mathbf{0} & \mathbf{I}_{12} \end{bmatrix}, \quad \mathbf{B} = \begin{bmatrix} \frac{1}{2}\Delta t^2 \mathbf{I}_3 \\ \Delta t \mathbf{I}_3 \\ \mathbf{0} \end{bmatrix} A= I300ΔtI3I3000I12 ,B= 21Δt2I3ΔtI30
a i m u \mathbf{a}_{imu} aimu 为 IMU 线加速度(转换到世界系,扣除重力)。
5.3.3 观测方程
y = C x ^ \mathbf{y} = \mathbf{C}\mathbf{\hat{x}} y=Cx^
C \mathbf{C} C 将状态映射到足端相对基座的位置/速度。
5.3.4 卡尔曼更新
预测:
P k ∣ k − 1 = A P k − 1 A T + Q \mathbf{P}_{k|k-1} = \mathbf{A}\mathbf{P}_{k-1}\mathbf{A}^T + \mathbf{Q} Pk∣k−1=APk−1AT+Q
更新:
K k = P k ∣ k − 1 C T ( C P k ∣ k − 1 C T + R ) − 1 \mathbf{K}_k = \mathbf{P}_{k|k-1}\mathbf{C}^T(\mathbf{C}\mathbf{P}_{k|k-1}\mathbf{C}^T + \mathbf{R})^{-1} Kk=Pk∣k−1CT(CPk∣k−1CT+R)−1
x ^ k = x ^ k ∣ k − 1 + K k ( y k − C x ^ k ∣ k − 1 ) \mathbf{\hat{x}}_k = \mathbf{\hat{x}}_{k|k-1} + \mathbf{K}_k(\mathbf{y}_k - \mathbf{C}\mathbf{\hat{x}}_{k|k-1}) x^k=x^k∣k−1+Kk(yk−Cx^k∣k−1)
P k = ( I − K k C ) P k ∣ k − 1 \mathbf{P}_k = (\mathbf{I} - \mathbf{K}_k\mathbf{C})\mathbf{P}_{k|k-1} Pk=(I−KkC)Pk∣k−1
5.3.5 接触自适应噪声
当足端接触时,足端位置可信度高(噪声小);未接触时不可信(噪声放大 100 倍):
Q f o o t , i = { Q f o o t if contact 100 ⋅ Q f o o t if swing \mathbf{Q}_{foot,i} = \begin{cases} \mathbf{Q}_{foot} & \text{if contact} \\ 100 \cdot \mathbf{Q}_{foot} & \text{if swing} \end{cases} Qfoot,i={Qfoot100⋅Qfootif contactif swing
5.3.6 外部里程计融合
支持订阅 /tracking_camera/odom/sample(如 T265 追踪相机)修正漂移,通过 updateFromTopic() 将外部位姿注入状态估计。
6. 关键模块详解
6.1 LeggedController(控制器主入口)
文件:legged_controllers/src/LeggedController.cpp
作为 ros-control 的 MultiInterfaceController 插件,管理整个控制流程:
init():加载 task/urdf/reference 文件,构建LeggedInterface、MPC、WBC、StateEstimate;starting():等待 MPC 首次策略就绪;update():每个控制周期(500Hz)执行:updateStateEstimation():读取 IMU/关节/接触,更新卡尔曼滤波,转换到质心状态;mpcMrtInterface_->setCurrentObservation():将当前状态发送给 MPC;mpcMrtInterface_->updatePolicy()/evaluatePolicy():获取优化后的状态/输入;wbc_->update():求解 WBC 得到力矩;safetyChecker_->check():姿态安全检查(翻车保护);hybridJointHandles_[j].setCommand():下发混合命令;
setupMrt():启动独立 MPC 线程,按mpcDesiredFrequency=100Hz调用advanceMpc()。
6.2 LeggedInterface(OCP 建模)
文件:legged_interface/src/LeggedInterface.cpp
构建 OCS2 的 OptimalControlProblem:
setupModel():从 URDF 创建PinocchioInterface与CentroidalModelInfo;setupOptimalControlProblem():组装动力学、代价、约束:- 动力学:
LeggedRobotDynamicsAD(CppAd 自动微分质心动力学); - 代价:
LeggedRobotStateInputQuadraticCost(Q/R 加权跟踪); - 约束:摩擦锥(软)、零力、零速度、法向速度、自碰撞(软);
- 动力学:
setupReferenceManager():创建SwitchedModelReferenceManager,管理步态调度与摆动轨迹规划;loadGaitSchedule():从reference.info加载步态模式序列。
6.3 WBC 实现
WbcBase(任务构造)
提供 7 个 formulateXxxTask() 方法,每个返回 Task{A, b, D, f} 结构。updateMeasured() 调用 Pinocchio 计算 M , h , J , J ˙ \mathbf{M}, \mathbf{h}, \mathbf{J}, \dot{\mathbf{J}} M,h,J,J˙。
WeightedWbc(默认)
文件:legged_wbc/src/WeightedWbc.cpp
将约束任务作为硬约束,跟踪任务加权求和构造代价:
H = ∑ i w i A i T A i , g = − ∑ i w i A i T b i \mathbf{H} = \sum_i w_i \mathbf{A}_i^T \mathbf{A}_i, \quad \mathbf{g} = -\sum_i w_i \mathbf{A}_i^T \mathbf{b}_i H=i∑wiAiTAi,g=−i∑wiAiTbi
调用 qpOASES 求解 QP:min 0.5 x^T H x + g^T x s.t. lbA ≤ A x ≤ ubA。
HierarchicalWbc(分层)
文件:legged_wbc/src/HierarchicalWbc.cpp
通过 HoQp 类实现零空间投影,严格保证优先级。HoQp 递归构造:HoQp(task2, HoQp(task1, HoQp(task0)))。
6.4 状态估计
文件:legged_estimation/src/LinearKalmanFilter.cpp
KalmanFilterEstimate 实现 5.3 节所述算法。关键点:
update()中根据contactFlag_动态调整 Q / R \mathbf{Q}/\mathbf{R} Q/R;- 通过
eeKinematics_计算足端位置/速度(正运动学); - 发布
nav_msgs/Odometry到/odom; - 支持外部里程计融合(T265 等)。
FromTopicStateEstimate(cheater 模式)直接从 Gazebo ground truth 话题读取,仅用于仿真调试。
6.5 硬件接口
LeggedHW(基类)
文件:legged_hw/include/legged_hw/LeggedHW.h
注册四种接口:
JointStateInterface:关节位置/速度/力矩;ImuSensorInterface:IMU 姿态/角速度/线加速度;HybridJointInterface:混合命令(pos_des, vel_des, kp, kd, ff);ContactSensorInterface:足端接触布尔值。
UnitreeHW(Unitree 实现)
文件:legged_examples/legged_unitree/legged_unitree_hw/include/legged_unitree_hw/UnitreeHW.h
通过 Unitree SDK(3.3.1 / 3.8.0)UDP 通信:
read():接收LowState(关节状态、IMU);write():发送LowCmd(混合命令转换为q_des, v_des, kp, kd, tau_ff);- 接触检测:足端力阈值判断(
contact_threshold=40)。
LeggedHWLoop(控制循环)
文件:legged_hw/include/legged_hw/LeggedHWLoop.h
独立线程按 loop_frequency=500Hz 执行 read() → controller_manager_->update() → write(),线程优先级 95(实时)。
6.6 TargetTrajectoriesPublisher(目标生成)
文件:legged_controllers/src/TargetTrajectoriesPublisher.cpp
将用户指令转换为 MPC 参考轨迹:
goalToTargetTrajectories():/move_base_simple/goal→ 目标位姿轨迹(用于导航);cmdVelToTargetTrajectories():/cmd_vel→ 速度参考轨迹(用于遥控)。
到达时间估计:t = max(|dyaw|/ω_rot, |disp|/v_disp),默认 v d i s p = 0.5 v_{disp}=0.5 vdisp=0.5 m/s, ω r o t = 1.57 \omega_{rot}=1.57 ωrot=1.57 rad/s。
7. 配置参数说明
7.1 task.info(核心配置)
文件:legged_controllers/config/a1/task.info
| 段落 | 参数 | 含义 | A1 默认值 |
|---|---|---|---|
centroidalModelType |
0/1 | 0=完整质心动力学, 1=单刚体 | 0 |
model_settings |
positionErrorGain |
零速度约束位置误差增益 | 0.0 |
phaseTransitionStanceTime |
相位切换站立时间 | 0.1 s | |
swing_trajectory_config |
swingHeight |
摆动高度 | 0.08 m |
liftOffVelocity |
起摆速度 | 0.05 m/s | |
touchDownVelocity |
落地速度 | -0.1 m/s | |
ddp/sqp/ipm |
dt |
离散时间步 | 0.015 s |
maxNumIterations/sqpIteration |
迭代次数 | 1 | |
nThreads |
并行线程 | 3 | |
mpc |
timeHorizon |
预测时域 | 1.0 s |
mpcDesiredFrequency |
MPC 求解频率 | 100 Hz | |
Q |
对角元素 | 状态跟踪权重 | 见文件 |
R |
对角元素 | 输入跟踪权重 | 见文件 |
frictionConeSoftConstraint |
frictionCoefficient |
摩擦系数 | 0.3 |
torqueLimitsTask |
HAA/HFE/KFE | 关节力矩限制 | 33.5 Nm |
swingLegTask |
kp/kd |
摆动腿 PD | 350/37 |
weight |
swingLeg/baseAccel/contactForce | WBC 任务权重 | 100/1/0.01 |
kalmanFilter |
各噪声参数 | KF 过程/观测噪声 | 见文件 |
7.2 gait.info(步态定义)
文件:legged_controllers/config/a1/gait.info
支持 12 种步态,通过 modeSequence 与 switchingTimes 定义。模式命名约定:LF_RH 表示左前+右后支撑。
常用步态:
| 步态 | 模式序列 | 周期 | 说明 |
|---|---|---|---|
| stance | STANCE | 0.5s | 原地站立 |
| trot | LF_RH ↔ RF_LH | 0.6s | 对角小跑 |
| flying_trot | LF_RH → FLY → RF_LH → FLY | 0.4s | 飞奔(有腾空相) |
| pace | LF_LH ↔ RF_RH | 0.6s | 侧步(同侧) |
| dynamic_walk | 三足→二足循环 | 1.0s | 动态步行 |
| static_walk | 三足轮换 | 1.2s | 静态步行 |
7.3 reference.info(参考轨迹)
文件:legged_controllers/config/a1/reference.info
comHeight:质心高度(A1 为 0.3 m);defaultJointState:默认关节角(站立姿态);targetDisplacementVelocity/targetRotationVelocity:目标到达速度;initialModeSchedule/defaultModeSequenceTemplate:初始与默认步态调度。
8. 运行步骤
8.1 环境准备
依赖:
- ROS Noetic (Ubuntu 20.04)
- OCS2 + Pinocchio + hpp-fcl(按 OCS2 文档编译
ocs2_legged_robot_ros) - qpOASES(已包含
qpoases_catkin)
8.2 编译
# 编译核心控制器与机器人描述
catkin build legged_controllers legged_unitree_description
# 编译 Gazebo 仿真(不要在机载电脑上编译)
catkin build legged_gazebo
# 编译真机硬件接口(仅真机需要)
catkin build legged_unitree_hw
8.3 仿真运行
步骤 1:设置机器人类型
export ROBOT_TYPE=a1 # 可选: a1, aliengo, go1
步骤 2:启动 Gazebo 仿真
roslaunch legged_unitree_description empty_world.launch
步骤 3:加载控制器
roslaunch legged_controllers load_controller.launch cheater:=false
cheater:=true 会额外加载 legged_cheater_controller(使用 Gazebo ground truth,禁止用于真机)。
步骤 4:启动控制器
rosservice call /controller_manager/switch_controller "start_controllers: ['controllers/legged_controller']
stop_controllers: ['']
strictness: 0
start_asap: false
timeout: 0.0"
或使用 GUI:
sudo apt install ros-noetic-rqt-controller-manager
rosrun rqt_controller_manager rqt_controller_manager
步骤 5:发送指令
- 步态切换:在
load_controller.launch终端输入步态名(trot/pace/walk 等); - 速度控制:发布
/cmd_vel; - 目标导航:在 RViz 中使用
2D Nav Goal发布/move_base_simple/goal。
8.4 真机运行
# 启动硬件接口(替代 Gazebo)
roslaunch legged_unitree_hw legged_unitree_hw.launch
# 后续步骤与仿真相同
roslaunch legged_controllers load_controller.launch cheater:=false
# 启动 legged_controller(切勿启动 cheater_controller)
机载电脑建议:使用 NUC 等外部计算设备,作者使用 11 代 NUC,MPC 频率可达 200 Hz。
8.5 注意事项
- 步态与目标相互独立:机器人四足着地时无需切换到 stance 步态(它已经是 stance);
target_trajectories_publisher仅为示例,可整合手柄/键盘输入实现步态与高度切换、启停控制;- 真机部署时务必先在仿真中验证参数。
9. 部署到自定义机器人
部署流程简单,仅需两步:
9.1 实现硬件接口
参考 legged_examples/legged_unitree/legged_unitree_hw 中的 UnitreeHW 类:
- 继承
LeggedHW; - 实现
read():从机器人读取关节状态、IMU、接触信息; - 实现
write():将HybridJointHandle的posDes/velDes/kp/kd/ff下发到电机; - 实现
setupJoints()/setupImu()/setupContactSensor()配置硬件。
关节命名约定(必须一致):
LF_HAA, LF_HFE, LF_KFE (左前: 髋外展/髋屈伸/膝屈伸)
LH_HAA, LH_HFE, LH_KFE (左后)
RF_HAA, RF_HFE, RF_KFE (右前)
RH_HAA, RH_HFE, RH_KFE (右后)
9.2 编写 URDF
参考 legged_examples/legged_unitree/legged_unitree_description:
- 编写 xacro 文件,joint/link 命名与上述约定一致;
- 在
const.xacro中定义质量、惯量、尺寸等常量; - 使用
legged_common/scripts/generate_urdf.sh生成 URDF 到/tmp/legged_control/<robot>.urdf; - 在
legged_controllers/config/<robot>/下创建task.info、gait.info、reference.info(可复制 A1 配置修改)。
9.3 调参建议
- 质量/惯量:URDF 中务必准确(影响动力学模型);
- Q/R 权重:基座位置权重应远大于关节权重(A1: 1000 vs 5);
- 摩擦系数:根据地面材质调整(A1 默认 0.3 偏保守);
- 力矩限制:根据电机规格设置(A1: 33.5 Nm);
- KF 噪声:IMU 噪声大则增大
imuProcessNoise*,足端接触可靠则减小footSensorNoise*。
10. 参考文献
-
T. Flayols, A. Del Prete, P. Wensing, A. Mifsud, M. Benallegue, and O. Stasse, “Experimental evaluation of simple estimators for humanoid robots,” IEEE-RAS Int. Conf. Humanoid Robot., pp. 889–895, 2017.
-
J. P. Sleiman, F. Farshidian, M. V. Minniti, and M. Hutter, “A Unified MPC Framework for Whole-Body Dynamic Locomotion and Manipulation,” IEEE Robot. Autom. Lett., vol. 6, no. 3, pp. 4688–4695, 2021.
-
R. Grandia, F. Jenelten, S. Yang, F. Farshidian, and M. Hutter, “Perceptive Locomotion through Nonlinear Model Predictive Control,” IEEE Trans. Robot., 2022.
-
C. Dario Bellicoso, C. Gehring, J. Hwangbo, P. Fankhauser, and M. Hutter, “Perception-less terrain adaptation through whole body control and hierarchical optimization,” IEEE-RAS Int. Conf. Humanoid Robots, pp. 558–564, 2016.
-
Q. Liao, Z. Li, A. Thirugnanam, J. Zeng, K. Sreenath, “Walking in narrow spaces: Safety-critical locomotion control for quadrupedal robots with duality-based optimization,” IEEE/RSJ IROS, pp. 2723–2730, 2023.
附录:关键数据维度速查
| 量 | 维度 | 说明 |
|---|---|---|
| NMPC 状态 x \mathbf{x} x | 24 | 6 质心动量 + 6 基座位姿 + 12 关节角 |
| NMPC 输入 u \mathbf{u} u | 24 | 12 接触力 + 12 关节速度 |
| WBC 决策变量 x w b c \mathbf{x}_{wbc} xwbc | 42 | 18 广义加速度 + 12 接触力 + 12 力矩 |
| 卡尔曼状态 x ^ \mathbf{\hat{x}} x^ | 18 | 3 基座位置 + 3 基座速度 + 12 足端位置 |
| 卡尔曼观测 y \mathbf{y} y | 28 | 12 足端相对位置 + 12 相对速度 + 4 足端高度 |
| MPC 时域 | 1.0 s | 离散为约 67 个节点(dt=0.015) |
| 控制频率 | 500 Hz | 硬件循环 + WBC |
| MPC 频率 | 100 Hz | SQP 求解 |
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐
所有评论(0)