4、MAVLink协议深度解析:消息结构、常见类型、收发流程与自定义扩展

MAVLink 是 QGC 和飞控之间的「通用语言」。说白了,没有它,地面站和无人机就是两个哑巴。我最早接触 QGC 时,以为 MAVLink 就是一堆数据包飞来飞去,后来踩了不少坑才明白——不理解消息结构,二次开发根本无从下手。

这一章,我会从底层结构讲起,带你拆解 MAVLink 的骨架,再聊聊实际收发中那些容易翻车的地方。最后,我会手把手教你定义一条自己的 MAVLink 消息。

4.1 MAVLink 消息结构:从字节流到结构化数据

MAVLink 目前有两个主流版本:v1.0 和 v2.0。QGC 默认使用 v2.0,因为它支持更大的消息负载和更灵活的安全机制。但不管哪个版本,消息的基本骨架是一样的。

一条 MAVLink 消息,本质上就是一个固定格式的二进制帧。我习惯把它拆成三部分:帧头、负载、校验尾。

字段 长度(字节) 说明
STX(起始标志) 1 v1.0 为 0xFE,v2.0 为 0xFD
LEN(负载长度) 1 负载部分的字节数,0~255
SEQ(序列号) 1 每个组件的发送序号,用于丢包检测
SYS ID(系统ID) 1 标识无人机、地面站等独立系统
COMP ID(组件ID) 1 标识系统内的模块,如飞控、GPS、相机
MSG ID(消息ID) 1~3 v1.0 为 1 字节,v2.0 为 3 字节
Payload(负载) 0~255 实际数据,结构由 MSG ID 决定
CKA / CKB(校验和) 2 CRC16 校验,保证数据完整性

嗯,这里要注意:MSG ID 是消息的唯一标识。QGC 收到一个字节流后,先解析帧头,提取 MSG ID,然后根据 ID 去查表,才知道怎么反序列化后面的负载数据。

核心要点:MAVLink 的帧头就是「快递单」,负载就是「包裹里的商品」。你写二次开发时,大部分工作都是在定义和解析这个「商品」的结构。

4.2 常见消息类型:你每天都在用的那些

MAVLink 协议定义了数百种消息。但说实话,日常二次开发中,你真正频繁打交道的也就二三十种。我把它们分成几类,方便你快速上手。

4.2.1 心跳与系统状态

HEARTBEAT(MSG ID: 0) 是最基础的消息。飞控每隔 1 秒发一次,告诉地面站「我还活着」。它包含了飞控的类型、状态、自定义模式等关键信息。

我记得有一次,QGC 连不上飞控,排查了半天,最后发现是 HEARTBEAT 的 SYS ID 配置错了。QGC 只认特定 ID 的 HEARTBEAT,其他的一律忽略。

4.2.2 位置与姿态数据

这类消息是 QGC 地图和仪表盘的数据来源:

  • GLOBAL_POSITION_INT(MSG ID: 33):经纬度、海拔、相对高度、地速
  • ATTITUDE(MSG ID: 30):横滚、俯仰、偏航角及角速度
  • LOCAL_POSITION_NED(MSG ID: 32):局部坐标系下的位置和速度

你想想看,QGC 地图上那个小飞机图标,就是靠 GLOBAL_POSITION_INT 的数据在动。如果你要做自定义仪表盘,这三条消息是绕不开的。

4.2.3 命令与任务消息

地面站给飞控发指令,靠的是 COMMAND_LONG(MSG ID: 76) 和 COMMAND_INT(MSG ID: 75)。前者用浮点数传参,后者用整数传参(适合经纬度)。

任务上传则依赖 MISSION_ITEM_INT(MSG ID: 73) 系列消息。QGC 的航点规划功能,底层就是通过这一系列消息把航点列表一条条塞给飞控的。

避坑指南:我曾经在自定义任务协议时,忽略了 COMMAND_ACK(MSG ID: 77)的等待机制。结果指令发出去后,飞控还没处理完,我就发了下一条,导致任务队列乱掉。记住:发完 COMMAND_LONG 后,一定要等 ACK 回来再发下一条。

4.3 消息收发流程:从 QGC 到飞控的完整链路

一条消息从 QGC 发出,到飞控响应,中间经历了什么?我画了一张流程图,帮你理清这个链路。

MAVLink 消息收发完整流程 QGC 应用层 MAVLink 库 串口 / UDP / TCP 飞控端 飞控响应 MAVLink 库 QGC 应用层 流程说明: 1. QGC 应用层调用 send_message() 接口 2. MAVLink 库将结构化数据序列化为二进制帧 3. 通过串口/UDP/TCP 发送到飞控 4. 飞控解析帧头,反序列化负载,执行命令 5. 响应消息沿相同路径返回 QGC

在 QGC 源码里,消息收发主要涉及两个核心类:MAVLinkProtocol 负责消息的序列化与反序列化,UAS(无人系统对象)负责消息的分发与回调。你写插件时,通常只需要跟 UAS 打交道。

4.4 自定义 MAVLink 消息:从定义到使用

很多时候,标准 MAVLink 消息不够用。比如你要传输一个自定义的传感器数据,或者实现一套私有协议。这时候就需要定义自己的消息。

我分三步带你走一遍。

4.4.1 第一步:在 XML 定义文件中声明消息

MAVLink 的消息定义是用 XML 写的。你需要在 mavlink/message_definitions/v1.0/ 目录下找到 common.xml(或新建一个自定义 XML 文件),在里面添加你的消息定义。

<message id="150" name="MY_CUSTOM_SENSOR">
    <description>自定义传感器数据消息</description>
    <field type="uint64_t" name="time_usec">时间戳(微秒)</field>
    <field type="float" name="temperature">温度(摄氏度)</field>
    <field type="float" name="humidity">湿度(百分比)</field>
    <field type="uint8_t" name="sensor_id">传感器ID</field>
</message>

注意:消息 ID 不能与标准消息冲突。标准消息使用 0~255(v1.0)或 0~16777215(v2.0)。我建议自定义消息从 15000 以后开始分配,避开官方预留范围。

4.4.2 第二步:生成 C/C++ 代码

定义好 XML 后,用 MAVLink 官方代码生成器(mavgenerate.py)生成 C 语言头文件。生成时选择语言为 C,协议版本为 2.0。

生成后,你会得到一堆头文件。把 mavlink.h 和 my_custom_sensor.h 复制到 QGC 源码的 libs/mavlink/include/mavlink/v2.0/ 目录下。

4.4.3 第三步:在 QGC 中收发自定义消息

发送消息的代码很简单:

// 创建消息对象
mavlink_my_custom_sensor_t sensor_msg;
sensor_msg.time_usec = get_time_usec();
sensor_msg.temperature = 25.6f;
sensor_msg.humidity = 68.2f;
sensor_msg.sensor_id = 1;

// 编码并发送
mavlink_message_t mav_msg;
uint8_t buf[MAVLINK_MAX_PACKET_LEN];
mavlink_msg_my_custom_sensor_encode(
    sysid, compid, &mav_msg, &sensor_msg
);
uint16_t len = mavlink_msg_to_send_buffer(buf, &mav_msg);
// 通过串口/UDP 发送 buf 中的 len 个字节

接收消息时,你需要在 QGC 的 UAS 子类中重写 handleMessage() 方法:

void MyUAS::handleMessage(LinkInterface* link, mavlink_message_t& message)
{
    switch (message.msgid) {
    case MAVLINK_MSG_ID_MY_CUSTOM_SENSOR: {
        mavlink_my_custom_sensor_t sensor;
        mavlink_msg_my_custom_sensor_decode(&message, &sensor);
        // 处理传感器数据
        emit sensorDataReceived(sensor.temperature, sensor.humidity);
        break;
    }
    default:
        // 调用父类处理标准消息
        UAS::handleMessage(link, message);
        break;
    }
}

个人经验:我曾经在自定义消息里放了一个很大的数组(超过 255 字节),结果 MAVLink v2.0 直接报错。后来查文档才发现,单条消息负载最大 255 字节。如果你要传大量数据,要么拆包,要么用 MAVLink 的 DATA_STREAM 机制。

4.5 本章小结

MAVLink 协议是 QGC 二次开发的基石。你不需要背下所有消息 ID,但必须理解帧头结构、常见消息的分类、以及消息收发的完整链路。自定义消息是扩展 QGC 功能最直接的手段,XML 定义 + 代码生成 + 编解码调用,三步走完就能跑起来。

嗯,最后提醒一句:调试 MAVLink 消息时,打开 QGC 的 MAVLink Inspector 视图。它能实时显示所有收发的消息内容,比看日志高效十倍。我每次调试自定义协议,第一件事就是打开这个面板。