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 v1 数据包结构 起始标志 0xFE 1字节 负载长度 0-255 1字节 序列号 0-255 1字节 系统ID 1-255 1字节 组件ID 1-255 1字节 消息ID 0-255 1字节 负载 n字节 0-255字节 校验和 CRC-16 2字节 数据包总长度 = 6 + n + 2 字节(n为负载长度) 最小8字节(n=0),最大263字节(n=255) 校验和计算范围(起始标志到负载末尾) 头部 负载 校验

这张图把整个数据包的布局和校验范围都标出来了。你可以看到,校验和覆盖了从起始标志到负载末尾的所有字节。这就是为什么它能检测出传输过程中的任何一位错误。

经验之谈:如果你在写MAVLink解析代码,建议先实现CRC校验。只有校验通过的数据包才值得进一步处理。这样可以过滤掉大部分噪声和错误数据。

好了,这一章的内容就到这里。数据包结构是MAVLink的基石,后面的所有内容都建立在这个基础之上。你把这个结构记熟了,后面学消息类型、通信模式都会轻松很多。

可可精品资料库