3. 实机系统组成
这一章,我们直接钻进 PX4 SITL 的核心。很多朋友跑仿真就是敲个 make px4_sitl gazebo,然后看飞机飘起来就完事了。但真机部署时遇到问题,往往就卡在对仿真底层的不理解上。我个人习惯是,先把架构摸透,再动手。这样出了问题,你知道该去哪个模块翻代码。
3.1 PX4 架构概览:分层与模块
PX4 的架构,说白了就是一套「飞控大脑」的分工体系。它不像 Arduino 写个循环那么直白,而是分成了几个清晰的层次。
核心分层:
- 应用层: 各种飞行模式(定高、悬停、任务)、导航逻辑。这部分你改得最多。
- 中间件层: uORB(微对象请求代理)消息总线。所有模块之间的数据交换都走这里。
- 硬件抽象层: 驱动层,负责跟传感器(IMU、GPS)、执行器(电调、舵机)打交道。
- 实时操作系统: NuttX,负责任务调度、内存管理。
我在项目中遇到过一个问题:真机上日志里某个传感器数据突然跳变,查了半天,最后发现是 uORB 消息队列被高频率的日志任务堵死了。嗯,这就是对中间件层不够了解吃的亏。
下面这张图,是我自己画的一个简化版架构图,帮你快速建立整体认知。
3.2 SITL 启动流程:一条命令背后发生了什么
你执行 make px4_sitl gazebo,机器就开始干活了。但具体干了什么?我把它拆成几个关键步骤。
- 编译 PX4 固件: 针对 x86_64 Linux 架构编译,而不是 ARM 或 STM32。所有硬件驱动被替换成仿真驱动。
- 启动 Gazebo 服务器: 加载世界模型和飞机模型(通常是 Iris 四旋翼)。
- 启动 PX4 主进程: 加载参数文件,初始化 uORB 总线,启动各个模块(传感器、导航、控制)。
- 建立仿真桥接: Gazebo 通过
gazebo_mavlink_interface插件,把仿真传感器的数据(IMU、GPS、气压计)通过 MAVLink 协议发给 PX4。 - 等待外部连接: PX4 在 UDP 端口 14540(给 QGC)和 14580(给 SDK)上监听。
避坑指南: 我曾经在启动 SITL 时遇到飞机直接坠地,查了半天发现是 Gazebo 的模型文件里惯性参数写错了。仿真环境里的 .sdf 或 .urdf 文件,质量、惯性矩阵这些参数一定要跟真机对得上,否则仿真结果毫无意义。
3.3 MAVSDK 与 MAVLink 通信原理
MAVLink 是无人机界的「通用语言」。它定义了消息格式,比如心跳包、姿态数据、GPS 位置。而 MAVSDK 是 PX4 官方提供的一个高级 SDK,帮你封装了 MAVLink 的底层细节。
你想想看,如果让你直接解析 MAVLink 的二进制流,处理消息 ID、CRC 校验、序列号,那得写多少代码?MAVSDK 把这些全干了。你只需要调用 Telemetry::position() 就能拿到位置。
3.3.1 MAVLink 消息结构
一个标准的 MAVLink v2 消息帧长这样:
| 字段 | 长度 (字节) | 说明 |
|---|---|---|
| STX (起始标志) | 1 | 0xFD 表示 MAVLink v2 |
| Payload Length | 1 | 有效载荷长度 |
| Incompatibility Flags | 1 | 签名、加密等标志 |
| Compatibility Flags | 1 | 兼容性标志 |
| Sequence | 1 | 消息序列号,用于丢包检测 |
| System ID | 1 | 系统编号(飞控通常是 1) |
| Component ID | 1 | 组件编号(自动驾驶仪、摄像头等) |
| Message ID | 3 | 消息类型(如 #30 是 ATTITUDE) |
| Payload | 0-255 | 实际数据 |
| Checksum (CRC) | 2 | 16 位 CRC 校验 |
| Signature (可选) | 13 | 用于安全认证 |
为什么要有 System ID 和 Component ID?我在做多机编队时,一台地面站同时控制 5 架飞机,就是靠这两个 ID 来区分消息该发给谁。
3.3.2 MAVSDK 的工作模式
MAVSDK 内部维护了一个 MAVLink 通信线程。你调用 API 时,它把请求打包成 MAVLink 消息发出去,然后异步等待响应。核心流程如下:
// C++ 示例:使用 MAVSDK 请求无人机位置
#include <mavsdk/mavsdk.h>
#include <mavsdk/plugins/telemetry/telemetry.h>
using namespace mavsdk;
int main() {
Mavsdk mavsdk;
ConnectionResult conn_result = mavsdk.add_any_connection("udp://:14540");
// 等待系统发现
auto system = mavsdk.first_autopilot(3.0); // 超时 3 秒
if (!system) {
std::cerr << "连接超时" << std::endl;
return 1;
}
Telemetry telemetry{system.value()};
// 订阅位置更新(异步回调)
telemetry.subscribe_position([](Telemetry::Position position) {
std::cout << "纬度: " << position.latitude_deg << std::endl;
std::cout << "经度: " << position.longitude_deg << std::endl;
});
// 保持主线程运行
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}
注意: 上面的代码里,add_any_connection("udp://:14540") 是监听本机 14540 端口。如果你在真机上,通常会用串口连接,地址会变成 serial:///dev/ttyUSB0:57600。端口和波特率一定要跟飞控参数匹配,否则连不上。
3.4 地面站 (QGC) 连接仿真
QGroundControl 是 PX4 官方推荐的地面站。连接 SITL 仿真非常简单,因为 PX4 启动后会自动在 UDP 14540 端口广播心跳包。
具体操作步骤:
- 启动 SITL:
make px4_sitl gazebo - 打开 QGC,在「应用程序设置」-「通信」-「添加」里,选择 UDP,端口填 14540。
- QGC 会自动发现飞控,状态栏从灰色变成绿色。
连接成功后,你可以在 QGC 上看到仿真飞机的姿态、位置、电池电压(仿真值)。你还可以在「计划」页面里上传一条航线,飞机就会自动执行。
我的经验: 如果你在 QGC 里看不到仿真飞机,八成是防火墙把 UDP 端口拦了。在 Linux 上可以临时关掉防火墙试试:sudo ufw disable。Windows 上记得在防火墙设置里放行 QGC 和 PX4 的进程。
下面这张流程图,总结了从 SITL 启动到 QGC 连接的完整数据流。
看到这里,你应该对 PX4 SITL 的架构、启动流程、通信原理和地面站连接有了一个立体的认识。这些东西,说白了就是一套「仿真-飞控-地面站」的铁三角关系。把这三者之间的数据流和协议搞明白,后面真机部署时,你就能快速定位问题是出在飞控逻辑、通信链路还是地面站配置上。