第三课:心跳包解析——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_mode 和 custom_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代表悬停模式……
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秒)没有收到,就应该认为通信中断了。
超时处理的伪代码:
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 知识体系图
下面这张图,是我自己画的心跳包解析知识体系。你可以把它当作一个快速索引:
3.7 小结
心跳包是地面站和飞控之间的“握手信号”。你只要掌握了它的结构,就能快速判断系统状态、飞行模式,以及通信是否正常。
嗯,这里要注意:不同飞控厂商对custom_mode的定义可能不同。如果你在开发地面站时遇到模式显示不对,先查一下飞控的文档,看看它的模式枚举值是怎么定义的。
好了,这一课就到这里。记住,心跳包解析是地面站开发的第一步,也是调试时最常用的工具。