2. MAVLink协议基础:消息格式、CRC校验、消息ID与消息类型
聊SITL仿真,绕不开MAVLink。说白了,MAVLink就是无人机世界里的一种“通用语言”。飞控、地面站、机载计算机之间怎么沟通?全靠它。我最早接触MAVLink是在一个Pixhawk项目上,当时被一堆十六进制报文搞得头大。后来摸清了它的消息格式和校验机制,才发现这东西设计得相当精巧。
2.1 消息帧结构:每一字节都有讲究
MAVLink 2.0是目前的主流版本。它的消息帧结构,我习惯叫它“信封”。每一帧都包含固定的头部、可选的扩展区、以及核心的有效载荷。你想想看,如果报文格式不统一,飞控和地面站根本没法对话。
下面这张图,是我自己总结的MAVLink 2.0帧结构。每次给团队新人培训,我都会先画这个。
每个字段都有它的使命。我挑几个重点说说:
- 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 参数,把原始报文打印出来。然后对照上面的代码手动解析一遍,对理解协议非常有帮助。