ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PX4 无人机集群实战:从 4 机 SITL 集群仿真到分布式任务分配的完整路径

PX4 无人机集群实战:从 4 机 SITL 集群仿真到分布式任务分配的完整路径

PX4 无人机集群实战:从 4 机 SITL 集群仿真到分布式任务分配的完整路径

【免费下载链接】PX4-AutopilotPX4 Autopilot Software项目地址: https://gitcode.com/gh_mirrors/px/PX4-Autopilot

PX4-Autopilot 是开源飞控系统的事实标准,但它的集群能力不是开箱即用的"黑盒功能",而是由单机内核 + 通信层 + 应用层算法拼出来的。这篇文章面向有 C++/嵌入式基础的开发者,讲清楚三件事:多机仿真怎么搭起来、任务分配和编队控制怎么写在 PX4 之上、从 SITL 到实飞集群要过哪些坎。核心关键词先放这里:PX4、无人机集群、分布式、MAVLink、uORB、SITL 多机仿真。

先让集群"认得脸":MAV_SYS_ID 身份链与多实例通信隔离

集群的第一性问题不是算法,是身份:谁是谁、消息发给谁、端口不撞车。PX4 的答案是一条很短但很关键的身份链。

px4_instance 到 MAV_SYS_ID 的分配链

SITL 环境下,每个进程启动时带一个实例号-i N,启动脚本ROMFS/px4fmu_common/init.d-posix/rcS里只有一行决定身份:

param set MAV_SYS_ID $((px4_instance+1))

实例 0 拿到 ID 1,实例 1 拿到 ID 2,依此类推。注意 ID 1 被刻意跳过,是为了与 ROS 2 命名空间的编号规则保持一致。实机上没有自动分配,每架飞机必须手动设置不同的**MAV_SYS_ID**,这是集群部署的第一件事,也是最容易翻车的地方。

另一个硬约束:MAVLink 协议里系统 ID 是 uint8,全网最多 255 个值,跳掉 1 之后,单机网络的集群规模上限就是254 架。超过这个数字不是配置问题,是协议问题。

多机仿真的端口分配:一组端口,全按 N 偏移

Tools/simulation/gazebo-classic/sitl_multiple_run.sh为每个实例 N 生成一套 SDF 模型参数:

--mavlink_tcp_port $((4560+${N})) --mavlink_udp_port $((4560+${N}+2000)) --mavlink_id $((1+${N})) --gst_udp_port $((5600+${N})) --mavlink_cam_udp_port $((14530+${N}))

四个端口各管一段:4560+N 是仿真器与 PX4 之间的 TCP 链路,14560+N 是 UDP 数据链路,5600+N 给视频流,14530+N 给相机链路。mavlink_id必须和该实例的MAV_SYS_ID对齐,否则地面站会把数据配对到错误的飞机上。改这个脚本加新机型时,端口偏移是唯一的自由度。

MAVLink 通道三旋钮:CONFIG、RATE、MODE

每架飞机的每路通信通道由**MAV_i_CONFIG**、**MAV_i_RATE**、**MAV_i_MODE**三组参数控制,参数定义在src/modules/mavlink/module.yaml,调度逻辑在src/modules/mavlink/mavlink_main.cpp。

  • **MAV_0_RATE**默认 2000 B/s,是通道总速率上限:所有流的频率之和超限时,每条流的发送频率自动被压低,不会丢包但会降频
  • **MAV_0_MODE**决定流消息集合,Normal 是全量、Minimal 是极简;集群机间链路建议用 Minimal,把带宽让给状态同步
  • **MAV_0_FORWARD**开启后,本通道收到的广播消息会转发给其他通道——这是地面站隔着飞控直连机上外设(如相机)的关键开关
  • **MAV_0_BROADCAST**控制点对点的广播/中转行为,集群里通常保持默认

分层架构与 uORB:为什么每架飞机都是完整的"小 PX4"

PX4 内核分三层:感知层(src/drivers/驱动 +src/modules/ekf2/状态估计)、决策层(src/modules/navigator/任务执行、src/modules/flight_mode_manager/模式管理)、执行层(位置/姿态控制 +control_allocator力矩分配)。层与层之间只通过 uORB 总线通信——进程内的发布/订阅消息队列,带零拷贝和队列限长,模块之间互不感知。

🚁 集群设计直接继承这套哲学:分布式自治。每架飞机本地感知、本地控制永远在线,只有"任务协同"依赖网络。断网时集群退化成 N 架独立飞机,而不是 N 架失控飞机。推论也很直接:集群逻辑应该做成 PX4 之上的增量模块,而不是重写 PX4 内核。

4 架飞机同时起飞:PX4 多机仿真怎么跑起来

两级脚本:无物理版与 Gazebo 版

# 无外部物理引擎:起 3 个 px4 进程,验证集群逻辑最快 ./Tools/simulation/sitl_multiple_run.sh 3 sihsim_quadx px4_sitl_sih # Gazebo Classic:带物理和 3D 场景 ./Tools/simulation/gazebo-classic/sitl_multiple_run.sh -m iris -n 4

第一条只是px4 -i N进程,没有真实物理,适合验证拍卖、编队、容错这类纯逻辑;第二条额外生成 SDF 模型并 spawn 进 Gazebo,看真实姿态。机型混合用-s "iris:2,plane:1,standard_vtol:1"一条命令搞定。

工作目录隔离:实例号是全链路的"种子"

脚本主循环很短,核心就两步:

working_dir="$build_path/instance_$n" $build_path/bin/px4 -i $n -d "$build_path/etc" >out.log 2>err.log &

每个实例独立工作目录,参数文件、日志、数据互不覆盖;-i $n这个实例号是整条身份链的种子——MAV_SYS_ID、通信端口偏移、ROS 2 命名空间全都从它推导。调试多机问题时,先确认你看的日志属于哪个实例,比看算法更快定位问题。

接入 ROS 2:一个 Agent 管全部飞机

集群逻辑用 C++ 写进 PX4 模块是一路,用 ROS 2 写在外层是另一路。PX4 走 UXRCE-DDS,每个实例的**UXRCE_DDS_KEY**取px4_instance+1,px4_instance > 0的实例自动挂uav_${px4_instance}命名空间。地面只需起一个MicroXRCEAgent udp4 -p 8888,所有飞机自动接入,不需要 N 个 Agent。

一个高频踩坑点:PX4 只处理target_system为 0(广播)或等于本机MAV_SYS_ID的VehicleCommand,其余直接丢弃。你的集群命令节点"发了但飞机没反应",八成是没填这个字段。细节见docs/zh/ros2/multi_vehicle.md。

谁来接这个任务:分布式拍卖算法的代价函数拆解

先说实话:PX4 内核不带集群任务分配模块。拍卖算法是跑在 PX4 之上的应用层——每架飞机一个独立节点算成本,MAVLink 广播出价,仲裁出任务。这一节拆成本函数、讲边际成本、给区域搜索的落地形态。

任务成本 = 距离 + 能耗 + 能力失配

每架飞机只需要本地状态就能算出价,位置来自VehicleGnss和 ekf2,电量来自battery_status模块,不需要全局信息:

float task_cost(const Task& t, const VehicleState& s) { float d = (t.start_position - s.position).norm(); float distance_cost = d * DISTANCE_WEIGHT; float energy_cost = (1.0f - s.battery_remaining) * ENERGY_WEIGHT; float cap_cost = capability_penalty(t.requirements, s.capabilities); return distance_cost + energy_cost + cap_cost; }

三项分别惩罚"跑得太远""电量见底""干不了这活",最低成本者中标,本质是首价密封拍卖。权重是主要调参面:距离权重占绝对主导时,永远是最近的飞机接活;抬高能耗权重,低电量机会自动减少,集群自然轮换休息。

边际成本:为什么"当前最便宜"不等于"分配最优"

纯首价拍卖有个结构性缺陷:三架性能接近的飞机会连续中标三个相似任务,其余飞机闲着。解法是把出价定义从"接这个任务的绝对成本"改成"在已有任务表基础上再塞进这个任务的边际成本"——每次投标隐含了该机的现有负载。

这意味着每架飞机必须维护一个本地任务队列(uORB 消息 + dataman 持久化),广播的出价只携带新的边际成本而不是完整时间表,带宽开销恒定。仲裁端(地面站或指定节点)拿到一组边际成本做贪心分配,故障节点的任务重新入池拍卖,天然获得任务容错。

区域搜索的网格覆盖与蛇形分配

📐 搜索场景把"任务"具体化成网格单元,分配退化成取模运算:

for i in range(grid_rows): for j in range(grid_cols): j_eff = j if i % 2 == 0 else grid_cols - j - 1 # 奇数行反向,蛇形走位 uav_id = (i * grid_cols + j_eff) % uav_count assignments[uav_id].append(grid_cell(i, j_eff))

奇数行反向遍历,让相邻网格在空间上尽量相邻,单机的搜索路径接近蛇形,总航程最短。分配完成后,每架飞机把自己那部分网格打包成 survey mission 经 Mission 协议上传,src/modules/navigator/接管执行,拍卖只负责"谁飞哪块"。

三层容错:心跳、任务池、定位降级

  • 通信容错:MAVLink 心跳超时 → 地面站把节点标记离线,其任务池合并重拍卖;阈值取 3 倍心跳周期比较稳
  • 任务容错:任务队列分布式持有,地面站存镜像,故障机队列整体回收,不逐条协商
  • 导航容错:GPS 失效时 ekf2 切视觉/相对定位,集群从"绝对位置编队"退到"相对位置编队"——领航-跟随本来就吃相对量,天然更抗 GPS 拒绝

故障检测与状态机在src/modules/commander/,仿真里验证容错路径可以用src/modules/failure_injection_manager/做故障注入。

跟着队长飞:编队保持与势场避障的工程权衡

领航-跟随:跟随者只需要"队长在哪"

角色分工很简单:领航机正常执行 mission,跟随者各自维护一个相对偏移。关键是跟随者不需要知道队长的航线意图,只需要队长的当前状态加上对运动的短时预测——这让编队成为带宽友好的问题:领航机状态广播 1~5 Hz 足够,其余时间本地外推。

Vector3f target = leader_pos + R_body * formation_offset[uav_index]; Vector3f vel_spd = leader_vel + (target - pos) * K_P;

第一行把编队定义里的相对偏移(菱形编队第 2 架就是(0, -2, 0)这类常量)按当前姿态变换到世界系得到目标点;第二行做"预测队长速度 + 编队误差"的 PD 跟踪,K_P决定收敛速度和超调量。预测-校正策略在此落地:跟随者本地预测队长轨迹,偏差超阈值才请求立即同步,稳态下同步频率能降一个量级。

人工势场的排斥项设计与局部极小值

直觉先说:把队友和障碍当"电荷",越近排斥越强,与指向编队目标点的吸引力合成,合力方向就是修正方向。

F_rep = k_rep × (1/d − 1/d₀) × p̂,仅 d < d₀ 时生效

d是实际距离,d₀是安全距离(集群通常 2~3 米),k_rep排斥系数,p̂从障碍指向本机的单位向量。(1/d − 1/d₀)这一项保证 d₀ 之外力为零、之内平滑增大,不会在边界跳变。

⚡ 工程上真正的坑是局部极小值:吸引和排斥恰好平衡时飞机"冻住"。两个实用解法:检测到速度持续低于阈值(如 100 ms 内位移不足 10 cm)就临时调高k_rep权重,或者加一个切向力打破平衡。PX4 侧的障碍输入来自 detect_and_avoid 距离传感和 geofence 围栏,势场算法本身跑在应用层,输出位置设定点走 offboard 或 mission 下发。

精度与通信开销的显式取舍

编队控制的带宽账要提前算:状态同步频率越高,保持误差 RMS 越小,但通道越早饱和。推荐起步值——领航机状态 5 Hz、任务出价 0.5~1 Hz、心跳 1 Hz,跑通后再逐项加码。更彻底的解法是感知补位:编队内环依赖 UWB/测距的相对量,网络只跑外环慢同步,这样网络抖动直接不进入保持回路。验证时固定记录四个指标:编队保持误差 RMS、端到端通信延迟、任务完成率、节点数-误差曲线,这是集群系统唯一的"体检报告"。

从 SITL 到实飞:硬件集群配置清单与扩展路线

硬件部署配置清单

组件配置要求关键参数/注意点
飞控Pixhawk 系列或 PX4 支持的开发板MAV_SYS_ID逐机唯一,SYS_AUTOSTART机型号统一
数传SiK 电台 / ESP32 Wi-Fi / LoRaSiK 57600 波特率实测约 2 KB/s(含 FEC),MAV_0_RATE按此设
定位每机独立 GNSS,建议 RTK集群共用参考坐标系;UWB 补相对定位
电源每机独立电池 + 电源管理7.4V 2200mAh 级,地面并联充电
地面站多路并发接收QGC 多连接,或每机一个 MAVLink 通道

集群里传感器一致性比单机更敏感:各机的 IMU/磁强计标定偏差会直接进入编队保持误差。逐机走完calibrate流程再谈协同,下图是绑固角(strapdown)标定在解决什么问题:

MAVLink 调优:先懂链路真实容量,再动参数

调参顺序不能反。第一步是弄清链路的真实容量:MAV_0_RATE的参数文档里写得明白,SiK 电台 57600 波特、64 kbps 空口速率下,开 FEC 实际只能扛约 2000 B/s,"波特率/20"的理论值会骗你。第二步才是设**MAV_0_MODE**收窄流集合,第三步调**MAV_0_RATE**顶到链路安全上限,最后用MAV_0_BROADCAST/MAV_0_FORWARD理清"谁该听谁的"。

带宽预算举例:4 机集群,每机状态流 5 Hz × ~40 B 加上 1 Hz 出价,单机约 1 KB/s,走共享电台通道就得降到 2~3 Hz 或者拆两路通道。高频控制量(位置设定点)永远不要走电台——有 ROS 2 需求就叠 UXRCE-DDS 走 UDP,电台只留低速状态与命令。

自写集群模块:从模板到消息定义再到测试

🔧 扩展路径四步,全在仓库里有参照物:

  1. 从src/templates/template_module/模板起新模块,注册进 Kconfig,参考src/modules/mc_nn_control/这类"旁路控制器"模块看接入姿势
  2. 在msg/目录定义 uORB 消息(如SwarmBid.msg),构建时自动生成头文件,不需要手写序列化
  3. 参数走模块参数定义(module.yaml 的px4_module_params机制),地面站可直接调
  4. 要过电台的消息,在src/modules/mavlink/streams/注册流

验证环节:test/mavsdk_tests/是 MAVSDK + Python 的自动化测试框架,集群场景按"用例"组织——N 机起飞、注入某机故障、断言任务重拍卖收敛。无头跑在 SIH 仿真目标上成本最低。下面这张图是 PX4 传统控制与神经网络控制模块的融合机制,集群算法可以借鉴同一套"主备并行 + 健康切换"思路:

学习入口按依赖顺序排:src/modules/navigator/(任务执行)→src/modules/mavlink/(通信)→src/modules/flight_mode_manager/(模式状态机)→src/modules/commander/(故障安全)。多机集成看docs/zh/ros2/multi_vehicle.md,多机仿真总览看docs/zh/simulation/multi-vehicle-simulation.md。

集群系统没有"一次搭好"的时刻,只有"每次加一架都能过回归测试"的纪律。先让 4 机 SITL 稳定跑通拍卖和编队,再谈 20 机实飞——PX4 的分层架构保证了这条路不用推翻重来。

【免费下载链接】PX4-AutopilotPX4 Autopilot Software项目地址: https://gitcode.com/gh_mirrors/px/PX4-Autopilot

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进