我们直接进入正题。这篇是写给真正想把ROS2和PX4跑通、而不是停留在PPT层面的开发者。我假设你已经接触过Ubuntu,知道终端怎么开,也大概知道PX4和ROS2分别是什么。如果你对ROS2的几个发行版还分不清,没关系,这一篇从选型讲起。
1. Humble、Iron、Jazzy、Rolling到底选哪个?
我现在打开电脑,Ubuntu 22.04,装的是ROS2 Humble,配的是PX4 v1.14.3的源码。实话说,我推荐新手直接选这个组合,不是因为Humble功能最强,而是因为Humble是当前ROS2 LTS发行版里生态最成熟、教程最多、踩坑记录最全的版本。Iron是非LTS版,Jazzy是2024年发布的新LTS,Rolling则是滚动版,天天变。
这几个版本的差异,用一句话概括:Humble保守但稳,Iron激进但边缘,Jazzy新特性多但生态还在长,Rolling是给开发者的试验田。
具体到PX4对接场景,我的建议是这样的:
| 发行版 | 支持周期 | PX4对接成熟度 | 适用场景 |
|---|---|---|---|
| Humble | 2027年(LTS) | 很高,文档和源码示例多 | 绝大多数开发、课程、毕业设计、产品原型 |
| Iron | 已结束短支持 | 一般,可跑通但记录少 | 特定依赖需求,不推荐新人 |
| Jazzy | 2030年(LTS) | 逐步完善,PX4官方还未完全默认支持 | 追新,想提前适配新API |
| Rolling | 滚动更新 | 不推荐 | 想持续适配ROS2上游变化的开发者 |
有一个关键点:PX4的官方ROS2接口(px4_ros_com、px4_msgs)针对的是Humble。也就是说,当你用Humble时,px4_msgs、px4_ros_com这些包可以直接编译,按官方README就能跑通。换到Jazzy之后我实际试验过,需要手动处理一些依赖改名问题,比如python3-vcstool的依赖链、rclpy接口的小变化,虽然能修,但是没必要在入门阶段给自己加这个难度。
所以我这篇所有实操步骤都以Ubuntu 22.04 + ROS2 Humble为例。如果你用的是Jazzy,步骤里我会特别标注需要改动的地方,但那样的话你就要同时调试PX4和ROS2两边的兼容问题,我的建议是不要这么折磨自己。
版本确定的另一个好处是,PX4固件、QGroundControl、Gazebo仿真这几个配套工具之间的兼容关系也基本定下来了。比如说PX4 v1.14.3对ROS2的支持就很稳,v1.15系列开始有较大重构,但是相关的教程和工具链反而不够全。
2. PX4和ROS2之间的本质关系
很多教程上来就先跑MicroXRCEAgent,然后ros2 topic list看到一堆话题,就以为“对接成功”了。但如果你不理解底层原理,后面遇到问题多半会懵。我花点篇幅讲清楚。
PX4内部用的是uORB——一种轻量级的发布订阅机制。uORB消息在PX4固件内部流转,比如vehicle_attitude、vehicle_local_position这些,都是uORB主题。ROS2用的是DDS——分布式中件,有自己的话题和服务机制。两者语言不同、协议不同、概念也不同。PX4的/fmu/out/vehicle_attitude话题和ROS2的/fmu/out/vehicle_attitude看似同名,其实不是一个系统里的东西。
把两者连起来的机制是XRCE-DDS(X-Robotics Communication Extension,旧称Micro XRCE-DDS)。PX4里面跑了一个XRCE客户端(microdds_client),PC端跑了一个MicroXRCEAgent,这个Agent就是桥接的一端。PX4的uORB主题如果被配置为“通过RTPS桥接出去”,那么数据就会从PX4的uORB封装成RTPS标准的DDS消息,通过UDP或者串口发给Agent,再由Agent注入到ROS2的DDS网络里。
你可以把这个机制理解成一个翻译网关:PX4说中文(uORB),ROS2说英文(DDS),Agent就是那个同声传译。你不需要在两个系统里分别订阅、分别处理,只需要在ROS2侧订阅从Agent翻译出来的英文话题就行。
默认情况下,PX4会通过配置文件决定哪些uORB主题被翻译出去。这个文件在PX4源码里的msg/tools/urtps_bridge_topics.yaml。打开看一下你就会明白,为什么ROS2里只能看到固定那些话题,而不是PX4内部所有uORB主题——因为默认配置文件限制了路由范围。
我再补一个点:ROS2侧不只是多了一个消息通道,PX4还通过这个机制支持了服务调用和动作调用。比如OFFBOARD模式的切换,是通过ROS2的服务或者动作消息实现的,不是简单往某个话题里发数据就行。这一点在下一章会重点讲。
3. 环境配置:Ubuntu、ROS2 Humble、PX4源码一次到位
这里我直接给出我现在机器上验证过的稳定方案。网上教程很多,但有些讲的版本太旧,有些没标注依赖版本,有些直接把系统搞崩了。我的这台测试机是Ubuntu 22.04.3,装了ROS2 Humble,固件源码是PX4-Autopilot v1.14.3,Gazebo用的是经典版,不是Ignition。
3.1 ROS2 Humble安装的关键动作
ROS2 Humble官方的安装方式是加ros2apt源,用apt装。我用的是ros-humble-desktop完整版,因为包含了Rviz2、gazebo、demo等一整套工具。安装命令很常规,但有两个细节容易出问题:
第一,必须正确设置locale。官方文档里那串locale命令不是走形式,是真实会影响编译和运行。我见过至少三个人跳过这步,后面跑ros2命令报编码错误。
第二,source的顺序。source /opt/ros/humble/setup.bash之后,再把PX4的、自己工作空间的setup.bash按顺序source。很多人把顺序搞反,结果找包找不到,或者找到了旧版本。
3.2 PX4固件源码克隆与子模块处理
PX4的源码不能直接git clone完就用,它的子模块非常多。正确做法是:
git clone --recursive https://github.com/PX4/PX4-Autopilot.git cd PX4-Autopilot git checkout v1.14.3 git submodule update --init --recursive如果克隆中途失败,最常见的原因是网络问题导致子模块拉取不完整。这时不要慌,进入源码目录后重新执行git submodule update --init --recursive,它会自动补拉缺失的子模块。但如果你用的是国内网络,这一这步可能要等很久,我劝你耐心点,不要中途Ctrl+C。
PX4编译依赖需要装一堆工具链:
bash ./PX4-Autopilot/Tools/setup/ubuntu.sh这个脚本会自动装好ARM交叉编译器、Python依赖、Gazebo等。我提醒一下:运行这个脚本之前,系统里最好已经装了ROS2 Humble,否则后面仿真和通信会缺依赖。
3.3 编译固件和启动仿真
编译PX4的SITL(软件在环)固件:
cd PX4-Autopilot make px4_sitl gazebo-classic这一行的意思是用SITL模式编译,用gazebo-classic作为仿真环境。编译至少要等10到20分钟,取决于你的机器。我用的是一台8核16线程的机器,第一次编译大概花了15分钟。如果中途报错,先看是不是子模块没更新完,其次看是不是系统的Python库版本不兼容。
启动仿真:
make px4_sitl gazebo-classic这条命令会同时启动Gazebo和PX4。注意,启动后你会看到一个PX4 shell。在这个shell里可以输入commander takeoff之类的命令测试PX4是否工作正常。
到这里,PX4的仿真端就算跑起来了。接下来要处理的是怎么让ROS2和它通信。
4. MicroXRCEAgent:让ROS2看见PX4的“翻译官”
这是整个对接流程里非常关键的一步,错过这一步就算PX4仿真在跑、ROS2也在跑,两边依然谁也看不见谁。
4.1 Agent的安装与运行方式
MicroXRCEAgent的安装方式有两种:二进制安装和源码编译。二进制安装很简单:
sudo apt install ros-humble-microxrcedds-agent ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888udp4表示用IPv4的UDP通道,端口默认8888。PX4那边默认的RTPS端口也是8888,所以这样就能接上。
如果是源码编译,需要单独克隆Micro-XRCE-DDS-Agent仓库,然后用colcon编译。我的建议是直接用apt源装,省时省力。但要注意,有些老教程还在用micro-ros-agent这个包名,在Humble上已经改名了,你搜micro_ros_agent才对。因为之前的热搜词里有“unable to find image 'microros/micro-ros-agent:humble' locally”这个报错,我在后面专门写一段排查。
4.2 跑通行测试的完整链路
现在我把完整链路跑给你看。
要开四个终端:
终端1:启动Gazebo和PX4 SITL
cd PX4-Autopilot make px4_sitl gazebo-classic终端2:启动Agent
ros2 run micro_ros_agent micro_ros_agent udp4 --port 8888终端3:启动px4_ros_com的飞行器控制接口(我后面会细讲)
cd ~/ws_px4 # 你的ROS2工作空间 source install/setup.bash ros2 launch px4_ros_com sensor_combined_listener.launch.py这个launch文件会启动一个订阅气流高度等话题的节点。如果它正常输出数据,说明链路已经通了。
终端4:查看话题列表
ros2 topic list你应该能看到/fmu/out/vehicle_attitude、/fmu/out/vehicle_odometry、/fmu/out/vehicle_status等话题。
如果你走到这一步,恭喜,这套环境基本搭成了。
4.3 Agent未连接时的常见表现
我有一个朋友,第一次跑的时候终端2一点输出都没有,他还以为Agent没启动成功。实际上Agent正常运行时也不会刷屏,只有在收到PX4发来的数据时才打印日志。所以“没有输出”不等于“没接上”,要判断是否接通,最直接的方式是在终端3看有没有收到消息。
如果终端3一直没有数据,优先排查:PX4是否成功启动了RTPS桥接,对齐下两边的端口号,检查一下防火墙。Gazebo环境下一般不会有真实网卡的问题,但虚拟机里偶尔会碰到UDP被隔离的情况。
5. 用px4_ros_com构建自己的节点:实现OFFBOARD控制
前面的技能都是热身,现在开始写真正的逻辑。我以“读取PX4当前的位置信息,并让飞机起飞悬停到指定点”为例,带你走一遍完整的消息流。
5.1 工作空间搭建与px4_msgs引入
先建工作空间并拉取官方接口包:
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这里有一个易错点:px4_ros_com里带了px4_msgs的依赖,如果你只clone了px4_ros_com而没clone px4_msgs,编译就会报找不到消息包。两者都要。
编译好后source:
source install/setup.bash5.2 OFFBOARD模式的触发机制
PX4的OFFBOARD模式是无人机接受外部控制的核心。在PX4 v1.14里,切换OFFBOARD有两种方式:通过MAVLink命令,或者通过ROS2 Action。
ROS2的Action是比“往话题里发指令”更可靠的方式。因为Action有反馈和结果,你可以明确知道PX4有没有接受你的指令。如果只是单纯往/fmu/in/offboard_control_mode里发消息,PX4到底有没有切过去,你是不确定的。
我写了一个最小节点,发布OFFBOARD模式的指令和期望位置:
// offboard_control.cpp #include <rclcpp/rclcpp.hpp> #include <px4_msgs/msg/offboard_control_mode.hpp> #include <px4_msgs/msg/trajectory_setpoint.hpp> class OffboardControl : public rclcpp::Node { public: OffboardControl() : Node("offboard_control") { offboard_pub_ = this->create_publisher<px4_msgs::msg::OffboardControlMode>("/fmu/in/offboard_control_mode", 10); trajectory_pub_ = this->create_publisher<px4_msgs::msg::TrajectorySetpoint>("/fmu/in/trajectory_setpoint", 10); timer_ = this->create_wall_timer(std::chrono::milliseconds(100), [this](){ publish(); }); } private: void publish() { auto offboard_msg = px4_msgs::msg::OffboardControlMode(); offboard_msg.position = true; offboard_pub_->publish(offboard_msg); auto setpoint_msg = px4_msgs::msg::TrajectorySetpoint(); setpoint_msg.position = {0.0, 0.0, -1.0}; setpoint_msg.yaw = 0.0; trajectory_pub_->publish(setpoint_msg); } rclcpp::Publisher<px4_msgs::msg::OffboardControlMode>::SharedPtr offboard_pub_; rclcpp::Publisher<px4_msgs::msg::TrajectorySetpoint>::SharedPtr trajectory_pub_; rclcpp::TimerBase::SharedPtr timer_; }; int main(int argc, char** argv) { rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<OffboardControl>()); rclcpp::shutdown(); return 0; }发布频率很关键。PX4要求OFFBOARD指令至少在2Hz以上,实际操作中我用的是10Hz,也就是100ms定时器。如果低于2Hz,PX4会认为连接丢失,自动退出OFFBOARD模式。很多人试了半天发现飞机不响应,先检查一下发布频率,这是最常见的隐藏问题。
5.3 从“收到消息”到“飞机飞起来”
光发OFFBOARD模式指令还不够,PX4里有一个“安全开关”机制——需要先解锁(arm),然后切到OFFBOARD模式。
在仿真环境里,我习惯用QGroundControl来解锁,因为可以看到飞机状态。如果要用命令行,可以在PX4 shell里输入:
commander arm commander mode offboard这个顺序不能反。有一次我图省事,直接在外部的PX4 shell里输入commander mode offboard,PX4会返回错误,因为没解锁之前它不接受OFFBOARD模式切换。
整个起飞逻辑是:
- 启动起飞位置指令发布节点(上面的代码)
- 在QGroundControl或命令行中解锁
- 在QGroundControl或命令行中切换OFFBOARD
- 飞机收到位置指令后,自动起飞到(0,0,-1)并悬停
注意上面位置里的z方向是负值,因为PX4的坐标系里高度向上为负。第一次接触的人经常会在这里栽跟头,我见过有人写了正的z值,结果飞机一头往地面钻。
5.4 QoS不匹配问题:一对“冤家”
PX4通过Agent发布的ROS2消息,默认使用的是Best Effort可靠性策略,而很多ROS2节点默认用的是Reliable。如果两边不匹配,你会看到一个很诡异的现象:ros2 topic list能看到话题,但ros2 topic echo收不到任何数据,或者时断时续。
我记得第一次遇到这个问题时,整整排查了一个下午,最后发现是QoS不匹配。解决办法是订阅时指定匹配的QoS:
auto qos = rclcpp::QoS(rclcpp::KeepLast(10)).best_effort().durability_volatile(); auto sub = this->create_subscription<px4_msgs::msg::VehicleAttitude>("/fmu/out/vehicle_attitude", qos, callback);用ros2 topic info /fmu/out/vehicle_attitude --verbose可以查看发布端的QoS策略,照着配就不会错。ROS2在Humble版本里,如果QoS不匹配,甚至不会报错,只会在底层悄悄把消息丢掉,这是新手最容易掉进去的坑。
6. 自定义uORB消息:用RTPS把私有话题桥接出来
官方提供的话题够用吗?日常开发基本够用,但如果你要做自定义功能,比如发送一个自己定义的传感器数据、控制某路舵机,就得会拓展。
6.1 自定义消息的完整流程
第一步,在PX4源码里新增uORB消息定义:
在PX4-Autopilot/msg/下新建一个.msg文件,格式类似:
uint64 timestamp float32 my_value第二步,在CMakeLists.txt或构建配置里找到消息列表,把新消息加进去。PX4 v1.14.3的构建体系会自动扫描msg目录,所以如果你只是新增一个.msg文件,通常不需要手动改CMakeLists。
第三步,把新消息加入RTPS桥接的YAML文件。打开msg/tools/urtps_bridge_topics.yaml,在rtps列表里加上你的消息:
- msg: my_custom_msg receive: truereceive表示是否可以从ROS2侧发送到这个主题。设置为false表示只能从PX4侧发出。
第四步,重新编译固件。
这个流程里最容易出错的是第三步忘了加YAML。很多人改了.msg,重新编译后,在ROS2侧一直看不到新话题,白白浪费了半小时。
6.2 自定义消息和px4_msgs的关系
有个概念必须澄清:PX4的uORB消息和ROS2的px4_msgs不是一套东西。即使你在PX4源码里新增了.msg,px4_msgs里的对应消息也不会自动出现。你需要再到px4_msgs/msg/里同样新建一个.msg,保持字段一致,重新编译colcon build。
这两边消息名字一样、字段一样,但本质上是两个不同系统里的定义。不理解这个对应关系,你会在自定义通信上被卡住很久。
7. 我踩过的几个坑,顺手帮你填平了
有些坑是我自己在踩,有些是帮人远程调试时遇到的。写在这,给你的排错路径省点时间。
7.1 Agent镜像拉取失败的排查
在Hot Search里你很可能看到过这个报错:unable to find image 'microros/micro-ros-agent:humble' locally。
这通常是在用Docker方式运行Agent时出现的。原因是 Docker Hub 上的镜像标签不是所有版本都有,或者在极少数网络环境下需要手动拉取。
我给出两个解决方案:
方案一(推荐),直接用apt装:
sudo apt install ros-humble-microxrcedds-agent方案二,如果一定要用Docker,先手动拉取指定标签:
docker pull microros/micro-ros-agent:humble如果拉取失败,换成docker pull microros/micro-ros-agent:latest,然后在运行命令里改对应标签。不过实话说,在本地开发环境里,用Docker反而多此一举,ros2 run的方式更顺手。
7.2 “ros2: command not found”的根因
这个问题的核心不是你没装ROS2,而是shell没有source环境。常见原因有三个:
第一,你忘了source /opt/ros/humble/setup.bash。解决办法很简单,在~/.bashrc末尾加上这一行,一劳永逸。
第二,你source了某个工作空间里的setup.bash,但是这个工作空间是在ROS2环境下编译的,如果你没source基础环境,里面的setup.bash就会报错,导致后续命令找不到。
第三,你用的是zsh,但只把source写进了~/.bashrc。这种问题经常发生在Ubuntu默认shell改成zsh的用户身上。检查一下~/.zshrc里有没有相应的source。
7.3 Gazebo里飞机不动、Agent无数据的排查
这种情况十次有八次是PX4进程没有真正启动RTPS桥接。判断方法是看PX4 shell启动日志里是否有类似RTPS link started的字样。如果没看到,就去urtps_bridge_topics.yaml检查一下,看看你的自定义配置有没有语法错误。
还有一次,我把Agent的端口写成了8899,而PX4默认是8888,结果Agent一直在等,PX4也一直在发,双方隔着网络互相看不见。改回8888就好了。
7.4 ROS2多机通信与网络发现(进阶)
如果你是用无人机的机载电脑远程连地面站,想让多个ROS2设备共享话题,那就要注意DDS的网络发现机制。ROS2默认使用多播进行节点发现,在复杂网络环境下可能不理想。可以改用ROS_DOMAIN_ID隔离通信环境,也可以设置ROS_AUTOMATIC_DISCOVERY_RANGE来限制发现范围。
PX4仿真在单机上跑,这些问题不明显,但真正上无人机平台之后就会遇到了。到时候先在松耦合网络里试通ROS2节点,再连PX4,不要一起上一锅乱炖。
8. 从仿真到真机:三个必须新增的防护
仿真跑通只是第一步。等你把代码部署到真正的无人机上时,有几个防护必须给代码加上,我建议现在就写在代码里,别等到炸机了再后悔。
第一,发布频率监测。如果指令发布频率掉到2Hz以下,PX4会退出OFFBOARD。你应该在代码里加入发布频率统计,低于阈值就主动触发着陆或切换到定高模式。
第二,指令超时保护。如果PX4在一定时间内收不到位置更新或者速度更新,应该立刻切换到安全模式。在代码里可以做一个看门狗,每次成功收到PX4的回推消息时重置计时器,超时就执行紧急着陆。
第三,控制目标合理性检查。你向PX4发送的期望位置需要经过校验,防止因为算法异常发出离谱的指令。我的习惯是加一个最大加速度和位置边界限制的校验函数,先判断目标是否在可飞区域内,再发送给PX4。代码上只是加几个if判断,但在真机上能避免非常多问题。
也许你看过一些视频或者教程,在真机上直接通过/fmu/in/offboard_control_mode发指令,然后发现飞机反应迟钝,其实那多半是发布频率不够。PX4的OFFBOARD是一个高实时性通道,不是随便发几条消息就行的。
我个人的体会是:仿真阶段一定要把通信链路、消息定义、控制逻辑全部调试稳定,因为你真正到了外场,没有那么多时间来排查ROS2和PX4的接口问题。外场时间应该花在调参和验证算法上,而不是浪费在“为什么主题收不到数据”这种低级问题上。
文章写到这里,从环境配置到数据通信的路径,基本已经捋清楚了。ROS2对接PX4这件事本身并不神秘:先理解uORB和DDS的翻译机制,再确认Agent通道是通的,最后通过px4_ros_com构建自己的控制逻辑。剩下的事情,就交给时间去踩坑吧。