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 仿真中,控制消息频率有两种方式:

  1. 通过 MAVProxy 的 set 命令:
    # 设置 ATTITUDE 消息为 50 Hz
    set ATTITUDE 50
    # 设置 GPS_RAW_INT 消息为 5 Hz
    set GPS_RAW_INT 5
  2. 通过修改飞控参数(在 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 仿真中的流转路径。你把它看懂了,就知道数据是从哪来、到哪去、中间经过了什么处理。

MAVLink 消息流在 SITL 仿真中的路径 MAVLink 消息流在 SITL 仿真中的路径 SITL 飞控 (ArduPilot / PX4) MAVLink 打包 (按 MSGID 封装 Payload) UDP/TCP 传输 (默认 14550 端口) MAVProxy 接收 (消息路由与转发) 数据采集脚本 (Python / C++ 解析) 日志文件 (.bin / .csv / .ulg) 说明: 1. SITL 飞控内部以固定频率(如 400 Hz)运行控制循环。 2. 只有被订阅的消息才会被 MAVLink 打包发送,默认频率由 SRx_ 参数控制。 3. 数据采集脚本通过 MAVProxy 转发接收,也可以直接监听 UDP 端口。 4. 日志文件是最终产物,用于后续的质量分析和可视化。

你看这个流程,从飞控到日志文件,中间经过了打包、传输、接收、解析四个环节。每个环节都可能引入延迟或丢包。我建议你在采集数据时,在 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 和解析代码中的类型名称完全一致。