飞行模式基础架构:ArduPilot飞行模式枚举、模式切换的顶层逻辑、飞行模式与状态机的关系

各位好,我是老张。今天咱们聊聊飞行模式的基础架构。说实话,很多新手一上来就盯着PID调参,却忽略了模式切换这个"交通枢纽"。我见过太多SITL仿真跑着跑着突然炸机,最后发现是模式切换逻辑没搞明白。嗯,咱们今天就把这块地基打牢。

一、ArduPilot飞行模式枚举:不只是数字编号

ArduPilot里每种飞行模式都有一个唯一的枚举值。你打开代码会看到类似这样的定义:

// 在 AP_Mode.h 或类似头文件中
enum class Mode : uint8_t {
    STABILIZE   = 0,  // 自稳模式
    ACRO        = 1,  // 特技模式
    ALT_HOLD    = 2,  // 定高模式
    AUTO        = 3,  // 自动模式
    GUIDED      = 4,  // 引导模式
    LOITER      = 5,  // 悬停模式
    RTL         = 6,  // 返航模式
    CIRCLE      = 7,  // 绕圈模式
    LAND        = 9,  // 降落模式
    // ... 还有更多
};

我个人习惯把模式枚举分成三类:

分类 典型模式 核心特征
手动模式 STABILIZE, ACRO 飞手直接控制姿态,飞控只做辅助稳定
辅助模式 ALT_HOLD, LOITER 飞控接管部分自由度(高度/位置)
自动模式 AUTO, GUIDED, RTL 飞控完全自主执行任务
小提示:枚举值不是连续的。比如LAND是9,中间跳过了8。这是因为历史版本中有些模式被废弃了,但枚举值保留了下来。你在SITL里写切换逻辑时,千万别用for循环遍历所有数字——会踩坑。

二、模式切换的顶层逻辑:谁在发号施令?

模式切换不是随便一个函数就能搞定的。ArduPilot里有一套完整的"切换仲裁"机制。说白了,就是问三个问题:

  1. 谁请求切换?——遥控器通道?地面站指令?还是任务脚本?
  2. 当前状态允许切换吗?——比如正在降落中,就不允许切到特技模式。
  3. 目标模式能正常初始化吗?——比如GPS没锁星,就别想切到AUTO模式。

核心代码在 GCS_Mavlink.cpp 和 AP_Mission.cpp 里。我截取一段关键逻辑:

// 简化后的模式切换入口
bool Copter::set_mode(Mode mode, ModeReason reason)
{
    // 1. 检查当前模式是否允许退出
    if (!mode_allows_exit(control_mode)) {
        gcs().send_text(MAV_SEVERITY_WARNING, "当前模式不允许退出");
        return false;
    }

    // 2. 检查目标模式是否有效
    if (!mode.is_valid()) {
        return false;
    }

    // 3. 尝试初始化目标模式
    if (!mode.enter()) {
        gcs().send_text(MAV_SEVERITY_CRITICAL, "模式初始化失败");
        return false;
    }

    // 4. 执行切换
    control_mode = mode;
    return true;
}
注意:我曾经在SITL仿真里遇到一个诡异问题——模式切换偶尔会失败,但没有任何报错。后来发现是mode.enter()里有个传感器健康检查,而仿真环境里某个虚拟传感器没按时响应。所以你在写SITL测试用例时,一定要模拟传感器延迟场景。

三、飞行模式与状态机的关系:模式是壳,状态机是核

很多人把"飞行模式"和"状态机"混为一谈。其实它们的关系是这样的:

  • 飞行模式是用户可见的"外壳"——比如你拨动遥控器开关,飞机从Loiter切到RTL。
  • 状态机是每个模式内部的"引擎"——比如RTL模式内部,要经历"爬升到安全高度→调转机头→飞向Home→下降降落"这几个状态。

说白了,每个模式内部都跑着一个独立的状态机。我画了张图帮你理解:

飞行模式与内部状态机的关系 飞行模式层(用户可见) LOITER 模式 RTL 模式 AUTO 模式 内部状态机层(每个模式独立) 爬升阶段 转向阶段 飞向Home 每个模式内部的状态机独立运行,切换模式时状态机重置

你看,RTL模式内部有三个子状态。每个子状态都有自己的进入条件、执行逻辑和退出条件。这就是状态机的典型用法。

四、状态机在SITL仿真中的调试技巧

在SITL里调试模式切换,我建议你关注这几个点:

  • 用 --console 参数启动SITL,可以实时看到模式切换的日志输出。
  • 在 set_mode() 函数里加断点,观察 ModeReason 参数——它能告诉你切换请求来自哪里。
  • 模拟遥控器通道变化:用 rc 3 1500 这样的命令模拟摇杆输入,看模式是否按预期切换。
核心要点:模式切换不是瞬间完成的。从请求到生效,中间要经过"检查→初始化→切换→后处理"四个阶段。任何一个阶段失败,切换都会被拒绝。你在SITL里测试时,一定要覆盖这些失败场景。

五、避坑指南:我踩过的三个坑

做SITL仿真这些年,我在模式切换上栽过不少跟头。挑三个典型的说说:

  1. 模式切换太快导致死锁——我曾经在测试脚本里每100ms切换一次模式,结果飞控直接卡死。后来发现是状态机还没退出完,新的切换请求就来了。解决方案:加一个mode_switch_in_progress标志位。
  2. 传感器未就绪就切模式——有一次SITL启动后立刻切到AUTO模式,结果飞控报"GPS not ready"。其实仿真环境里GPS需要几秒才能完成搜星。我后来在测试脚本里加了sleep(5)。
  3. 模式枚举值混淆——ArduPilot不同版本之间,模式枚举值可能不一样。我吃过亏,把Copter-3.6的枚举值直接用在4.0上,结果切到了完全不同的模式。所以一定要检查你用的固件版本对应的枚举定义。

好了,模式切换的基础架构就聊到这儿。记住:模式是用户看到的壳,状态机是内部跑的核。搞懂这两层关系,后面学高级切换逻辑就轻松多了。