基于 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(全身控制) 的分层控制架构。

该框架的主要优势:

  1. 据作者所知,这可能是性能最佳的开源足式机器人 MPC 控制框架;
  2. 可在数小时内部署到 Unitree A1 真机;
  3. 得益于 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} xR24 与输入 u ∈ R 24 \mathbf{u} \in \mathbb{R}^{24} uR24 定义为:

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} hcomR6:归一化质心动量 [ 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} qbR6:基座位姿 [ 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} qjR12:12 个关节角(4 腿 × 3 关节 HAA/HFE/KFE);
  • f c ∈ R 12 \mathbf{f}_c \in \mathbb{R}^{12} fcR12:4 个足端接触力(每个 3 维);
  • v j ∈ R 12 \mathbf{v}_j \in \mathbb{R}^{12} vjR12: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]=[m1i=14fc,i+gi=14(rc,ircom)×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=Jb1(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)=(xxref(t))TQ(xxref(t))+(uuref(t))TR(uuref(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=JbasefeetTRtaskJbasefeet

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 μfzfx2+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,istance

(3) 零力约束(摆动腿):

f c , i = 0 , i ∈ swing \mathbf{f}_{c,i} = \mathbf{0}, \quad i \in \text{swing} fc,i=0,iswing

(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(序列二次规划) 求解:

  1. 时间窗 [ t 0 , t I ] [t_0, t_I] [t0,tI] 离散为 N N N 段,每段 Δ t = 0.015 \Delta t = 0.015 Δt=0.015 s;
  2. 每个时间点设状态 x k \mathbf{x}_k xk 与输入 u k \mathbf{u}_k uk 为决策变量;
  3. 动力学约束通过 RK2 积分器耦合相邻节点;
  4. SQP 每次迭代求解一个 QP 子问题,QOASES/HPIPM 求解;
  5. 默认 sqpIteration=1(每次 MPC 只迭代一次 SQP,依赖热启动)。

配置参数(task.infosqp 段):

  • 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} fcR12: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} Dxf

任务 类型 公式 优先级
浮动基座动力学方程 等式(约束) 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} μfzfx2+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¨desJ˙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(pdespmeas)+kd(p˙desp˙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 [MJTST] 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} xminiwiAixbi2s.t.Aconx=bcon, Dconxfcon

权重(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(qdesq)+kd(q˙desq˙)

其中 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} yR28

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,1pbasepfoot,4pbasevfoot,1vbasevfoot,4vbasezfoot,1zfoot,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^kk1=Ax^k1+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} Pkk1=APk1AT+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=Pkk1CT(CPkk1CT+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^kk1+Kk(ykCx^kk1)

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=(IKkC)Pkk1

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={Qfoot100Qfootif 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 文件,构建 LeggedInterfaceMPCWBCStateEstimate
  • starting():等待 MPC 首次策略就绪;
  • update():每个控制周期(500Hz)执行:
    1. updateStateEstimation():读取 IMU/关节/接触,更新卡尔曼滤波,转换到质心状态;
    2. mpcMrtInterface_->setCurrentObservation():将当前状态发送给 MPC;
    3. mpcMrtInterface_->updatePolicy() / evaluatePolicy():获取优化后的状态/输入;
    4. wbc_->update():求解 WBC 得到力矩;
    5. safetyChecker_->check():姿态安全检查(翻车保护);
    6. hybridJointHandles_[j].setCommand():下发混合命令;
  • setupMrt():启动独立 MPC 线程,按 mpcDesiredFrequency=100Hz 调用 advanceMpc()

6.2 LeggedInterface(OCP 建模)

文件:legged_interface/src/LeggedInterface.cpp

构建 OCS2 的 OptimalControlProblem

  • setupModel():从 URDF 创建 PinocchioInterfaceCentroidalModelInfo
  • setupOptimalControlProblem():组装动力学、代价、约束:
    • 动力学:LeggedRobotDynamicsAD(CppAd 自动微分质心动力学);
    • 代价:LeggedRobotStateInputQuadraticCost(Q/R 加权跟踪);
    • 约束:摩擦锥(软)、零力、零速度、法向速度、自碰撞(软);
  • setupReferenceManager():创建 SwitchedModelReferenceManager,管理步态调度与摆动轨迹规划;
  • loadGaitSchedule():从 reference.info 加载步态模式序列。

6.3 WBC 实现

WbcBase(任务构造)

文件:legged_wbc/src/WbcBase.cpp

提供 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=iwiAiTAi,g=iwiAiTbi

调用 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 种步态,通过 modeSequenceswitchingTimes 定义。模式命名约定: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 类:

  1. 继承 LeggedHW
  2. 实现 read():从机器人读取关节状态、IMU、接触信息;
  3. 实现 write():将 HybridJointHandleposDes/velDes/kp/kd/ff 下发到电机;
  4. 实现 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

  1. 编写 xacro 文件,joint/link 命名与上述约定一致;
  2. const.xacro 中定义质量、惯量、尺寸等常量;
  3. 使用 legged_common/scripts/generate_urdf.sh 生成 URDF 到 /tmp/legged_control/<robot>.urdf
  4. legged_controllers/config/<robot>/ 下创建 task.infogait.inforeference.info(可复制 A1 配置修改)。

9.3 调参建议

  1. 质量/惯量:URDF 中务必准确(影响动力学模型);
  2. Q/R 权重:基座位置权重应远大于关节权重(A1: 1000 vs 5);
  3. 摩擦系数:根据地面材质调整(A1 默认 0.3 偏保守);
  4. 力矩限制:根据电机规格设置(A1: 33.5 Nm);
  5. KF 噪声:IMU 噪声大则增大 imuProcessNoise*,足端接触可靠则减小 footSensorNoise*

10. 参考文献

  1. 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.

  2. 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.

  3. R. Grandia, F. Jenelten, S. Yang, F. Farshidian, and M. Hutter, “Perceptive Locomotion through Nonlinear Model Predictive Control,” IEEE Trans. Robot., 2022.

  4. 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.

  5. 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 求解
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐