ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用OCS2搭建四足机器人MPC控制器:Pinocchio配置与实战指南

用OCS2搭建四足机器人MPC控制器:Pinocchio配置与实战指南 做四足机器人控制这两年我身边不少人都是被同一个问题拦住的论文里MPC模型预测控制的数学推导看得懂GitHub上OCS2仓库也找到了可真到在自己机器上跑起来第一步编译装环境就能卡一个星期。更不用说OCS2封装了大量最优控制的底层逻辑把urdf模型导进去、用Pinocchio做动力学解算、再和Gazebo或真实机器人对接每一步都有看不见的坑。这篇文章想把“用OCS2工具箱搭建四足机器人MPC控制器”这条路完整走一遍重点放在标题里已经点名的Pinocchio配置上以及从零到能跑通MPC的完整流程。内容覆盖OCS2在整个四足控制系统中负责什么、环境怎么搭、Pinocchio怎么装怎么链、MPC控制器怎么初始化、参数怎么调。对已经看过一些MPC理论但被工程实现劝退的人应该能省下不少走弯路的时间。1. OCS2和MPC在四足控制里的角色定位1.1 为什么四足机器人需要MPC而不是一套固定步态先聊一个最基本的问题四足机器人为什么非要上MPC早年的四足控制多半是“规划一条固定轨迹底层再用PID或者计算力矩去跟踪”。这套思路在平整地面上没问题走到粗糙地形、遇到外力推搡、走过草丛这类场景就开始露馅因为轨迹是事先算好的机器人脚下突然多了块石头或者身体被撞偏了控制器不会主动重新规划未来的运动只能被动纠偏结果要么越纠越晃要么直接摔。MPC的做法完全不一样。它每一控制周期都在做三件事用当前状态做初值基于动力学模型预测未来一段时间几秒甚至零点几秒的运动在这段预测里求解一个最优控制问题得到当前这一步该给的力矩。等下一时刻状态更新了再重新预测、重新求解这就是“滚动优化”。说白了MPC不是瞄准一个固定终点硬跟而是走一步看一步每次都在根据实际情况重算接下来的路。打个比方传统控制像开车时只盯着眼前五米压到坑了才猛打方向盘MPC像开车时看着前方五十米的道路提前规划好方向盘的转动遇到突发情况再随时更新路线。这个“提前量”和“在线重算”的能力正好命中四足机器人在非结构化地形上需要的动态稳定性。OCS2正是为这类问题设计的开源工具箱。它的全称是Optimal Control for Switched Systems由苏黎世联邦理工ETH Zurich机器人系统实验室开源。这名字里的“Switched Systems”不是白叫的——四足机器人跑步时脚与地面周期性接触本质上就是一个混成系统支撑相和摆动相不断切换OCS2的处理方式正好契合这种切换结构。官方仓库里已经带了四足机器人相关的模块不需要从零去写SQP求解器和约束处理这也是我最终选定OCS2而不是自己撸轮子的原因。1.2 OCS2的功能组成最优控制工具箱与四足专用模块OCS2不是一个单一大包而是一组分工明确的模块。核心求解部分包括ocs2_core、ocs2_mpc、ocs2_sqp这些负责最优控制问题的描述、求解和滚动实现外围则有ocs2_pinocchio_interface、ocs2_robotic_tools这类与机器人模型和仿真打交道的部分再往上还有针对具体机器人形态的模块比如四足相关的ocs2_quadruped_*系列。实际搭建控制器时你打交道最多的是这几块ocs2_quadruped_interface负责从urdf模型和配置文件里读出机器人信息建立统一的模型接口把Pinocchio的动力学计算结果暴露给MPC求解器。ocs2_quadruped_mpc直接实现四足MPC的循环包括状态更新、目标轨迹更新、MPC求解、控制指令输出。ocs2_pinocchio_interface这是OCS2和Pinocchio之间的桥梁。它把Pinocchio的刚体动力学计算封装成OCS2想要的系统动态函数让MPC在预测阶段能算“给这个力矩下一步机器人会怎么动”。ocs2_mpc和ocs2_sqp底层优化求解器。四足MPC默认走SQP路线序列二次规划把非线性最优控制问题拆成一系列二次规划子问题迭代求解。所以“用OCS2搭建四足MPC控制器”这件事本质上是用Pinocchio提供动力学数学工具OCS2提供最优控制的求解框架你再把自家机器人的urdf、关节配置、MPC参数填进去得到一个可以迭代优化的控制律。1.3 OCS2与MPPI、传统工业MPC的差异热词里出现了“mppi mpc”也出现了“霍尼韦尔mpc控制”。这两类和OCS2里的MPC都不是一个东西。做了几年控制的人可能对霍尼韦尔这类工业DCS上的MPC更熟悉那是用在化工、炼油这类慢流程上的模型预测控制器采样周期以秒、分钟计而机器人上的MPC采样周期通常是毫秒级二者虽然同源于预测控制思想但工程形态差别很大千万不要拿工业MPC配置软件的心态来对待OCS2。MPPIModel Predictive Path Integral则是另一种求解MPC的思路不用解析梯度而是通过大量采样轨迹加权平均得到控制量在非线性强、成本函数复杂的时候有一定优势。OCS2里的SQP需要提供动力学雅可比对模型质量要求高但在计算效率和收敛性方面更稳定。不是说MPPI不好而是OCS2的定位就是“基于模型的高效非线性最优控制”搞懂这一层后面调参时才不会拿MPPI的经验硬套。2. 搭建前的环境准备版本选型与依赖清单2.1 Ubuntu和ROS版本怎么选OCS2对系统的要求不算苛刻但版本选错了会引发一连串编译问题。我实测下来最省心的组合是Ubuntu 20.04 ROS Noetic或者Ubuntu 22.04 ROS 2 Humble。如果你是第一次接触这个工具箱我的建议是直接上Ubuntu 20.04 Noetic四足机器人领域大量现成代码、教程、镜像都是围绕这个组合写的遇到问题搜起来命中率高很多。ROS 2版本的OCS2也支持但现在的功能更新更多还是集中在ROS 1生态里。特别是你接下来要连Gazebo、用ocs2_ros话题收发机器人状态Noetic下几乎每一步都有先例可查踩坑成本低。系统装好后建议先确认gcc、g、cmake、git都就位这几个是编译基础版本太老会有莫名其妙的问题。有一个容易忽略的点OCS2官方仓库目前对C标准要求是C17如果你系统里默认编译器比较老比如Ubuntu 18.04自带的gcc 7.x编译时经常冒出模板解析错误。别浪费时间排查直接把编译器升级到gcc-9以上或者换到20.04系统。2.2 核心依赖包清单在clone OCS2仓库之前先把依赖装齐。依赖分两大块ROS侧的和纯C侧。ROS侧建议用apt直接安装sudo apt install ros-noetic-robot-state-publisher ros-noetic-urdf \ ros-noetic-eigen-conversions ros-noetic-cmake-modules ros-noetic-controller-interface \ ros-noetic-gazebo-ros-pkgs ros-noetic-ros-control ros-noetic-ros-controllers纯C侧的核心依赖包括Eigen3、Boost、CppADCodeGen、urdfdom、pybind11部分接口需要以及最重要的Pinocchio。CppADCodeGen是OCS2做自动微分和代码生成用的如果你后面的MPC想追求实时性CppADCodeGen生成代码的环节会很关键建议用源码编译最新版。sudo apt install libeigen3-dev libboost-all-dev liburdfdom-dev liburdfdom-headers-dev \ libcppad-dev libcppadcodegen-dev libyaml-cpp-dev这段装完后再用一个命令确认Eigen版本pkg-config --modversion eigen3OCS2和Pinocchio对Eigen版本比较敏感。老版本的Eigen3.3系列在部分Pinocchio 2.x上能编译过但运行会崩建议直接上Eigen 3.4以上。如果apt源里的Eigen版本不够新可以源码装一份最新的Eigen注意Eigen是头文件库装的时候把/usr/local/include/eigen3软链到/usr/include/eigen3否则有些CMake找不到。2.3 用一套可复现的环境避免污染系统我自己的经验是OCS2这套东西的依赖树不算浅而且一旦Pinocchio、Eigen、CppADCodeGen版本之间出现错位排查起来非常痛苦。强烈建议用Docker做一个固定镜像或者至少用一个干净的虚拟机。这样即使环境折腾坏了随时可以回滚不用重装系统。如果你选择Docker路线推荐基于ros:noetic-ros-base镜像在这个基础上把上述依赖装进去再编译OCS2。https://github.com/leggedrobotics/ocs2仓库根目录下本身也有Dockerfile可以参考虽然它可能针对的是他们自家开发环境直接拿来改一改也比自己从零写省事。3. Pinocchio配置指南这篇博文的重头戏3.1 Pinocchio在OCS2中承担什么任务Pinocchio是法国LAAS-CNRS和INRIA联合开发的刚体动力学库。它在机器人学圈子里的地位基本相当于“C版的RBDL增强版”能解析urdf模型能做正逆运动学、正逆动力学能算质量矩阵、科氏力、重力项还提供CRBA、RNEA、ABA这些高效算法。在OCS2里Pinocchio承担的角色很具体MPC求解器在做预测时必须回答“给定当前状态、关节位置和力矩下一步系统状态会是什么”。这个系统动态函数就是由Pinocchio的动力学计算提供的。可以说Pinocchio是MPC对模型求导和积分的数学引擎。OCS2之所以单独封装一个ocs2_pinocchio_interface而不是让大家直接调用Pinocchio原生API是因为OCS2的求解器需要“带自动微分信息的动力学”。Pinocchio内部虽然也支持计算动力学导数但OCS2希望把这些导数和CppADCodeGen自动微分机制统一起来生成高效的导数代码。这层封装也意味着你在写自己的控制器时通常不需要直接和Pinocchio的API打交道但编译时必须让CMake能找到Pinocchio。3.2 安装路线对比apt和源码编译Pinocchio的安装有两条路各有利弊。apt安装的好处是快sudo apt install libpinocchio-dev但坑也很明显Ubuntu官方源里的Pinocchio版本往往偏旧某些版本和最新的OCS2有接口兼容问题。如果你只是试试OCS2自带示例apt版勉强能跑一旦你想用Pinocchio的新功能或者遇到“函数找不到定义”这类编译错误大概率就是版本不匹配的锅。源码编译更可控推荐路线如下# 先装编译依赖 sudo apt install libeigen3-dev libboost-all-dev liburdfdom-dev liburdfdom-headers-dev \ libcppad-dev libassimp-dev liboctomap-dev git clone https://github.com/stack-of-tasks/pinocchio.git cd pinocchio mkdir build cd build cmake -DCMAKE_BUILD_TYPERelease -DBUILD_PYTHON_INTERFACEOFF -DBUILD_TESTINGOFF .. make -j$(nproc) sudo make install编译参数里-DBUILD_PYTHON_INTERFACEOFF值得多说一句很多人在这一步被Python绑定编译卡住因为编译Python接口需要额外的Boost.Python和pybind11依赖。OCS2用的是Pinocchio的C接口完全用不到Python绑定直接关掉能省出一堆事。-DBUILD_TESTINGOFF也一样测试代码编译起来又慢又容易出警告关掉就好。装完之后用下面这段代码验证一下基本动力学接口能不能用#include pinocchio/multibody/model.hpp #include pinocchio/multibody/data.hpp #include pinocchio/parser/urdf.hpp int main() { std::string urdf_path your_robot.urdf; pinocchio::Model model; pinocchio::urdf::buildModel(urdf_path, model); pinocchio::Data data(model); return 0; }能编译通过说明Pinocchio基本安装没问题。3.3 与OCS2链接时的坑和排查Pinocchio装好了和OCS2对接的时候才是真正容易出问题的地方。我列几个高频坑。第一个坑是find_package(pinocchio)找不到。Pinocchio的CMake配置文件路径通常在/usr/local/lib/cmake/pinocchio或/usr/lib/cmake/pinocchio。如果找不到多半是安装路径没进CMake搜索范围可以在CMakeLists.txt里手动加list(APPEND CMAKE_PREFIX_PATH /usr/local) find_package(pinocchio REQUIRED)第二个坑是编译期Eigen对齐报错。Eigen的固定大小向量类型比如Eigen::Vector4d在C17以前涉及内存对齐问题OCS2底层大量使用Eigen类型一旦某个接口函数的参数没用EIGEN_MAKE_ALIGNED_OPERATOR_NEW宏运行时可能直接崩溃报错里通常能看到assertion failed这类字眼。遇到这个问题不要慌检查一下你自定义的类里是否包含Eigen固定长度成员并且声明了对齐宏同时确认所有编译单元都用了同样的Eigen版本。第三个坑是链接时缺urdfdom符号。OCS2中的urdf解析依赖urdfdom库而Pinocchio内部也用了urdfdom这两个库如果版本不匹配链接时会报一堆undefined reference。解决办法是保证系统里只有一套urdfdom版本不要混用apt和源码安装的版本。第四个坑是Release和Debug混编。OCS2官方推荐用Release模式编译如果你自己的包是Debug而依赖库是Release配合Eigen时会出现奇奇怪怪的运行问题。统一用Release编译能省去很多烦恼。最后无论怎么踩坑编译OCS2时建议加一个环境变量确认库版本信息printenv | grep -i pinocchio如果发现LD_LIBRARY_PATH里既有旧版又有新版Pinocchio路径大概率会产生运行时加载错误的库。稳妥的做法是把无关的库路径清理干净只保留你要用的那一个版本。4. 搭建四足机器人MPC控制器的核心链路4.1 从模型描述到代码urdf、配置和生成器环境通了之后真正开始写控制器时第一步是把机器人模型准备好。OCS2的四足模块首先要读入urdf文件但它的要求不只是“把urdf丢进去”还需要关节顺序和基座命名满足约定。打开OCS2四足示例里的配置通常会看到一个类似这样的YAML配置文件model_settings: fileName: your_robot.urdf baseName: base jointNames: - LF_HIP - LF_THIGH - LF_CALF - RF_HIP - RF_THIGH - RF_CALF ...这里的jointNames顺序非常关键。OCS2内部所有向量状态向量、控制向量、雅可比矩阵的列都按照这个顺序组织顺序一错MPC预测的运动方向就是乱的表现是机器人原地乱抖看起来完全没道理。我调试时遇到过这样一个问题前腿关节顺序写反了MPC解出来的力矩全部对不上号Gazebo里机器人四条腿都在蹬但就是不走。后来把urdf里的关节顺序和配置逐一对齐才恢复正常。还有一个小细节urdf里的关节限位limit和OCS2配置里的状态约束要一致。如果urdf里髋关节限位是±2.0弧度而MPC求解器里的约束还是默认的±1.0求解器的解可能在实机上直接碰到硬件限位非常危险。4.2 MPC参数与求解器设置MPC的核心参数写在mpc_settings里约等于“控制器行为说明书”。我常用的配置项大致如下mpc: timeHorizon: 1.5 dt: 0.05 iterationsPerMpc: 10 solver: SQPtimeHorizon是预测时域通俗说就是MPC“向前看多远”。四足机器人上1.2到2.0秒之间是常见区间太长计算量暴涨太短预判不足。dt是离散时间步长决定预测阶段的状态更新颗粒度。0.02到0.1秒之间常见越小越精细但越慢。iterationsPerMpc是每个控制周期内SQP迭代的次数。数值越大解越收敛计算代价也越高。还有一个容易被忽视的参数是任务文件里的cost权重一般长这样weights: stateCost: [1.0, 1.0, 1.0, 10.0, 10.0, 10.0, 0.01, ...] inputCost: 0.001stateCost里每一项对应状态量位置x、y、z、姿态角、关节速度等。姿态项的权重通常比位置项大因为四足机器人稍微歪一下就可能摔倒关节力矩项的inputCost一般给一个很小的值相当于“可以输出大力矩但不要浪费”这个值设太大控制器会变得“佛系”腿上没劲稍微受点扰动就倒。4.3 控制器初始化与主循环OCS2四足MPC的初始化路径大致是先根据urdf和配置文件创建QuadrupedInterface再实例化MPC控制器然后进入主循环。伪代码结构大概是这样// 1. 加载模型和配置 auto modelLoader std::make_sharedQuadrupedModelLoader(taskFile); auto interface modelLoader-getInterface(); // 2. 初始化状态、输入 vector_t initState Eigen::VectorXd::Zero(interface-getStateDim()); vector_t initInput Eigen::VectorXd::Zero(interface-getInputDim()); // 3. 创建MPC控制器 QuadrupedMPC mpc(interface, taskFile); mpc.reset(initState, initInput); // 4. 主循环接收状态估计 - 更新MPC目标 - 求解 - 输出力矩 while (ros::ok()) { auto currentState getStateFromEstimator(); mpc.update(currentState, initInput, mpcTime); auto mpcSolution mpc.getSolution(); sendCommandToActuators(mpcSolution); }这里有个容易忽略的概念当MPC在预测里计算未来n步的状态和控制量时你真正拿来发给执行器的通常只有当前时刻那一步。其他步的力矩去哪了它们都被预测阶段“预演”了一遍给下一步决策提供参考并不真正发送。这也就是MPC和传统最优控制的本质区别——它不是算出整条轨迹然后开环执行而是闭环地不断重算。主循环的频率由MPC的dt和每次迭代耗时决定。理论上如果dt0.05、每次MPC更新耗时小于50ms就可以实时跑。如果你的求解耗时超标优先压缩iterationsPerMpc其次考虑减小timeHorizon不要一上来就砍dt容易让预测太粗导致控制质量下降。5. 调试阶段的高频问题与参数调节经验5.1 权重矩阵和horizon的调节逻辑把MPC跑起来之后调参才是真正的磨人环节。很多人拿到官方示例直接改权重发现越调越乱根因是没搞清楚自己控制的瓶颈在哪。四足MPC里的核心权重有三类位置误差权重、姿态误差权重、输入代价权重。我的经验是第一步先把姿态权重调到位置权重的3到5倍先保证机器人不摔倒再去调位置跟踪精度。姿态一旦垮了后续一切都是白搭。等机器人能站住、能抗推开再去提高位置权重让MPC更有“追目标”的欲望。timeHorizon的调整要配合步态周期来。四足机器人的步态周期一般0.3到0.8秒timeHorizon至少得覆盖一个完整步态周期否则MPC在预测时根本看不到这条腿何时落地规划出来的支撑腿切换会很奇怪。举个例子如果你的trot步态周期是0.4秒timeHorizon低于0.4秒就是政治错误至少设到0.5到0.6秒才有意义。5.2 Gazebo中常见的抖动问题仿真环境里的抖动跟实机还不完全一样但调试思路相通。我在Gazebo里碰到的抖动多半来自状态反馈频率和MPC更新频率不匹配。Gazebo的物理仿真步长默认是1ms但OCS2的MPC更新频率可能只有20到50Hz。这就意味着MPC每执行一次中间会过去几十个仿真步机器人状态已经变了。如果你的状态估计没做滤波MPC拿到的状态是突跳的控制量也会跟着抖。解决办法是给状态信号加一个简单的低通滤波或者在MPC读取状态前做一次滑动平均。这个看似土办法的操作实际效果立竿见影。另一个常见抖动来源是接触检测。OCS2的MPC在预测阶段会假设足端按一定模式接触地面如果Gazebo里的接触力检测不稳定模型预测和实际仿真不一致控制器就会“猜错”地面在哪表现就是腿在触地瞬间猛发力。这时可以检查一下Ground Contact的配置或者调低接触力阈值让接触状态更干净。5.3 从仿真到实机的几个关键差异仿真能站稳不代表实机也能。最大的差异是延时。OCS2的MPC解决方案里默认有一定的时延补偿机制但实机上关节执行器响应、CAN总线和状态估计器的延迟很可能超模。最直接的办法是给MPC发指令时加一个可调的延迟补偿项或者把实机状态估计的时间戳对齐确保MPC计算的决策时刻和机器人实际所处时刻一致。实机上另一个明显差异是模型不准。Pinocchio里用的动力学参数来自urdf但实机每个电机的摩擦、连杆的质量分布都有误差。我建议第一步先用系统辨识工具把摩擦项和关节极限标定出来把修正后的参数更新到urdf里这样MPC预测用的模型才贴近真机。跳过这一步直接在实机上调MPC实际上是在用一个错误模型做预测地上一滑就会摔是很危险的。还有一点关于初始状态。MPC启动时如果收到的初始状态和实际机器人姿态差太多求解器会尝试用巨大控制量把“预测状态”拉回目标实机上可能直接造成飞腿或爆关节。所以控制器上线前一定要确认状态估计已收敛。我自己的习惯是先把机器人放在安全位置启动让MPC先跑几步空载观察输出无异常后再接入执行器。6. 参考资源与常见错误速查6.1 常见编译和运行错误对照这一节把前面散落提到的问题汇总成一张速查表方便你卡住的时候翻。症状可能原因解决办法find_package(pinocchio) failedCMake搜索路径不含Pinocchio安装路径在CMakeLists中追加CMAKE_PREFIX_PATH或apt重装Eigen对齐崩溃编译选项不一致或Eigen版本不统一统一编译模式为Release检查EIGEN_MAKE_ALIGNED_OPERATOR_NEW链接时urdfdom未定义系统存在多版本urdfdom保留一套清理其他库路径MPG求解耗时过长iterationsPerMpc或timeHorizon太大压缩iterationsPerMpc减少预测步数四条腿乱蹬但不前进关节顺序和urdf不一致逐个比对关节名称和顺序仿真中接触时猛烈发力接触力检测不稳定调整接触阈值或增加滤波6.2 跑通你的机器人需要额外确认的细节如果你已经能跑通官方示例准备换到自己设计的机器人上下面几件事提前确认到位能省掉大量排查时间。第一检查关节方向。urdf里关节旋转轴的正负方向会影响MPC预测的力矩方向。最简单的方法是在rviz里加载urdf手动拖一下各个关节确认正方向和你代码里定义的jointNames一致。第二确认机器人的基座坐标系。OCS2里基座的位姿通常用x、y、z加上四元数表示坐标系的朝向比如z轴向上还是向下必须和urdf基座、仿真世界坐标系一致。坐标系轴向搞反MPC会把“前进”理解成“横着走”有时候看起来像是控制器坏了其实只是坐标系没对齐。第三留意你使用的中文或特殊字符路径。urdf文件路径、配置文件路径和工程路径里如果出现中文或空格某些版本的Boost和Pinocchio解析会出问题。工程规范一点全英文路径是最稳的。6.3 遇到问题去哪翻资料OCS2的官方文档在https://leggedrobotics.github.io/ocs2/但说实话文档更新速度赶不上代码更新速度不少细节还是得靠读源码。ocs2_quadruped_mpc目录下的示例是很好的参考入口建议从QuadrupedMPC类入手逐步追它的初始化流程和run函数。Pinocchio的问题建议去它们的GitHub Discussions里搜官方维护者非常活跃。Eigen和CppADCodeGen的问题则推荐在StackOverflow搜英文关键词通常能碰到前人同样的坑。最后再分享一个我在实际调试中养成的习惯每次改动配置文件或参数前先用git把当前状态提交一次或者至少复制一份带日期的备份。MPC参数调试经常会出现“改了三个参数不知道是哪个起了作用”的情况有版本记录才能精准回退和对比。这个小习惯在好几次调参调崩之后救了我强烈建议你用起来。
返回列表