3. 核心通信协议:MAVLink 协议详解、消息类型与自定义消息、心跳包与状态监控机制

做多无人机地面站,通信协议是绕不开的坎。我最早接触 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 次才判定为离线。

状态监控的逻辑其实很简单:

  1. 地面站收到 HEARTBEAT,记录时间戳
  2. 每次循环检查当前时间 - 最后心跳时间 > 阈值?
  3. 如果超时,触发失联处理(自动返航、降落或切换备用链路)

下面是我常用的心跳监控伪代码:

// 伪代码:心跳监控
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 协议架构与数据流 物理层 / 链路层 串口 (UART) | UDP/TCP | 数传电台 | 4G/5G MAVLink 协议层 帧封装/解析 消息路由 校验与签名 超时重传 应用层(消息处理) 心跳监控 HEARTBEAT 状态解析 SYS_STATUS 位置/姿态 ATTITUDE / GPS 命令执行 COMMAND_LONG 自定义 地面站 UI 显示与控制

从图里能看出来,MAVLink 协议层是承上启下的关键。物理层只管收发字节流,应用层只管处理业务逻辑,而 MAVLink 负责把字节流变成有意义的消息。这种分层设计,说白了就是为了解耦——换通信链路不用改上层代码,加新消息也不用动底层驱动。

3.6 实战建议

我的几个习惯:

  • 开发阶段把 mavlink_status_t 的 packet_rx_success_count 和 packet_rx_drop_count 打印出来,能快速发现丢包问题
  • 自定义消息的 ID 从 42000 开始,按功能模块递增(比如编队用 42001-42010,传感器用 42011-42020)
  • 心跳包发送频率设为 1Hz 就够了,别学某些教程设成 10Hz——纯属浪费带宽

嗯,这一章的内容差不多就这些。MAVLink 协议看着复杂,其实核心就是「帧结构 + 消息类型 + 心跳监控」这三板斧。你只要把心跳监控写稳了,地面站就不会莫名其妙丢飞机。自定义消息是加分项,等基础功能跑通了再搞也不迟。