
开头把PX4的offboard开发跑通是我这几年在无人机方向做过最值得的一件事。如果你在网上搜过“PX4开发”大概率会看到一堆零零散散的资料有人讲怎么编译固件有人贴一段mavros的Python代码还有人直接扔给你一套仿真环境让你自己折腾。但真到了自己上手从环境搭建到第一个offboard程序跑起来中间隔着的不是一道坎而是一整条沟。这篇文章就是帮你把这条沟填平用的。先说清楚offboard是什么。简单讲offboard是PX4的一种飞行模式飞行控制权从遥控器或地面站转移到机载计算机由你写的程序直接下发速度、姿态或位置指令。它解决的核心问题是无人机能不能脱离人的操作按照算法自主飞行。无论是航点巡航、视觉跟随、轨迹规划还是集群编队底层都得靠offboard模式把控制指令送进飞控。这篇文章适合刚搭好PX4环境、想跑第一个自主飞行程序的开发者也适合那些已经在仿真里折腾过、但还没理清消息流和控制链路的同学。内容以软件在环仿真SITL为主线穿插实机注意事项全程基于我实际操作过的经验来写。1. 先想清楚offboard模式到底在做什么1.1 一条飞控指令背后的消息流刚接触offboard时最容易犯的错就是把“发指令”想得太简单。你可能以为飞控像黑盒程序发一个坐标它就飞过去了。实际上offboard的逻辑是分层级的机载计算机通过MAVLink协议把期望的姿态、速度或位置打包成消息发给飞控飞控内部的姿态控制器和位置控制器再根据当前状态去解算电机输出。这里有个关键点PX4默认的地面控制链路是遥测电台速率低、延迟高而offboard场景下机载计算机和飞控之间通常走的是高带宽的串口或WiFi。消息流大概是机载程序 - MAVLink消息如SET_POSITION_TARGET_LOCAL_NED - PX4的mavlink模块 - 根据当前飞行模式分发到对应的控制器 - 控制输出 - 电机。理解这条链路你才能明白为什么很多offboard问题最后都出在“消息没送到”或“模式没切对”上。1.2 控制层级位置、速度、还是姿态offboard指令可以下到三个层级姿态ATTITUDE、速度VELOCITY、位置POSITION。PX4内部对这些指令的处理方式完全不一样。位置指令会经过位置控制器的P和D解算输出期望速度给速度控制器再输出期望姿态和油门给姿态控制器最后才是混控器。速度指令则跳过位置控制器直接进速度控制环。我的建议是新手先做位置控制再做速度最后再做姿态。原因很简单位置控制容错空间最大悬停时就算指令有点抖动飞机也只会在测量精度级别附近来回飘不会出现姿态模式下一脚油门窜出去的情况。等你把位置控制玩明白了对PX4的控制层级也就有了真正的体感。2. 开发环境搭建从Ubuntu到PX4仿真2.1 基础环境准备我用的环境是Ubuntu 20.04 ROS 2 Foxy PX4 v1.13这套组合资料最多踩坑最少。如果你用Ubuntu 22.04配ROS 2 Humble也不是不行只是PX4 v1.13对Humble的官方支持比较晚某些中间件版本有兼容问题新手不建议一上来就挑战。依赖安装这些基础操作我就不流水账了。说几个容易忽略的点PX4编译工具链里面g版本不能太老否则编译px4_sitl的C代码会报一堆模板错误python3-jinja2、python3-numpy这些Python依赖必须装否则生成混合器定义文件时直接报错。另外建议把~/src这个目录专门用来放PX4和ROS2工作空间路径里不要出现中文和空格。# 拉取PX4固件 git clone https://github.com/PX4/PX4-Autopilot.git --recursive cd PX4-Autopilot git checkout v1.13.0 # 安装依赖Ubuntu 20.04 bash ./Tools/setup/ubuntu.sh # 编译 make px4_sitl px42.2 编译PX4固件与运行软件在环仿真编译完成后运行仿真只需要一条命令make px4_sitl gazebo这一步会启动Gazebo仿真环境并加载一架默认的Iris四旋翼模型。很多人到这里就卡住了Gazebo窗口开起来但无人机不动控制台也没有任何输出。其实这是个正常现象因为飞控在SITL模式下默认处于手动模式等待遥控器输入。如果你想让它直接从offboard代码接管需要在启动仿真前把MAV_0_CONFIG等参数提前设好但初期建议还是保留默认参数因为你还要在里面手动切模式、看日志。顺便说一句SITL仿真里PX4和Gazebo是共享时钟的吗答案是否定的PX4内部有自己的一套模拟时间两者有偏移是正常的。很多人在QGroundControl里看到GPS时间对不上就以为程序错了其实不影响控制效果。2.3 把ROS2接进来ROS2和PX4之间的桥接层我推荐直接用官方维护的px4_msgs和px4_ros_com仓库。这套桥接方案最大的好处在于不需要额外运行mavros那样的中间件消息类型直接基于PX4的uORB。mkdir -p ~/ws/src cd ~/ws/src git clone https://github.com/PX4/px4_msgs.git git clone https://github.com/PX4/px4_ros_com.git cd ~/ws colcon build source install/setup.bash这里有个细节px4_ros_com默认是在PX4固件源码的同级目录下找PX4-Autopilot的如果你的目录结构不一样需要在CMakeLists里改PX4_SOURCE_DIR路径。我因为一开始把工作空间分开建编译卡了好一阵最后发现是路径没对上。启动顺序也有讲究先启动PX4仿真再启动ros2桥接节点最后才运行你的offboard程序。顺序反了会导致消息订阅不到程序起不来或者飞控收不到指令。3. 第一个offboard程序从起飞悬停开始3.1 程序框架与代码实现我第一个offboard程序是用ROS2的C写的。为什么不用Python因为ROS2的Python接口在实时性上不如C而且PX4的offboard指令频率建议不低于10Hz、最好20Hz以上Python的GIL和消息序列化在高频发送时容易丢帧。下面是核心代码我简化掉了错误处理只保留了主干逻辑// offboard_test.cpp #include chrono #include cmath #include rclcpp/rclcpp.hpp #include px4_msgs/msg/offboard_control_mode.hpp #include px4_msgs/msg/trajectory_setpoint.hpp #include px4_msgs/msg/vehicle_command.hpp #include px4_msgs/msg/vehicle_attitude.hpp using namespace std::chrono_literals; class OffboardTest : public rclcpp::Node { public: OffboardTest() : Node(offboard_test) { offboard_control_pub_ this-create_publisherpx4_msgs::msg::OffboardControlMode( /fmu/offboard_control_mode/in, 10); trajectory_pub_ this-create_publisherpx4_msgs::msg::TrajectorySetpoint( /fmu/trajectory_setpoint/in, 10); command_pub_ this-create_publisherpx4_msgs::msg::VehicleCommand( /fmu/vehicle_command/in, 10); timer_ this-create_wall_timer(50ms, [this]() { publish_offboard_command(); }); } void arm() { publish_vehicle_command(px4_msgs::msg::VehicleCommand::VEHICLE_CMD_COMPONENT_ARM_DISARM, 1.0); } void engage_offboard() { publish_vehicle_command(px4_msgs::msg::VehicleCommand::VEHICLE_CMD_DO_SET_MODE, 1.0, 6.0); // 6 OFFBOARD } private: void publish_offboard_command() { px4_msgs::msg::OffboardControlMode offboard_msg{}; offboard_msg.timestamp this-now().nanoseconds() / 1000; offboard_msg.position true; offboard_msg.velocity false; offboard_msg.acceleration false; offboard_msg.attitude false; offboard_msg.body_rate false; offboard_control_pub_-publish(offboard_msg); px4_msgs::msg::TrajectorySetpoint trajectory_msg{}; trajectory_msg.timestamp this-now().nanoseconds() / 1000; trajectory_msg.position {0.0f, 0.0f, -5.0f}; // NED坐标Z轴向下-5即飞行到5米高度 trajectory_msg.yaw 0.0f; trajectory_pub_-publish(trajectory_msg); } void publish_vehicle_command(uint32_t command, float param1, float param2 0.0f) { px4_msgs::msg::VehicleCommand msg{}; msg.timestamp this-now().nanoseconds() / 1000; msg.param1 param1; msg.param2 param2; msg.command command; msg.target_system 1; msg.target_component 1; msg.source_system 1; msg.source_component 1; msg.from_external true; command_pub_-publish(msg); } rclcpp::Publisherpx4_msgs::msg::OffboardControlMode::SharedPtr offboard_control_pub_; rclcpp::Publisherpx4_msgs::msg::TrajectorySetpoint::SharedPtr trajectory_pub_; rclcpp::Publisherpx4_msgs::msg::VehicleCommand::SharedPtr command_pub_; rclcpp::TimerBase::SharedPtr timer_; };3.2 参数设置与模式切换在启动程序前有两个参数必须确认。第一个是COM_RC_IN_MODE如果设置为默认飞控会等待遥控器信号没有RC信号时offboard指令不会生效。在纯offboard场景下建议设为0禁用RC这样飞控就不会因为遥控器失联而自动退出offboard。第二个是NAV_RCL_ACT这个是“遥控器失联行为”我建议设为0保持offboard但前提是你对程序有绝对信心——不然就设成1返航给自己留条后路。还有一个容易忽略的细节PX4对offboard有一个“超时保护”机制。如果飞控在COM_OF_LOSS_T默认1秒内没有收到新的offboard指令会自动退出offboard模式。所以程序里发布指令的循环一定不能停频率也不能低于10Hz。我见过有人把指令放在一个if条件里条件不满足就一直不发结果飞机飞着飞着突然掉回自稳模式当场翻车。3.3 怎么验证它真的在“offboard”运行跑起来之后怎么确认程序真的把指令送进飞控了最直接的验证方式是看QGroundControl的飞行模式指示。如果切到了offboard界面上会显示绿色的“OFFBOARD”。但更可靠的方式是看PX4控制台日志pxh commander: using new offboard control mode这条消息出现说明飞控已经收到了offboard模式切换指令。接下来观察飞机位置是否在朝着-5米高度移动。SITL仿真里的GZCS模型有大约1米的定位误差飞机稳定在-4.7米到-5.3米之间都算正常。另外一个验证技巧直接把代码里的目标位置改成{1.0f, 2.0f, -5.0f}看看飞机是否会从悬停点向量(1, 2, -5)方向移动。如果飞机反应了但方向不对先检查坐标系定义如果完全没反应多半是消息没进飞控先去排查桥接节点是否启动。4. 进阶从悬停到轨迹跟踪与编队的思考4.1 串级PID与控制周期跑通悬停之后你可能会想为什么我的飞机飞得不“直”为什么轨迹上一顿一顿的这里就要提到PX4控制器的核心——串级PID。PX4的位置控制环和姿态控制环是分内外层的外环是位置或速度内环是姿态角。外环输出作为内环的输入率在10~50Hz姿态环则跑到250~400Hz。如果轨迹跟踪效果差八成不是PID参数没调好而是你的指令频率太低或时间戳不连续。PX4的轨迹指令里带时间戳如果时间戳忽大忽小控制器会认为指令乱序直接忽略或者产生跳变。我在仿真里测过20Hz发送和50Hz发送同样是(1, 2, -5) - (2, -1, -3) - (0, 0, -4)这条轨迹50Hz下的位置误差能小一个量级而且没有明显折线感。4.2 从单机到编队的扩展问题单机offboard跑通后很多人会想玩编队。这里给你泼盆冷水直接用多机SITL做编队仿真结构复杂度不比实机低多少。编队的核心问题有两个一是时间同步二是消息路由。PX4的target_system和target_component字段在单机里可能无所谓但在多机场景下一定要精确区分否则A飞机的指令可能发到B飞机上。我在多机仿真里用的方案是每架机跑一个独立的PX4 SITL实例通过不同的UDP端口通信机载计算机端用ROS2的命名空间区分话题比如/uav1/fmu/offboard_control_mode/in、/uav2/fmu/offboard_control_mode/in。编队控制算法倒是相对独立可以是虚拟结构法、领航跟随法或者一致性方法。我推荐新手先试领航跟随一架飞机飞固定轨迹其他飞机跟踪领航者的相对位置。实现时只需要把每架飞机的目标位置都换算成世界系坐标再用单机的offboard下发就行了。实验时先在地面把所有轨迹算好再一次性启动所有节点不要在飞行过程中动态改参数——多机场景下任何一个节点掉链子整个队形都会散。5. 开发踩坑实录这些问题我几乎都遇到过5.1 编译与仿真阶段的坑第一个坑PX4固件编译到一半报[Errno 13] Permission denied。这个八成是ubuntu.sh装的依赖没给全或者PX4源码目录的属主不对。解决办法很简单sudo chown -R $USER:$USER ~/src/PX4-Autopilot第二个坑make px4_sitl gazebo启动后Gazebo黑屏或闪退。这个一般是显卡驱动问题或者是Gazebo版本和PX4自带的models不匹配。我遇到过最离谱的一次是系统显卡驱动更新后PX4自带的Iris模型渲染不出机体纹理看起来像隐身了。解决办法是检查~/.gazebo/models下是否有Iris模型缓存有就删掉重新下。第三个坑ROS2桥接节点启动后px4_msgs的消息定义和固件实际uORB消息定义不匹配。这个多发生在你不小心把px4_msgs从main分支拉下来而固件是旧的v1.13。两个仓库的px4_msgs是跟着固件版本走的一定要自己切分支不要盲信main是最新版就会自动适配。5.2 消息通信与参数设置的坑offboard不生效先检查这个vehicle_command消息里的confirmation字段不是0。很多人用其他语言的MAVLink库发消息时会顺手把confirmation设成1表示“需要确认”但PX4对confirmation非0的响应处理完全不同会导致切换指令不被执行。在ROS2的px4_msgs里这个字段默认是0但如果你自己拼MAVLink裸消息一定注意别踩。还有一个大坑在SITL仿真里vehicle_command的source_system和source_component要填1但实机上必须填机载计算机的真实MAVLink系统ID否则地面站和机载程序会打架都认为自己的指令是合法的。我自己的习惯是机载计算机系统ID固定为240地面站为255互不冲突。参数设置上MAV_1_CONFIG和MAV_1_MODE这两个参数直接影响机载计算机的MAVLink通道能不能正常工作。默认的MAV_1_CONFIG对应的是TELEM 1口如果你接的是TELEM 2或者USB没改参数之前机载程序的消息根本不会进飞控。5.3 实机前的安全检查清单仿真跑通、实机前有一份清单我每次必过。你现在可以先收藏后面一定用得上检查项操作方式通过标准遥控器安全模式设置RC_MAP_MODE_SW与RC_MAP_ARM_SW可随时切回自稳/定高并手动解锁/上锁offboard超时保护确认COM_OF_LOSS_T设置断链时按预设行为执行而不是失控乱飞电池电压阈值校验BAT_CRIT_THR与BAT_EMER_THR自动返航或紧急降落触发时机合理罗盘/加速度计校准完成全部磁力计与加速度计校准QGC界面无红色告警桨叶方向与混控器手动推油确认电机方向与实际桨叶安装完全一致GPS信号质量至少搜到10颗星以上HDOP小于1.5位置环定位稳定无漂移代码异常保护程序内加“指令冻结”逻辑意外异常时能保持悬停而不是飞向未知方向最后一条要展开讲讲。即使你做了所有参数和保护设置代码逻辑本身也可能出bug。如果程序因为数组越界崩溃ROS2节点直接退出飞控在COM_OF_LOSS_T秒后自动退出offboard回到之前的落体状态——这非常危险。所以我建议程序里加入“死亡时间”逻辑循环里正常发offboard指令但一旦超过500毫秒没有任何主循环心跳就切换到位置保持的setpoint原地悬停同时打印告警日志。这个机制在实机测试中救过我两次。结尾老规矩最后再分享一个小技巧。不管是SITL还是实机第一次飞offboard目标高度永远设低一点比如1米到2米之间。不要一上来就设5米。不是因为飞机飞不上去而是你在初期调试的时候大概率需要多次调整参数、重启程序飞机离地越低容错空间越大。等位置控制足够稳定再一层一层往上升高度、加轨迹复杂度就行。我个人在实际操作中的体会是PX4的offboard开发70%的精力都消耗在环境搭建和消息通信上真正写控制逻辑只占了很小一部分。但这不意味着控制算法不重要——恰恰相反只有把前面那70%的基础打牢你对控制算法的理解才能真正落到代码里。把这篇内容里的环境、流程和坑都过一遍你的第一个自主飞行程序就不远了。