第三课:心跳包解析——HEARTBEAT消息结构、系统状态与模式解读

各位同学,今天我们来聊聊MAVLink协议里最基础、也最重要的一个消息——HEARTBEAT(心跳包)。

我记得刚接触无人机地面站开发那会儿,第一件事就是盯着串口助手看数据流。满屏的十六进制数里,怎么找到有用的信息?说白了,就是从心跳包入手。你想想看,地面站和飞控之间能不能正常通信,全看这个包。

3.1 心跳包是什么?为什么它如此重要?

HEARTBEAT,顾名思义,就是飞控每隔一段时间(通常是1秒)发给地面站的一个“我还活着”的信号。它不携带传感器数据,也不带控制指令,但它携带了三个关键信息:

  • 系统类型:你是谁?(飞控?地面站?相机?)
  • 系统状态:你还好吗?(正常?报错?紧急?)
  • 飞行模式:你现在在干嘛?(悬停?返航?任务执行?)

我在一个项目中遇到过这样的情况:地面站收不到心跳包,但数据流明明在跑。后来发现是波特率配置错了,飞控发的是57600,地面站设的是115200。嗯,这种低级错误,排查起来最费时间。

核心要点:没有心跳包,地面站就无法确认飞控的存在。所有后续的指令发送、数据解析都无从谈起。

3.2 HEARTBEAT消息结构详解

MAVLink 2.0协议中,HEARTBEAT的消息ID是0。它的载荷部分固定为9个字节。我们直接看结构:

字段名 类型 字节数 说明
type uint8_t 1 系统类型(MAV_TYPE_*)
autopilot uint8_t 1 飞控类型(MAV_AUTOPILOT_*)
base_mode uint8_t 1 系统基础模式(位掩码)
custom_mode uint32_t 4 自定义模式(具体飞行模式)
system_status uint8_t 1 系统状态(MAV_STATE_*)
mavlink_version uint8_t 1 MAVLink协议版本(固定为3)

你看,总共9个字节,信息量却很大。我个人习惯在解析时,先把这9个字节提取出来,然后逐个字段去匹配枚举值。

3.3 系统状态与模式解读

这里有两个容易混淆的概念:base_modecustom_mode

base_mode 是一个位掩码,它告诉你系统的基本状态:

  • bit 0: 是否已上电(MAV_MODE_FLAG_DECODE_POSITION_SAFETY)
  • bit 1: 是否已加锁(MAV_MODE_FLAG_DECODE_POSITION_ARMED)
  • bit 2: 是否在手动控制模式(MAV_MODE_FLAG_DECODE_POSITION_MANUAL)
  • bit 3: 是否在引导模式(MAV_MODE_FLAG_DECODE_POSITION_GUIDED)
  • bit 4: 是否在自动模式(MAV_MODE_FLAG_DECODE_POSITION_AUTO)
  • bit 5: 是否在测试模式(MAV_MODE_FLAG_DECODE_POSITION_TEST)
  • bit 6: 是否在自定义模式(MAV_MODE_FLAG_DECODE_POSITION_CUSTOM_MODE_ENABLED)

custom_mode 则是一个32位的整数,具体含义由飞控厂商定义。比如在ArduPilot中,0代表稳定模式,1代表定高模式,2代表悬停模式……

避坑指南:我曾经在解析custom_mode时,直接把它当作枚举值来用。结果发现不同版本的ArduPilot,同一个模式对应的数值竟然不一样!后来我学乖了,一定要先读取飞控的固件版本,再根据版本号去匹配模式表。

3.4 实战:如何解析一个心跳包

假设我们从串口收到了一帧MAVLink消息,经过帧头校验后,确认是HEARTBEAT。下面是一个简单的C语言解析示例:

// 假设 msg 是已经解码好的 mavlink_message_t 结构体
mavlink_heartbeat_t heartbeat;
mavlink_msg_heartbeat_decode(&msg, &heartbeat);

// 解析系统类型
switch (heartbeat.type) {
    case MAV_TYPE_QUADROTOR:
        printf("系统类型:四旋翼\n");
        break;
    case MAV_TYPE_HELICOPTER:
        printf("系统类型:直升机\n");
        break;
    case MAV_TYPE_GCS:
        printf("系统类型:地面站\n");
        break;
    default:
        printf("系统类型:未知 (0x%02X)\n", heartbeat.type);
        break;
}

// 解析系统状态
switch (heartbeat.system_status) {
    case MAV_STATE_STANDBY:
        printf("系统状态:待命\n");
        break;
    case MAV_STATE_ACTIVE:
        printf("系统状态:活跃\n");
        break;
    case MAV_STATE_CRITICAL:
        printf("系统状态:严重告警\n");
        break;
    case MAV_STATE_EMERGENCY:
        printf("系统状态:紧急\n");
        break;
    default:
        printf("系统状态:未知 (0x%02X)\n", heartbeat.system_status);
        break;
}

// 解析基础模式
if (heartbeat.base_mode & MAV_MODE_FLAG_SAFETY_ARMED) {
    printf("电机已解锁\n");
} else {
    printf("电机已锁定\n");
}

// 解析自定义模式(以ArduPilot为例)
const char* flight_mode = get_ardupilot_mode_name(heartbeat.custom_mode);
printf("飞行模式:%s\n", flight_mode);

你想想看,这段代码其实就做了三件事:解码、匹配、输出。但实际项目中,我们往往需要把这些信息实时显示在地面站的界面上。

3.5 心跳包的超时处理

地面站不能无限期地等待心跳包。如果超过一定时间(通常是3-5秒)没有收到,就应该认为通信中断了。

我的经验:超时时间不要设得太短。有一次我在调试时,飞控因为GPS信号不好,偶尔会跳过一两个心跳包。如果超时设成2秒,地面站就会频繁报错。后来我改成5秒,问题就解决了。当然,如果你做的是竞速无人机,超时时间可以适当缩短。

超时处理的伪代码:

uint32_t last_heartbeat_time = 0;
uint32_t current_time = get_system_time_ms();

if (current_time - last_heartbeat_time > 5000) {
    // 超过5秒没收到心跳
    set_connection_status(CONNECTION_LOST);
    // 可以尝试重新连接,或者弹出告警
} else {
    set_connection_status(CONNECTION_OK);
}

3.6 知识体系图

下面这张图,是我自己画的心跳包解析知识体系。你可以把它当作一个快速索引:

HEARTBEAT心跳包解析知识体系 消息结构 系统状态与模式 超时处理 type autopilot base_mode custom_mode system_status MAV_STATE_* MAV_MODE_* 位掩码解析 超时阈值 重连机制 告警提示 核心:解码 → 匹配 → 显示 → 超时监控

3.7 小结

心跳包是地面站和飞控之间的“握手信号”。你只要掌握了它的结构,就能快速判断系统状态、飞行模式,以及通信是否正常。

嗯,这里要注意:不同飞控厂商对custom_mode的定义可能不同。如果你在开发地面站时遇到模式显示不对,先查一下飞控的文档,看看它的模式枚举值是怎么定义的。

好了,这一课就到这里。记住,心跳包解析是地面站开发的第一步,也是调试时最常用的工具。


专注资料整理