3. 核心通信协议
做多无人机地面站,通信协议是绕不开的坎。我最早接触 MAVLink 是在一个四轴飞行器项目上,当时被各种消息 ID 搞得晕头转向。后来踩的坑多了,才慢慢摸清门道。说白了,MAVLink 就是无人机世界的「通用语言」——地面站发指令、飞机回状态,全靠它。
3.1 MAVLink 协议基础
MAVLink(Micro Air Vehicle Link)是 Pixhawk、ArduPilot 等主流飞控的标准通信协议。它有两种版本:v1.0 和 v2.0。我个人习惯直接用 v2.0,因为它的签名机制能防篡改,而且消息 ID 范围更大。
核心特点:
- 轻量级:帧头只有 8-12 字节,适合低带宽无线链路
- 可扩展:支持 256 个标准消息 + 自定义消息
- 跨平台:C/C++、Python、Java 都有现成库
先看一个标准的 MAVLink v2.0 帧结构:
| STX(1) | LEN(1) | INC(1) | CMP(1) | SEQ(1) | SYS(1) | COMP(1) | MSGID(3) | PAYLOAD(0-255) | CKA(1) | CKB(1) |
| 0xFD | 长度 | 标志 | 校验 | 序号 | 系统ID | 组件ID | 消息ID | 数据 | 校验A | 校验B |
嗯,这里要注意:MSGID 在 v2.0 里是 3 字节,所以最大支持 16,777,215 个消息类型。v1.0 只有 1 字节,最多 255 个。我在做多机协同项目时,就因为 v1.0 消息 ID 不够用,被迫升级到 v2.0。
3.2 消息类型与分类
MAVLink 消息按功能可以分为几大类。我整理了一个常用表格,方便你快速查阅:
| 类别 | 消息 ID | 名称 | 说明 |
|---|---|---|---|
| 心跳 | 0 | HEARTBEAT | 无人机存活状态、飞行模式 |
| 姿态 | 30 | ATTITUDE | 滚转、俯仰、偏航角 |
| 位置 | 33 | GLOBAL_POSITION_INT | 经纬度、海拔、相对高度 |
| 系统状态 | 1 | SYS_STATUS | 电池电压、传感器健康度 |
| 命令 | 76 | COMMAND_LONG | 起飞、降落、航点上传 |
| 自定义 | 42000+ | 自定义消息 | 用户扩展数据 |
你想想看,地面站要同时监控 10 架无人机,每架每秒发 50 条消息,那就是 500 条/秒。如果每条都解析全部字段,CPU 扛不住。所以实际项目中,我通常只订阅 HEARTBEAT、ATTITUDE 和 GLOBAL_POSITION_INT 这三个核心消息,其他按需订阅。
3.3 心跳包与状态监控机制
心跳包是 MAVLink 里最重要的消息,没有之一。它就像无人机的「脉搏」——地面站通过它判断飞机是否在线、处于什么模式、有没有故障。
心跳包关键字段:
type:飞行器类型(四旋翼、固定翼、地面站等)autopilot:飞控类型(PX4、ArduPilot 等)base_mode:基础模式标志(已上锁、已解锁、已校准等)system_status:系统状态(待命、激活、紧急降落等)mavlink_version:协议版本
我曾经遇到过一个坑:某架无人机在飞行中突然失联,地面站显示「连接断开」。后来排查发现,是心跳包超时阈值设得太短(1 秒),而无线链路偶尔有 2 秒的延迟。从那以后,我习惯把超时阈值设为 5 秒,同时增加一个「心跳丢失计数」——连续丢失 3 次才判定为离线。
状态监控的逻辑其实很简单:
- 地面站收到 HEARTBEAT,记录时间戳
- 每次循环检查当前时间 - 最后心跳时间 > 阈值?
- 如果超时,触发失联处理(自动返航、降落或切换备用链路)
下面是我常用的心跳监控伪代码:
// 伪代码:心跳监控
void check_heartbeat(uint8_t sys_id) {
uint32_t now = get_time_ms();
uint32_t last = heartbeat_last_time[sys_id];
if (now - last > HEARTBEAT_TIMEOUT_MS) {
heartbeat_lost_count[sys_id]++;
if (heartbeat_lost_count[sys_id] >= 3) {
set_vehicle_offline(sys_id);
trigger_failsafe(sys_id);
}
} else {
heartbeat_lost_count[sys_id] = 0;
}
}
3.4 自定义消息:扩展你的通信能力
标准消息虽然够用,但总有些场景需要传特殊数据。比如我在做多机编队时,需要把每架飞机的「编队位置偏移」和「相对速度」传给其他飞机。标准消息里没有这个,只能自己定义。
MAVLink 自定义消息的 ID 范围是 42000 到 65535。定义方式很简单,写一个 XML 文件,然后用代码生成工具编译。
自定义消息注意事项:
- 消息 ID 不要与标准消息冲突(42000 以下是保留区)
- 负载长度不要超过 255 字节(v2.0 限制)
- 发送频率不要太高,避免挤占带宽
- 地面站和飞控两端必须使用同一份 XML 定义
举个例子,定义一个编队位置消息:
<!-- formation_position.xml -->
<mavlink>
<messages>
<message id="42001" name="FORMATION_POSITION">
<description>编队相对位置和速度</description>
<field type="uint8_t" name="target_sys">目标飞机系统ID</field>
<field type="float" name="offset_x">X轴偏移(米)</field>
<field type="float" name="offset_y">Y轴偏移(米)</field>
<field type="float" name="offset_z">Z轴偏移(米)</field>
<field type="float" name="vel_x">X轴速度(米/秒)</field>
<field type="float" name="vel_y">Y轴速度(米/秒)</field>
<field type="float" name="vel_z">Z轴速度(米/秒)</field>
</message>
</messages>
</mavlink>
生成代码后,发送端这样用:
mavlink_message_t msg;
mavlink_msg_formation_position_pack(
sys_id, comp_id, &msg,
target_sys, offset_x, offset_y, offset_z,
vel_x, vel_y, vel_z
);
send_mavlink_message(&msg);
接收端解析:
mavlink_formation_position_t formation;
mavlink_msg_formation_position_decode(&msg, &formation);
printf("目标: %d, 偏移: %.2f %.2f %.2f\n",
formation.target_sys,
formation.offset_x,
formation.offset_y,
formation.offset_z);
3.5 协议架构总览
为了让你更直观地理解 MAVLink 在系统中的位置,我画了一张架构图:
从图里能看出来,MAVLink 协议层是承上启下的关键。物理层只管收发字节流,应用层只管处理业务逻辑,而 MAVLink 负责把字节流变成有意义的消息。这种分层设计,说白了就是为了解耦——换通信链路不用改上层代码,加新消息也不用动底层驱动。
3.6 实战建议
我的几个习惯:
- 开发阶段把
mavlink_status_t的packet_rx_success_count和packet_rx_drop_count打印出来,能快速发现丢包问题 - 自定义消息的 ID 从 42000 开始,按功能模块递增(比如编队用 42001-42010,传感器用 42011-42020)
- 心跳包发送频率设为 1Hz 就够了,别学某些教程设成 10Hz——纯属浪费带宽
嗯,这一章的内容差不多就这些。MAVLink 协议看着复杂,其实核心就是「帧结构 + 消息类型 + 心跳监控」这三板斧。你只要把心跳监控写稳了,地面站就不会莫名其妙丢飞机。自定义消息是加分项,等基础功能跑通了再搞也不迟。