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 帧结构全景图
下面这张图把整个帧结构串起来了。你可以把它当作一个速查表,写代码时随时参考。
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