3. MAVLink 协议基础:协议概述、消息格式与常用消息类型

好,咱们进入第三讲。前面两章我们把数据采集的硬件链路和整体架构理清了,现在该聊聊无人机和地面站之间到底用什么语言“对话”了。

这个语言就是 MAVLink。说白了吧,它就是无人机领域的“通用语”。不管你是大疆、Pixhawk 还是自己攒的飞控,只要想和 QGC 这样的地面站通信,基本都绕不开它。我个人习惯把 MAVLink 看作一套“电报码”——短小、精悍、容错高,特别适合在带宽有限的无线链路上跑。

3.1 MAVLink 协议概述

MAVLink 全称是 Micro Air Vehicle Link,最早由苏黎世联邦理工的 Lorenz Meier 在 2009 年捣鼓出来的。初衷很简单:让飞控和地面站能互相听懂对方在说什么。

它有几个核心特点:

  • 轻量级:一条消息最短只有 8 个字节(心跳包),这在 915MHz 或 2.4GHz 的无线链路上非常宝贵。
  • 版本迭代:目前主流是 v1.0 和 v2.0。v2.0 支持 24 位系统 ID,还能带签名,安全性更好。我建议新项目直接上 v2.0,别走回头路。
  • 双向通信:飞控可以主动上报状态,地面站也能下发指令,比如“解锁”、“切换模式”、“飞到这里”。
  • 跨平台:从 STM32 到 Linux,从 Python 到 C++,都有现成的库。

核心概念:MAVLink 本质上是一种基于“消息”的串行化协议。每条消息都有一个唯一的 ID,接收方根据 ID 来解析数据。你想想看,如果每次通信都传一长串 JSON,那无线链路早就堵死了。

我在项目中遇到过最典型的一个坑:有人把 MAVLink 当成了“万能协议”,什么数据都想往里塞。结果消息包越来越大,丢包率直线上升。记住,MAVLink 只适合传“控制”和“状态”类的小数据,大块的日志或图像数据,请走别的通道。

3.2 消息格式详解

每条 MAVLink 消息都有固定的“信封”。咱们以 v2.0 为例,拆开看看里面到底长什么样。

字段 长度(字节) 说明
起始标志 1 固定为 0xFD,表示 v2.0 帧
载荷长度 1 后面 payload 部分的字节数
不兼容标志 1 如果接收方不认识这个标志,必须丢弃整条消息
兼容标志 1 如果接收方不认识,可以忽略该标志继续处理
序列号 1 0~255 循环,用于检测丢包
系统 ID 1 标识发送设备,比如飞控是 1,地面站是 255
组件 ID 1 标识同一系统内的不同组件,比如 GPS 模块、IMU
消息 ID 3 v2.0 扩展到 24 位,最多支持 16777216 种消息类型
载荷 0~255 真正的数据内容,格式由消息 ID 决定
校验和 2 CRC-16,用于检测传输错误
签名(可选) 13 v2.0 新增,用于防篡改

小技巧:调试时如果发现 QGC 收不到数据,先检查“起始标志”对不对。我曾经花了一下午排查,最后发现是串口配置把 0xFD 当成了控制字符给过滤掉了。

嗯,这里要注意:v1.0 和 v2.0 的起始标志不一样。v1.0 是 0xFE,v2.0 是 0xFD。如果你在同一个链路上混用两个版本,接收方会直接丢弃不认识的帧。所以,统一版本很重要。

3.3 常用消息类型

MAVLink 定义了几百种消息,但日常开发中常用的也就二三十种。我把它们分成几类,咱们挑重点说。

3.3.1 心跳与状态类

  • HEARTBEAT (ID=0):最基础的消息,每秒发一次。告诉对方“我还活着”。里面包含系统类型(四旋翼、固定翼、地面站)、飞控模式(自稳、定高、悬停)和自定义模式。
  • SYS_STATUS (ID=1):系统整体状态,包括电池电压、剩余电量、传感器健康度。QGC 右上角的电池图标就是靠这个更新的。
  • GPS_RAW_INT (ID=24):GPS 原始数据,经纬度、海拔、地面速度、卫星数量。注意经纬度是以 1e7 为单位的整数,解析时要除以 1e7。

避坑指南:我曾经在解析 GPS 数据时忘了除以 1e7,结果飞机显示在非洲。嗯,那天的测试数据全废了。所以,拿到 MAVLink 数据后,第一件事就是确认单位。

3.3.2 姿态与位置类

  • ATTITUDE (ID=30):三轴姿态角(roll、pitch、yaw)和角速度。单位是弧度,不是度。QGC 的 3D 视图就是靠这个数据来旋转飞机模型的。
  • LOCAL_POSITION_NED (ID=32):机体坐标系下的位置(北、东、下)和速度。这个在自主飞行中非常关键,飞控的导航算法主要依赖它。
  • GLOBAL_POSITION_INT (ID=33):全球坐标系下的位置,包含经纬度、海拔、相对高度和地面速度。QGC 地图上的飞机图标就是根据这个来定位的。

3.3.3 遥控与指令类

  • RC_CHANNELS (ID=65):遥控器各通道的 PWM 值。如果你在做遥控器信号转发或模拟,这个消息是核心。
  • COMMAND_LONG (ID=76):地面站下发指令的“万能接口”。比如解锁(MAV_CMD_COMPONENT_ARM_DISARM)、起飞(MAV_CMD_NAV_TAKEOFF)、返航(MAV_CMD_NAV_RETURN_TO_LAUNCH)。
  • MANUAL_CONTROL (ID=69):手动控制指令,用于摇杆或键盘控制无人机移动。

3.3.4 参数与调试类

  • PARAM_REQUEST_LIST (ID=21):请求飞控发送所有参数列表。QGC 的参数页面就是靠这个来刷新的。
  • PARAM_VALUE (ID=22):飞控返回的单个参数值。参数名是 16 字节的字符串,值可以是浮点数或整数。
  • DEBUG_VECT (ID=250):调试用的三维向量。我在做 PID 调参时,经常把三个轴的期望姿态角塞进去,然后在 QGC 的曲线图里实时观察。

下面这张图能帮你快速理解 MAVLink 消息在无人机系统中的流转路径:

MAVLink 消息流转与常用类型 飞控 (系统ID=1) Pixhawk / Cube / 自研 地面站 (系统ID=255) QGC / Mission Planner MAVLink 消息通道 串口 / UDP / TCP v1.0 (0xFE) / v2.0 (0xFD) 上行:指令类 COMMAND_LONG (76) MANUAL_CONTROL (69) 下行:状态类 HEARTBEAT (0) ATTITUDE (30) GPS_RAW_INT (24) SYS_STATUS (1) 💡 解析要点 先校验 CRC,再按消息 ID 分发解析

从这张图可以看得很清楚:飞控主要“说”下行状态消息,地面站主要“发”上行指令消息。两者通过 MAVLink 协议在同一个通道上双向传输,互不干扰。

3.4 如何抓包与分析

光看理论不过瘾,咱们得动手。我常用的工具有两个:

  1. MAVLink Inspector:QGC 自带的,在“工具”菜单里。可以实时看到每一条消息的原始数据,非常适合现场调试。
  2. mavlink-router:命令行工具,可以记录所有 MAVLink 流量到文件,方便事后回放分析。

举个例子,你想看飞控当前的心跳状态:

# 用 mavlink-router 抓取串口数据并打印到终端
mavlink-routerd -f /dev/ttyACM0:57600 -e 0.0.0.0:14550

# 或者用 Python 直接解析
from pymavlink import mavutil

master = mavutil.mavlink_connection('/dev/ttyACM0', baud=57600)
msg = master.recv_match(type='HEARTBEAT', blocking=True)
print(f"系统类型: {msg.type}")
print(f"飞控模式: {msg.custom_mode}")
print(f"系统状态: {msg.system_status}")

个人经验:调试时别急着写复杂逻辑。先确保能稳定收到 HEARTBEAT,这是所有通信的基础。如果连心跳都没有,后面的姿态、GPS 数据就更别想了。