3. MAVLink 1与MAVLink 2协议差异

MAVLink 1 与 MAVLink 2 在应用层消息语义上高度兼容,但帧层差异显著。理解这些差异的关键不是背字段表,而是能在一段真实字节流中定位帧头、判断版本、解释载荷长度与 CRC 覆盖范围。本节先对比帧结构,再通过同一消息在两种版本下的字节差异分析,最后完成一次版本协商实验。

一、帧结构对比:从 8 字节帧头到 12 字节帧头

MAVLink 1 的帧头固定 6 字节,加上 1 字节起始标志 0xFE,共 7 字节引导部分;MAVLink 2 起始标志为 0xFD,帧头扩展为 9 字节,加起始标志共 10 字节。两者尾部结构相同:2 字节 CRC,MAVLink 2 在启用签名时额外追加 13 字节签名块。

字段MAVLink 1MAVLink 2说明
起始标志 STX0xFE0xFD抓包时第一判据
载荷长度 len1 字节1 字节v2 可截断尾部零
序列号 seq1 字节1 字节每发一帧递增
系统/组件 IDsysid+compidsysid+compid标识发送方
消息 ID1 字节3 字节v2 支持 24 位 ID
兼容性标志无1 字节incompat_flags
兼容性标志无1 字节compat_flags
CRC2 字节2 字节覆盖范围不同
签名无13 字节可选

MAVLink 2 的 incompat_flags 表示“不兼容标志”:若接收方不认识其中任何一位,必须丢弃该帧。目前仅定义了 0x01 表示签名有效。而 compat_flags 表示“兼容标志”:接收方即使不认识也可以忽略并继续解析,为未来扩展留出空间。这是 v2 在向前兼容设计上的核心机制。

二、载荷扩展与 CRC 计算范围

MAVLink 2 允许消息在末尾追加扩展字段(extension fields),这些字段不参与 CRC 计算,因此旧版接收方即使不认识它们,也能正确校验并解析原有字段。CRC 计算范围在两种版本中都以“消息 ID + 载荷 + CRC_EXTRA”为基础,但 v2 的 CRC_EXTRA 种子表按 24 位消息 ID 索引,且载荷在计算前会先去除尾部零截断。

CRC 计算伪代码(两版通用逻辑,差异在 ID 宽度与截断):
crc = 0xFFFF
crc = crc_accumulate(msg_id_low,  crc)
crc = crc_accumulate(msg_id_mid,  crc)   // v1 无此步
crc = crc_accumulate(msg_id_high, crc)   // v1 无此步
for b in payload_trimmed:
    crc = crc_accumulate(b, crc)
crc = crc_accumulate(crc_extra, crc)

注意:v1 的 CRC_EXTRA 种子表按 8 位消息 ID 索引,v2 按 24 位索引。同一消息(如 HEARTBEAT,ID=0)在两版中的 CRC_EXTRA 值相同,因此 CRC 差异主要来自载荷截断与 ID 字节数。

三、同一消息在 v1 与 v2 下的字节差异

下面用 HEARTBEAT(ID=0)为例,假设载荷为 9 字节:00 00 00 00 02 03 51 04 03(type=2 四旋翼、autopilot=3、base_mode=0x51、custom_mode=0x0403、system_status=3)。v1 帧共 8+9+2=19 字节,v2 帧共 12+9+2=23 字节(未签名)。

HEARTBEAT 消息在 MAVLink 1 与 MAVLink 2 下的字节布局对比 MAVLink 1(19 字节) FE 09 seq sys cmp 00 payload 9 字节(00 00 00 00 02 03 51 04 03) CRC16 帧头 6 字节 + STX 1 字节 = 7 字节引导;消息 ID 仅 1 字节 MAVLink 2(23 字节,未签名) FD 09 seq sys cmp 00 00 00 inc cmp payload 9 字节 CRC16 帧头 9 字节 + STX 1 字节 = 10 字节引导;消息 ID 3 字节;新增 inc/cmp 标志 关键差异标注 ① STX:0xFE → 0xFD,抓包第一判据 ② 消息 ID:1 字节 → 3 字节,v2 支持 24 位 ID 空间 ③ 新增 incompat_flags / compat_flags 各 1 字节,决定签名与扩展兼容性 ④ 帧总长:19 → 23 字节;启用签名后 v2 再 +13 字节

从图中可见,同一 HEARTBEAT 消息在 v2 下多出 4 字节:消息 ID 从 1 字节扩为 3 字节(+2),新增两个标志字节(+2)。若启用签名,再追加 13 字节,总长变为 36 字节。抓包时若看到 FD 开头且第 6 字节为 00 00 00,即可判定为 v2 的 HEARTBEAT。

四、消息签名机制

MAVLink 2 的签名块 13 字节由三部分组成:1 字节 link ID、6 字节时间戳(自 2015-01-01 起的 10 微秒计数)、6 字节签名值。签名值是对“密钥 + 帧头 + 载荷 + CRC + link ID + 时间戳”做 SHA-256 后取前 6 字节。接收方用相同密钥重算并比对,同时检查时间戳是否在允许窗口内,防止重放攻击。

签名不加密载荷,只保证完整性与来源可信。密钥通过 SETUP_SIGNING 消息分发,双方需预先共享同一 32 字节密钥。

五、版本协商实验

MAVLink 2 定义了 MAV_PROTOCOL_CAPABILITY_MAVLINK2 能力位,通过 AUTOPILOT_VERSION 消息的 capabilities 字段广播。协商流程如下:

  1. 地面站先以 v2 发送 HEARTBEAT,并在 compat_flags 中置位 MAV_PROTOCOL_CAPABILITY_MAVLINK2 对应标志。
  2. 飞控若支持 v2,回发 v2 帧并在 AUTOPILOT_VERSION.capabilities 中置位该能力位。
  3. 地面站收到后确认双方均支持 v2,后续通信保持 v2。
  4. 若飞控仅支持 v1,会以 v1 帧回复或丢弃 v2 帧,地面站检测到超时后降级为 v1 重试。

实验可用 mavlink-router 或 pymavlink 搭建。以下为 pymavlink 发送 v2 HEARTBEAT 并读取响应的示例:

from pymavlink import mavutil
master = mavutil.mavlink_connection('udp:127.0.0.1:14550',
                                    source_system=255,
                                    source_component=0)
master.mav.heartbeat_send(
    mavutil.mavlink.MAV_TYPE_QUADROTOR,
    mavutil.mavlink.MAV_AUTOPILOT_GENERIC,
    0, 0, 0)
msg = master.recv_match(type='HEARTBEAT', blocking=True, timeout=3)
print(msg.get_srcSystem(), msg.get_srcComponent())
print('协议版本:', master.mavlink20())

判断依据:若 master.mavlink20() 返回 True 且收到的帧以 0xFD 开头,说明协商成功;若持续收到 0xFE 帧或超时,则对端仅支持 v1,需降级。抓包时可用 Wireshark 的 MAVLink 解析器,或直接十六进制查看首字节。

降级时注意:v2 的扩展字段在 v1 中不存在,若业务依赖扩展字段,降级后需改用 v1 等效字段或禁用该功能。签名在 v1 中不可用,安全链路必须保持 v2。

六、实践检查清单

  • 抓包首字节:0xFE 为 v1,0xFD 为 v2。
  • v2 帧第 6~8 字节为 24 位消息 ID,注意小端序。
  • 检查 incompat_flags 的 0x01 位判断是否签名。
  • CRC 校验失败时,先确认 CRC_EXTRA 种子是否按正确版本索引。
  • 协商实验至少验证三种情况:双方 v2、一方 v1、签名启用。