4. MAVLink消息结构
心跳,是无人机通信的命脉。
你想想看,地面站怎么知道飞机还活着?飞控怎么知道遥控器信号断了?全靠这条「心跳线」。MAVLink 里最基础、最重要的消息,就是 HEARTBEAT(消息 ID 0)。
我刚开始做飞控开发时,第一个调通的 MAVLink 消息就是它。当时看着 QGroundControl 上那个心跳图标从灰色变成绿色,心里踏实多了。嗯,今天我们就把它彻底讲透。
4.1 HEARTBEAT 消息结构
一条心跳消息只有 9 个字节的有效载荷。结构如下:
| 字段名 | 类型 | 字节数 | 说明 |
|---|---|---|---|
type |
uint8_t | 1 | 系统类型(MAV_TYPE) |
autopilot |
uint8_t | 1 | 飞控类型(MAV_AUTOPILOT) |
base_mode |
uint8_t | 1 | 基础模式位掩码(MAV_MODE_FLAG) |
custom_mode |
uint32_t | 4 | 自定义模式(由飞控厂商定义) |
system_status |
uint8_t | 1 | 系统状态(MAV_STATE) |
mavlink_version |
uint8_t | 1 | MAVLink 协议版本(固定为 3) |
这里有个细节:custom_mode 占了 4 个字节,是唯一一个超过 1 字节的字段。为什么?因为飞控的模式太多了。ArduPilot 有几十种模式,PX4 也有十几二十种,1 个字节根本不够用。
核心要点:地面站判断无人机是否在线,就看心跳消息的接收间隔。如果超过 3 秒没收到,就认为连接丢失。我一般把心跳发送间隔设为 1 秒,既不会太频繁占用带宽,又能及时检测断连。
4.2 系统类型与状态枚举
系统类型 MAV_TYPE 告诉地面站:你连的是什么玩意儿?
常用的有这些:
| 枚举值 | 名称 | 说明 |
|---|---|---|
| 0 | MAV_TYPE_GENERIC | 通用类型(不推荐) |
| 1 | MAV_TYPE_FIXED_WING | 固定翼 |
| 2 | MAV_TYPE_QUADROTOR | 四旋翼 |
| 3 | MAV_TYPE_COAXIAL | 共轴直升机 |
| 4 | MAV_TYPE_HELICOPTER | 直升机 |
| 12 | MAV_TYPE_SUBMARINE | 水下潜航器 |
| 16 | MAV_TYPE_GCS | 地面站 |
系统状态 MAV_STATE 则告诉地面站:飞机现在什么状况?
| 枚举值 | 名称 | 说明 |
|---|---|---|
| 0 | MAV_STATE_UNINIT | 未初始化 |
| 1 | MAV_STATE_BOOT | 正在启动 |
| 2 | MAV_STATE_CALIBRATING | 正在校准 |
| 3 | MAV_STATE_STANDBY | 待机(已解锁但未起飞) |
| 4 | MAV_STATE_ACTIVE | 激活(正在飞行) |
| 5 | MAV_STATE_CRITICAL | 严重警告 |
| 6 | MAV_STATE_EMERGENCY | 紧急状态 |
| 7 | MAV_STATE_POWEROFF | 正在关机 |
避坑指南:我曾经遇到过一个情况,飞机明明在飞,地面站却显示「待机」。查了半天,发现是飞控代码里忘了在起飞后把状态从 MAV_STATE_STANDBY 切换到 MAV_STATE_ACTIVE。地面站看到状态不对,直接触发了安全保护,把飞机给降落了。嗯,从那以后我每次都会检查状态切换逻辑。
4.3 base_mode 位掩码详解
base_mode 是一个位掩码,每个 bit 代表一个开关状态。这是 MAVLink 里比较巧妙的设计——用 1 个字节表达了 8 个布尔值。
| Bit | 标志位 | 含义 |
|---|---|---|
| 0 | MAV_MODE_FLAG_CUSTOM_MODE_ENABLED | 启用自定义模式 |
| 1 | MAV_MODE_FLAG_TEST_ENABLED | 测试模式 |
| 2 | MAV_MODE_FLAG_AUTO_ENABLED | 自动模式 |
| 3 | MAV_MODE_FLAG_GUIDED_ENABLED | 引导模式 |
| 4 | MAV_MODE_FLAG_STABILIZE_ENABLED | 增稳模式 |
| 5 | MAV_MODE_FLAG_HIL_ENABLED | 硬件在环仿真 |
| 6 | MAV_MODE_FLAG_MANUAL_INPUT_ENABLED | 手动输入 |
| 7 | MAV_MODE_FLAG_SAFETY_ARMED | 已解锁(电机可转) |
举个例子:一架正在自动飞行的四旋翼,它的 base_mode 可能是这样:
// 位掩码计算
base_mode = 0;
base_mode |= (1 << 0); // 启用自定义模式
base_mode |= (1 << 2); // 自动模式
base_mode |= (1 << 6); // 手动输入(遥控器信号正常)
base_mode |= (1 << 7); // 已解锁
// 结果:base_mode = 0b11000101 = 197
我个人习惯在代码里用位操作来设置和读取这些标志位,而不是直接写死数值。这样可读性高,也不容易出错。
4.4 自定义模式(custom_mode)
这才是真正体现飞控差异的地方。
MAVLink 标准只定义了基础模式位掩码,但具体到「悬停」「返航」「跟随」这些模式,每个飞控厂商都有自己的定义。这就是 custom_mode 存在的意义——它是一个 32 位的自由空间,你想怎么用就怎么用。
以 ArduPilot 为例,它的自定义模式枚举:
| 值 | 模式名 | 说明 |
|---|---|---|
| 0 | STABILIZE | 增稳模式 |
| 1 | ACRO | 特技模式 |
| 2 | ALT_HOLD | 定高模式 |
| 3 | AUTO | 自动模式 |
| 4 | GUIDED | 引导模式 |
| 5 | LOITER | 悬停模式 |
| 6 | RTL | 返航模式 |
| 7 | CIRCLE | 绕圈模式 |
| 9 | LAND | 降落模式 |
| 16 | POSHOLD | 定点模式 |
PX4 的定义则完全不同,它用的是 PX4_CUSTOM_MAIN_MODE 和 PX4_CUSTOM_SUB_MODE 的组合。所以地面站在解析 custom_mode 时,必须先知道 autopilot 字段的值,才能正确映射。
实战技巧:如果你在开发自己的飞控,建议把 custom_mode 的高 16 位留给厂商自定义,低 16 位留给模式编号。这样既兼容了标准,又保留了扩展空间。我自己的飞控项目就是这么设计的,用了两年多,没出过兼容性问题。
4.5 心跳消息的发送与接收
发送心跳其实很简单。以 PX4 的代码为例:
// 发送心跳消息
mavlink_message_t msg;
uint8_t buf[MAVLINK_MAX_PACKET_LEN];
mavlink_msg_heartbeat_pack(
1, // system_id(飞控自身 ID)
1, // component_id(飞控组件 ID)
&msg, // 输出消息
MAV_TYPE_QUADROTOR, // 系统类型
MAV_AUTOPILOT_PX4, // 飞控类型
MAV_MODE_FLAG_MANUAL_INPUT_ENABLED |
MAV_MODE_FLAG_SAFETY_ARMED, // base_mode
0, // custom_mode(这里用 0 简化)
MAV_STATE_ACTIVE // 系统状态
);
uint16_t len = mavlink_msg_to_send_buffer(buf, &msg);
// 然后通过串口/UDP 发送 buf 中的 len 个字节
接收端解析:
// 收到消息后解析
mavlink_message_t msg;
mavlink_status_t status;
// 假设从串口读到一个字节
if (mavlink_parse_char(MAVLINK_COMM_0, c, &msg, &status)) {
if (msg.msgid == MAVLINK_MSG_ID_HEARTBEAT) {
uint8_t type;
uint8_t autopilot;
uint8_t base_mode;
uint32_t custom_mode;
uint8_t system_status;
mavlink_msg_heartbeat_decode(&msg, &type, &autopilot,
&base_mode, &custom_mode,
&system_status);
// 检查是否解锁
if (base_mode & MAV_MODE_FLAG_SAFETY_ARMED) {
printf("飞机已解锁!\n");
}
}
}
避坑指南:我曾经在调试时发现地面站一直收不到心跳,但串口数据明明在发。最后发现是 mavlink_parse_char 的返回值判断写错了——我把 if (mavlink_parse_char(...)) 写成了 while (mavlink_parse_char(...)),导致解析完一条消息后,缓冲区里的下一个字节被当成新消息的起始字节,整个解析就乱掉了。嗯,这种低级错误,犯过一次就记住了。
4.6 知识体系总览
下面这张图把心跳消息的整个知识结构串起来了:
这张图把心跳消息拆成了五个维度:消息结构、系统类型与状态、base_mode 位掩码、custom_mode 自定义、以及发送与接收的 API。每个维度之间都有依赖关系——比如解析 custom_mode 之前,必须先知道 autopilot 类型。
好了,心跳消息就讲到这里。记住一句话:心跳是无人机通信的基石,它的每个字段都有明确用途。搞懂了它,后面再学其他消息就会轻松很多。