2. 消息格式深度解析:MAVLink消息帧结构

上一章我们聊了MAVLink是什么、能干什么。这一章,咱们直接动手拆开一条MAVLink消息,看看它到底长什么样。

我刚开始接触MAVLink时,拿着协议文档看了半天,脑子里全是十六进制数。后来我干脆把一条消息打印出来,对着文档一个字节一个字节地抠。嗯,今天我就带你走一遍这个过程。

2.1 消息帧的整体骨架

一条MAVLink消息,说白了就是一串字节。这串字节有固定的排列顺序,就像快递包裹有固定的打包方式一样。你想想看,如果每个厂家都按自己的喜好打包,那飞控和地面站之间就没法通信了。

MAVLink 2.0的消息帧结构如下(MAVLink 1.0我们后面会对比):

+------+------+------+------+------+------+------+------+------+------+------+
| STX  | LEN  | INCOMPAT | COMPAT | SEQ  | SYSID | COMPID | MSGID | Payload | CKA | CKB |
| 1 B  | 1 B  | FLAGS 1B | FLAGS 1B| 1 B  | 1 B   | 1 B    | 3 B   | 0-255B | 1 B | 1 B |
+------+------+------+------+------+------+------+------+------+------+------+

别被这一长串吓到。我们把它分成三块来看:帧头、数据体、校验尾。帧头告诉你怎么解析,数据体是你要的信息,校验尾保证数据没被篡改。

核心要点:MAVLink 2.0帧头固定10字节,数据体0-255字节,校验尾2字节。整条消息最短12字节(空消息),最长267字节。

2.2 各字段含义与作用

下面我们逐个字段拆解。我会结合我在实际项目中踩过的坑来讲。

2.2.1 STX(起始标志)

STX占1个字节,是消息的“门牌号”。MAVLink 1.0的STX是0xFE,MAVLink 2.0是0xFD。解析器读到0xFD就知道后面是2.0格式,读到0xFE就知道是1.0格式。

我曾经在一个项目中,地面站同时接收1.0和2.0的混合数据流。一开始没做版本判断,结果解析全乱套了。后来我加了一个简单的switch-case,根据STX分流处理,问题就解决了。

2.2.2 LEN(负载长度)

LEN占1个字节,表示后面Payload部分的字节数。取值范围0-255。注意,这个长度不包括校验尾。

为什么要有这个字段?因为MAVLink消息的Payload长度是可变的。没有LEN,解析器就不知道从哪里开始读校验尾。

2.2.3 INCOMPAT_FLAGS 和 COMPAT_FLAGS(兼容标志)

这两个字段是MAVLink 2.0新增的,各占1个字节。它们用来标记消息是否使用了扩展功能。

  • INCOMPAT_FLAGS:不兼容标志。如果接收方不支持该标志位对应的功能,必须丢弃这条消息。比如第0位表示“消息已签名”。
  • COMPAT_FLAGS:兼容标志。接收方如果不支持,可以忽略该标志位,继续解析后面的内容。

我的建议:在二次开发时,如果你只是做基础的数据收发,这两个字段通常填0。但如果你要实现消息签名或加密,一定要先检查INCOMPAT_FLAGS,否则接收方可能直接丢弃你的消息。

2.2.4 SEQ(序列号)

SEQ占1个字节,取值范围0-255。它是发送方维护的一个循环计数器,每发一条消息就加1,溢出后归零。

这个字段有什么用?检测丢包。接收方可以记录上一个SEQ值,如果发现当前SEQ不连续,就知道中间有消息丢了。我在做无人机链路质量分析时,就是靠统计SEQ的跳变来估算丢包率的。

2.2.5 SYSID(系统ID)

SYSID占1个字节,标识消息来自哪个系统。比如飞控通常设为1,地面站设为255,摄像头设为某个自定义值。

在一个多无人机协同的项目中,我同时控制3架飞机。每架飞机的SYSID分别是1、2、3。地面站根据SYSID就能区分消息来自哪架飞机,不会搞混。

2.2.6 COMPID(组件ID)

COMPID占1个字节,标识系统内的哪个组件。同一个系统里可能有多个组件:飞控主处理器(COMPID=1)、GPS模块(COMPID=220)、摄像头(COMPID=100)等。

SYSID + COMPID的组合,可以唯一确定一个通信节点。这在复杂系统中非常有用。

2.2.7 MSGID(消息ID)

MSGID在MAVLink 2.0中占3个字节(MAVLink 1.0只占1个字节)。它告诉解析器:Payload里装的是什么数据。

比如MSGID=0表示HEARTBEAT消息,MSGID=30表示ATTITUDE消息。每个MSGID对应一个固定的数据结构。解析器根据MSGID去查表,就知道怎么解析后面的Payload字节。

注意:MAVLink 2.0的MSGID是24位的,取值范围0-16777215。而MAVLink 1.0只有8位,取值范围0-255。如果你从1.0迁移到2.0,MSGID的解析逻辑必须改。

2.2.8 Payload(数据体)

Payload是消息的核心,长度由LEN字段指定。它里面存放的是具体的数据,比如姿态角、GPS坐标、电池电压等。Payload的格式由MSGID决定。

举个例子,HEARTBEAT消息的Payload固定为9字节,包含系统类型、飞控类型、系统状态等字段。而ATTITUDE消息的Payload是28字节,包含滚转角、俯仰角、偏航角等浮点数。

2.2.9 CKA / CKB(校验码)

校验尾占2个字节,CKA是低字节,CKB是高字节。它使用CRC-16-CCITT算法,对整条消息(从LEN开始到Payload结束)进行校验。

接收方收到消息后,会用同样的算法重新计算CRC,然后跟收到的CKA/CKB对比。如果不一致,说明消息在传输过程中被损坏了,直接丢弃。

我记得有一次调试时,发现地面站总是收不到飞控的响应。排查了半天,原来是飞控端的CRC计算代码里多了一个字节偏移。这种问题很难肉眼发现,但用逻辑分析仪一抓波形就露馅了。

2.3 MAVLink 1.0 vs 2.0 帧结构对比

很多老设备还在用MAVLink 1.0。我整理了一个对比表,方便你快速区分:

字段 MAVLink 1.0 MAVLink 2.0
STX 0xFE 0xFD
帧头长度 6字节 10字节
MSGID长度 1字节(0-255) 3字节(0-16777215)
兼容标志 无 2字节(INCOMPAT + COMPAT)
消息签名 不支持 可选(13字节签名)
最大Payload 255字节 255字节

说白了,MAVLink 2.0最大的改进就是支持了更多的消息ID和消息签名。如果你是从零开始做新项目,我建议直接上2.0。

2.4 帧结构全景图

下面这张图把整个帧结构串起来了。你可以把它当作一个速查表,写代码时随时参考。

MAVLink 2.0 消息帧结构全景图 帧头(10字节) STX LEN INCOMPAT COMPAT SEQ SYSID COMPID MSGID (3B) 数据体(0-255字节,由LEN指定) Payload(格式由MSGID决定) 校验尾(2字节) CKA(低字节) CKB(高字节) 说明: CRC校验范围:从LEN到Payload结束 STX本身不参与CRC计算

2.5 一个完整的解析示例

光说不练假把式。我们拿一条真实的HEARTBEAT消息来走一遍解析流程。

假设飞控发出来的原始字节流是这样的(十六进制):

FD 09 00 00 01 01 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00