ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

PX4与ROS2联调实战:从环境搭建到Gazebo无人机起飞降落全流程

PX4与ROS2联调实战:从环境搭建到Gazebo无人机起飞降落全流程 一开始接触 PX4 和 ROS2 联调的时候我花了一整周才搞明白一件事要跑通第一个无人机控制节点真正卡人的不是写代码而是环境里那一大堆组件之间的关系没理顺。仿真里的飞机、QGroundControl 地面站、ROS2 节点、Gazebo 世界这四个东西谁先启动、走哪个端口、消息从哪条路流到飞控全部理解清楚之后整个流程其实就是一个固定套路。这篇分享就是把这条链路完整拆开结合我实际踩过的坑带你从零建好环境、用 VSCode 开发并最终在 Gazebo 里让无人机离地起飞再降落。适合刚入门 PX4 二次开发、准备用 ROS2 写第一个控制节点的朋友也适合被网上那些版本混杂的旧教程搞到晕头转向的人。1. 为什么是 ROS2 而不是老一套 MAVLink 脚本PX4 与 ROS2 的通信原理1.1 两套通信体系之间的翻译官问题PX4 和外界通信最传统的通道是 MAVLink。QGroundControl 地面站、MAVSDK 脚本都是通过 MAVLink 和飞控说话的。ROS2 则完全是另一套体系它走的是 DDSData Distribution Service节点之间通过话题、服务、动作来通信话题名类似/fmu/in/offboard_control_mode这样数据包是结构化的消息而不是 MAVLink 那种扁平的字节流。很多新手会问既然已经有了 MAVLink为什么还要折腾 ROS2原因很简单MAVLink 本质是为遥测和遥控设计的而 ROS2 面向的是机器人开发里的分布式计算。你想在机载电脑上跑视觉 SLAM、路径规划、多机协同用 MAVLink 一条条解析消息极为痛苦而 ROS2 里的传感器数据、点云、位姿估计、控制指令天然就是一堆话题互相订阅发布非常自然。所以 PX4 在 ROS2 场景下干的活就是充当一个能执行指令、能回报状态的底层飞行平台。这里必须理清一个容易混淆的概念PX4 内部各模块之间用的是 uORB 消息ROS2 用的是 DDS 话题两边虽然都是发布/订阅模型但协议完全不同。想让 ROS2 节点直接收发 PX4 的内部 uORB 消息中间必须有一座桥。这座桥在 ROS1 时代叫 MAVROS在 ROS2 时代官方主推的是 uXRCE-DDS。1.2 uXRCE-DDS 到底桥接了什么uXRCE-DDS 的全称是 Micro XRCE-DDS它由两部分组成跑在飞控 / SITL 仿真里的 client 端以及跑在机载电脑 / 开发机上的 agent 端。client 端直接编译在 PX4 固件里负责把 PX4 内部的 uORB 消息映射成 DDS 话题agent 端是一个独立进程负责接收这些话题并转发给 ROS2 环境。两者之间通过串口或 UDP 连接。在 SITL 仿真场景下agent 和 PX4 都在本机运行走的是 UDP 回路。默认情况下agent 监听 UDP 8888 端口PX4 SITL 启动后会自动往这个端口发送消息。整个消息路径是这样的你的ROS2节点 → DDS → uXRCE-DDS Agent → UDP 8888 → PX4 SITL (uXRCE-DDS Client) → uORB → 飞控模块反方向的传感器状态、位姿数据则原路返回。注意这条链路和 MAVLink 各走各的MAVLink 用于地面站遥测uXRCE-DDS 用于 ROS2 数据交互两者互不干扰。SITL 环境下 MAVLink 默认走 UDP 14540QGC 通过 14550 接收uXRCE-DDS 走 8888三条端口各司其职。1.3 版本搭配与选型建议PX4 版本迭代很快不同版本默认的仿真器和 ROS2 接口差异很大。网上能找到的大量教程还停留在 Gazebo 8/9 ROS1 时代照抄必踩坑。我实测下来的稳定组合有下面几组直接照着选就行开发场景操作系统ROS2 版本PX4 版本仿真器备注主推资料多Ubuntu 22.04Humble1.15.xGazebo Harmonicgz sim官方默认px4_msgs 接口完整尝鲜新特性Ubuntu 24.04Jazzy1.16Gazebo Harmonic / Ionic编译工具链较新老项目维护Ubuntu 20.04Foxy / Noetic1.13 或更早Gazebo 11Classic大量旧教程基于此不推荐新手为什么主推 Ubuntu 22.04 Humble PX4 1.15因为这一套在官方文档里维护最成熟px4_msgs 的消息类型、Gazebo 的模型加载、QGC 的连接逻辑都经过大量验证。PX4 1.14 及更早版本默认走的是 Gazebo Classic命令也不同make px4_sitl gazebo-classic之类如果你看的是 1.14.3 的源码注意别把 Gazebo 命令行参数搞混。记住一个原则源码版本和仿真器参数强相关先确认你的 PX4 版本再去找对应文档否则编译过了也飞不起来。2. 环境准备先把工具链接到同一条路上2.1 系统资源与运行方式选择跑 PX4 SITL Gazebo ROS2 VSCode 整套环境对电脑的要求不算低。我的建议是 8 核 CPU、16GB 内存起步磁盘至少留 60GB。Gazebo 的物理引擎和渲染非常吃资源ROS2 编译也要占用大量内存8GB 内存会非常紧张编译到一半 OOM 是常事。运行方式上原生 Ubuntu 22.04 最省心。如果你已经在 Windows 上用 WSL2 也能跑WSLg 可以显示 Gazebo 和 QGC 的图形界面但 GPU 性能会有损耗仿真加载模型和渲染大场景时会偏慢。虚拟机VirtualBox、VMware能跑但体验较差GPU 穿透困难Gazebo 里稍复杂的场景就掉帧不推荐。如果你是 WSL2 用户编译工程文件时一定要把代码放在 Linux 文件系统里~目录下不要放在/mnt/c/下否则大量小文件的编译速度会让人崩溃。这一步是纯经验之谈我第一次把 PX4 源码放 Windows 分区编译等了快一个小时还没编完挪回 Linux 目录后十几分钟就搞定了。2.2 PX4 源码获取与子模块初始化PX4 源码仓库现在官方名为PX4-Pilot以前叫PX4-Autopilot旧地址访问时会自动重定向问题不大。注意一个高频坑直接git clone不带子模块后面编译必报错。正确姿势是sudo apt install git-lfs git lfs install cd ~ git clone --recursive https://github.com/PX4/PX4-Pilot.git如果你已经克隆完了才发现没带--recursive或者子模块拉取到一半因为网络中断失败别慌用这组命令修复cd PX4-Pilot git submodule sync --recursive git submodule update --init --recursive子模块更新失败通常表现为终端反复报Clone of https://github.com/... into submodule path ... failed原因大部分是网络不稳定导致中途断开。这时候不要反复重试整条命令可以分批次更新或者先更新单个子模块git submodule update --init --recursive --depth 1如果还不行检查一下git lfs install是否执行过PX4 里有些模型文件和二进制资源是通过 Git LFS 管理的没装 LFS 会导致文件拉取成占位符后续编译或模型加载诡异报错。2.3 ROS2 安装与常用工具Ubuntu 22.04 对应 ROS2 Humble用官方 apt 源安装桌面版即可sudo apt update sudo apt install software-properties-common sudo add-apt-repository universe sudo apt update sudo apt install curl 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 $(. /etc/os-release echo $UBUNTU_CODENAME) main | sudo tee /etc/apt/sources.list.d/ros2.list /dev/null sudo apt update sudo apt upgrade sudo apt install ros-humble-desktop python3-colcon-common-extensions python3-rosdep国内网络环境下如果上面的源拉取缓慢可以把packages.ros.org替换成国内高校镜像配置方式一样把 URL 换成镜像地址即可。另外我建议装一个python3-pip和python3-argcompleteROS2 命令行补全依赖它。环境变量记得写进~/.bashrcsource /opt/ros/humble/setup.bash source /usr/share/colcon_cd/function/colcon_cd.sh source ~/ros2_ws/install/setup.bash # 等创建工作区后再加这行ROS2 还有个一键安装脚本国内不少入门者用确实能省去手动配源的时间但脚本会改动系统源有时会和现有的 apt 源冲突。我个人的建议是普通学习环境用官方源手动装一次彻底搞懂装了什么排错心里才有底。2.4 QGroundControl 与 Gazebo 的准备QGroundControl 是地面站跑 SITL 时用它看飞机状态、切模式、手动解锁起飞。下载 AppImage 后执行chmod x QGroundControl.AppImage ./QGroundControl.AppImage首次启动会提示安装一些依赖包照提示装完即可。注意 QGC 默认会占用 UDP 14550 端口如果启动时发现端口被占用通常是有残留进程先排查再继续。Gazebo 这边PX4 1.15 默认用的是 Gazebo Harmonicgz sim不是老的 Gazebo Classic。PX4 的make px4_sitl gz_x500命令会自动检查和安装缺少的 Gazebo 组件但为了不被打断建议先单独装好sudo apt install gz-harmonic装完后可以用gz sim --version验证。老教程里提到的gazebo sim, version 8.15.0是 ROS1 时代的产物和现在的 PX4 1.15 完全不是一回事看到这类内容可以跳过。2.5 VSCode 核心插件配置VSCode 在这里的价值不只是写代码更重要的是看消息、编译、调试一锅端。我日常必装的插件有这几类Remote - WSL如果你用 WSL2先装这个然后窗口左下角点绿色按钮连接到 WSL 环境再在里面装其他插件。Windows 端插件和 WSL 端是两套体系经常有人漏装导致 IntelliSense 失效。C/Cms-vscode.cpptools提供 C 代码补全、跳转、调试。CMake Tools编译 C 工程时用ROS2 的 ament 构建不直接走 CMake Tools 的流程但可以辅助查看 CMake 错误。Pythonms-python.python PylancePython 节点开发必备。ROSms-iot.vscode-ros能识别 catkin / colcon 工作区但新版支持一般我主要拿它做话题可视化。装好之后建议把 VSCode 的终端集成打开这样在编辑器内就能切到已经 source 过 ROS2 环境的 shell省去来回切换终端窗口的麻烦。后面的编译、运行节点、查看话题全在 VSCode 里完成。3. 在 Gazebo 里拉起 PX4 SITL先让飞机在仿真里飞起来3.1 一条命令背后的三个进程先把 PX4 在 Gazebo 里跑起来。进入源码目录执行cd ~/PX4-Pilot make px4_sitl gz_x500这条命令其实帮你做了好几件事启动 PX4 SITL 的飞控进程、启动 Gazebo Harmonic 仿真器、把 x500 四旋翼模型加载进仿真世界、打开 MAVLink 通信通道。终端里会持续刷日志你会看到类似pxh的 shell 提示符同时弹出 Gazebo 窗口一架四旋翼停在停机坪上。这里始终要记住仿真里这架飞机的大脑就是那个pxh终端它在运行完整的 PX4 固件传感器数据来自 Gazebo 的仿真模型执行机构指令输出给 Gazebo 里的电机模型。你在 QGC 里看到的地图、姿态、高度全部来自这个 SITL 进程而不是任何真实的硬件。启动完成之后先不要急着做任何事情打开另一个终端验证一下链路通没通。PX4 SITL 默认的 MAVLink 通道是 UDP 14540QGC 会自动发现它。如果 QGC 没检测到飞机在 QGC 里手动添加“UDP”通信方式监听端口 14540 即可。3.2 第一次用 QGC 起飞验证QGC 连接上之后地图上应该出现无人机图标页面顶部显示飞控版本、GPS 状态等。SITL 模式下 GPS 是仿真出来的几秒内就能定位。左侧滑出工具栏能看到 Takeoff 按钮填入期望起飞高度点击后飞机就会在 Gazebo 里离地。这一步非常重要。它能验证你的仿真链路是否正常也能让你提前感受一下 PX4 在 Offboard 之外的基础飞行逻辑。如果连 QGC 手动起飞都有问题那后面 ROS2 控制节点大概率也会出问题先把基础链路调通再往后走。起飞成功后留意 Gazebo 里飞机的姿态和 QGC 里的高度表如果一切正常说明 PX4 与 Gazebo 的传感器/执行器通信是闭合的。接下来要做的是在 QGC 里把飞机降落并解锁然后关掉 QGC 的起飞指令准备跑 ROS2 侧的控制节点。3.3 模型切换与常用仿真操作gz_x500只是其中一个模型目标PX4 1.15 里你还可以换成其他机型的启动参数比如make px4_sitl gz_r1_rover地面车、make px4_sitl gz_plane固定翼。日常调试多旋翼控制节点x500 是最合适的选择因为它的动力学模型比较标准参数文档丰富。Gazebo Harmonic 窗口内有一些常用的快捷键和操作鼠标左键旋转视角右键平移滚轮缩放。右上角的 Reset 按钮可以重置仿真世界飞机回到初始位置。暂停/播放按钮可以冻结物理引擎方便你在某个瞬间慢慢看状态。还有一个很实用的功能Gazebo 里可以通过gz model --info查看当前世界里的模型列表和位姿这在多机仿真或者挂载额外传感器时非常有用。3.4 先启动 uXRCE-DDS Agent再看话题流动如果你在 PX4 SITL 启动后直接执行ros2 topic list大概率看不到/fmu/out/*这些话题。原因很简单uXRCE-DDS Agent 还没起来PX4 的 DDS 桥没有对端。Agent 需要在一个独立的终端里提前启动PX4 SITL 会自动发现并连接它# 终端 A启动 Micro XRCE-DDS Agent MicroXRCEAgent udp4 -p 8888Agent 的具体安装方式是从源码编译git clone https://github.com/eProsima/Micro-XRCE-DDS-Agent.git cd Micro-XRCE-DDS-Agent mkdir build cd build cmake .. make -j$(nproc) sudo make install编译好之后启动 Agent然后重新打开一个新终端启动 PX4 SITL保持 Agent 终端在跑。数秒后再开一个新终端执行ros2 topic list你会看到大量/fmu/out/...话题比如vehicle_attitude、vehicle_local_position、vehicle_status以及/fmu/in/...话题比如offboard_control_mode、trajectory_setpoint、vehicle_command。看到这些话题说明 PX4 和 ROS2 的通信桥已经打通后面的控制节点才能工作。验证数据流动可以执行ros2 topic echo /fmu/out/vehicle_attitude终端会持续输出四元数形式的姿态这就是 Gazebo 里飞机的实时姿态。如果你看到这条消息在滚动恭喜PX4 与 ROS2 联调最关键的一环已经通了。4. VSCode 工作区搭建让补全、编译和调试都顺手4.1 创建自己的工作区与功能包我习惯在~/ros2_ws下建一个独立的工作区把和自己开发相关的包都放在里面而不去动 PX4 源码目录里那些官方包。PX4 源码本身不是 ROS2 包不要试图把 ROS2 节点写进 PX4 源码目录里维护性和隔离性都很差。先创建 ROS2 工作区和控制节点功能包mkdir -p ~/ros2_ws/src cd ~/ros2_ws/src git clone https://github.com/PX4/px4_msgs.git cd ~/ros2_ws colcon build --packages-select px4_msgs source install/setup.bashpx4_msgs这个包包含了 PX4 的 ROS2 消息定义编译之后会生成 Python 和 C 两套接口它是任何 ROS2 控制节点都绕不开的基础依赖。注意必须先编译这个包否则后面创建节点包时依赖解析会失败。接着创建自己的 Python 包cd ~/ros2_ws/src ros2 pkg create --build-type ament_python offboard_py --dependencies rclpy px4_msgs4.2 CMakeLists 与 package.xml 的关键点用ament_python创建的包不需要手动维护 CMakeLists 里的复杂逻辑但package.xml里的depend标签要写对。用--dependencies rclpy px4_msgs生成时package.xml会自动包含这两项依赖。真正容易出问题的是setup.py如果你新建的包名和目录名不一致或者节点文件不在offboard_py/offboard_py/目录下entry_points里的路径就要对应修改。我见过太多人在这里报No module named offboard_py原因就是setup.py的packagesfind_packages(exclude[test])没有找到实际的 Python 模块目录。最简单的办法是保持默认目录结构把节点文件放进src/offboard_py/offboard_py/下。4.3 IntelliSense 配置让补全认识 PX4 消息在 VSCode 里打开~/ros2_ws目录C 的智能提示默认是不会认识px4_msgs头文件的需要手动配置.vscode/c_cpp_properties.json。下面是我常用的配置{ configurations: [ { name: Linux, includePath: [ ${workspaceFolder}/**, /opt/ros/humble/include/**, ${HOME}/ros2_ws/install/px4_msgs/include/**, ${HOME}/PX4-Pilot/src/** ], defines: [], cStandard: c17, cppStandard: c17, intelliSenseMode: linux-gcc-x64 } ], version: 4 }注意includePath里不要直接写~VSCode 的 JSON 配置不会展开 shell 波浪号要写${HOME}/${env:HOME}或者绝对路径。不写绝对路径的话跳转到px4_msgs头文件时会一直报错。Python 的补全稍微简单一点在 VSCode 设置里加一条python.analysis.extraPaths: [ ${env:HOME}/ros2_ws/install/px4_msgs/lib/python3.10/site-packages ]配好后.msg生成的消息类型就能通过 Pylance 正确补全了。这一步做好你写OffboardControlMode()的时候会自动弹出字段提示效率高很多也能少很多低级错误。4.4 一键编译与调试配置在.vscode/tasks.json里定义编译任务以后按CtrlShiftB就能直接编译{ version: 2.0.0, tasks: [ { label: build offboard_py, type: shell, command: cd ~/ros2_ws source /opt/ros/humble/setup.bash colcon build --packages-select offboard_py, group: { kind: build, isDefault: true }, problemMatcher: [] } ] }调试 Python 节点时用.vscode/launch.json配置{ version: 0.2.0, configurations: [ { name: offboard_node, type: debugpy, request: launch, program: ${workspaceFolder}/src/offboard_py/offboard_py/offboard_node.py, console: integratedTerminal, cwd: ${workspaceFolder} } ] }注意 Python 扩展在较新版本里调试类型名是debugpy不是旧的python用老配置可能会报错。通过这个配置你可以在节点的create_subscription回调里打断点实时看收到的位置消息、状态消息排查问题比单纯打印日志快得多。5. 手写第一个无人机控制节点离地起飞再降落5.1 控制节点需要哪些话题要让 PX4 完全接收 ROS2 节点的控制指令核心在两组话题的配合。第一组是/fmu/in/offboard_control_mode告诉 PX4“我现在要从 ROS2 侧发送控制模式”对应消息类型OffboardControlMode第二组是/fmu/in/trajectory_setpoint携带期望的位置、速度、加速度或姿态对应消息类型TrajectorySetpoint。理解它们的配合逻辑很关键。PX4 内部有自己的导航和控制栈但在 Offboard 模式下它会把外部传入的 setpoint 当作期望值经过姿态控制器输出电机指令。这就好比你请了一个司机PX4 内部控制器但你ROS2 节点得持续告诉他往哪开。如果中途 0.5 秒内没有新 setpointPX4 会超时退出 Offboard 模式飞机立刻悬停或切换回之前的模式这在实机上是非常危险的所以代码里 setpoint 的发布频率至少 10Hz20Hz 更稳。另外还需要/fmu/in/vehicle_command来发送解锁arming和切换 Offboard 模式的命令对应消息类型VehicleCommand。订阅侧至少订阅/fmu/out/vehicle_local_position用于在代码里确认当前位置和高度。5.2 完整 Python 节点代码下面是一个最小可用的节点逻辑简单清楚持续发布 Offboard 控制模式和轨迹设定点10 个周期后发送解锁指令随后发送切换 Offboard 模式指令把飞机送到 2 米高度悬停30 秒后再降落到 0 米。#!/usr/bin/env python3 import rclpy from rclpy.node import Node from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy from px4_msgs.msg import OffboardControlMode, TrajectorySetpoint, VehicleCommand, VehicleLocalPosition def sensor_qos() - QoSProfile: PX4遥测类消息通常用 BestEffort return QoSProfile( depth5, reliabilityReliabilityPolicy.BEST_EFFORT, historyHistoryPolicy.KEEP_LAST, ) def command_qos() - QoSProfile: 控制指令类消息必须可靠传输 return QoSProfile( depth5, reliabilityReliabilityPolicy.RELIABLE, historyHistoryPolicy.KEEP_LAST, ) class OffboardControl(Node): def __init__(self): super().__init__(offboard_control_node) self.pub_offboard_mode self.create_publisher( OffboardControlMode, /fmu/in/offboard_control_mode, command_qos()) self.pub_trajectory self.create_publisher( TrajectorySetpoint, /fmu/in/trajectory_setpoint, command_qos()) self.pub_vehicle_command self.create_publisher( VehicleCommand, /fmu/in/vehicle_command, command_qos()) self.sub_local_pos self.create_subscription( VehicleLocalPosition, /fmu/out/vehicle_local_position, self.local_position_callback, sensor_qos()) self.timer self.create_timer(0.05, self.timer_callback) # 20Hz self.counter 0 self.local_pos VehicleLocalPosition() def local_position_callback(self, msg): self.local_pos msg def timer_callback(self): self.counter 1 # 先发 offboard 模式和轨迹设定点保证 PX4 进入 Offboard 后不会超时 self.publish_offboard_control_mode() self.publish_trajectory_setpoint() # 前 10 个周期只发设定点第 10 个周期后发解锁和 Offboard 指令 if self.counter 10: self.publish_vehicle_command(VehicleCommand.VEHICLE_CMD_COMPONENT_ARM_DISARM, param11.0) self.publish_vehicle_command(VehicleCommand.VEHICLE_CMD_DO_SET_MODE, param11.0, param20.0) if self.counter 600: # 约 30 秒后开始降落 self.takeoff_height 0.0 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.pub_offboard_mode.publish(msg) def publish_trajectory_setpoint(self): msg TrajectorySetpoint() msg.timestamp int(self.get_clock().now().nanoseconds / 1000) # NED 坐标系z 向下所以 -2.0 表示离地 2 米 takeoff_height -2.0 if self.counter 600: takeoff_height 0.0 msg.position [0.0, 0.0, takeoff_height] msg.yaw 0.0 self.pub_trajectory.publish(msg) def publish_vehicle_command(self, command, param10.0, param20.0): msg VehicleCommand() msg.timestamp int(self.get_clock().now().nanoseconds / 1000) msg.command command msg.param1 param1 msg.param2 param2 msg.target_system 1 msg.target_component 1 msg.source_system 1 msg.source_component 1 msg.from_external True self.pub_vehicle_command.publish(msg) def main(argsNone): rclpy.init(argsargs) node OffboardControl() try: rclpy.spin(node) except KeyboardInterrupt: pass finally: node.destroy_node() rclpy.shutdown() if __name__ __main__: main()代码里VEHICLE_CMD_COMPONENT_ARM_DISARM的值是400VEHICLE_CMD_DO_SET_MODE的值是176直接用px4_msgs.msg.VehicleCommand里的枚举常量更安全。PX4 在 Offboard 模式下如果持续收到满足条件的 setpoint会正常执行如果指令之间间隔超过 0.5 秒就会超时退出所以定时器里发布TrajectorySetpoint的间隔必须小于 500ms。上面代码用 0.05 秒定时器发布频率 20Hz留足了余量。5.3 C 版最小示例与调试思路如果你更习惯 C核心逻辑完全一样只是 API 形式不同。CMake 包创建命令ros2 pkg create --build-type ament_cmake offboard_cpp --dependencies rclcpp px4_msgsC 头文件路径是#include px4_msgs/msg/offboard_control_mode.hpp #include px4_msgs/msg/trajectory_setpoint.hpp #include px4_msgs/msg/vehicle_command.hpp发布器的 QoS 设置rclcpp::QoS qos_command(10); qos_command.reliability(RMW_QOS_POLICY_RELIABILITY_RELIABLE); auto pub_trajectory create_publisherpx4_msgs::msg::TrajectorySetpoint( /fmu/in/trajectory_setpoint, qos_command);C 调试时在launch.json里配置type: cppdbgprogram指向编译生成的可执行文件。注意调试前要先source install/setup.bash否则可执行文件找不到运行时依赖。用std::chrono::steady_clock计算消息时间戳单位同样是微秒PX4 对时间戳的校验比较严格乱写可能导致指令被丢弃。5.4 跑通后的验证与常见失败原因节点启动后关注几个验证点QGC 里飞机状态从Disarmed变成Armed。QGC 右上角模式显示Offboard。Gazebo 里飞机开始离地悬停在约 2 米高度。终端里执行ros2 topic echo /fmu/out/vehicle_local_position能看到 z 从 0 变成 -2说明高度指令生效。如果飞机纹丝不动优先检查下面这张表现象原因处理方式飞机不解锁没有先进入 Offboard 模式就发送解锁先发VEHICLE_CMD_DO_SET_MODE再发VEHICLE_CMD_COMPONENT_ARM_DISARM顺序不能反飞机抖动但不离地setpoint 发布时间戳不连续或频率不足确认定时器发布频率 10Hz时间戳使用微秒单位且每帧递增启动后几秒退出 Offboard进入 Offboard 前 setpoint 没有持续发布代码里先发 10 个周期的 setpoint再发模式切换指令ros2 topic list看不到/fmu/inuXRCE-DDS Agent 没启动或端口错误确认 Agent 在跑且监听 8888 端口QGC 里能看到飞机但 ROS2 收不到状态PX4 SITL 未启用 uXRCE-DDS 模块检查 PX4 终端日志里是否有 uXRCE-DDS 连接成功的信息实机飞行时解锁和 Offboard 切换顺序必须严格遵循安全规程SITL 环境下摔了不心疼但养成好的代码习惯很重要先持续发布 setpoint确认数据链路稳定再发解锁指令。6. 排错经验与工具箱从源码子模块到 Gazebo 加载慢6.1 源码仓库子模块未初始化的完整排查这个坑基本每个用 PX4 源码的人都会遇到。现象是编译到一半报错说找不到mavlink子模块、NuttX子模块或者某个工具链目录为空。最典型的表现Submodule mavlink (https://github.com/mavlink/mavlink.git) registered for path mavlink ... Clone of https://github.com/mavlink/mavlink.git into submodule path mavlink failed排查步骤我给一个固定的套路# 1. 查看子模块状态红色/前缀-的说明未更新 git submodule status # 2. 同步远端 URL 配置 git submodule sync --recursive # 3. 强制更新所有子模块 git submodule update --init --recursive --force # 4. 如果某个子模块坏了就单独进目录重新拉 cd mavlink git fetch origin git checkout 对应commit还有一个小技巧git submodule update时如果中途失败有时会在.git/modules下留下锁文件后续更新一直报index.lock删掉对应锁文件再重试即可。遇到子模块一直失败的情况可以先确认本机是否能正常访问 GitHub、是否设置了代理如果网络环境确实差把子模块地址整体替换成可访问的镜像源是最快的解决办法。注意源码版本和子模块 commit 是配套的不要手动改子模块的分支用官方锁定的 commit 最稳。6.2 WSL2 下的典型问题在 WSL2 里跑这套环境问题集中在四个方面GUI 显示WSLg 对 Gazebo 和 QGC 的显示支持已经不错但如果你用的是老版本 Windows 或者没有开启 WSLgGazebo 窗口可能无法弹出。命令行执行echo $DISPLAY看有没有值没有就检查 Windows 更新到 Win11 或安装较新的 WSL 版本。性能瓶颈Gazebo 渲染对 GPU 要求高WSL2 的虚拟 GPU 性能和原生 Linux 有差距。卡顿时可以设置环境变量强制软件渲染export LIBGL_ALWAYS_SOFTWARE1但这样渲染更慢只建议在纯物理计算场景使用。USB 设备映射如果你的阶段目标是从 Pixhawk 实机读取数据WSL2 不能直接访问 USB 设备需要通过usbipd-win把 USB 设备映射到 WSL2 里。SITL 仿真阶段用不到这个但提前了解可以避免以后踩坑。文件系统和代码位置前面已经强调过源码和工作区务必放在 Linux 原生目录。WSL2 里访问/mnt/c/的性能极差编译几百个 C 文件时差异非常明显。6.3 Gazebo 加载模型慢与卡死Gazebo 第一次启动某个世界时如果模型带视觉外观资源它会尝试从在线模型数据库下载。网络不好的时候表现非常诡异Gazebo 窗口开了但世界空白或者飞机悬浮在白色地面上等很久才出现纹理。更严重的会整个仿真进程卡住。解决办法是提前把常用模型下载到本地然后设置资源路径export GZ_SIM_RESOURCE_PATH$HOME/PX4-Pilot/Tools/simulation/gz/models把这个环境变量写进~/.bashrc每次启动都会自动找本地模型。如果只是想让仿真尽快跑起来也可以先关掉在线模型下载export GZ_SIM_SKIP_MODEL_DOWNLOAD1但这样的话缺少的纹理和模型会变成空白占位所以最好的方式还是提前下载。此外如果你同时开很多 Gazebo 或 QGC 窗口内存占用会飙升16GB 内存也未必够建议关闭浏览器里多余的标签页编译和仿真不要同时进行。6.4 端口冲突与进程残留开发过程中最烦的就是端口冲突。常见组合是QGC 占着 14550 连不上、PX4 SITL 报 14540 绑定失败、uXRCE-DDS Agent 报 8888 被占。排查命令# 查看端口占用 ss -ulpn | grep -E 14540|14550|8888 # 强制释放端口 fuser -k 14540/udp fuser -k 14550/udp fuser -k 8888/udp还有一类问题来自进程残留Gazebo 窗口关了但后台还有gz server、px4进程在跑下一次启动时端口错乱、模型加载异常。所以我的习惯是每次调试结束执行pkill -9 -f px4 pkill -9 -f gz pkill -9 -f qgroundcontrol开发机上可以放心用生产环境当然不能这么粗暴。另外启动顺序也影响稳定性我最终固定的顺序是启动 MicroXRCE-DDS AgentUDP 8888。启动 PX4 SITLmake px4_sitl gz_x500。等待 Gazebo 窗口出现、飞机稳定。启动 QGC 连接。在 VSCode 里编译并运行自己的 ROS2 节点。这套顺序经过多次验证出错率最低。如果中途某个组件退出建议把后面的组件全部停掉从第一步重新来而不是试图热重启某个进程。最后说点我自己的体会。第一次在这套环境里看到 ROS2 节点把无人机送上 2 米高空时那种感觉跟实飞完全不一样但代码路径是完全一致的。后来我做视觉避障、在 Gazebo 里加载 ArUco 二维码做室内定位都是在这套联调环境上扩展的。如果你也是从零开始建议先别急着上复杂算法把 PX4 当成一个能响应 setpoint 的黑盒子先把消息流动的路径走通把话题、QoS、时间戳这些基础功打扎实后面自然就有数了。
返回列表