2、MAVLink数据包结构深度拆解
各位同学,今天我们来聊聊MAVLink协议最核心的部分——数据包结构。说实话,我刚开始接触这个协议时,也被那一串十六进制数搞得头晕。但后来我发现,只要把每个字节的位置和含义搞清楚,这东西其实挺直观的。
MAVLink的数据包,说白了就是一组固定格式的二进制数据。每个包都像是一个信封,里面装着你要发送的消息。这个信封的格式是严格定义的,收发双方都得按这个规矩来。
2.1 数据包字节布局
先看整体结构。一个标准的MAVLink v1数据包,总共包含以下字段:
| 偏移 | 字段名 | 长度(字节) | 说明 |
|---|---|---|---|
| 0 | 起始标志 | 1 | 固定值 0xFE |
| 1 | 负载长度 | 1 | 有效负载的字节数 |
| 2 | 序列号 | 1 | 0-255,循环递增 |
| 3 | 系统ID | 1 | 标识发送方系统 |
| 4 | 组件ID | 1 | 标识系统内组件 |
| 5 | 消息ID | 1 | 标识消息类型 |
| 6 到 6+n-1 | 负载 | n | 实际消息数据 |
| 6+n | 校验和(低字节) | 1 | CRC-16 低8位 |
| 6+n+1 | 校验和(高字节) | 1 | CRC-16 高8位 |
嗯,这里要注意:MAVLink v2版本把起始标志改成了0xFD,消息ID也扩展到了3个字节。但v1版本目前仍然广泛使用,尤其是Pixhawk等飞控的默认配置。我个人习惯先从v1讲起,因为它的结构更简单,容易理解。
核心要点:整个数据包的最小长度是8字节(负载长度为0时),最大长度是263字节(负载长度为255时)。这个范围覆盖了绝大多数无人机通信场景。
2.2 各字段作用与取值范围
咱们逐个字段拆开看。每个字段都有它的设计意图,不是随便定的。
起始标志(STX)
固定值0xFE。它的作用就是告诉接收端:「嘿,一个新的数据包开始了」。我在项目中遇到过一个问题:有时候串口数据流里会出现乱码,接收端如果没找到这个起始标志,就会一直丢数据直到下一个有效包。所以这个字段也是数据同步的关键。
负载长度(LEN)
取值范围0到255。它表示后面跟着的负载数据有多少个字节。注意,这个长度不包括校验和。我曾经见过有人把校验和也算进去,结果解析出来的数据全是错的。避坑指南:长度字段只算负载,不算头也不算是。
序列号(SEQ)
0到255,循环递增。每发送一个数据包,序列号就加1。到了255之后,下一个包又从0开始。这个字段用来检测丢包和乱序。你想想看,如果接收端发现序列号跳了,那就说明中间有包丢了。我在调试数传链路时,经常靠这个字段来判断链路质量。
系统ID(SYSID)
取值范围1到255。每个无人机系统(比如一架飞机、一个地面站)都有一个唯一的系统ID。0是保留值,一般不用。我建议把地面站设为1,第一架飞机设为2,第二架设为3,这样管理起来清晰。
组件ID(COMPID)
取值范围1到255。同一个系统里可能有多个组件,比如飞控、GPS模块、摄像头等。组件ID就是用来区分它们的。常用的组件ID有:
- 1:自动驾驶仪(飞控)
- 2:自动舵
- 3:相机
- 4:伺服机构
- 5-100:其他组件
消息ID(MSGID)
取值范围0到255。每个消息ID对应一种特定的消息类型。比如:
- 0:心跳包(HEARTBEAT)
- 1:系统状态(SYS_STATUS)
- 24:GPS原始数据(GPS_RAW_INT)
- 30:姿态数据(ATTITUDE)
接收端拿到消息ID后,就知道该用哪种数据结构来解析后面的负载了。
负载(Payload)
长度可变,最大255字节。这里装的就是实际的消息数据。比如心跳包只有9个字节,而一些复杂的消息可能占用几十个字节。负载的格式由消息ID决定,每个消息类型都有自己固定的字段排列。
校验和(CKA / CKB)
两个字节,CRC-16-CCITT校验结果。低字节在前,高字节在后。这个我们下面单独讲。
小技巧:在调试时,你可以用Wireshark抓包,然后对照这个表格逐字节解析。我当年就是这么学会的,效果很好。
2.3 校验和算法详解(CRC-16-CCITT)
校验和是数据包的「防伪标签」。它用来检测数据在传输过程中有没有被篡改或损坏。MAVLink用的是CRC-16-CCITT算法,生成多项式是0x1021。
说白了,CRC算法就是把整个数据包(从起始标志到负载末尾)当作一个巨大的二进制数,然后用一个固定的多项式去除它,得到的余数就是校验和。接收端用同样的方法计算一遍,如果结果和发送端的一致,就说明数据是完整的。
具体实现是这样的:
uint16_t mavlink_crc_calculate(const uint8_t *buffer, uint16_t length)
{
uint16_t crc = 0xFFFF; // 初始值
for (uint16_t i = 0; i < length; i++)
{
crc ^= buffer[i]; // 异或当前字节
for (uint8_t j = 0; j < 8; j++)
{
if (crc & 0x0001) // 检查最低位
{
crc >>= 1;
crc ^= 0x8408; // 多项式反转
}
else
{
crc >>= 1;
}
}
}
return crc;
}
这段代码里有个细节:多项式0x8408是0x1021的反转形式。为什么?因为硬件实现时,有些处理器是低位在前,有些是高位在前。MAVLink选择了低位在前的实现方式,所以多项式也要反转。
注意:校验和的计算范围包括起始标志、负载长度、序列号、系统ID、组件ID、消息ID和负载。但不包括校验和本身。另外,MAVLink v2版本在计算校验和时还会额外加上消息的CRC_EXTRA字节,这个我们后面再讲。
我在实际项目中遇到过一个问题:明明数据包看起来没问题,但接收端就是报校验和错误。后来发现是发送端和接收端用的CRC初始值不一样。一个用了0xFFFF,另一个用了0x0000。所以,一定要确认两边的算法参数完全一致。
2.4 数据包结构总览
为了让你更直观地理解整个数据包的结构,我画了一张图:
这张图把整个数据包的布局和校验范围都标出来了。你可以看到,校验和覆盖了从起始标志到负载末尾的所有字节。这就是为什么它能检测出传输过程中的任何一位错误。
经验之谈:如果你在写MAVLink解析代码,建议先实现CRC校验。只有校验通过的数据包才值得进一步处理。这样可以过滤掉大部分噪声和错误数据。
好了,这一章的内容就到这里。数据包结构是MAVLink的基石,后面的所有内容都建立在这个基础之上。你把这个结构记熟了,后面学消息类型、通信模式都会轻松很多。