2. 无人机通信基础

上一章我们聊了MAVLink是什么、能干什么。这一章,咱们得把它的“骨架”拆开看看。

说白了,MAVLink的消息就是一串字节流。你发出去、收回来,总得有个规矩——哪个字节是干啥的,哪个字节校验对不对。这个规矩,就是消息帧格式。

我刚开始接触MAVLink的时候,拿着协议文档看了半天,觉得不就是几个字节嘛,有啥难的?结果第一次自己拼包就出了岔子——飞控死活不认我的数据。后来才发现,是CRC校验算错了。嗯,这玩意儿看着简单,坑可不少。

2.1 消息帧格式:从字节流到结构化数据

先看一张总图,把整个消息帧的结构印在脑子里。

MAVLink 2 消息帧结构 帧头 0xFD 载荷长度 1字节 不兼容 1字节 兼容 1字节 序列号 1字节 系统ID 1字节 组件ID 1字节 消息ID 3字节 载荷 0~255字节 CRC16 2字节 签名 13字节 固定 可变 校验 说明: MAVLink 1 帧头为 0xFE,消息ID只有1字节(最多255种消息)。 MAVLink 2 帧头为 0xFD,消息ID扩展为3字节,支持更多消息类型。 签名段为可选,用于安全通信场景。

这张图你得记住。每次解析MAVLink消息,脑子里都要浮现出这个结构。

2.2 帧头与消息ID:消息的“身份证”

帧头是消息的开始标志。MAVLink 1用0xFE,MAVLink 2用0xFD。我建议你直接上MAVLink 2,别在1上浪费时间了——除非你还在维护十年前的设备。

消息ID是整条消息的灵魂。它告诉接收方:“嘿,我是一条HEARTBEAT消息”或者“我是一条GPS_RAW_INT”。

在MAVLink 2里,消息ID占3个字节,取值范围0~16777215。常用的ID范围是:

消息ID范围 用途 示例
0~255 MAVLink 1 兼容消息 HEARTBEAT (0), SYS_STATUS (1)
256~65535 MAVLink 2 扩展消息 ALTITUDE (141), VIBRATION (241)
65536~16777215 用户自定义消息 你的私有协议
我的经验:做二次开发时,自定义消息ID建议从100000开始往上走。这样既不会跟官方冲突,也方便自己管理。我曾经见过有人直接用0x01当自定义ID,结果跟HEARTBEAT撞车了——调试了整整两天才找到原因。

2.3 CRC校验:别让错误数据毁了你的无人机

CRC校验,说白了就是给消息算一个“指纹”。接收方收到消息后,重新算一遍指纹,跟发过来的比对。不一样?说明数据在传输过程中被污染了,直接扔掉。

MAVLink用的CRC16算法,多项式是0x1021。但有个坑——它不光算载荷,还要把消息ID也掺进去一起算。为什么?

你想想看,如果只算载荷,那消息ID传错了怎么办?比如你发的是HEARTBEAT(ID=0),结果接收方收到的是ID=1的SYS_STATUS,但载荷校验通过了——这不就乱套了嘛。

所以MAVLink的CRC计算规则是:

// 伪代码:MAVLink CRC计算
uint16_t crc = 0xFFFF;
// 先算载荷
for (int i = 0; i < payload_len; i++) {
    crc = crc_accumulate(payload[i], crc);
}
// 再算消息ID(MAVLink 2 需要算3字节)
crc = crc_accumulate(msg_id & 0xFF, crc);
crc = crc_accumulate((msg_id >> 8) & 0xFF, crc);
crc = crc_accumulate((msg_id >> 16) & 0xFF, crc);
// 最后异或0xFF
crc ^= 0xFFFF;
注意:MAVLink 1和MAVLink 2的CRC算法一样,但参与计算的字段不同。MAVLink 1只算载荷和1字节消息ID。如果你混用两个版本,CRC校验必挂。我曾经在项目中遇到过——地面站用MAVLink 2,飞控固件还是MAVLink 1,结果所有消息都被当成无效包丢弃了。

2.4 消息载荷与填充字节:数据怎么塞进去

载荷就是消息真正要传的数据。比如GPS坐标、姿态角、电池电压等等。每个消息类型都有固定的载荷结构,说白了就是一个C语言结构体。

举个例子,HEARTBEAT消息的载荷:

// MAVLink HEARTBEAT 载荷结构
typedef struct __mavlink_heartbeat_t {
    uint8_t  type;           // 飞行器类型(1字节)
    uint8_t  autopilot;      // 飞控类型(1字节)
    uint8_t  base_mode;      // 基础模式(1字节)
    uint32_t custom_mode;    // 自定义模式(4字节)
    uint8_t  system_status;  // 系统状态(1字节)
    uint8_t  mavlink_version;// MAVLink版本(1字节)
} mavlink_heartbeat_t;
// 总载荷长度:9字节

这里有个细节——填充字节。C语言结构体为了内存对齐,会在成员之间塞一些“空字节”。比如上面那个结构体,如果编译器按4字节对齐,custom_mode前面可能会被填充3个字节。

但MAVLink协议规定:载荷必须紧凑排列,不允许有填充字节。所以你在定义自定义消息时,一定要用__attribute__((packed))或者手动调整成员顺序,避免编译器自动填充。

避坑指南:我曾经写过一个自定义消息,里面混了uint8_t和uint32_t。在PC上测试一切正常,结果部署到STM32上就出问题了——因为ARM编译器默认4字节对齐,结构体大小跟PC上不一样。从那以后,我所有MAVLink消息结构体都加上了__attribute__((packed))。

2.5 完整解析流程:从字节到消息

好了,理论说完了。咱们来走一遍完整的解析流程。假设你从串口收到了一串字节:

// 收到的原始字节(十六进制)
FD 09 00 00 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 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00