MAVLink 协议教程

本文面向准备自己开发地面站的开发者,目标是解释 MAVLink 是什么、MAVLink 2 包含什么、QGroundControl 为什么围绕 MAVLink 2 工作,以及在自己的地面站里应该怎样使用 MAVLink。

参考资料:

MAVLink,全称 Micro Air Vehicle Link,是无人机、地面站、云台、相机、伴随计算机等组件之间常用的轻量级通信协议。

它解决的问题是:不同设备之间如何用统一格式交换飞行状态、控制命令、任务航点、参数、日志、相机控制、视频状态等信息。

常见通信关系如下:

text 复制代码
QGC / 自研地面站
        |
        | MAVLink over UDP / TCP / Serial
        |
飞控 PX4 / ArduPilot
        |
        | MAVLink
        |
相机 / 云台 / 伴随计算机 / 遥测电台

MAVLink 不是传输链路本身。它可以跑在多种链路上:

  • 串口:USB、UART、数传电台
  • UDP:仿真、Wi-Fi、以太网
  • TCP:网络连接、代理转发
  • 其他链路:只要能收发字节流,理论上都可以承载 MAVLink

MAVLink 的核心特点:

  • 二进制协议,包很小,适合低带宽链路。
  • 消息是结构化的,每种消息都有固定 ID 和字段定义。
  • 支持多系统、多组件通信,例如一台地面站同时连接多架飞机。
  • 支持任务、参数、命令、日志、相机、云台等上层微服务。
  • MAVLink 2 支持更大的消息 ID 空间、扩展字段和消息签名。

QGroundControl 当前默认使用 MAVLink 2 的 C 语言生成库,也就是 mavlink/c_library_v2。MAVLink 2 是现在开发新地面站时应该优先考虑的版本。

主要区别:

能力 MAVLink 1 MAVLink 2
起始字节 0xFE 0xFD
Message ID 8 bit,最多 256 个消息 ID 24 bit,空间大很多
扩展字段 不支持 支持
消息签名 不支持 支持 13 字节签名
兼容性标志 不支持 支持 incompat_flagscompat_flags
新项目推荐 不推荐作为首选 推荐

MAVLink 2 仍然可以处理很多 MAVLink 1 场景,但新功能应该按 MAVLink 2 设计。

一个 MAVLink 2 包大致由包头、载荷、校验和、可选签名组成。

text 复制代码
+------------+----------------+---------+------------+
| Header     | Payload        | CRC     | Signature  |
+------------+----------------+---------+------------+
| 10 bytes   | 0 - 255 bytes  | 2 bytes | 0 / 13 B   |
+------------+----------------+---------+------------+

MAVLink 2 头部字段:

字段 长度 说明
magic 1 起始字节,MAVLink 2 是 0xFD
len 1 payload 长度
incompat_flags 1 不兼容标志。接收方不理解时必须拒绝该包
compat_flags 1 兼容标志。接收方不理解也可以继续处理
seq 1 发送序号,用于检测丢包
sysid 1 系统 ID,例如某一架飞机或地面站
compid 1 组件 ID,例如飞控、相机、云台、地面站
msgid 3 消息 ID,决定 payload 应按哪种消息结构解析
payload 0-255 消息载荷
checksum 2 CRC 校验
signature 0 或 13 可选签名,启用签名时存在

开发时通常不要手写这些字节。正确做法是使用 MAVLink 官方生成库或成熟封装库,让库负责打包、解析、CRC、字段布局和字节序。

4.1 System ID

sysid 表示一个系统。常见例子:

  • 飞控:通常是 1
  • 地面站:常用 255
  • 多机时:不同飞机使用不同 sysid

地面站连接多架飞机时,应该用 sysid 区分每一架飞机。

4.2 Component ID

compid 表示系统里的组件。常见例子:

  • 飞控自驾仪组件
  • 地面站组件
  • 相机组件
  • 云台组件
  • 伴随计算机组件

同一个 sysid 下可以有多个 compid

4.3 Message ID

msgid 表示消息类型。例如:

消息 用途
HEARTBEAT 心跳包,发现设备、判断连接是否在线
SYS_STATUS 系统状态、电池、电压、电流等
GPS_RAW_INT GPS 原始信息
ATTITUDE 姿态角、角速度
GLOBAL_POSITION_INT 全球位置、高度、航向等
COMMAND_LONG 发送通用命令
COMMAND_ACK 命令执行结果
PARAM_REQUEST_LIST 请求全部参数
PARAM_VALUE 参数值返回
MISSION_ITEM_INT 航点任务项
MISSION_COUNT 任务数量
MISSION_ACK 任务传输结果

4.4 Dialect

MAVLink 消息由 XML 文件定义,这些 XML 被称为 dialect。

常见 dialect:

  • common.xml:通用消息,大部分地面站和飞控都会支持。
  • ardupilotmega.xml:ArduPilot 扩展消息。
  • standard.xml:标准消息集合。
  • 自定义 dialect:给自己的设备扩展新消息。

开发地面站时,建议先支持 common.xml。如果要兼容 ArduPilot,再加入 ardupilotmega.xml。如果要支持自定义硬件,再设计自己的 XML dialect。

MAVLink 不只是一些单独消息,它还定义了一组常见通信流程,也就是 microservices。

地面站最常用的内容如下。

5.1 心跳与设备发现

核心消息:

  • HEARTBEAT

地面站通过接收飞控发来的 HEARTBEAT 判断:

  • 是否连接成功
  • 飞控类型
  • 飞行器类型
  • 是否解锁
  • 当前飞行模式
  • MAVLink 版本

地面站也应该周期性发送自己的 HEARTBEAT,让飞控知道地面站在线。

5.2 遥测状态

常见消息:

  • SYS_STATUS
  • BATTERY_STATUS
  • GPS_RAW_INT
  • GLOBAL_POSITION_INT
  • LOCAL_POSITION_NED
  • ATTITUDE
  • VFR_HUD
  • RC_CHANNELS

这些消息用于显示:

  • 电池
  • GPS
  • 姿态
  • 高度
  • 速度
  • 位置
  • 航向
  • 遥控输入
  • 飞行状态

5.3 命令协议

常见消息:

  • COMMAND_LONG
  • COMMAND_INT
  • COMMAND_ACK

常见命令:

  • 解锁 / 上锁
  • 起飞
  • 降落
  • 返航
  • 设置模式
  • 设置航向
  • 控制相机
  • 控制云台

基本流程:

text 复制代码
地面站发送 COMMAND_LONG
        |
        v
飞控执行或拒绝命令
        |
        v
飞控返回 COMMAND_ACK

地面站不能只管发送命令,还必须处理 COMMAND_ACK,否则无法知道命令是否成功。

5.4 参数协议

常见消息:

  • PARAM_REQUEST_LIST
  • PARAM_REQUEST_READ
  • PARAM_VALUE
  • PARAM_SET

用途:

  • 读取飞控全部参数
  • 读取单个参数
  • 修改参数
  • 保存参数缓存

基本流程:

text 复制代码
地面站发送 PARAM_REQUEST_LIST
        |
        v
飞控连续返回多个 PARAM_VALUE
        |
        v
地面站根据 param_count 和 param_index 判断是否收齐

参数协议容易遇到丢包,所以地面站通常需要:

  • 记录已收到的参数索引
  • 对缺失参数重新请求
  • 设置超时重试
  • 区分参数类型
  • 本地缓存参数

5.5 任务协议

常见消息:

  • MISSION_COUNT
  • MISSION_REQUEST_INT
  • MISSION_ITEM_INT
  • MISSION_ACK
  • MISSION_CLEAR_ALL
  • MISSION_REQUEST_LIST
  • MISSION_CURRENT
  • MISSION_ITEM_REACHED

用途:

  • 上传航点任务
  • 下载飞控已有任务
  • 清空任务
  • 跟踪当前执行到哪个航点
  • 显示航点是否到达

上传任务的典型流程:

text 复制代码
地面站发送 MISSION_COUNT
        |
        v
飞控请求第 0 个任务项:MISSION_REQUEST_INT
        |
        v
地面站发送第 0 个 MISSION_ITEM_INT
        |
        v
飞控继续请求下一个任务项
        |
        v
全部发送完成后,飞控返回 MISSION_ACK

任务协议也需要超时、重试和状态机。不要用简单的 for 循环一次性把所有航点发出去。

5.6 文件传输协议

常见用途:

  • 下载日志
  • 访问飞控文件系统
  • 获取任务或配置文件

相关协议通常称为 MAVLink FTP。它比普通消息流程复杂,建议等基础连接、参数、任务稳定后再实现。

5.7 日志协议

常见能力:

  • 查询日志列表
  • 下载日志
  • 实时日志流

QGC 里有 PX4 的 MAVLink 2 日志相关功能,但这类功能依赖飞控支持和带宽条件。

5.8 相机与云台协议

常见能力:

  • 拍照
  • 开始 / 停止录像
  • 设置相机模式
  • 获取相机信息
  • 控制云台角度
  • 跟踪目标

如果你的地面站后续要做专业载荷控制,需要单独设计 CameraGimbal 模块。

5.9 消息签名

MAVLink 2 支持消息签名,用于认证消息来源,降低被伪造命令控制飞机的风险。

签名会给消息追加 13 字节签名数据。启用后,收发双方需要共享密钥,并检查时间戳和签名。

注意:

  • 签名是认证,不是加密。
  • 签名不能隐藏消息内容。
  • 签名配置错误会导致通信失败。
  • 初期开发可以先不启用签名,等基础链路稳定后再加入。

开发一个最小地面站,可以按下面流程做。

推荐路线:

  • C++/Qt:使用官方生成的 C MAVLink 头文件,外层用 C++ 封装。
  • Python 原型:使用 pymavlink 快速验证流程。
  • Rust/Go/Java:选择对应生态里成熟的 MAVLink 库。

如果你做 Qt 地面站,建议后续在 libs/mavlink/ 放 MAVLink 生成代码或作为子模块引入。

6.2 建立通信链路

先实现两种链路就够:

  • UDP:用于 PX4 SITL、仿真和本机测试。
  • Serial:用于真实飞控、USB、数传。

建议模块划分:

text 复制代码
src/Comms/
  LinkInterface        抽象链路接口
  UdpLink              UDP 实现
  SerialLink           串口实现

src/MAVLink/
  MAVLinkProtocol      MAVLink 解析、打包、路由
  MAVLinkMessageRouter 消息分发

6.3 解析字节流

MAVLink 是字节流协议。串口或 UDP 收到的是 bytes,不是一条条自动分好的消息。

处理方式:

text 复制代码
链路收到 bytes
        |
        v
逐字节喂给 MAVLink parser
        |
        v
parser 解析出完整 mavlink_message_t
        |
        v
根据 msgid 分发给对应模块

伪代码:

cpp 复制代码
for (uint8_t byte : receivedBytes) {
    mavlink_message_t message;
    mavlink_status_t status;

    if (mavlink_parse_char(channel, byte, &message, &status)) {
        handleMavlinkMessage(message);
    }
}

6.4 发送心跳

地面站应该定时发送 HEARTBEAT。常见周期是 1 Hz。

心跳用于告诉飞控:

  • 我是地面站
  • 我还在线
  • 我支持 MAVLink

伪代码:

cpp 复制代码
mavlink_message_t msg;

mavlink_msg_heartbeat_pack(
    groundStationSystemId,
    groundStationComponentId,
    &msg,
    MAV_TYPE_GCS,
    MAV_AUTOPILOT_INVALID,
    0,
    0,
    MAV_STATE_ACTIVE
);

sendMavlinkMessage(msg);

6.5 处理飞控心跳

收到 HEARTBEAT 后,地面站应该创建或更新 Vehicle 对象。

建议逻辑:

text 复制代码
收到 HEARTBEAT
        |
        v
读取 sysid / compid
        |
        v
如果是新 sysid,创建 Vehicle
        |
        v
更新飞控类型、飞行器类型、解锁状态、飞行模式、最后在线时间

如果超过一定时间没有收到某个 Vehicle 的心跳,比如 3-5 秒,可以标记为失联。

6.6 请求数据流

不同飞控的数据流控制方式不完全一样。

常见方式:

  • 老方式:REQUEST_DATA_STREAM
  • 新方式:MAV_CMD_SET_MESSAGE_INTERVAL

新项目建议优先使用 MAV_CMD_SET_MESSAGE_INTERVAL 请求指定消息频率。

例子:

text 复制代码
请求 ATTITUDE 以 20 Hz 发送
请求 GLOBAL_POSITION_INT 以 5 Hz 发送
请求 SYS_STATUS 以 1 Hz 发送

6.7 分发消息

不要在一个巨大函数里处理所有 MAVLink 消息。建议按模块分发:

text 复制代码
MAVLinkProtocol
        |
        +-- Vehicle              HEARTBEAT / SYS_STATUS / ATTITUDE
        +-- MissionManager       MISSION_*
        +-- ParameterManager     PARAM_*
        +-- CameraManager        CAMERA_*
        +-- LogManager           LOG_*
        +-- TerrainManager       TERRAIN_*

这样后续模块会更清楚,也更接近 QGC 的组织方式。

6.8 发送命令并等待 ACK

命令发送必须带状态跟踪。

推荐流程:

text 复制代码
创建 CommandTransaction
        |
        v
发送 COMMAND_LONG
        |
        v
启动超时定时器
        |
        v
收到 COMMAND_ACK 后结束
        |
        v
超时则重试或返回失败

你需要处理这些结果:

  • MAV_RESULT_ACCEPTED
  • MAV_RESULT_TEMPORARILY_REJECTED
  • MAV_RESULT_DENIED
  • MAV_RESULT_UNSUPPORTED
  • MAV_RESULT_FAILED
  • MAV_RESULT_IN_PROGRESS

6.9 做好丢包和重试

MAVLink 常跑在无线链路上,丢包是正常情况。

地面站必须设计:

  • 消息序号统计
  • 丢包率计算
  • 超时重试
  • 请求缺失数据
  • 连接断开检测
  • 自动重连
  • 对关键命令显示失败原因

第一阶段建议只做这些:

功能 必要消息
发现飞控 HEARTBEAT
显示连接状态 HEARTBEAT、链路状态
显示电池 SYS_STATUSBATTERY_STATUS
显示姿态 ATTITUDE
显示 GPS GPS_RAW_INT
显示位置 GLOBAL_POSITION_INT
解锁 / 上锁 COMMAND_LONGCOMMAND_ACK
起飞 / 降落 / 返航 COMMAND_LONGCOMMAND_ACK
设置模式 COMMAND_LONG 或固件适配接口

第二阶段再做:

  • 参数读取和修改
  • 航点任务上传下载
  • 地图航线显示
  • 飞行模式完整适配
  • 日志下载

第三阶段再做:

  • 视频
  • 相机
  • 云台
  • 多机
  • MAVLink 签名
  • 自定义消息

8. 在本项目里的建议模块设计

结合当前目录结构,建议这样安排:

text 复制代码
src/Comms/
  负责 UDP、TCP、Serial 等链路,不理解具体 MAVLink 消息含义。

src/MAVLink/
  负责 MAVLink parser、message encode/decode、协议版本、签名、消息路由。

src/Vehicle/
  负责飞行器对象、连接状态、飞行模式、解锁状态、基础遥测。

src/FirmwarePlugin/
  负责 PX4、ArduPilot 差异,比如飞行模式映射、命令差异、参数差异。

src/MissionManager/
  负责 MISSION_* 协议状态机。

src/Settings/
  负责地面站设置,比如默认 UDP 端口、系统 ID、组件 ID、自动连接策略。

qml/FlightDisplay/
  显示飞行状态、姿态、高度、速度、电池、GPS。

qml/PlanView/
  编辑和上传航线任务。

关键原则:

  • Comms 只负责收发字节,不解析业务含义。
  • MAVLink 只负责协议层,不直接控制界面。
  • Vehicle 负责把零散 MAVLink 消息聚合成地面站可用的飞行器状态。
  • FirmwarePlugin 处理 PX4 和 ArduPilot 的差异,不要把差异写得到处都是。
  • QML 只绑定状态和触发动作,不直接写 MAVLink 协议状态机。

9. 常见端口和连接方式

常见默认值:

场景 地址 / 端口
PX4 SITL 到地面站 UDP 14550
地面站主动连仿真 UDP 或 TCP,按仿真配置
ArduPilot SITL UDP/TCP 端口视启动参数而定
USB 飞控 串口设备,例如 Linux /dev/ttyACM0,Windows COMx
数传电台 串口,波特率常见 57600115200

实际项目里不要把端口写死,应该放入 Settings

只有在通用消息不够用时才建议自定义消息。

流程:

  1. 新建自己的 XML dialect。
  2. 在 XML 里定义 enum 和 message。
  3. 使用 mavgen 生成目标语言代码。
  4. 地面站和设备端使用同一份 dialect。
  5. 给消息 ID 留好范围,避免和已有消息冲突。

注意:

  • 能用 common.xml 解决的,不要自定义。
  • 自定义消息会降低和其他地面站、飞控、工具的兼容性。
  • 自定义协议必须写清版本和字段含义。

11. 安全注意事项

MAVLink 本身默认不加密。真实飞行时要注意:

  • 不要在开放网络里裸跑可控飞机的 MAVLink。
  • 关键命令必须检查 ACK。
  • UI 上要对解锁、起飞、降落、返航做确认和状态反馈。
  • 对外部输入的 MAVLink 消息做来源和目标检查。
  • 多机时必须严格区分 sysid
  • 有安全要求时考虑 MAVLink 2 signing、VPN、专用链路或其他加密通道。

12. 学习和实现路线

建议按下面顺序写:

  1. UDP 收发字节。
  2. 接入 MAVLink parser。
  3. 收到并打印 HEARTBEAT
  4. 地面站发送自己的 HEARTBEAT
  5. 创建 Vehicle,显示在线 / 离线状态。
  6. 解析 ATTITUDEGPS_RAW_INTGLOBAL_POSITION_INTSYS_STATUS
  7. 实现 COMMAND_LONGCOMMAND_ACK
  8. 实现参数协议。
  9. 实现任务协议。
  10. 做固件适配层,区分 PX4 和 ArduPilot。
  11. 加入串口连接。
  12. 加入日志、相机、云台、视频等高级功能。

13. 最小调试方法

开发初期推荐使用仿真,不要一开始就连真实飞机。

推荐组合:

  • PX4 SITL + 自研地面站
  • ArduPilot SITL + 自研地面站
  • Wireshark MAVLink dissector 抓包
  • QGroundControl 对照同一飞控的行为

调试时重点看:

  • 是否收到 HEARTBEAT
  • sysidcompid 是否正确
  • 是否使用 MAVLink 2 包头 0xFD
  • 是否有 CRC 错误
  • 是否有丢包
  • 命令是否收到 COMMAND_ACK
  • 参数和任务流程是否有超时重试

14. 结论

MAVLink 是地面站和飞控之间的核心通信协议。开发自己的 QGC 风格地面站时,不要把 MAVLink 当作简单的收发字符串,而要把它当作一套完整的协议系统:

  • 底层是字节流解析和消息打包。
  • 中层是心跳、命令、参数、任务、日志等微服务状态机。
  • 上层是 Vehicle、Mission、Camera、Settings 等业务对象。
  • UI 只应该使用业务对象提供的状态和动作。

先把心跳、遥测、命令 ACK 做稳,再逐步扩展参数、任务和高级功能。这样项目结构会更清楚,也更容易接近 QGroundControl 的工程质量。

相关推荐
实心儿儿1 小时前
Linux —— 线程控制(2)
linux·运维·服务器
墨白曦煜1 小时前
算法实战笔记:剥开回溯算法的外衣——从通用模板到高阶去重(八)
笔记·算法
烛衔溟1 小时前
TypeScript 模块与声明文件全解
linux·ubuntu·typescript
量子炒饭大师1 小时前
【Linux系统编程:进程概念】——【从 冯诺依曼系统体系结构 到 操作系统】
linux·运维·服务器·操作系统·冯诺依曼
z200509302 小时前
今日算法(回溯子集)(模版题)
数据结构·算法·leetcode
吴佳浩2 小时前
Vibe Coding 时代,研发经理为何越来越值钱?
算法·架构
IronMurphy2 小时前
【算法五十四】72. 编辑距离
算法
QiLinkOS2 小时前
【用呼吸重构创造价值关系——QiLink生态】
c语言·数据结构·c++·人工智能·单片机·嵌入式硬件·算法
sxstj2 小时前
STM32F103 串口数量 + 对应 GPIO
单片机·嵌入式硬件