4、心跳与系统状态:HEARTBEAT消息解析、系统状态机、自定义模式与标准模式
各位同学,今天我们来聊聊MAVLink里最基础、也最容易被忽视的一个话题——心跳包。说白了,它就是无人机和地面站之间的“我还在”信号。没有它,整个通信系统就像断了线的风筝。
我记得刚入行那会儿,调试一架四旋翼,地面站死活连不上飞控。折腾了半天,最后发现是心跳包的发送间隔配错了。嗯,从那以后,我对HEARTBEAT就格外上心。
4.1 HEARTBEAT消息结构
HEARTBEAT是MAVLink协议里唯一一个必须定期发送的消息。它的ID是0,优先级最高。你想想看,如果地面站收不到心跳,它怎么知道飞机还活着?
来看一下它的核心字段:
| 字段名 | 类型 | 说明 |
|---|---|---|
| type | uint8_t | 系统类型(飞控、地面站、相机等) |
| autopilot | uint8_t | 自驾仪类型(PX4、ArduPilot等) |
| base_mode | uint8_t | 位掩码,表示系统状态 |
| custom_mode | uint32_t | 自定义模式(由自驾仪定义) |
| system_status | uint8_t | 系统健康状态 |
| mavlink_version | uint8_t | MAVLink协议版本(固定为3) |
这里我特别想强调base_mode和custom_mode这两个字段。很多新手容易搞混,其实它们分工很明确:base_mode是标准化的通用状态,而custom_mode留给各家自驾仪自己发挥。
关键点:HEARTBEAT的发送间隔通常为1Hz(每秒一次)。如果连续3次没收到,地面站就应该判定通信中断。我在项目里一般设成5次超时,留点余量,避免无线干扰导致误判。
4.2 系统状态机解析
系统状态机,说白了就是无人机在不同阶段的状态切换。MAVLink通过system_status字段来标识当前状态。常见的状态值有:
- MAV_STATE_UNINIT (0):未初始化,刚上电
- MAV_STATE_BOOT (1):正在启动
- MAV_STATE_CALIBRATING (2):校准中(传感器校准)
- MAV_STATE_STANDBY (3):待机,准备就绪
- MAV_STATE_ACTIVE (4):活跃,正在执行任务
- MAV_STATE_CRITICAL (5):严重告警
- MAV_STATE_EMERGENCY (6):紧急状态
- MAV_STATE_POWEROFF (7):关机中
我给大家画了一张状态迁移图,方便理解:
这张图里,我特意把CALIBRATING标成了橙色。为什么?因为校准阶段最容易出问题。我曾经遇到过一架飞机,每次上电都卡在校准状态,最后发现是加速度计数据异常。嗯,这种坑踩过一次就记住了。
4.3 base_mode位掩码详解
base_mode是一个8位的位掩码,每一位代表一个独立的状态。我个人习惯用十六进制来读写它,这样更直观:
| 位 | 掩码 | 含义 |
|---|---|---|
| 0 | 0x01 | 系统已上电并初始化 |
| 1 | 0x02 | 系统已连接(通信链路正常) |
| 2 | 0x04 | 系统已解锁(电机可转动) |
| 3 | 0x08 | 系统处于引导加载模式 |
| 4-7 | 0xF0 | 保留位,供未来扩展 |
举个例子,如果base_mode = 0x07,表示系统已上电、已连接、已解锁。地面站看到这个值,就知道飞机随时可能起飞。
小技巧:在代码里判断解锁状态时,直接用位运算:if (base_mode & 0x04)。别去比较整个字节,那样容易出错。我见过有人写if (base_mode == 0x07),结果系统在0x06(已上电+已连接,未解锁)时误判为已解锁,差点出事。
4.4 标准模式 vs 自定义模式
这里有个容易混淆的点。base_mode里的位只表示“是否解锁”、“是否连接”这类通用状态。但无人机具体在飞什么模式——比如定点、悬停、返航——这些信息放在custom_mode里。
为什么这么设计?因为不同自驾仪的模式定义不一样。PX4有20多种模式,ArduPilot也有自己的一套。MAVLink没法统一,干脆把模式定义权交给各家。
来看一下常见的模式映射:
| 自驾仪 | 模式名称 | custom_mode值 | 说明 |
|---|---|---|---|
| PX4 | MANUAL | 0 | 手动模式 |
| PX4 | ALTCTL | 1 | 定高模式 |
| PX4 | POSCTL | 2 | 定点模式 |
| PX4 | AUTO | 3 | 自动任务模式 |
| ArduPilot | STABILIZE | 0 | 自稳模式 |
| ArduPilot | ALT_HOLD | 2 | 定高模式 |
| ArduPilot | LOITER | 5 | 悬停模式 |
| ArduPilot | RTL | 6 | 返航模式 |
你看,PX4和ArduPilot的custom_mode值完全不同。所以地面站在解析时,必须先知道autopilot字段的值,才能正确翻译custom_mode。
注意:千万不要在代码里硬编码custom_mode的值!我曾经接手过一个项目,代码里写死了if (custom_mode == 3) // 自动模式。结果后来固件升级,PX4的模式编号变了,整个地面站全部失效。正确的做法是通过MAVLink的MAV_MODE枚举或者调用自驾仪提供的模式转换函数。
4.5 实际应用:如何解析HEARTBEAT
说了这么多理论,咱们来点实际的。下面是一段解析HEARTBEAT的C代码片段,我平时在嵌入式项目里就这么写:
void handle_heartbeat(mavlink_message_t *msg) {
mavlink_heartbeat_t heartbeat;
mavlink_msg_heartbeat_decode(msg, &heartbeat);
// 检查系统类型
if (heartbeat.type == MAV_TYPE_QUADROTOR) {
printf("检测到四旋翼\n");
}
// 检查解锁状态
if (heartbeat.base_mode & MAV_MODE_FLAG_SAFETY_ARMED) {
printf("警告:电机已解锁!\n");
}
// 解析自定义模式(以PX4为例)
uint8_t autopilot = heartbeat.autopilot;
uint32_t custom_mode = heartbeat.custom_mode;
if (autopilot == MAV_AUTOPILOT_PX4) {
switch (custom_mode) {
case PX4_CUSTOM_MAIN_MODE_MANUAL:
printf("当前模式:手动\n");
break;
case PX4_CUSTOM_MAIN_MODE_AUTO:
printf("当前模式:自动\n");
break;
default:
printf("未知模式:%lu\n", custom_mode);
break;
}
}
// 检查系统状态
if (heartbeat.system_status == MAV_STATE_CRITICAL ||
heartbeat.system_status == MAV_STATE_EMERGENCY) {
printf("紧急状态!请立即处理!\n");
}
}
这段代码里,我用了MAV_MODE_FLAG_SAFETY_ARMED这个宏来判断解锁。为什么不用0x04?因为宏定义更可读,而且万一协议更新,宏定义也会跟着变。你想想看,如果全篇都是0x04、0x07这种魔法数字,三个月后你自己都看不懂。
4.6 避坑指南
最后,我总结几个实际项目中容易踩的坑:
- 心跳超时设置太短:无线链路偶尔会丢包,1Hz的心跳如果设成2秒超时,稍微有点干扰就误判。我一般设5秒。
- 忽略system_status:很多人只看base_mode和custom_mode,不看system_status。结果飞机都进入紧急状态了,地面站还显示“正常”。
- 自定义模式硬编码:前面说过了,这个坑我踩过,大家千万别再踩。
- 忘记校验mavlink_version:虽然现在主流是v2,但万一遇到老设备,版本不匹配会导致解析错误。
总结一下:HEARTBEAT是MAVLink通信的基石。base_mode管通用状态,custom_mode管具体模式,system_status管健康状态。三者配合,才能完整描述一架无人机的当前状况。下次调试时,记得先看看心跳包——很多时候问题就出在这里。