MAVLink 消息结构深度解析
各位同学,欢迎来到第二章。上一章我们聊了 MAVLink 是什么、能干什么。今天咱们直接钻进它的肚子里,看看一条 MAVLink 消息到底长什么样。
说实话,我第一次看 MAVLink 协议文档时,也被那一堆字节搞晕过。但后来我发现,只要把消息帧格式拆开看,其实特别简单。说白了,它就是一套“信封”规则——规定了数据怎么装、怎么封口、怎么防拆。
消息帧格式:数据怎么打包的?
MAVLink 1.0 和 2.0 的帧格式略有不同,但核心思想一致。我们先从最常用的 MAVLink 2.0 讲起。一条完整的消息帧,由以下几个字段组成:
| 字段名 | 长度(字节) | 说明 |
|---|---|---|
| STX(起始标志) | 1 | 固定值 0xFD,表示 MAVLink 2.0 |
| LEN(载荷长度) | 1 | Payload 部分的字节数(0-255) |
| INC(不兼容标志) | 1 | 标记扩展功能,如签名 |
| CMP(兼容标志) | 1 | 标记兼容性扩展 |
| SEQ(序列号) | 1 | 0-255 循环,用于丢包检测 |
| SYS(系统 ID) | 1 | 标识发送设备(如飞控=1) |
| COMP(组件 ID) | 1 | 标识组件(如自动驾驶仪=1) |
| MSG(消息 ID) | 3 | 消息类型标识,0-16777215 |
| Payload(载荷) | 0-255 | 实际数据内容 |
| CRC(校验码) | 2 | 16 位 CRC-16-IBM 校验 |
| Signature(签名) | 0/13 | 可选,用于安全认证 |
嗯,这里要注意:MAVLink 1.0 的 STX 是 0xFE,消息 ID 只有 1 字节,而且没有 INC/CMP 字段。如果你在解析时发现第一个字节是 0xFE,那就得按 1.0 的格式来。我在项目中就踩过这个坑——地面站同时接收 1.0 和 2.0 的混合数据流,一开始没做版本判断,结果解析全乱套了。
核心要点:解析消息的第一步,永远是读第一个字节判断版本。0xFD 走 2.0,0xFE 走 1.0。别搞反了。
CRC 校验机制:怎么保证数据没被篡改?
CRC 校验,说白了就是给消息算一个“指纹”。发送方算一遍,接收方再算一遍。如果两个指纹对不上,说明数据在传输过程中被改了——直接丢掉,不处理。
MAVLink 的 CRC 校验有个特别之处:它不光校验 Payload,还把整个消息头(除了 STX 和 CRC 本身)也算进去了。你想想看,如果有人篡改了消息 ID 或者系统 ID,接收方也能发现。
具体算法是 CRC-16-IBM(多项式 0x8005)。但 MAVLink 在计算时,还会额外加一个“密钥字节”——这个密钥是根据消息 ID 查表得到的。为什么要这么做?
我个人的理解是:防止不同消息类型之间误匹配。比如你发了一条 HEARTBEAT,结果接收方按 ATTITUDE 的格式去校验,密钥不同,CRC 肯定对不上,直接就过滤掉了。这是一种“软类型检查”。
小技巧:如果你在写地面站,建议把 CRC 校验表提前生成好,存成静态数组。运行时直接查表,比现场计算快得多。我早期版本就是现场算,结果在低端 MCU 上卡得不行。
消息 ID 与消息包:怎么区分不同消息?
消息 ID(MSG 字段)是 MAVLink 的灵魂。每个消息类型都有一个唯一的 ID。比如:
- HEARTBEAT:ID = 0
- SYS_STATUS:ID = 1
- GPS_RAW_INT:ID = 24
- ATTITUDE:ID = 30
- GLOBAL_POSITION_INT:ID = 33
接收方拿到消息后,先解析出 MSG 字段,然后根据这个 ID 去查表,找到对应的消息结构体,再按结构体定义去解析 Payload。
这里有个容易犯的错误:MAVLink 2.0 的消息 ID 是 3 字节的,但很多初学者只读了 1 字节就停了。结果高 2 字节被当成 Payload 的一部分,解析出来的数据全是错的。我曾经帮一个学员调试,他折腾了两天,最后发现是消息 ID 读少了。
避坑指南:MAVLink 2.0 的消息 ID 是 3 字节,小端序存储。解析时一定要读满 3 字节,然后拼成一个 uint32_t。别偷懒只读 1 字节。
心跳包(HEARTBEAT)详解
HEARTBEAT 是 MAVLink 里最重要的消息,没有之一。它的消息 ID 是 0,发送频率通常是 1Hz。说白了,它就是设备在喊“我还活着”。
HEARTBEAT 的 Payload 结构如下:
| 字段名 | 类型 | 说明 |
|---|---|---|
| type | uint8_t | 设备类型(如 MAV_TYPE_QUADROTOR = 2) |
| autopilot | uint8_t | 飞控类型(如 MAV_AUTOPILOT_PX4 = 12) |
| base_mode | uint8_t | 位掩码,表示当前模式状态 |
| custom_mode | uint32_t | 自定义模式(如 PX4 的飞行模式) |
| system_status | uint8_t | 系统状态(如 MAV_STATE_ACTIVE = 4) |
| mavlink_version | uint8_t | MAVLink 协议版本(固定为 3) |
base_mode 字段是个位掩码,每一位代表一个状态:
- Bit 0:是否已上电(MAV_MODE_FLAG_DECODE_POSITION_SAFETY)
- Bit 1:是否已加锁(MAV_MODE_FLAG_MANUAL_INPUT_ENABLED)
- Bit 2:是否在遥控控制下(MAV_MODE_FLAG_HIL_ENABLED)
- Bit 3:是否在稳定模式(MAV_MODE_FLAG_STABILIZE_ENABLED)
- Bit 4:是否在引导模式(MAV_MODE_FLAG_GUIDED_ENABLED)
- Bit 5:是否在自动模式(MAV_MODE_FLAG_AUTO_ENABLED)
- Bit 6:是否在测试模式(MAV_MODE_FLAG_TEST_ENABLED)
- Bit 7:是否已解锁(MAV_MODE_FLAG_CUSTOM_MODE_ENABLED)
举个例子:如果 base_mode = 0x81,说明设备已上电且已解锁。如果 base_mode = 0x01,说明已上电但未解锁。这个字段在判断无人机是否可起飞时非常关键。
实战经验:我在地面站开发中,通常把 HEARTBEAT 作为“设备在线”的唯一判断标准。如果连续 5 秒没收到 HEARTBEAT,就认为设备离线了。这个超时时间可以根据实际场景调整——飞控通常 1Hz 发一次,5 秒没收到基本可以确认断连了。
custom_mode 字段在 PX4 中特别重要。它存储了具体的飞行模式,比如“定高模式”、“悬停模式”、“返航模式”等。不同飞控对这个字段的定义不同,解析时需要查对应的飞控文档。
我记得有一次,一个学员问我:“为什么我的地面站显示的模式总是错的?”我让他打印 custom_mode 的原始值,发现是 4。查了 PX4 的源码才知道,4 对应的是“返航模式”。他之前按 APM 的映射表去解析,当然对不上。
建议:解析 custom_mode 时,一定要先确认飞控类型(autopilot 字段),然后根据飞控类型选择对应的模式映射表。不要硬编码。
知识体系总览
下面这张图,把本章的核心知识点串起来了。你可以把它当作一个“消息解析路线图”。
这张图把消息帧格式、CRC 校验、消息 ID 和 HEARTBEAT 的关系都串起来了。你可以把它当作一个“解析路线图”——从收到原始字节流开始,到最终解析出 HEARTBEAT 里的各个字段,每一步都有对应的知识点。
好了,这一章的内容就到这儿。消息帧格式是 MAVLink 的基石,CRC 校验是数据完整性的保障,HEARTBEAT 是所有消息中最基础也最重要的一个。把这些搞明白了,后面解析其他消息就轻松多了。