3. MAVLink协议解析:消息结构、常用ID与频率控制
MAVLink 这东西,说白了就是飞控和地面站之间的“普通话”。没有它,你采集到的数据就是一盘散沙。我个人习惯,在开始任何 SITL 仿真数据采集之前,都会先把 MAVLink 的底裤扒干净——消息怎么打包的、哪些 ID 最常用、频率怎么调。搞懂这些,你才知道采集回来的数据到底靠不靠谱。
3.1 MAVLink 消息结构:从比特说起
MAVLink 有两种版本:v1.0 和 v2.0。现在主流是 v2.0,但很多老设备还在用 v1.0。我建议你直接学 v2.0,因为它向下兼容。嗯,这里要注意:v2.0 的消息头比 v1.0 多了 4 个字节,主要是为了签名和扩展。
一个标准的 MAVLink v2.0 消息长这样:
| 字段 | 长度(字节) | 说明 |
|---|---|---|
| 起始标志 (STX) | 1 | 固定 0xFD,表示 v2.0 |
| 载荷长度 (LEN) | 1 | 后面 payload 的字节数 |
| 不兼容标志 (INC) | 1 | 如果接收方不支持,直接丢包 |
| 兼容标志 (CMP) | 1 | 接收方可安全忽略的扩展 |
| 序列号 (SEQ) | 1 | 0~255 循环,用于检测丢包 |
| 系统 ID (SYS) | 1 | 飞控通常为 1 |
| 组件 ID (COMP) | 1 | 飞控为 1,GPS 为 220 等 |
| 消息 ID (MSGID) | 3 | 这才是关键,标识消息类型 |
| 载荷 (Payload) | 0~255 | 具体数据,按消息 ID 定义解析 |
| 校验和 (CRC) | 2 | 对整个消息做 CRC-16 |
| 签名 (SIGNATURE) | 0/13 | 可选,用于安全认证 |
你想想看,每次解析消息,其实就是按这个结构拆包。我在项目中遇到过一个问题:某个传感器数据老是跳变,排查了半天,发现是消息 ID 解析错了,把 GPS 数据当成了 IMU 数据来用。所以,解析的第一步,永远是确认 MSGID。
核心要点:MAVLink 消息的解析入口是 MSGID。拿到原始字节流后,先找 0xFD,然后读 LEN,再读 MSGID,最后根据 MSGID 去查对应的数据结构来解析 Payload。
3.2 常用消息 ID:你必须要认识的几个
MAVLink 定义了上百种消息 ID,但做数据采集,你真正需要关心的不超过 10 个。我列个表,这些都是我平时用得最多的:
| 消息 ID | 名称 | 频率(默认) | 主要数据 | 我的使用场景 |
|---|---|---|---|---|
| #30 | ATTITUDE | 10~50 Hz | roll, pitch, yaw (弧度) | 姿态控制分析 |
| #24 | GPS_RAW_INT | 5~10 Hz | 经纬度、海拔、地面速度 | 位置精度评估 |
| #32 | LOCAL_POSITION_NED | 10~50 Hz | 局部坐标系下的位置、速度 | VIO/光流融合验证 |
| #65 | RC_CHANNELS | 10 Hz | 遥控器通道值 | 遥控信号质量检查 |
| #74 | VFR_HUD | 10 Hz | 空速、地速、高度、爬升率 | 飞行状态快照 |
| #147 | BATTERY_STATUS | 1~5 Hz | 电压、电流、剩余电量 | 电源系统健康监测 |
为什么 ATTITUDE 和 LOCAL_POSITION_NED 频率高?因为它们是飞控内环控制的关键输入。GPS_RAW_INT 频率低,因为 GPS 本身更新率就慢。我曾经犯过一个错:把 GPS 数据以 50 Hz 去采集,结果发现全是重复值,白白浪费了存储空间。
小技巧:在 SITL 仿真中,你可以通过 --speedup 参数加速仿真时间,但 MAVLink 消息的频率不会自动翻倍。如果你需要高频数据,记得在启动 SITL 时手动设置 --mavlink-freq 参数。
3.3 消息频率控制:别让数据淹死你
数据采集不是越多越好。频率太高,你的存储和带宽扛不住;频率太低,关键动态信息就丢了。我一般遵循这个原则:内环数据(姿态、角速度)用 50 Hz,外环数据(位置、速度)用 10~20 Hz,状态数据(电池、GPS)用 5 Hz 就够了。
在 SITL 仿真中,控制消息频率有两种方式:
- 通过 MAVProxy 的
set命令:
# 设置 ATTITUDE 消息为 50 Hz set ATTITUDE 50 # 设置 GPS_RAW_INT 消息为 5 Hz set GPS_RAW_INT 5 - 通过修改飞控参数(在 SITL 启动前设置):
# 在启动 SITL 时,通过 --set 参数修改 sim_vehicle.py -v ArduCopter --set SR0_EXTRA1=50 --set SR0_EXTRA2=10这里的
SR0_EXTRA1对应 ATTITUDE,SR0_EXTRA2对应 LOCAL_POSITION_NED。具体映射关系可以查 ArduPilot 的SRx_参数表。
警告:不要把所有消息都设成 100 Hz。飞控的 CPU 资源是有限的,过高的消息频率会导致飞控主循环卡顿,甚至造成仿真崩溃。我曾经为了采集“完美”数据,把 ATTITUDE 设成了 200 Hz,结果 SITL 直接报 CPU overload 错误。后来我学乖了,50 Hz 对于姿态分析已经绰绰有余。
3.4 一张图看懂 MAVLink 消息流
下面这张图,是我自己总结的 MAVLink 消息在 SITL 仿真中的流转路径。你把它看懂了,就知道数据是从哪来、到哪去、中间经过了什么处理。
你看这个流程,从飞控到日志文件,中间经过了打包、传输、接收、解析四个环节。每个环节都可能引入延迟或丢包。我建议你在采集数据时,在 MAVProxy 里打开 --console 和 --map 窗口,实时观察消息的到达率和丢包率。如果发现某个消息的频率远低于设定值,那就要检查一下是不是网络拥堵或者飞控负载过高了。
3.5 实战:用 Python 解析 ATTITUDE 消息
光说不练假把式。下面这段代码,是我在数据采集脚本里最常用的片段——从 MAVLink 流中提取 ATTITUDE 消息:
from pymavlink import mavutil
# 连接到 SITL(默认 UDP 端口 14550)
master = mavutil.mavlink_connection('udp:127.0.0.1:14550')
# 等待心跳包,确认连接
master.wait_heartbeat()
print("已连接到飞控,系统ID:", master.target_system)
# 循环采集 ATTITUDE 消息
while True:
msg = master.recv_match(type='ATTITUDE', blocking=True)
if msg:
# 提取关键数据
roll = msg.roll # 弧度
pitch = msg.pitch # 弧度
yaw = msg.yaw # 弧度
time_boot_ms = msg.time_boot_ms # 毫秒
# 打印到控制台(实际使用中应写入文件)
print(f"t={time_boot_ms}ms, roll={roll:.3f}, pitch={pitch:.3f}, yaw={yaw:.3f}")
这段代码的核心是 recv_match 函数。它有两个关键参数:type 指定消息 ID 的名称,blocking=True 表示如果没有新消息就阻塞等待。我个人习惯把 blocking 设为 False,然后加一个 time.sleep(0.001),这样 CPU 占用更低。
避坑指南:我曾经在采集数据时,发现 roll 和 pitch 的值一直在 0 附近抖动,但明明飞机在剧烈晃动。后来排查发现,我解析的是 ATTITUDE_QUATERNION(消息 ID #31),而不是 ATTITUDE(消息 ID #30)。前者输出的是四元数,需要额外转换才能得到欧拉角。所以,一定要确认你订阅的消息 ID 和解析代码中的类型名称完全一致。