我先把这套东西吃透再动手写。说实话,在无人机开发圈子里,PX4 + Gazebo + XRCE-DDS + QGC这四个词凑到一起,基本就是一条完整的仿真开发主链路。但网上大部分教程都只讲了其中一个环节,把四个串起来讲的很少,能讲透的更少。我写这篇的时候,会直接从实际搭建的角度出发,把每个环节为什么需要、怎么配置、踩过什么坑都交代清楚,不绕弯子。
1. 这套组合到底是什么,解决什么问题
先把话说在前面:PX4、Gazebo、XRCE-DDS、QGC,这四个东西单独拎出来,每一个都不是什么新概念,但把它们串成一条完整链路,就是当前开源无人机系统开发最主流的仿真验证环境。我自己的理解是,这套组合解决的核心问题只有一句话:代码改了到底能不能飞?只不过不是在真机上试,而是在一个高度接近真机的仿真环境里先跑一遍。
先快速介绍一下这四个角色在整条链路里的分工:
- PX4是飞控固件,跑在飞控硬件上的完整自动驾驶系统,负责姿态控制、位置控制、任务规划、传感器融合这些脏活累活。在仿真环境下,它跑的是同一套代码,只是底层驱动变成了仿真驱动。
- Gazebo是3D物理仿真环境,提供机身模型、传感器仿真、物理碰撞、空气动力学模拟。飞机在它里面飞,和在真实世界里飞的物理逻辑是同一套。
- XRCE-DDS是PX4和外部通信的桥接层,它让PX4内部的uORB消息能够通过DDS协议发给外部的ROS 2节点,或者反过来接收外部指令。没有它,你就没法用ROS 2去控制或者读取飞机的状态。
- **QGC(QGroundControl)**是地面站软件,提供人机交互的界面,相当于飞机驾驶舱仪表盘,可以实时看到飞机的姿态、位置、电池电压、飞行模式,也能下发任务指令。
这四者串起来的完整数据流大概是这样的:PX4在Gazebo里面运行,模拟出一架无人机,把自身的状态消息通过XRCE-DDS桥接出来,QGC作为地面站和PX4通过MAVLink通信,你可以一边在QGC上看着飞机数据,一边在ROS 2节点里发布控制指令,共同驱动这架虚拟无人机完成起飞、巡航、降落。
这个组合适合谁?说白了,任何想搞无人机开发但不想每次测试都炸机的人。不管是做视觉SLAM的、做路径规划的、做目标跟踪的,还是就想搞清楚PX4内部数据流的,这套环境都是绕不开的入场券。你可以在里面随便折腾,代码写得稀烂也就炸个仿真飞机,重启一下又是一条好汉。
2. 工具选型的核心逻辑,以及为什么绕不开
很多刚开始接触这套技术栈的人,首先遇到的问题不是"怎么装",而是"为什么是这几个软件的组合"。我先把这个底层逻辑讲清楚,因为理解了为什么,后面照着做的时候才不会被各种坑绕晕。
2.1 为什么ROS 2要用XRCE-DDS而不是MAVROS旧方案
PX4和ROS 2之间通信,历史上换过好几波方案。早期主流是MAVROS走MAVLink协议,通过串口或者UDP转发消息。这种方案的问题是MAVLink消息有固定的消息格式,发什么消息、消息里带什么字段,都被协议限定死了,自定义性差,而且封装层次多,链路过长的导致消息延迟不可控。
PX4从v1.14版本之后,官方主推的是PX4-ROS 2 Interface,走的就是XRCE-DDS这条链路。XRCE-DDS的全称是eXtremely Resource Constrained Environments DDS,这个协议本身就是为了资源受限的嵌入式系统(也就是飞控MCU)设计的。它允许老旧的MCU设备通过一个轻量级的Agent和标准DDS网络建立通信,PX4侧不用跑完整的DDS协议栈,只需要维护一个轻量级的XRCE-DDS客户端,跟电脑上跑的Micro XRCE-DDS Agent做桥接。
这么做的好处是多方面的:
- 消息延迟可控性更好,可以直接做实时性要求高的任务
- 完整支持ROS 2的发布/订阅模型,不用像MAVROS那样做消息转换适配
- 直接支持自定义uORB主题映射,PX4内部几乎任何topics都可以暴露出来
换个说法,你要是只跑个固定翼航点飞行,MAVLink+MAVROS够用是够用,但你要是想在ROS 2里做offboard控制、做机载SLAM、做编队协同,MAVROS那套消息转换适配层能让你痛苦到怀疑人生。XRCE-DDS直接打通了PX4内部uORB世界和ROS 2外部世界,整个开发体验是完全不一样的。
2.2 Gazebo版本选择的纠结,我终于弄明白了
Gazebo的版本问题,是一个巨大的坑。如果你看网上的老教程,很多人会让你装Gazebo 11,那个是配ROS 1和PX4老版本的。但如果你用的是Ubuntu 22.04 + ROS 2 Humble,PX4 v1.15及以上,你的选择就应该是Gazebo Garden(PX4官方默认)或者Fortress(Humble官方支持)。
我自己一开始栽在这个地方,照着旧教程装了Gazebo 11,结果PX4编译完启动仿真直接闪退,半天排查不出原因。后来查了官方文档才弄清楚,PX4 v1.15的gazebo仿真插件是默认配套Gazebo Garden构建的,旧版Gazebo 11的插件接口不兼容。
这里要分清两个事情:
- Gazebo classic(11及以前):ROS 1时代的老牌仿真器,插件API和物理引擎都比较旧,虽然社区积累大,但在PX4 v1.15+里已经不是亲儿子了
- Gazebo Garden/Ignition:新一代仿真器,重新设计了插件系统、传感器系统和渲染架构,PX4官方从v1.15开始把默认支持切换到了这个系列
如果你用的是Ubuntu 22.04,我建议直接挂上PX4官方工具链脚本让它自动装,或者你自己手动搞定Garden。Fortress也能凑合,官方文档说支持和Gazebo Fortress搭配,但是Fortress在Ubuntu 22.04上的依赖问题还挺多的,不是特别推荐。别的不说,光Fortress的渲染后端在虚拟机里闪退这个问题,就能折腾你一个周末。
2.3 RK3588这些ARM板子在整条链路里的位置
热词里有"基于rk3588 px4飞控开发"这个说法,就顺带说一下ARM平台在这套环境里的角色。严格意义上,PX4固件跑在STM32这类MCU上,RK3588这类ARM SoC跑的是Linux,在PX4架构里属于机载计算机。也就是说,仿真阶段你完全可以在x86电脑上把整套环境搭好,跑熟了之后再把代码交叉编译部署到RK3588上,去驱动真实的PX4飞控。
因为你问的是完整的PX4 + Gazebo + XRCE-DDS + QGC教程,这是个纯软件模拟环境,和Linux机载电脑的配合是后面真机移植的事。真机阶段,PX4跑在飞控上,RK3588上跑机载Linux,PX4通过串口/UART和RK3588通信,XRCE-DDS Agent跑在RK3588上。这个扩展逻辑,我在文章最后会稍微提一下。
3. 环境准备与工具链搭建,一步步来
正式开始动手前,先摆清楚建议的系统配置。我自己实际测试,Ubuntu 22.04是当前最省心的选择,因为PX4 v1.15+和ROS 2 Humble在这个版本上都有官方验证支持。Ubuntu 20.04理论上也可以,但ROS 2 Foxy已经停止维护了,不建议新项目还在Foxy上折腾。
这里说一下版本对照,方便你自己排查问题:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| Ubuntu | 22.04 LTS | 稳定且各组件官方支持最完善 |
| ROS 2 | Humble | Ubuntu 22.04对应的长期维护版本 |
| Gazebo | Garden(默认)/ Fortress | PX4 v1.15+官方默认Garden |
| PX4-Autopilot | v1.15+ | DDS接口完整可用的版本 |
| QGroundControl | 最新稳定版 | 直接下载AppImage即可 |
| Micro-XRCE-DDS-Agent | 最新master | 和PX4配套更新 |
3.1 PX4源码获取与编译
PX4源码的获取非常直接,官方仓库在GitHub上叫PX4-Autopilot,直接拉就行。但这里有一个重要建议:不要用main分支做开发,直接切到最新稳定版tag。我见过太多人拉完main分支编译到一半报错,然后网上查了一圈发现是main分支某个提交引起的临时代码问题,白白浪费时间。
# 克隆仓库 git clone --recurse-submodules https://github.com/PX4/PX4-Autopilot.git px4 cd px4 # 切换到最新稳定版 tag,我写这篇文章时的稳定版是 v1.15.4 git checkout v1.15.4 git submodule update --init --recursive这个--recurse-submodules参数很重要。PX4源码里包含大量子模块,比如内置的MAVLink消息库、各种驱动源码,不提前拉全会导致编译的时候报各种莫名其妙的头文件缺失错误。如果你已经克隆到一半才发现子模块没拉,可以后面补拉。
编译的话,PX4官方提供了一个自动化脚本,帮你装好所有依赖。这套过程大概需要10到20分钟,取决于你的网速和CPU性能:
bash ./Tools/setup/ubuntu.sh这个脚本会帮你安装ROS 2 Humble、Gazebo以及PX4编译所需的全部工具链。执行完之后,官方建议重启电脑,让环境变量生效。如果你不想重启,也可以手动source一下:
source ~/.bashrc3.2 Gazebo仿真环境的验证
很多教程直接跳过这个验证步骤,但我建议你单独先试一次Gazebo能不能正常跑起来。因为如果Gazebo本身有问题,后面启动PX4仿真时会报一堆和模型加载、渲染相关的错误,你想排查都不容易分清是PX4的问题还是Gazebo的问题。
可以直接启动一个最简单的PX4仿真环境来验证:
cd ~/px4 make px4_sitl gz_x500如果一切正常,你会看到Gazebo窗口打开,一架x500四旋翼出现在世界坐标原点附近,同时终端窗口会输出PX4的启动日志,显示姿态估计器初始化完成、传感器校准完成之类的信息。首次启动时,Gazebo会从云端下载模型材质文件,这一步可能很慢,需要耐心等待。
如果你看到的是终端卡住半天没反应,或者Gazebo里面黑屏,大概率是模型下载超时或者GPU渲染不兼容。前者的解决办法是提前手动下载模型文件放到指定缓存目录,后者需要在启动前加上软件渲染参数。第三章我会把这两个问题的排查细节展开讲。
3.3 XRCE-DDS Agent编译安装
Micro XRCE-DDS Agent是PX4仿真和ROS 2之间的桥,是一个独立的小工具,负责在网络里代表PX4的DDS客户端和ROS 2主机进行通信。它的编译非常简单:
git clone https://github.com/eProsima/Micro-XRCE-DDS-Agent.git cd Micro-XRCE-DDS-Agent mkdir build && cd build cmake .. make sudo make install装完以后,你可以在任意终端启动Agent监听UDP端口,默认是8888端口:
MicroXRCEAgent udp4 -p 8888这里有个值得注意的点:PX4仿真启动时,固件会在内部自动启动一个本地的DDS客户端,试图连接这个Agent。如果Agent没开,PX4会打印连接失败的错误日志,但不会影响仿真本身的运行。也就是说,PX4仿真可以跑起来,只是ROS 2外部没法建立通信。所以建议每次跑仿真之前,先把Agent启动起来,省得后面看到报错还要回头排查。
4. PX4固件配置与Gazebo仿真启动详解
搞定了环境,接下来进入核心实操环节。很多新手在PX4仿真启动这一步就开始碰壁,大部分原因是对PX4的启动方式不够理解,不知道怎么去控制启动参数,也不清楚不同环境变量对仿真行为的影响。这一节我把这些细节掰开揉碎讲清楚。
4.1 理解PX4仿真启动的机制,绕过大部分坑
PX4的仿真环境本质上还是PX4固件跑在电脑上(称为SITL,Software In The Loop),不同的是它不用连接真实的传感器和电机,而是把传感器数据和电机控制指令都转发到Gazebo里,由Gazebo里的物理引擎和传感器插件来响应。
在PX4 v1.15+版本里,启动仿真的命令统一通过make来执行:
make px4_sitl gz_x500这条命令的逻辑是:
px4_sitl:编译并运行PX4的SITL版本固件gz_x500:指定启动Gazebo仿真,并使用x500四旋翼机型模型
这里有一些常用变量可以自定义启动行为:
| 环境变量 | 作用 | 示例 |
|---|---|---|
PX4_GZ_MODEL | 指定Gazebo仿真模型 | PX4_GZ_MODEL=x500_depth加载带深度相机的x500 |
PX4_GZ_WORLD | 指定仿真世界 | PX4_GZ_WORLD=windy加载带风场的世界 |
PX4_SIM_SPEED_FACTOR | 仿真速度倍率 | 设为2表示以2倍速运行仿真 |
PX4_NO_MAVLINK | 禁用MAVLink输出 | 主要在只想跑DDS时使用 |
PX4_SYS_AUTOSTART | 指定要加载的机型配置 | make px4_sitl gz_x500 PX4_SYS_AUTOSTART=4001 |
举个例子,启动时你想跑一个带风场扰动的仿真,同事加载x500深度相机版本,可以这样写:
make px4_sitl gz_x500_depth PX4_GZ_WORLD=windy4.2 内八解锁和参数设置,飞起来了却启动不了电机?
热词里专门提到"px4内八解锁要设置哪个参数",这个问题挺常见的。在QGC地面站里解锁无人机有两种方式:一种是内八解锁(就是两个摇杆同时向下向外掰),另一种是在QGC界面里直接用鼠标点击解锁按钮。
如果你用的是仿真环境,解锁前有一道坎很关键:PX4在解锁之前会做安全检查,其中一项是姿态估计是否正常工作。如果你启动仿真后立刻在马达还没就绪的状态下尝试解锁,会发现飞机根本没有任何反应。这个时候你应该先看PX4的终端日志,确认姿态估计器的工作状态:
pxh> commander status正常情况下,你应该看到状态显示为Arming check passed之类的信息,说明飞控自检通过,电机才能被解锁指令驱动。
如果自检没有通过,最常见的参数是COM_ARM_CHECK,它控制解锁时是否需要执行完整的系统自检。你可以通过QGC的参数面板,搜索并把它设为0来跳过检查:
注意:
COM_ARM_CHECK=0是跳过所有的解锁安全检查,包括传感器状态、电池电压等,虽然仿真中不会有真实危险,但建议搞清楚为什么自检失败再决定是否跳过。
另外一个内八解锁相关的重要参数是MC_AIRMODE。航线模式下这个参数的意义不太大,但如果你飞的是穿越机或者要用手动模式做特技飞行,airmode会允许解锁状态下电机在油门零位时仍保持旋转,确保姿态可控。默认值0(disabled)就够,不需要改动。
至于"内八解锁"这个动作本身要不要设置参数?其实不需要。内八解锁是RC遥控器在默认配置下通过摇杆位置组合触发的解锁方式,对应的是PX4的RC_MAP_ARM_SW这个参数。如果你用的是遥控器且没有单独设置解锁拨杆,那么默认就是把油门降到最低并保持内八位置一段时间。在QGC界面上直接点解锁按钮更方便,不需要依赖遥控器摇杆位置。
4.3 多机仿真与自定义世界
再补充一个后续扩展相关的内容:多机联合仿真。编队、集群这类项目在仿真里验证几乎是标配,PX4对此有完整的支持:
cd ~/px4 make px4_sitl gz_x500 PX4_SYS_AUTOSTART=4001 PX4_GZ_MODEL_POSE="0,0,0" # 新开终端启动第二架 make px4_sitl gz_x500 PX4_SYS_AUTOSTART=4002 PX4_GZ_MODEL_POSE="2,0,0"这里的PX4_GZ_MODEL_POSE参数指定了每个飞机在Gazebo世界中的初始位置,PX4_SYS_AUTOSTART参数则让每架飞机对应不同的PX4机型实例。这个过程涉及多机MAVLink和端口分配,具体细节我以后有空再单独写一篇。
4.4 QGroundControl地面站的连接与使用
QGC的安装比自己编译省事得多,直接在官网下载最新稳定版的AppImage,给他赋予执行权限就能跑:
chmod +x ./QGroundControl.AppImage ./QGroundControl.AppImage在仿真环境里,QGC会自动检测到PX4在本地UDP端口14550上广播的MAVLink消息。所以不需要手动配置连接,打开软件就能看到飞机出现在主界面,类型显示为"QGroundControl UDP Link"。
为什么会这么丝滑?原因在于PX4的SITL默认启动脚本会自动启动一个MAVLink实例,向本机的14550端口发送消息,这个正好是QGC的默认监听端口。
打开QGC后,你可以看到顶部有一个与真实飞行几乎一致的界面,包括:
- 左下角的姿态仪表盘
- 右下角的虚拟摇杆
- 中央的地图视图
- 左上角的飞行模式下拉菜单
此时你的飞机还在Gazebo世界中的原点位置,地面上静止状态。点击右上角的起飞按钮,QGC会执行一次自动起飞检查,确认GPS定位、电池电压都正常后,飞到设定高度并悬停。
在QGC里切换飞行模式时,选择"Offboard"模式后,飞机就等着接收来自外部ROS 2节点的控制指令了。关于具体的Offboard控制和自定义消息收发,在第六章的完整融合演示里再展开。
5. XRCE-DDS桥接配置与ROS 2/C++通信实操
现在的重点,转向整条链路的另一条主线:XRCE-DDS桥接,以及基于ROS 2的通信实施。前文已经提过它的核心作用是打通PX4内部uORB消息和外部ROS 2世界。这一节带你亲手把这条通路建起来,并跑通一个简单的例子。
5.1 理解PX4内部机制,才能理解桥接层的存在价值
PX4内部所有传感器数据、状态估计、执行器控制指令,都是通过一种叫uORB的发布/订阅消息总线传递的。比如姿态估计器输出的四元数发布在vehicle_attitude这个主题上,位置估计值发布在vehicle_local_position上,电机的控制指令则是actuator_motors。
这套消息机制是PX4在单芯片嵌入式环境里高效运作的基石,因为它不需要引入复杂网络协议。但问题来了,当外部程序(比如你在ROS 2里写的节点)想要读取这个姿态数据,它怎么拿到?它不可能直接访问PX4的内存总线。
XRCE-DDS方案给出的答案是:PX4启动一个轻量级的DDS客户端,它订阅PX4内部指定的uORB主题,然后通过UDP把消息传给Micro XRCE-DDS Agent。Agent再把这些消息翻译成标准的DDS消息,直接进入ROS 2的网络世界。反过来也一样,ROS 2发布的消息,经由Agent翻译后,写回到PX4内部的uORB总线上。
这个结构的好处在于:PX4本身的代码不需要引入繁重的DDS库,MCU的性能开销几乎可以忽略;而外部ROS 2节点完全按照标准DDS方式通信,不涉及任何PX4内部协议的细节。
5.2 用px4_ros_com包搭桥,跑通离线航点控制
在PX4官方支持的ROS 2接口工具链里,有一个很关键的配套仓库叫px4_ros_com。它的作用是把PX4中需要在ROS 2侧配对的uORB消息定义转换成ROS 2接口,并提供一个micro-ROS代理节点,帮你从ROS 2世界向PX4世界发送Offboard指令。
我们可以在一个ROS 2工作区里安装它:
mkdir -p ~/ws_px4/src cd ~/ws_px4/src git clone https://github.com/PX4/px4_msgs.git git clone https://github.com/PX4/px4_ros_com.git cd ~/ws_px4 colcon build source install/setup.bash编译完成后,这个工作区里就有了所有PX4相关消息的自定义ROS 2消息类型,比如px4_msgs::msg::VehicleAttitude、px4_msgs::msg::TrajectorySetpoint等等。
现在,一个最典型的验证实验是:在Offboard模式下,程序自动发布一组航点位置指令,让PX4朝设定目标飞行。我写一个最简单的Python节点来说明整个逻辑:
#!/usr/bin/env python3 import rclpy from rclpy.node import Node from px4_msgs.msg import OffboardControlMode, TrajectorySetpoint, VehicleCommand from px4_msgs.msg import VehicleLocalPosition, VehicleAttitude import math class OffboardControl(Node): def __init__(self): super().__init__('offboard_control') # 创建发布器 self.offboard_mode_pub = self.create_publisher(OffboardControlMode, '/fmu/in/offboard_control_mode', 10) self.trajectory_pub = self.create_publisher(TrajectorySetpoint, '/fmu/in/trajectory_setpoint', 10) self.vehicle_command_pub = self.create_publisher(VehicleCommand, '/fmu/in/vehicle_command', 10) # 订阅位置和姿态消息用于反馈 self.local_position_sub = self.create_subscription(VehicleLocalPosition, '/fmu/out/vehicle_local_position', self.local_position_callback, 10) self.attitude_sub = self.create_subscription(VehicleAttitude, '/fmu/out/vehicle_attitude', self.attitude_callback, 10) # 定时器,20Hz 发送控制指令 self.timer = self.create_timer(0.05, self.timer_callback) # 目标位置 self.target_position = [5.0, 0.0, -8.0] # 前方5米,高度8米 self.current_position = [0.0, 0.0, 0.0] self.arm_state = False self.offboard_state = False self.flight_phase = 0 def local_position_callback(self, msg): self.current_position = [msg.x, msg.y, msg.z] def attitude_callback(self, msg): pass # 这里可以用四元数来判断姿态稳定 def timer_callback(self): self.publish_offboard_control_mode() self.publish_trajectory_setpoint() self.arm_and_offboard_command() def publish_offboard_control_mode(self): msg = OffboardControlMode() msg.timestamp = int(self.get_clock().now().nanoseconds / 1000) msg.position = True msg.velocity = False msg.acceleration = False msg.attitude = False msg.body_rate = False self.offboard_mode_pub.publish(msg) def publish_trajectory_setpoint(self): msg = TrajectorySetpoint() msg.timestamp = int(self.get_clock().now().nanoseconds / 1000) msg.position = self.target_position msg.yaw = 0.0 self.trajectory_pub.publish(msg) def arm_and_offboard_command(self): # 这里只是简化演示,实际需要配合arming request和offboard mode命令 pass def main(args=None): rclpy.init(args=args) node = OffboardControl() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()这个节点做的事情是:每50毫秒发布一次Offboard控制模式消息(声明当前控制优先使用位置通道),同时发布一个目标位置指令。只要你在QGC上先把飞机设成Offboard模式并解锁,飞机就会飞向你指定的目标位置。
5.3 验证DDS桥接状态,避免数据静默断开
实际开发中,最令人抓狂的问题是:程序跑得好好的,没有任何报错,但飞机就是没有反应。这种情况十有八九就是XRCE-DDS链路断开了。
怎么排查?最直接的办法是盯着PX4的终端日志,看有没有以下这行输出:
[chrt] [px4_dds_client] connecting to UDP endpoint 127.0.0.1:8888如果这行之后紧跟着显示connected successfully,说明链路通畅。如果你看到的是连接超时的提示,说明Micro XRCE-DDS Agent没有启动,或者启动的端口不对。
另一个角度是从ROS 2侧验证:用ros2 topic list确认PX4的消息主题是否可见:
ros2 topic list # 应该能看到 /fmu/out/vehicle_attitude、/fmu/out/vehicle_local_position 等主题如果你能看到/fmu/out/前缀的主题,说明PX4的DDS数据已经进入了ROS 2网络。看不到的话,大概率Agent的问题,先排查Agent。
6. 完整实操案例:从零开始跑通一次仿真飞行
前面都是拆开来讲,这一节我把整套流程完整串一遍,就像你第一天来实验室,拿到的任务是"把四旋翼在仿真里飞起来"。按照这个步骤走,大概率不会翻车。
6.1 一键启动的完整操作序列
整个流程的步骤看起来多,实际跑熟了每次就三条命令的事。我第一次跑的时候大概花了一个小时,主要时间花在编译和Gazebo模型加载上。
Step 1:启动XRCE-DDS Agent
MicroXRCEAgent udp4 -p 8888启动后终端会保持挂起状态,显示等待客户端连接。这个窗口先留着别关,也可以在后面操作中随时切回来看日志。
Step 2:启动PX4 + Gazebo仿真
cd ~/px4 make px4_sitl gz_x500等编译启动完毕,看到PX4命令行提示符pxh>出现,说明飞控进程已经就绪。另一头Gazebo窗口里,x500四旋翼模型应该悬停在原点位置。
Step 3:启动QGC地面站
./QGroundControl.AppImage打开后等几秒,主界面出现飞机图标和实时姿态数据,说明MAVLink通信正常。
Step 4:在QGC里起飞
点击左上角的"Takeoff"按钮,确认弹出的起飞检查项全部通过,再点击确认。此时Gazebo窗口中的飞机应该缓缓升起,QGC界面的高度数据同步变化。
6.2 ROS 2节点控制飞机飞向指定点
在飞机悬停稳定后,切到ROS 2工作区,运行刚才建好的示例节点:
cd ~/ws_px4 source install/setup.bash ros2 run offboard_control offboard_control # 实际包名和可执行文件名按你自己的配置运行之前,记得先在QGC界面上把飞机的飞行模式切换为Offboard并解锁。切换模式可以通过QGC顶部的模式下拉菜单实现,也可以用RC遥控器(如果仿真里配了遥控器模拟模块)。
节点开始运行后,飞机会先保持当前悬停位置,然后向目标点移动。你在QGC的地图上应该能看到飞机图标从原点逐步向指定位置移动。
6.3 飞行数据记录与回放,验证算法结果
飞行过程中的数据记录这件事儿,我建议从第一次仿真就开始做,别嫌麻烦。后面你调算法的时候会发现,数据回放比实时观察要高效得多,因为你可以在同一段飞行过程中反复调整数据处理逻辑,不用一遍遍重复飞行。
PX4自带ULog日志系统,仿真环境下日志会自动记录在~/tmp目录下,文件名类似于session_YYYY-MM-DD_HH-MM-SS.ulg。你可以用Flight Review在线工具或者pyulog库离线分析:
pip install pyulog ulog2csv 你的日志文件.ulg这条命令会把ulog里的所有消息转换成CSV表格,方便你用Python、Excel或者MATLAB做后续分析。如果你在QGC里开了Log Download功能,还可以直接从QGC的日志面板下载带完整姿态估计器内部状态的日志,用Flight Review生成图表报告。
7. 常见问题与排查技巧实录
这一节是整个实操经验里价值密度最高的部分,提到的每个问题我自己都踩过,而且是很典型的、论坛上反复出现的问题。整理成一个速查表,你遇到类似情况直接按图索骥。
7.1 Gazebo启动相关的典型问题
问题1:Gazebo窗口卡在黑屏或者界面特别卡
这个问题的根源基本是虚拟机显卡不支持OpenGL硬件加速。Gazebo新版对渲染要求很高,如果运行在VMware或者VirtualBox里,默认的3D加速能力往往不够用。
解决方案是把Gazebo的渲染降到软件渲染模式,在启动仿真的命令前加上环境变量:
export LIBGL_ALWAYS_SOFTWARE=1 export GZ_GUI_CLIENT_PLUGIN_PATH= make px4_sitl gz_x500如果还不行,检查一下Gazebo的日志看有没有跟渲染相关的报错。另一个更省事的选择是直接放弃在虚拟机里跑仿真,用WSL2或者双系统。
问题2:模型加载特别慢,卡在"Downloading model"
Gazebo首次加载模型时会去模型仓库在线拉取模型文件,国内网络环境下这个下载速度很可能让人崩溃。解决办法是手动把模型文件下到本地缓存目录。
打开~/.gz/gazebo/目录,看看是否存在models目录,如果不存在就创建一个,然后去https://github.com/gazebo-tools/gazebo-models把对应的模型文件下载下来放进去。如果实在下载不下来,可以考虑给Gazebo设置代理加速。
问题3:飞机在Gazebo里看起来是在飘,而不是稳定的悬停
如果飞机在仿真里出现剧烈抖动、像喝醉了一样乱飘,多半是PX4的姿态估计器没有正确初始化。检查终端日志有没有类似EKF2 IMU data not ready的警告。
解决方案是在pxh>命令行里手动重启EKF2:
pxh> commander mode auto pxh> ekf2 stop pxh> ekf2 start正常来说,EKF2在2秒内就能收敛,之后飞机姿态就能稳定下来。注意要在飞机解锁之前操作,否则飞行状态下重启EKF2会让飞机失控。
7.2 DDS和ROS 2通信相关的典型问题
问题4:在QGC里能看到飞机数据,但ROS 2订阅不到任何PX4消息
这代表MAVLink链路正常,但DDS链路断了。最可能的原因是Micro XRCE-DDS Agent没有启动,或者PX4仿真启动时环境变量没写对。排查步骤:
- 确认Agent窗口还活着
- 在
pxh>命令行里手动启动DDS客户端:dds start - 如果提示
DDS client already running,那就Agent的问题,重启Agent
问题5:ROS 2报错找不到px4_msgs消息类型
大概率就是没有source工作的install目录。在运行节点前一定要执行:
source ~/ws_px4/install/setup.bash或者在~/.bashrc里加上这一行,免得每次开终端都要手动source。我自己就是这么干的,省心太多。
7.3 解锁和任务执行相关的典型问题
问题6:一直解锁失败,QGC提示Preflight check failed
出现这个提示,先不要急着改COM_ARM_CHECK跳过自检,应该在QGC的"Analyze Tools"里查看具体的检查项到底哪一项不满足。最常见的原因是GPS没有收到信号。仿真里GPS信号默认是正常的,但如果你把世界模型改成了室内场景或者手动关掉了GPS仿真插件,就会导致定位检查失败。
仿真环境下最简单的处理方式就是直接把COM_ARM_CHECK改成0跳过自检。真机上绝对不要这么玩,安全第一。
问题7:飞机起飞后垂直往下掉或者直接翻跟头
这通常是电机方向和旋转方向配置错误。检查PX4加载的机型配置和Gazebo模型是否匹配。我用的是gz_x500,机型对应的是标准四旋翼,电机布局是十字型。如果你用了一个非标准的电机布局,而不是在QGC里调整对应的参数(比如CA_ROTOR_COUNT、CA_ROTOR0_AXIS、CA_ROTOR0_DIR等),就会发生这种现象。
这种问题的排查思路很简单:打开QGC的电机测试界面,逐个电机测试旋转方向和转速,确认和PX4固件内部的编号一致。
7.4 一个性价比极高的调试习惯
最后分享一个我觉得非常实用的习惯:用PX4自带的手动飞行模式来做基础验证。很多新手一上来就写Offboard代码,遇到问题根本说不清是飞控的问题还是自己代码的问题。我建议每次改动完PX4参数或者Gazebo模型,先切到Stabilized模式掐着手柄飞一圈,确认基本操控正常,再上Offboard代码调试。这样问题边界一下就能缩小到代码层,排查效率翻倍。
8. 我实际操作中的一些体会
整套环境跑通之后回头看,其实技术门槛并没有想象中那么高,真正的门槛在于对这套工具链的把控能力和调试心态。
我自己最初搭这套环境的时候,前后折腾了两个晚上。第一个晚上卡在Gazebo渲染问题上,什么东西都没跑起来;第二个晚上卡在DDS通信上,看了一堆资料才搞清楚Agent和Client的关系。现在回想起来,大部分时间其实都花在了"不知道某个报错是什么意思"上面。所以我在这篇文章里尽可能把每个报错对应的问题根源和排查思路写清楚,希望你能少走点弯路。
还有一个因为长期使用养成的习惯要推荐给你:搭建环境这件事本身,值得写成一个脚本提交到你的代码仓库里。这样不管你是换电脑、换GPU服务器、还是带新同学入门,一套脚本直接跑完,省去每次手动复现环境的痛苦。我自己就是把PX4编译、ROS 2依赖、Agent安装、QGC下载全部写进了一个setup脚本,Github Codespaces里也能直接拉起一套可用的仿真环境。这个做法的收益随着时间推移会越来越大。
如果后面你把算法在仿真里调通了,准备往真机上搬,可以在PX4 v1.15自带的offboard例程基础上做增量开发。机载端用Raspberry Pi 4或你提到的RK3588跑Linux,用MAVLink或者XRCE-DDS和飞控通信。仿真里写的代码逻辑可以复用大半,只要把消息接口从仿真驱动换成串口驱动。这个阶段又是一个完全不同的挑战,回头有机会写一篇硬件在环的教程再展开。