2. MAVLink协议基础:版本、消息结构与心跳包

MAVLink这玩意儿,说白了就是无人机和地面站之间的“普通话”。没有它,飞控再牛也跟地面站聊不上。我最早接触MAVLink是在一个开源飞控项目里,当时被一堆十六进制报文搞得头大,后来摸清了门道,才发现它设计得其实挺精巧。

这一章咱们就把MAVLink的底裤扒开看看——版本差异、消息怎么组装的、CRC校验怎么算、以及那个“我还在,别慌”的心跳包机制。

2.1 MAVLink v1.0 vs v2.0:到底差在哪?

MAVLink目前有两个主流版本:v1.0和v2.0。v1.0是2009年推出的老将,v2.0是2017年升级的。你可能会问:“我该用哪个?”

我的建议是:新项目直接上v2.0。除非你是在维护十年前的老设备。

特性 MAVLink v1.0 MAVLink v2.0
消息ID范围 0~255(单字节) 0~16777215(三字节)
有效载荷长度 0~255字节 0~255字节
目标系统/组件 仅系统ID 系统ID + 组件ID
签名支持 无 可选(防止篡改)
兼容性标志 无 有(方便扩展)
最小帧头长度 6字节 10字节

核心差异一句话:v2.0把消息ID从1字节扩到3字节,还加了目标寻址和签名。说白了,v1.0能定义的报文类型只有256种,v2.0直接干到1600多万种——未来十年都够用。

我记得有一次调试一个多机协同项目,v1.0的消息ID不够用,不得不自己魔改协议。后来换了v2.0,ID空间大了,再也不用抠抠搜搜地分配ID了。

2.2 消息结构:报文长什么样?

MAVLink的报文结构,其实就是一个精心设计的二进制帧。咱们先看v2.0的完整帧格式:

起始标志(1B) | 有效载荷长度(1B) | 不兼容标志(1B) | 兼容标志(1B) |
序列号(1B) | 系统ID(1B) | 组件ID(1B) | 消息ID(3B) |
有效载荷(0~255B) | CRC校验(2B) | 签名(可选,13B)

嗯,这里要注意:v1.0没有不兼容标志、兼容标志、组件ID和签名,帧头只有6字节。

咱们拆开来看几个关键字段:

  • 起始标志:v1.0是0xFE,v2.0是0xFD。接收端靠这个字节判断版本。
  • 有效载荷长度:告诉你后面跟了多少数据字节。
  • 不兼容标志:如果接收端不认识这个标志位,必须丢弃报文。v2.0新增的。
  • 兼容标志:不认识可以忽略,继续处理。这个设计很聪明——向前兼容。
  • 消息ID:v2.0用3字节,小端序。比如HEARTBEAT的ID是0,SYS_STATUS是1。

我的小技巧:调试时用Wireshark抓MAVLink包,直接看十六进制。先找FD或FE定位帧头,然后数长度字段,就能快速解析。我曾经靠这个在野外现场半小时定位了一个通信丢包问题。

2.3 消息ID:报文的“身份证”

消息ID决定了这条报文是心跳、是GPS数据、还是遥控指令。每个消息ID对应一个固定的数据结构。

举个例子,HEARTBEAT(ID=0)的结构是:

typedef struct {
    uint8_t type;           // 飞控类型(如MAV_TYPE_QUADROTOR)
    uint8_t autopilot;      // 自驾仪类型(如MAV_AUTOPILOT_PX4)
    uint8_t base_mode;      // 系统状态(如是否已解锁)
    uint32_t custom_mode;   // 自定义模式(如PX4的飞行模式)
    uint8_t system_status;  // 系统状态(如STANDBY、ACTIVE)
} mavlink_heartbeat_t;

你想想看,地面站收到ID=0的报文,就知道该按这个结构去解析后面的字节。如果ID对不上,解析出来的数据全是乱的。

MAVLink官方维护了一个庞大的消息ID表,从0到几百。常用的有:

  • 0:HEARTBEAT(心跳)
  • 1:SYS_STATUS(系统状态)
  • 24:GPS_RAW_INT(GPS原始数据)
  • 30:ATTITUDE(姿态)
  • 33:GLOBAL_POSITION_INT(全球位置)

避坑指南:我曾经在自定义消息时,不小心把消息ID设成了跟标准ID冲突的值。结果地面站把自定义数据当成了姿态数据,画出来的飞机姿态在天上乱转。后来排查了半天才发现是ID冲突。所以,自定义消息一定要用MAVLINK_MSG_ID_EXTENDED范围(256~511)。

2.4 CRC校验:数据完整性守护神

通信链路难免有干扰,CRC校验就是用来检测数据有没有被篡改或损坏的。MAVLink用的是16位CRC,具体算法是CRC-16-IBM(多项式0x8005)。

校验范围包括:

  • 有效载荷(Payload)
  • 消息ID(v1.0是1字节,v2.0是3字节)
  • 额外CRC种子(每个消息ID对应一个固定值,防止误解析)

这个“额外CRC种子”是MAVLink的一个巧妙设计。每个消息类型都有一个预定义的CRC种子,比如HEARTBEAT的种子是0x50。这样即使两个不同消息的载荷一样,CRC值也不同,接收端不会搞混。

计算流程大致如下:

  1. 初始化CRC寄存器为0xFFFF
  2. 对每个字节(载荷 + 消息ID + 额外种子)进行CRC计算
  3. 最终CRC值取反,得到2字节校验码

实际经验:我在PX4上遇到过CRC校验失败导致丢包的问题。排查后发现是波特率设置太高(921600),信号质量不好,偶尔会跳变。降到57600后,CRC错误率从5%降到了0.1%以下。所以,CRC校验失败率超过1%时,先检查物理层。

2.5 心跳包机制:系统还活着吗?

心跳包(HEARTBEAT)是MAVLink里最重要的消息,没有之一。它每隔一定时间(通常是1Hz)发送一次,告诉对方“我还活着”。

心跳包的核心作用有三个:

  • 发现设备:地面站通过心跳包发现飞控的存在,获取系统ID和组件ID。
  • 状态监控:通过base_mode和system_status字段,实时了解飞控是否解锁、是否在飞行、有没有故障。
  • 超时判断:如果连续N秒没收到心跳包,地面站就认为飞控失联了,触发紧急处理。

在PX4中,心跳包的发送逻辑是这样的:

// PX4中心跳包发送频率:1Hz
// 在mavlink_main.cpp中
if (now - last_heartbeat_sent > 1_s) {
    mavlink_msg_heartbeat_send(
        channel,
        MAV_TYPE_QUADROTOR,    // 飞行器类型
        MAV_AUTOPILOT_PX4,     // 自驾仪类型
        MAV_MODE_FLAG_CUSTOM_MODE_ENABLED, // base_mode
        0,                     // custom_mode(实际会传当前飞行模式)
        MAV_STATE_ACTIVE       // 系统状态
    );
    last_heartbeat_sent = now;
}

地面站收到心跳包后,会更新设备列表。如果超过10秒没收到某个设备的心跳,就会标记为“失联”。

我的调试习惯:在开发阶段,我会在串口助手上每秒打印一次心跳包的内容。如果看到system_status从MAV_STATE_ACTIVE变成了MAV_STATE_CRITICAL,说明飞控检测到了严重故障。有一次就是靠这个提前发现了GPS天线松动的问题。

2.6 知识体系总览

下面这张图把本章的核心知识点串起来了:

MAVLink协议基础知识体系 MAVLink协议基础核心知识体系 MAVLink协议 v1.0 vs v2.0 消息结构 消息ID CRC校验 心跳包机制 ID范围不同 签名支持 帧头+载荷+CRC 起始标志0xFD 0~16777215 标准ID表 CRC-16-IBM 额外种子防误判 1Hz固定频率 超时失联判断 核心要点 新项目选v2.0 | 消息ID是解析关键 | CRC校验不可省 心跳包是系统“生命线”

这张图把五个核心知识点串在了一起。你可以看到,版本选择决定了消息结构,消息结构里包含了消息ID和CRC校验,而心跳包则是整个通信系统的基础保障。

嗯,这一章的内容就到这儿。MAVLink的协议基础是后面所有章节的基石——消息编解码、通信模式、参数同步,全都建立在这些概念之上。搞懂了这些,后面就顺了。