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 消息帧的完整结构图。你可以对照着看,加深理解。
你看,整个帧结构其实不复杂。从 STX 开始,到 CRC 结束。解析的时候,先找到 STX,然后按顺序读下去就行。我刚开始写解析器时,犯过一个低级错误——忘了检查 STX 的值,结果把乱码当成了有效消息。后来加了个简单的判断:如果第一个字节不是 0xFD,直接跳过,直到找到 0xFD 为止。
好了,这一章的内容就这些。你只要把帧结构、消息ID、CRC、填充字节和 HEARTBEAT 这几个概念搞清楚了,后面搭建仿真环境时就会顺手很多。