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):关机中

我给大家画了一张状态迁移图,方便理解:

MAVLink系统状态机 UNINIT BOOT CALIBRATING STANDBY ACTIVE CRITICAL EMERGENCY POWEROFF 状态迁移说明 正常流程:UNINIT → BOOT → CALIBRATING → STANDBY → ACTIVE 异常处理 ACTIVE → CRITICAL CRITICAL → EMERGENCY

这张图里,我特意把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管健康状态。三者配合,才能完整描述一架无人机的当前状况。下次调试时,记得先看看心跳包——很多时候问题就出在这里。