2. MAVLink 消息结构:消息帧格式详解

好,咱们正式开始聊 MAVLink 的消息结构。说白了,就是地面站和飞控之间到底怎么“说话”的。你发一句“我在这”,对方得知道这是谁说的、说了什么、对不对。MAVLink 的帧格式,就是干这个的。

我记得刚接触 MAVLink 那会儿,看着一长串十六进制数,头都大了。后来拆开一看,其实就几个固定部分。今天咱们把它掰开揉碎了讲清楚。

2.1 消息帧格式

MAVLink 1.0 的帧结构,我习惯把它分成三块:头部、负载、尾部。每一块都有明确分工。来看这个标准格式:

| 起始标志 | 负载长度 | 序列号 | 系统ID | 组件ID | 消息ID | 负载数据 | 校验码 |
|  0xFE   |   1字节  | 1字节  | 1字节  | 1字节  | 1字节  | 0-255字节 | 2字节  |

嗯,这里要注意,MAVLink 2.0 的起始标志是 0xFD,消息 ID 也扩展到了 3 字节。但咱们先从 1.0 讲起,因为大部分入门场景都在用。

起始标志(STX)

就一个字节,固定值 0xFE。接收端看到这个字节,就知道“哦,一条新消息来了”。我曾经在调试时遇到过一个问题:串口线接触不良,飞控疯狂发乱码,地面站就是收不到有效数据。后来发现,就是因为起始标志被干扰了,接收端根本找不到消息头。

负载长度(LEN)

告诉你后面跟着的负载数据有多少字节。范围是 0 到 255。这个字段很关键,接收端靠它知道要读多少个字节才算完。

序列号(SEQ)

每发一条消息,这个数就加 1。从 0 开始,到 255 后归零。干嘛用的?检测丢包。你想想看,如果地面站收到序列号 5,下一个却是 7,那中间 6 就丢了。我在做数传链路测试时,就靠这个字段算丢包率。

系统 ID(SYS ID)

标识是哪个设备发的。比如飞控是 1,地面站是 255。多机场景下,这个字段特别重要。我见过有人把两架飞机的系统 ID 设成一样,结果地面站分不清谁是谁,控制指令全乱套了。

组件 ID(COMP ID)

同一个系统里可能有多个组件。比如飞控本身是一个组件,挂载的摄像头是另一个,GPS 模块又是一个。组件 ID 就是区分它们的。

消息 ID(MSG ID)

这个最核心。它告诉接收端“这条消息是什么类型”。比如 0 是心跳包,30 是姿态数据,24 是 GPS 数据。接收端根据消息 ID,就知道怎么解析后面的负载数据。

负载数据(PAYLOAD)

真正的数据内容。长度由 LEN 字段决定。不同消息 ID 对应不同的负载结构。比如心跳包只有 9 个字节,而 GPS 数据可能有 30 多个字节。

校验码(CRC)

两个字节,用来检查整条消息在传输过程中有没有被改过。发送端算一遍,接收端再算一遍,对不上就扔掉。嗯,这个机制很可靠。我遇到过数传模块受干扰,数据包被改得面目全非,全靠 CRC 把它们拦下来。

核心要点:MAVLink 帧结构就是“头 + 负载 + 尾”。头告诉你怎么读,负载告诉你读什么,尾告诉你读得对不对。

2.2 消息类型分类

MAVLink 定义了上百种消息类型。但咱们入门阶段,真正常用的也就那么几种。我按功能把它们分成了几类:

心跳消息(HEARTBEAT)

消息 ID 为 0。这是最基础的消息,飞控每隔 1 秒发一次。它告诉地面站:我还活着,我的状态是啥。负载里包含系统类型(四旋翼、固定翼、地面站等)、飞控类型(ArduPilot、PX4 等)、以及当前模式(悬停、返航、自动等)。

我个人的习惯是,地面站启动后第一件事就是等心跳。如果 5 秒内收不到,直接弹窗提示“飞控未连接”。这个逻辑简单但很实用。

姿态消息(ATTITUDE)

消息 ID 为 30。包含飞行器的横滚角、俯仰角、偏航角,以及三个轴的角速度。地面站拿到这些数据,就能在界面上画出飞行器的姿态。

我记得有一次做室内定位测试,飞控的姿态数据突然跳变,地面站上的模型直接翻了个跟头。后来查出来是 IMU 的安装方向错了。嗯,姿态数据对不上,其他都白搭。

GPS 消息(GLOBAL_POSITION_INT)

消息 ID 为 33。包含经纬度、海拔高度、地面速度、航向角等。这是地面站显示飞行器位置的核心数据。

这里有个坑:经纬度是以 度 * 1e7 的格式传输的。也就是说,收到 116397428 要除以 1e7 才是 116.397428 度。我曾经忘了做这个转换,结果地面站上显示飞行器在非洲……

其他常用消息

消息名称 消息 ID 主要数据 用途
BATTERY_STATUS 147 电压、电流、剩余电量 电池监控
RC_CHANNELS 65 遥控器各通道值 遥控信号监测
COMMAND_ACK 77 命令执行结果 确认指令是否成功
SYS_STATUS 1 传感器状态、负载情况 系统健康检查

小技巧:刚开始学的时候,不用记所有消息 ID。记住心跳(0)、姿态(30)、GPS(33)这三个就够了。其他的用到再查。

2.3 消息帧结构图解

光说理论不够直观。我画了一张图,把整个消息帧的流转过程展示出来。从飞控发送,到地面站接收解析,每一步都清清楚楚。

MAVLink 消息帧结构及解析流程 起始标志 负载长度 序列号 系统ID 组件ID 消息ID 负载 校验码 消息帧头部(8字节) 负载+尾部 发送端(飞控) 接收端(地面站) 步骤1:检测起始标志 步骤2:读取负载长度 步骤3:读取消息ID 步骤4:解析负载 💡 说明: 接收端按顺序执行以上步骤。如果任何一步校验失败(如CRC不匹配),整条消息直接丢弃,不会进入解析流程。

这张图把整个流程串起来了。从飞控发出消息帧,到地面站按步骤解析,每一步都有明确的任务。你想想看,如果没有这个结构,地面站收到一堆乱码,根本不知道从哪开始读。

注意:MAVLink 1.0 和 2.0 的帧结构不完全一样。2.0 的消息 ID 扩展到了 3 字节,还增加了时间戳和兼容性标志。但 1.0 的结构更简单,适合入门学习。等 1.0 玩熟了,再切 2.0 也不迟。

2.4 实际抓包分析

光看理论不过瘾。咱们来拆一个真实的心跳包。假设串口上收到这么一串数据:

FE 09 01 01 01 00 00 00 00 00 00 00 00 00 00 05 3C

咱们按帧结构拆开看:

字节位置 值 字段 说明
0 0xFE 起始标志 MAVLink 1.0 固定值
1 0x09 负载长度 负载有 9 个字节
2 0x01 序列号 第 2 条消息(从 0 开始)
3 0x01 系统 ID 系统 1(飞控)
4 0x01 组件 ID 组件 1(飞控主控)
5 0x00 消息 ID 心跳包
6-14 9 字节 负载 心跳数据(系统类型、飞控类型、模式等)
15-16 0x05 0x3C 校验码 CRC 校验值

看到了吧?整个消息才 17 个字节,但包含了所有必要信息。地面站收到后,先检查起始标志,再读长度,然后根据消息 ID 知道这是心跳包,最后用校验码确认数据没被篡改。

个人经验:我刚开始调试时,习惯用串口助手直接看十六进制数据。把收到的每一帧都按这个表格拆开,很快就能摸清规律。等熟悉了,再用 MAVLink 库自动解析。