
写这篇博客的时候我其实一直在想要怎么安排内容结构。PX4、Gazebo、ROS2、无人机轨迹跟踪、仿真环境搭建这几个词拆开看都不难但把它串成一条完整的链路从零开始走到能跑通一个带轨迹跟踪的仿真环境中间涉及的工具、版本、通信协议和调试手段实在太多了。如果你在搜索引擎里看到这个标题大概率是和我当初一样拿着一台装着Ubuntu的电脑准备把整个生态搭起来然后在仿真里验证自己的控制算法。这篇文章就按我实际的搭建流程来写把每一步的选型原因、操作命令和踩过的坑都留在里面。1. 整体方案设计与版本选型1.1 先搞清楚这套仿真环境到底在仿什么很多初学者第一次接触PX4仿真会以为Gazebo只是用来渲染一个炫酷的3D场景ROS2只是用来发发消息。实际上这套环境里承担的任务比想象中多PX4固件以纯软件的形式在电脑上编译运行模拟飞控的传感器融合、姿态控制、位置估计和状态机Gazebo提供物理引擎和传感器模型把飞机在虚拟世界里的运动、受力、IMU数据、视觉信息都算出来ROS2则负责把PX4和外部算法连接起来我们写的轨迹跟踪控制器运行在ROS2节点里通过MAVROS或MAVSDK与PX4通信再把期望的位置或速度指令返回给飞控。简单说Gazebo是“身体”PX4是“小脑”ROS2里的控制器是“大脑”。你要做的就是让这三者协同工作最终让虚拟飞机沿着预设轨迹飞行。1.2 版本组合选择为什么我推荐Ubuntu 22.04 ROS2 Humble Gazebo Classic 11 PX4 v1.13版本选型是整个搭建过程中最容易被低估的一步。我见过太多人卡在编译报错上其实不是操作的问题而是版本矩阵不兼容。选这套组合的原因很直接Ubuntu 22.04是当前社区支持度最高的长期支持版本ROS2 Humble也是和Ubuntu 22.04配套的LTS发行版两者在2022年之后形成了稳定的生态组合PX4官方文档里对v1.13的支持非常成熟配套的Gazebo版本是Gazebo Classic 11而不是新版的Gazebo Garden或Harmonic。如果你选PX4 v1.15及以上官方已经把默认仿真器迁移到了新版Gazeboros_gz桥接和offboard控制接口都变了网上的教程数量和质量远不如经典组合。而我一开始就是在Ubuntu 24.04 ROS2 Jazzy Gazebo Harmonic的环境里踩了几天坑最后换回22.04才跑通。对于只想专注轨迹跟踪算法的开发者来说稳定大于追新。这里额外说一句网上很多人问“ROS1和ROS2可以共存吗”答案是可以但我强烈不建议在你刚入门的时候去搞共存。环境变量互相污染、依赖冲突的问题会让你丧失排查问题的耐心。我的原则是一台干净的环境专注一个ROS主版本能规避掉一大半幺蛾子。1.3 工具链全景图从通信链路到可视化需要准备哪些东西在开始动手之前先在脑子里搭一张工具链全景图后面操作就不容易迷路。整个链路分成四层PX4 Autopilot固件源码负责SITL仿真模式下的飞控逻辑运行编译后生成可执行程序Gazebo Classic 11负责物理仿真与传感器数据生成同时提供飞机的三维模型MAVROSPX4与ROS2之间的通信桥梁负责把MAVLink协议转换成ROS2的topic和service自研控制器节点订阅飞机状态发布期望位置或速度最终还要配套Rviz2做轨迹可视化。如果你准备用QGroundControl地面站那它就是第五个角色用于观察姿态数据、参数调整和手动解锁。这套链路里MAVROS是最容易出问题的一环因为它的版本要和ROS2严格匹配而且依赖的GeographicLib地理库需要单独下载数据集后面我会把具体命令完整写出来。2. 环境搭建与PX4固件编译2.1 Ubuntu基础环境配置换源、显卡驱动和基础依赖拿到一台干净的Ubuntu 22.04机器之后先别急着装ROS2把基础环境处理好再动手能省很多事。首先是软件源国内用户建议直接换成阿里云或清华的镜像源不换源的话后面apt下载依赖时那个速度真是让人崩溃。其次是显卡驱动如果用的是NVIDIA独立显卡建议装好官方驱动这个直接影响Gazebo的渲染性能。不装驱动虽然也能跑软件渲染但画面会卡顿加载地形时风扇狂转体验比较差。换源和装驱动的操作步骤网上很多我这里只说一个容易忽略的点安装完显卡驱动后去“软件和更新”里确认选中的是NVIDIA驱动而不是nouveau开源驱动然后重启在命令行里执行nvidia-smi能看到显卡信息才算装好。很多人在Gazebo界面闪烁或黑屏的问题源头就是显卡驱动没配对。2.2 ROS2 Humble完整安装从apt源到rosdep环境检测ROS2 Humble的安装官方文档写得很清楚这里只强调几个关键坑。第一ros2的apt源必须使用官方源不能和系统源混用。第二安装完ros-dev-tools之后建议顺手安装rqt、rviz2、gazebo-ros-pkgs等常用包避免后面用到的时候缺依赖。第三环境变量source /opt/ros/humble/setup.bash建议写到~/.bashrc里并且放在最后防止被其他setup覆盖。sudo apt update sudo apt install curl lsb-release -y sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key -o /usr/share/keyrings/ros-archive-keyring.gpg echo deb [arch$(dpkg --print-architecture) signed-by/usr/share/keyrings/ros-archive-keyring.gpg] http://packages.ros.org/ros2/ubuntu $(lsb_release -cs) main | sudo tee /etc/apt/sources.list.d/ros2.list /dev/null sudo apt update sudo apt install ros-humble-desktop -y sudo apt install ros-dev-tools -y echo source /opt/ros/humble/setup.bash ~/.bashrc装完之后在新终端里运行ros2 doctor做一个环境自检这个命令会提示你缺失的依赖和配置问题。我建议每个人装完都执行一次很多后续编译报错其实在环境自检阶段就能发现。2.3 PX4固件源码拉取与子模块同步避开“浅克隆”大坑PX4固件的拉取是整个搭建中非常关键的一步。官方推荐直接git clone但这里有一个深坑因为网络原因子模块下载经常失败导致后续编译缺文件。建议第一次拉取时使用--recursive参数保证所有子模块一并克隆。另外如果网络不稳定可以把Git的SSL校验临时关掉或者使用代理具体根据自己的网络环境来。git clone --recursive https://github.com/PX4/PX4-Autopilot.git cd PX4-Autopilot git checkout v1.13.3 git submodule update --init --recursive需要特别提醒的是如果你直接拉取master分支大概率会遇到编译环境和工具链不匹配的问题。我在写这篇博客时PX4已经迭代到了v1.15以上很多编译脚本和依赖都发生了变化。建议直接checkout v1.13.3这样的稳定版本后面跑仿真和写轨迹跟踪代码都更稳妥。如果拉取子模块时断断续续可以反复执行git submodule update --init --recursive直到没有报错为止。2.4 SITL编译过程实录常用编译指令与make参数解析PX4的SITL编译其实并不复杂关键是选对仿真器参数。v1.13版本对应的仿真器名称是gazebo-classic而在新版本里这个参数已经被改掉了。所以在编译之前一定要确认自己checkout的版本号再选择对应的编译目标。cd PX4-Autopilot make px4_sitl gazebo-classic这个命令默认会编译一个四旋翼型号如果你想要别的机型在gazebo-classic后面加上机型名称即可例如make px4_sitl gazebo-classic_iris。编译时间取决于机器配置正常在5到15分钟之间。首次编译会下载一些依赖比如cmake模块、python包如果卡在某一步时间过长可以按CtrlC中断后重新执行make命令本身支持断点续编这点倒是挺友好。编译完成后终端会出现一个PX4的命令行shell同时自动启动Gazebo窗口里面应该有一架四旋翼停在草地上。如果你只看到命令行、没有弹窗渲染别急着认为是失败的可能是显示器环境的问题我在后面的常见问题章节会单独讲Gazebo界面闪烁和黑屏的排查方法。3. 核心链路打通MAVROS与PX4仿真通信3.1 MAVROS安装与GeographicLib数据集的正确配置MAVROS是ROS2和PX4之间的标准通信渠道。在Ubuntu 22.04 ROS2 Humble环境下安装命令很简单但有一个前置步骤经常被遗漏——GeographicLib地理库数据集。缺少这个数据集MAVROS启动时会报一个GPS相关的错误导致整个节点无法正常工作。sudo apt install ros-humble-mavros ros-humble-mavros-extras -y sudo /opt/ros/humble/lib/mavros/install_geographiclib_datasets.shinstall_geographiclib_datasets.sh脚本会从官方源下载数据这个步骤对网络要求比较高。我实测下来如果中途失败可以重复执行几次或者检查网络环境后重试。数据安装完成后建议测试一下MAVROS的启动确保没有报错。ros2 launch mavros px4.launch fcu_url:udp://:14540127.0.0.1:14557这条命令的意思是MAVROS通过UDP端口14540接收PX4发送的MAVLink数据包本机回环地址的14557端口是PX4的SITL对外通信端口。命令执行后你会看到MAVROS开始尝试连接飞控并且在日志中周期性打印连接状态。如果这里显示MAVLink连接成功那么整条通信链路就已经通了一半。3.2 启动顺序与端口约定为什么先开Gazebo再开MAVROSPX4 SITL下的MAVLink通信端口是有固定约定的。默认情况下PX4的SITL会通过UDP 14540端口发送数据给地面站或者MAVROS同时监听14557端口接收外部指令。QGroundControl则默认监听14550端口。这里的关键是启动顺序必须先启动PX4/Gazebo仿真让飞控进入运行状态然后再启动MAVROS去建立连接。反过来的话MAVROS会一直处于等待连接的状态时间长了会自己超时退出。我自己的习惯是开一个终端运行make px4_sitl gazebo-classic等Gazebo弹出窗口并且看到PX4命令行出现之后再开一个新终端运行MAVROS的launch命令。这时MAVROS的日志会从“WAIT”状态快速变成“HEARTBEAT”状态说明飞控和MAVROS已经建立了心跳连接。不妨同时在QGroundControl里观察地面的状态应从“连接中”变成“已连接”飞机的姿态数据和电池信息都会实时刷新。3.3 ROS2话题与服务模型状态订阅、指令发布与设备解锁MAVROS正常连接后PX4的飞控状态就会以ROS2话题的形式暴露出来。对于轨迹跟踪任务我们最常用的几个话题包括/mavros/state飞控的解锁状态、飞行模式、系统状态/mavros/local_position/odom飞控估计的本地位置、速度和姿态四元数/mavros/setpoint_position/local发布期望位置设定点数据类型为geometry_msgs/PoseStamped/mavros/setpoint_velocity/cmd_vel发布期望速度设定点数据类型为geometry_msgs/TwistStamped。除此之外我们还需要调用两个服务一个是/mavros/cmd/arming用于解锁另一个是/mavros/set_mode用于切换飞行模式。这两个服务如果调用时机不对很容易导致起飞失败或者飞机不听指令后面的轨迹跟踪代码里我会把控制逻辑的时序讲清楚。4. 轨迹跟踪控制器实现与Rviz2可视化4.1 Offboard模式原理外环位置控制、内环姿态控制的协作分工在动手写控制器之前先理解PX4的Offboard模式到底是什么。PX4内部自带了姿态控制器和速率控制器它接收外部的期望姿态或期望位置/速度设定点然后通过内部环路的计算最终输出PWM信号控制电机转速。我们写轨迹跟踪控制器本质上是做外环的位置控制根据当前飞机的位置和目标轨迹的偏差计算出期望的加速度、速度或者直接生成期望位置然后把这些设定点发给PX4。PX4官方要求在进入Offboard模式之前必须持续不断以大于2Hz的频率发送期望设定点。如果发送频率不够或者断流PX4会在短时间内自动退出Offboard模式也就是所谓的“失去控制”保护。我习惯设定为20Hz发送频率这个值既不会给通信链路带来负担也远高于PX4的要求在实验里非常稳定。4.2 基于rclpy的轨迹跟踪节点设计起飞逻辑、参考轨迹生成与控制律计算下面这个Python节点是我在实际项目中使用的最简版本核心逻辑包括等待连接、解锁起飞、切Offboard模式和周期发送轨迹点。轨迹我选了Lissajous曲线因为它在X和Y方向上都是光滑的正弦组合适合用来观察跟踪效果而且参数调起来很直观。import rclpy import math from rclpy.node import Node from geometry_msgs.msg import PoseStamped, TwistStamped from mavros_msgs.msg import State from mavros_msgs.srv import CommandBool, SetMode class TrajectoryTracker(Node): def __init__(self): super().__init__(trajectory_tracker) self.state_sub self.create_subscription(State, /mavros/state, self.state_cb, 10) self.pos_pub self.create_publisher(PoseStamped, /mavros/setpoint_position/local, 10) self.arm_service self.create_client(CommandBool, /mavros/cmd/arming) self.mode_service self.create_client(SetMode, /mavros/set_mode) self.current_state State() self.timer self.create_timer(0.05, self.control_loop) # 20Hz self.start_time self.get_clock().now() def state_cb(self, msg): self.current_state msg def control_loop(self): now self.get_clock().now() t (now - self.start_time).nanoseconds * 1e-9 # 1. 进入Offboard模式前必须先以2Hz发送设定点 pose PoseStamped() pose.pose.position.x 0.0 pose.pose.position.y 0.0 pose.pose.position.z 2.0 if t 5.0: # 2. 5秒后开始跟踪Lissajous轨迹 pose.pose.position.x 2.0 * math.sin(0.5 * t) pose.pose.position.y 2.0 * math.sin(1.0 * t) pose.pose.position.z 3.0 0.5 * math.sin(0.25 * t) self.pos_pub.publish(pose) # 3. 切换到Offboard模式并解锁注意顺序先发送设定点再切换模式 if self.current_state.mode ! OFFBOARD and t 3.0: mode_req SetMode.Request() mode_req.custom_mode OFFBOARD self.mode_service.call_async(mode_req) elif not self.current_state.armed and t 4.0: arm_req CommandBool.Request() arm_req.value True self.arm_service.call_async(arm_req) def main(argsNone): rclpy.init(argsargs) tracker TrajectoryTracker() rclpy.spin(tracker) tracker.destroy_node() rclpy.shutdown() if __name__ __main__: main()这段代码里有一个顺序上的细节值得多说一句我习惯先发送3秒左右的初始设定点然后再请求切换到Offboard模式最后再请求解锁。因为PX4在进入Offboard模式时会校验设定点流是否有效如果你先解锁再切模式偶发情况下飞控会直接进入FailSafe表现为电机转一下又立刻停掉。先切模式后解锁的顺序我试过很多次稳定性最高。4.3 控制律与参数整定位置P控制、前馈速度与阻尼项上面的示例代码只做了位置设定点的直接发布如果想让跟踪效果更好就需要在控制器里加更细致的控制律。PX4内部的姿态控制器已经很快但外环位置控制如果只是纯比例飞机容易在目标点附近振荡。我实际用的控制器通常包含三部分位置比例项、速度阻尼项和前馈速度项。期望加速度的计算方式可以写成这样a_des Kp * (p_des - p_cur) Kd * (v_des - v_cur)其中p_des和v_des来自轨迹方程对位置和时间求导得到的参考位置与参考速度。得到期望加速度后可以通过PX4的加速度控制接口转换成期望姿态角也可以更简单地转换成期望速度指令发送到/mavros/setpoint_velocity/cmd_vel。这样控制起来飞机更平稳轨迹跟踪的延迟也更小。参数整定方面Kp建议从0.8开始Kd从0.3开始。如果飞机在某一个方向上出现高频抖动说明Kp过大或者Kd过小如果跟踪轨迹时曲线明显滞后说明Kp偏小。调试时建议先只做悬停把位置环调稳了再切换到轨迹跟踪这是最省时间的做法。4.4 Rviz2可视化显示飞机模型、真实飞行轨迹与目标轨迹对比Rviz2虽然不直接影响控制效果但调试时没有可视化真的寸步难行。你需要装好robot_state_publisher然后在Rviz2里添加TF显示和Path显示。MAVROS本身会发布/mavros/local_position/odom话题你可以用这个话题作为TF的odom来源再用一个简单的Python节点把飞机历史位置累积成Path消息发布出来。目标参考轨迹也可以用同样的方式发布这样两条轨迹叠在一起跟踪误差一眼就能看明白。如果Rviz2里看不到飞机模型大概率是TF树不完整。PX4仿真环境下通常发布的TF是odom到base_link你需要确保map到odom的静态坐标系存在不然在Rviz2里选择Fixed Frame为map时整个场景就是空的。这个问题卡了不少人我建议在启动launch文件时一并加载静态坐标变换代码里几行就能解决node pkgtf2_ros execstatic_transform_publisher args0 0 0 0 0 0 map odom/5. 常见问题与排查技巧实录5.1 “为什么Gazebo界面一直在闪”以及其它渲染问题的根治方案Gazebo界面闪烁是绝对的高频问题几乎每个搭建PX4仿真的人都会遇到。常见原因有三种渲染引擎切换异常、显卡驱动不对、以及软件OpenGL渲染与Qt的兼容性问题。最简单的临时方案是修改Gazebo的高级渲染设置把“软阴影”和“粒子效果”全部关掉同时把视角的分辨率从最高调低一档。如果问题依旧尝试用环境变量强制Gazebo走软件渲染export LIBGL_ALWAYS_SOFTWARE1 gazebo --verbose软件渲染不卡了基本可以确定问题出在显卡驱动或者OpenGL库版本上去把NVIDIA驱动重装一遍就可以了。如果你的机器没有独立显卡跑Gazebo会觉得非常卡这是正常的软件渲染模式下我只能说勉强能操作想要流畅的物理仿真还是得配一张显卡。5.2 编译报错、子模块下载失败和make中断后的恢复方法PX4编译过程最常见的错误集中在子模块缺失和依赖工具链不完整两类。git clone之后子模块缺失会导致编译到一半提示找不到某个头文件依赖工具链不完整则会在cmake阶段直接报错。遇到这类问题先用git submodule update --init --recursive确认子模块完整再检查依赖工具。如果反复报某个python模块缺失比如empy、toml用pip补上即可。make中断后重跑同样命令通常能继续但如果中途改过分支或子模块建议先make clean一下避免缓存冲突。5.3 MAVROS连不上PX4、心跳超时与Offboard模式闪退排查MAVROS连不上PX4是最考验耐心的问题。排查思路按顺序来第一确认PX4 SITL已经启动Gazebo窗口处于运行状态第二确认MAVROS的fcu_url端口写的是14540而不是别的端口第三检查两台程序是否在同一台机器上、网络命名空间是否隔离第四看PX4命令行有没有报MAVLink相关的错误。如果还是不行尝试在启动MAVROS之前先用QGroundControl连接一次确认PX4的MAVLink广播本身没问题。Offboard模式闪退的场景也常见表现为解锁后电机转了一会儿就停下或者飞机原地掉高度。原因大多是设定点发送频率不足2Hz或者发送的话题类型不对。检查你的发布频率确认消息是发到local还是global坐标系的topic这段逻辑没问题Offboard模式一般就稳了。5.4 避坑经验速查表我把自己在搭建过程中踩过的坑整理成了一个表格按出现的频率排序方便你直接对照排查。现象大概率原因解决办法Gazebo窗口弹不出显卡驱动问题或DISPLAY变量错误确认nvidia-smi正常尝试软件渲染Gazebo界面闪烁渲染选项过高或OpenGL兼容问题关闭软阴影降低分辨率重装显卡驱动子模块下载失败网络不稳定导致git中断反复执行submodule update或配置代-理make编译报错缺少python依赖或cmake缓存冲突pip安装empy等重试前make cleanMAVROS连接超时端口写错或PX4未启动检查fcu_url先启动PX4再启动MAVROSOffboard闪退设定点频率低于2Hz设定点发布周期设为0.05秒进入Offboard后飞机乱飞控制增益过大或设定点阶跃过大调小Kp起点设定为当前飞机高度附近Rviz2里飞机模型为空TF树缺少map到odom变换加载静态坐标变换Fix Frame选odom6. 把环境升级为工程项目的几个扩展方向按照上面的步骤跑通整套仿真之后你的工作台已经具备了“全栈”能力但从能跑通到能用来做研究中间还差几个扩展方向。第一个方向是更换飞机模型和传感器配置PX4自带的机型很多比如固定翼、垂直起降、无人车在make命令中指定不同机型即可仿真环境对不同机器人的支持比大多数人想象的完善。第二个方向是把视觉传感器加进来Gazebo里可以挂载摄像头、激光雷达和深度相机配合ROS2的图像话题就能做视觉定位或者目标检测相关的实验。第三个方向是把控制器从位置跟踪升级为轨迹规划与避障这需要你在ROS2里加一个全局规划器把参考轨迹先经过避障算法过滤一次再发送给底层的轨迹跟踪控制器。这些扩展听起来复杂但因为底层链路已经打通本质上只需要在ROS2节点层面做替换和增加订阅不再需要碰任何PX4和Gazebo的底层代码。这正是全栈仿真环境最有价值的地方换算法、换传感器、换机型的成本被压到了最低。我个人的体会是整套环境中真正需要耐心的是前几步的版本选型和环境安装一旦PX4能够在Gazebo里以SITL模式跑起来后面你用ROS2控制飞机就是一个纯算法层面的问题。最后再分享一个小技巧多关注PX4官方源码里的Examples和IntegrationTests目录里面藏着大量可以直接改来用的offboard控制示例远比自己在网上搜的零散代码要规范和权威。