消息帧结构详解:起始标志、负载长度、序列号、系统ID、组件ID、消息ID、负载、校验和
各位同学,咱们今天来聊聊 MAVLink 消息帧的骨头架子。说白了,就是数据在线上跑的时候,到底长什么样。
我记得刚接触 MAVLink 那会儿,看着一堆十六进制数直发懵。后来拆开一条一条看,才发现这结构设计得挺讲究——每个字节都有它的使命。
一、帧结构总览
MAVLink 有两种版本:v1 和 v2。咱们主要讲 v2,因为现在主流飞控基本都用它了。v2 的帧结构是这样的:
| 字节偏移 | 字段名 | 长度(字节) | 说明 |
|---|---|---|---|
| 0 | 起始标志 | 1 | 固定 0xFD(v2) |
| 1 | 负载长度 | 1 | 有效负载的字节数 |
| 2 | 序列号 | 1 | 递增计数,防丢包 |
| 3 | 系统 ID | 1 | 标识发送设备 |
| 4 | 组件 ID | 1 | 标识设备内的模块 |
| 5 | 消息 ID | 3 | v2 扩展到 24 位 |
| 8 ~ 8+N-1 | 负载 | N | 实际数据内容 |
| 8+N ~ 8+N+1 | 校验和 | 2 | CRC-16 |
关键点:v2 的消息 ID 从 v1 的 1 字节扩展到了 3 字节,这意味着可以定义 1600 万种消息类型。实际用不了那么多,但给未来留足了空间。
二、起始标志——帧的身份证
起始标志就一个字节,v1 是 0xFE,v2 是 0xFD。为什么选这两个值?
我个人的理解是:它们跟常见的 ASCII 字符不冲突,不容易被误判。你想想看,串口上如果传的是纯文本,0xFD 这种值几乎不会出现,所以接收端看到它就知道“嘿,新消息来了”。
小技巧:调试的时候,如果发现解析器一直找不到帧头,先检查波特率对不对。我曾经在 115200 的链路上误配成 57600,结果收到的全是乱码,起始标志根本对不上。
三、负载长度——告诉你要读多少
这个字段很直白:它告诉你后面跟着的有效负载有多少字节。范围是 0 到 255。
但有个坑:v2 里如果负载长度是 0,表示这是一个“空消息”,只有头部没有数据。有些心跳包就用这个来节省带宽。
实际项目中,我见过有人把负载长度写死了,结果消息体变了长度就出问题。嗯,这里要注意:负载长度必须跟消息定义严格对应,不能偷懒。
四、序列号——防丢包的第一道防线
序列号是一个递增的计数器,每发一条消息就加 1。从 0 开始,到 255 后回绕到 0。
为什么需要它?
无线链路不稳定,丢包是家常便饭。接收端通过检查序列号是否连续,就能判断中间有没有丢消息。如果发现跳号了,可以请求重传,或者至少知道数据不完整。
注意:序列号是每个系统独立维护的,不是全局统一的。也就是说,飞控的序列号和地面站的序列号各算各的,别搞混了。
五、系统 ID 与组件 ID——谁发的?哪个模块发的?
这两个字段一起用,就能唯一确定消息的来源。
- 系统 ID:标识一个完整的设备,比如飞控是 1,地面站是 255。
- 组件 ID:标识设备内部的子模块,比如飞控里的 GPS 模块是 220,IMU 是 81。
举个例子:系统 ID=1,组件 ID=220,表示这是“飞控上的 GPS 模块”发来的消息。
我记得有一次调试多机协同,三台飞控同时发数据,地面站全乱了。后来发现是系统 ID 没配好,三台都用的默认值 1。改完之后,数据就分得清清楚楚了。
六、消息 ID——这条消息是干啥的?
消息 ID 是 MAVLink 里最重要的字段之一。它告诉接收端:这条消息是什么类型。
v1 只有 1 字节,最多 256 种消息。v2 扩展到 3 字节,理论上支持 16,777,216 种。常用的消息 ID 有:
| 消息名 | ID(十进制) | 说明 |
|---|---|---|
| HEARTBEAT | 0 | 心跳包,最基本的保活消息 |
| SYS_STATUS | 1 | 系统状态,电池、CPU 负载等 |
| GPS_RAW_INT | 24 | GPS 原始数据(整数格式) |
| ATTITUDE | 30 | 姿态数据(滚转、俯仰、偏航) |
| LOCAL_POSITION_NED | 32 | 局部位置(北东地坐标系) |
避坑指南:我曾经在自定义消息时,随手选了一个 ID,结果跟官方消息冲突了。接收端解析出来的数据完全不对。后来我学乖了:自定义消息一定要用 MAVLink 协议里预留的 ID 范围(比如 256~511),别跟官方抢。
七、负载——真正的数据
负载就是消息要传递的实际内容。长度由前面的“负载长度”字段指定,最大 255 字节。
负载的格式由消息 ID 决定。比如 HEARTBEAT 消息的负载只有几个字节,包含飞控类型、状态、模式等。而 GPS_RAW_INT 的负载就长一些,包含经纬度、海拔、速度等。
解析负载的时候,必须严格按照消息定义来。字节顺序、数据类型、对齐方式,一个都不能错。
个人经验:写解析代码的时候,别用手动移位拼接。用结构体或者 protobuf 那样的序列化工具,既快又不容易出错。我早期用手动解析,一个符号写反了,定位花了两天。
八、校验和——数据完整性的守护神
校验和是帧的最后两个字节,用的是 CRC-16 算法。它覆盖的范围包括:
- 负载长度
- 序列号
- 系统 ID
- 组件 ID
- 消息 ID
- 负载
起始标志不参与校验。为什么?因为接收端先靠起始标志找到帧头,然后才开始校验。如果起始标志也参与校验,那就成了“先有鸡还是先有蛋”的问题了。
校验算法是 MAVLink 协议里定义好的,不是标准的 CRC-16-CCITT,而是带了一个额外的密钥字节。这个密钥跟消息 ID 有关,目的是防止恶意篡改。
重要提醒:校验和算不对,接收端会直接丢弃整条消息。如果你发现地面站收不到数据,先检查 CRC 计算对不对。我见过最离谱的 bug 是有人把 CRC 的初始值设成了 0xFFFF,而协议要求是 0x0000。
九、整体结构图
下面这张图把整个帧结构串起来了,方便你理解数据在缓冲区里是怎么排列的:
十、实际抓包示例
咱们来看一条真实的心跳包数据(十六进制):
FD 09 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