2、MAVLink消息结构:消息帧格式详解、消息ID与CRC校验、消息载荷与填充字节、心跳包(HEARTBEAT)深度解析

好,咱们进入正题。MAVLink 说白了就是无人机和地面站之间的“黑话”协议。你得先懂它的消息长什么样,才能跟飞控对上话。我刚开始调 MAVLink 的时候,对着二进制流看了半天,愣是没看出门道。后来把帧结构拆开一看,其实就那么几块东西。

2.1 消息帧格式详解

MAVLink 的消息帧,就像快递包裹。有包裹皮(帧头)、有收件人信息(目标系统/组件)、有物品清单(消息ID)、有填充物(填充字节)、还有防拆封条(CRC校验)。

MAVLink 有两个主流版本:v1 和 v2。v2 是 v1 的升级版,主要解决了 v1 的一些局限性。咱们先看 v2 的帧格式,因为它更常用。

字节偏移 字段名 长度(字节) 说明
0 STX (起始标志) 1 v2 固定为 0xFD
1 LEN (载荷长度) 1 消息载荷的字节数,0-255
2 INC_FLAGS (不兼容标志) 1 接收方必须理解的标志位
3 CMP_FLAGS (兼容标志) 1 接收方可忽略的标志位
4 SEQ (序列号) 1 每个组件的发送序列号,用于丢包检测
5 SYS_ID (系统ID) 1 发送方的系统编号,比如 1 号飞控
6 COMP_ID (组件ID) 1 发送方的组件编号,比如 1 号飞控里的 IMU
7-9 MSG_ID (消息ID) 3 v2 用 3 字节,支持 16777216 种消息
10..(10+LEN-1) PAYLOAD (消息载荷) LEN 消息的具体数据
10+LEN CRC (校验码) 2 对整个消息的 CRC-16/MCRF4XX 校验

嗯,这里要注意。v1 的帧头只有 6 个字节,MSG_ID 只有 1 个字节,最多支持 256 种消息。v2 把 MSG_ID 扩展到了 3 字节,还加了 INC_FLAGS 和 CMP_FLAGS 两个标志位。我个人习惯直接用 v2,因为兼容性更好,而且能应对未来新增的消息类型。

关键点:MAVLink v2 的帧头固定为 10 字节(不含载荷和 CRC)。解析时,先读到 STX=0xFD,再读 LEN,然后根据 LEN 确定后续字节数。千万别把 v1 的 0xFE 和 v2 的 0xFD 搞混了,我刚开始就踩过这个坑。

2.2 消息ID与CRC校验

消息ID 是 MAVLink 的灵魂。每个消息类型都有一个唯一的 ID。比如 HEARTBEAT 是 0,SYS_STATUS 是 1,GPS_RAW_INT 是 24。你想想看,地面站收到一串二进制流,怎么知道该用哪个结构体去解析?就是靠这个 MSG_ID。

CRC 校验呢,说白了就是给消息加个“指纹”。MAVLink 用的是 CRC-16/MCRF4XX 算法,多项式是 0x1021。但有个细节——CRC 计算时,会把整个消息(从 LEN 开始到载荷结束)都算进去,但 不包括 STX。而且,每个消息类型还有一个额外的 CRC 种子(CRC_EXTRA),这个种子是消息定义里固定写死的。

为什么会这样?为了防止消息定义不一致。比如地面站和飞控用的 MAVLink 版本不同,同一个消息 ID 的字段定义可能不一样。加上 CRC_EXTRA 后,如果两边定义不匹配,CRC 校验就会失败,直接丢弃。我在一次联调中就遇到过,飞控固件升级后,地面站没更新,结果 HEARTBEAT 都收不到,查了半天才发现是 CRC_EXTRA 对不上。

避坑指南:我曾经在自定义消息时,忘了更新 CRC_EXTRA 的生成表。结果地面站一直报 CRC 错误。后来我写了个小脚本,每次修改消息定义后自动重新生成 CRC_EXTRA 表,再也没出过问题。建议你也这么做。

2.3 消息载荷与填充字节

消息载荷就是消息里真正有用的数据。比如 HEARTBEAT 的载荷里包含了飞控类型、飞控状态、MAVLink 版本号等。载荷的长度由 LEN 字段指定,最大 255 字节。

但这里有个小坑——填充字节。MAVLink v2 要求载荷长度必须是 4 字节对齐的。如果载荷本身不是 4 的倍数,就需要在载荷末尾补 0,直到长度能被 4 整除。这些填充字节不参与 CRC 计算,但会占用传输带宽。

举个例子:HEARTBEAT 的载荷是 9 字节,9 不是 4 的倍数,所以实际传输时会在后面补 3 个 0x00,变成 12 字节。接收方收到后,根据 LEN 字段(9)只取前 9 个字节,后面的填充字节直接扔掉。

注意:填充字节只在 v2 中存在,v1 没有这个要求。如果你在 v2 中解析消息时,发现载荷长度和实际数据对不上,先检查一下是不是填充字节在作怪。我曾经在写解析器时忘了跳过填充字节,结果解析出来的数据全是乱的。

2.4 心跳包(HEARTBEAT)深度解析

HEARTBEAT 是 MAVLink 里最重要的消息,没有之一。它是飞控和地面站之间的“心跳”,告诉对方“我还活着”。如果地面站连续几秒没收到 HEARTBEAT,就会认为飞控掉线了。

HEARTBEAT 的消息 ID 是 0,载荷固定为 9 字节。咱们来看看它的结构:

字段名 类型 长度(字节) 说明
type uint8_t 1 飞控类型,如 MAV_TYPE_QUADROTOR (2)
autopilot uint8_t 1 飞控固件类型,如 MAV_AUTOPILOT_PX4 (12)
base_mode uint8_t 1 系统基础模式,如 MAV_MODE_FLAG_MANUAL (0x01)
custom_mode uint32_t 4 自定义模式,由飞控固件定义
system_status uint8_t 1 系统状态,如 MAV_STATE_STANDBY (3)
mavlink_version uint8_t 1 MAVLink 协议版本,固定为 3

你看,这 9 个字节里包含了飞控的“身份信息”和“当前状态”。base_mode 和 custom_mode 尤其重要,它们决定了飞控当前在什么模式下运行——是手动模式、定高模式、还是自主飞行模式。

我举个例子。有一次我在调试无人机,地面站显示“已连接”,但无人机就是不动。我一看 HEARTBEAT 的 base_mode,发现是 0x01(手动模式),但摇杆没校准。说白了,飞控认为自己在手动模式,但摇杆信号是乱的,所以它不执行任何动作。后来校准了摇杆,base_mode 变成了 0x81(手动模式+已解锁),问题就解决了。

实战要点:HEARTBEAT 的发送频率通常是 1Hz。如果你在仿真环境中收不到 HEARTBEAT,先检查串口波特率、端口号、以及 MAVLink 版本是否匹配。我建议你在调试时,先用 MAVLink 的调试工具(如 mavlink-router 或 MAVSDK)抓一下原始数据,看看帧头是不是 0xFD,MSG_ID 是不是 0。

下面这张图,是我画的一个 MAVLink v2 消息帧的完整结构图。你可以对照着看,加深理解。

MAVLink v2 消息帧结构 STX 0xFD LEN 载荷长度 INC_FLG 不兼容标志 CMP_FLG 兼容标志 SEQ 序列号 SYS_ID 系统ID COMP_ID 组件ID MSG_ID 3字节 PAYLOAD LEN字节 填充 补0对齐 CRC 2字节 帧头(10字节) 载荷 + 填充 校验 CRC计算范围:从LEN开始到载荷结束(不含STX,不含填充字节)

你看,整个帧结构其实不复杂。从 STX 开始,到 CRC 结束。解析的时候,先找到 STX,然后按顺序读下去就行。我刚开始写解析器时,犯过一个低级错误——忘了检查 STX 的值,结果把乱码当成了有效消息。后来加了个简单的判断:如果第一个字节不是 0xFD,直接跳过,直到找到 0xFD 为止。

好了,这一章的内容就这些。你只要把帧结构、消息ID、CRC、填充字节和 HEARTBEAT 这几个概念搞清楚了,后面搭建仿真环境时就会顺手很多。