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 协议在同一个通道上双向传输,互不干扰。
3.4 如何抓包与分析
光看理论不过瘾,咱们得动手。我常用的工具有两个:
- MAVLink Inspector:QGC 自带的,在“工具”菜单里。可以实时看到每一条消息的原始数据,非常适合现场调试。
- 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 数据就更别想了。