2. MAVLink协议基础:消息格式、CRC校验、消息ID与消息类型

聊SITL仿真,绕不开MAVLink。说白了,MAVLink就是无人机世界里的一种“通用语言”。飞控、地面站、机载计算机之间怎么沟通?全靠它。我最早接触MAVLink是在一个Pixhawk项目上,当时被一堆十六进制报文搞得头大。后来摸清了它的消息格式和校验机制,才发现这东西设计得相当精巧。

2.1 消息帧结构:每一字节都有讲究

MAVLink 2.0是目前的主流版本。它的消息帧结构,我习惯叫它“信封”。每一帧都包含固定的头部、可选的扩展区、以及核心的有效载荷。你想想看,如果报文格式不统一,飞控和地面站根本没法对话。

下面这张图,是我自己总结的MAVLink 2.0帧结构。每次给团队新人培训,我都会先画这个。

MAVLink 2.0 消息帧结构 STX 0xFD LEN 1字节 INC 1字节 COMP 1字节 SEQ 1字节 SYS 1字节 COMP 1字节 MSG ID 3字节 PAYLOAD 0-255字节 CRC 2字节 SIGNATURE 13字节(可选) 💡 关键字段说明 STX: 帧起始标志 | LEN: 载荷长度 | INC/COMP: 兼容性标志 | SEQ: 序列号(防丢包) SYS/COMP: 源地址标识 | MSG ID: 消息类型标识 | CRC: 循环冗余校验

每个字段都有它的使命。我挑几个重点说说:

  • STX (0xFD):帧起始标志。接收端看到这个字节,就知道新报文来了。
  • LEN:载荷长度。告诉你后面跟着多少字节的有效数据。
  • SEQ:序列号。从0开始递增,接收端靠它判断有没有丢包。我在调试数传链路时,经常盯着这个字段看。
  • SYS / COMP:系统ID和组件ID。比如飞控是系统1,GPS是组件220。这样地面站就知道数据从哪来的。
  • MSG ID:消息ID。这是核心中的核心,后面会细讲。
  • CRC:校验和。保证数据在传输过程中没被篡改。

💡 我的习惯:在SITL仿真中,我经常用 mavlink inspect 工具实时抓取报文。看到SEQ字段连续递增,说明链路稳定;如果出现跳号,那就要检查是不是有丢包了。

2.2 CRC校验:数据完整性的守门员

CRC校验,说白了就是给报文算一个“指纹”。发送端算一遍,接收端再算一遍。如果两个指纹对不上,说明数据在传输过程中被破坏了。我遇到过最典型的情况:数传线缆屏蔽没做好,CRC频繁报错,飞控直接丢弃报文。

MAVLink 2.0的CRC计算,有个细节很多人会忽略——它把整个报文(包括头部和载荷)都纳入了校验范围。而且,每个消息类型都有一个固定的CRC种子(CRC_EXTRA)。这个种子是协议定义好的,用来防止消息类型误判。

举个例子,HEARTBEAT 消息的CRC_EXTRA是 0x50。计算时,先把载荷算一遍,再把CRC_EXTRA加进去再算一遍。这样即使载荷内容一样,不同消息类型的CRC结果也不同。

🔑 核心要点:CRC校验失败时,接收端会直接丢弃该报文。在SITL仿真中,如果你发现某些消息收不到,先检查CRC计算逻辑对不对。我曾经花了一下午排查一个bug,最后发现是CRC_EXTRA表用错了版本。

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

消息ID(MSG ID)是MAVLink协议里最重要的字段。它用3个字节表示,取值范围从0到16777215。每个消息ID对应一种特定的消息类型。比如:

消息ID 消息名称 用途 载荷长度
0 HEARTBEAT 心跳包,表示设备在线 9字节
1 SYS_STATUS 系统状态(电池、传感器等) 28字节
24 GPS_RAW_INT GPS原始数据(经纬度、海拔) 30字节
30 ATTITUDE 姿态数据(滚转、俯仰、偏航) 28字节
74 VFR_HUD 飞行仪表数据(空速、地速、高度) 20字节

接收端解析报文时,先读MSG ID,然后根据ID去查表,找到对应的消息结构体,再按结构体定义去解析载荷。这个过程,我习惯叫它“按图索骥”。

⚠️ 避坑指南:我曾经在自定义消息时,不小心把MSG ID设成了和标准消息冲突的值。结果地面站把自定义消息当成了标准消息来解析,数据全乱了。所以,自定义消息一定要用 MAVLINK_COMMAND_0 到 MAVLINK_COMMAND_3 范围内的ID(255-258),或者使用用户扩展区。

2.4 消息类型分类:从心跳到任务

MAVLink的消息类型,按功能可以分成几大类。我整理了一个表格,方便你快速对照:

分类 典型消息 说明
心跳与状态 HEARTBEAT, SYS_STATUS 设备在线检测、系统健康状态
导航数据 GPS_RAW_INT, ATTITUDE, VFR_HUD 位置、姿态、速度等核心飞行数据
遥控与指令 RC_CHANNELS, COMMAND_INT 遥控器通道值、任务指令
任务管理 MISSION_ITEM, MISSION_REQUEST 航点上传、下载、执行
参数管理 PARAM_VALUE, PARAM_REQUEST_LIST 飞控参数读写
日志与调试 LOG_DATA, DEBUG_VECT 日志下载、调试数据输出

在SITL仿真中,最常用的就是心跳和导航数据。你启动仿真后,飞控会以1Hz的频率发送HEARTBEAT,以10-50Hz的频率发送ATTITUDE和GPS数据。地面站收到这些消息,才能实时显示无人机状态。

2.5 实战:在Python中解析一条MAVLink消息

光说不练假把式。下面我用Python演示一下,如何手动解析一条MAVLink消息。这段代码我在SITL仿真调试中经常用:

import struct

# 模拟一条HEARTBEAT消息的原始字节(MAVLink 2.0)
raw_bytes = bytes([
    0xFD,       # STX
    0x09,       # LEN = 9字节载荷
    0x00,       # INC
    0x00,       # COMP
    0x01,       # SEQ
    0x01,       # SYS
    0x01,       # COMP
    0x00, 0x00, 0x00,  # MSG ID = 0 (HEARTBEAT)
    # 载荷开始
    0x01,       # type
    0x00,       # autopilot
    0x04,       # base_mode
    0x00,       # custom_mode (低字节)
    0x00,       # custom_mode (高字节)
    0x00,       # system_status
    0x00, 0x00, 0x00,  # mavlink_version (填充)
    # CRC (2字节)
    0x50, 0x03
])

# 解析头部
stx = raw_bytes[0]
length = raw_bytes[1]
msg_id = struct.unpack('<I', raw_bytes[6:9] + b'\x00')[0]  # 3字节小端

print(f"帧起始: 0x{stx:02X}")
print(f"载荷长度: {length}")
print(f"消息ID: {msg_id}")

# 根据消息ID解析载荷
if msg_id == 0:  # HEARTBEAT
    payload = raw_bytes[10:10+length]
    heartbeat = struct.unpack('<BBBBBB', payload)
    print(f"HEARTBEAT: type={heartbeat[0]}, autopilot={heartbeat[1]}, "
          f"base_mode={heartbeat[2]}, status={heartbeat[5]}")

这段代码虽然简单,但核心逻辑都在了:读STX -> 读LEN -> 读MSG ID -> 根据ID解析载荷。实际项目中,我会用pymavlink库,它已经封装好了这些细节。但理解底层原理,能帮你更快定位问题。

💡 我的建议:在SITL仿真中,你可以用 mavlink-router 或 mavproxy 的 --dump 参数,把原始报文打印出来。然后对照上面的代码手动解析一遍,对理解协议非常有帮助。

2.6 小结