3. 有限状态机基础及其在飞控中的映射

有限状态机(Finite State Machine,FSM)是飞控软件中最基础、也最可靠的行为建模工具之一。它的核心思想非常朴素:系统在任意时刻只处于有限个“状态”中的一个,只有当某个“事件”发生、并且满足“守卫条件”时,才沿着一条“转移”切换到另一个状态,并在进入、退出或转移过程中执行相应的“动作”。飞控中的飞行模式管理,本质上就是一台或多台状态机在协同工作。

一、五个基本概念:状态、事件、转移、动作、守卫条件

状态(State)表示系统在某一时间段内持续保持的一种行为模式。例如“手动模式”“定高模式”“定点模式”“返航模式”“降落模式”。状态具有持续性,它不是一个瞬间动作,而是一段稳定的行为区间。在飞控中,状态通常对应一组被激活的控制律、一组被允许的传感器输入以及一组被限制的输出通道。

事件(Event)是触发状态机思考“是否要改变”的外部或内部信号。它可以是遥控器开关拨动、地面站指令、传感器阈值越限、任务航点到达、电池电压低于门限、GPS 定位质量下降等。事件是瞬时的,它只表示“某件事发生了”,并不直接决定状态是否切换。

转移(Transition)是状态之间的有向连接,表示“从当前状态到目标状态”的切换路径。一条完整的转移通常写作:源状态 --[事件 / 守卫条件 / 动作]--> 目标状态。转移是状态机的骨架,它决定了系统所有可能的行为路径。

动作(Action)是状态机在特定时机执行的具体操作,常见的有三类:进入动作(entry action)、退出动作(exit action)和转移动作(transition action)。例如进入“返航模式”时设置目标航点为 Home 点、退出“定点模式”时清除位置保持积分项、转移过程中点亮模式指示灯等。

守卫条件(Guard Condition)是转移上的布尔判断,只有为真时转移才被允许。它是状态机的“安全闸门”。例如“切换到定点模式”的守卫条件可能是:GPS 定位类型 ≥ 3D Fix、卫星数 ≥ 8、水平精度因子 HDOP ≤ 1.5、遥控器模式开关处于指定档位。守卫条件不满足时,即使事件发生,状态机也保持原状态。

记忆口诀:状态是“我在哪”,事件是“发生了什么”,守卫是“能不能走”,转移是“往哪走”,动作是“走的时候做什么”。

二、用状态机描述飞行模式:一张可读的模式转移图

下面这张图把一架多旋翼常见的飞行模式抽象为一台状态机。图中每个圆角矩形是一个状态,箭头是转移,箭头上的文字标注了触发事件与守卫条件。它不追求覆盖所有厂商的全部模式,而是展示“如何用状态机语言描述飞行模式”这一方法本身。

多旋翼飞行模式状态机(示例) 圆角矩形 = 状态 箭头 = 转移 箭头文字 = 事件 [守卫条件] / 动作 上电 DISARMED 未解锁 / 电机停转 STABILIZE 手动增稳 ALT_HOLD 定高 LOITER 定点悬停 AUTO 自动任务 RTL 返航 LAND 降落 自检通过 解锁事件 [GPS≥3D, 遥控在线] / 电机怠速 模式开关→定高 [气压计有效] / 锁定当前高度 模式开关→定点 [GPS Fix≥3D, HDOP≤1.5] / 记录悬停点 GPS 丢失 [Fix<3D] 任务启动 [航点已加载] / 切换导航源 任务暂停 / 遥控接管 任务完成 / 低电量 [电压<3.5V/节] / 设置 Home 为目标 返航指令 / 失控保护 到达 Home 上方 [高度<2m] / 下降至触地 触地检测 / 上锁 图例 正常模式切换 安全 / 保护性转移 降级 / 回退转移 [ ] 内为守卫条件

三、从图到代码:状态机在飞控中的映射方式

把上面的图落到飞控代码里,通常有两种主流实现方式。第一种是显式状态机:用一个枚举变量保存当前状态,在主循环或定时任务中根据事件和守卫条件做 switch-case 判断。第二种是表驱动状态机:把“源状态、事件、守卫、目标状态、动作”组织成一张转移表,由统一的引擎查表执行。前者直观、易调试,后者扩展性好、便于形式化验证。

下面是一段表驱动状态机的核心数据结构示例,它把图中的一条转移直接映射为一行记录:

typedef enum { DISARMED, STABILIZE, ALT_HOLD, LOITER, AUTO, RTL, LAND } FlightState;
typedef enum { EV_ARM, EV_MODE_SW, EV_GPS_LOST, EV_BATT_LOW, EV_TOUCHDOWN } Event;

typedef struct {
    FlightState src;        // 源状态
    Event       evt;        // 触发事件
    bool      (*guard)(void); // 守卫条件函数指针
    FlightState dst;        // 目标状态
    void      (*action)(void); // 转移动作
} Transition;

/* 示例:LOITER -> ALT_HOLD 的降级转移 */
{ LOITER, EV_GPS_LOST, gps_fix_less_than_3d, ALT_HOLD, clear_loiter_target },
/* 示例:ALT_HOLD -> LOITER 的升级转移 */
{ ALT_HOLD, EV_MODE_SW, gps_fix_ok_and_hdop_ok, LOITER, record_hover_point },

引擎在每个控制周期(例如 400 Hz 的主循环中降频到 50 Hz 执行)做三件事:读取当前状态、收集本周期发生的事件、按优先级扫描转移表。一旦某条转移的 src 与当前状态匹配、evt 已发生、guard() 返回真,就执行 action() 并把状态更新为 dst。若同一周期有多条转移同时满足,必须按安全优先级排序,例如“低电量返航”优先于“用户切换到定点”。

四、守卫条件与动作的工程细节

守卫条件不是装饰,它直接决定飞行安全。以“进入 LOITER”为例,一个可用的守卫条件组合是:定位类型为 3D Fix 或更高、可见卫星数不少于 8、HDOP 不大于 1.5、垂直速度绝对值小于 0.5 m/s、遥控器信号强度正常。这些条件全部为真才允许切换,否则状态机保持在 ALT_HOLD,并通过地面站发送一条“模式切换被拒绝”的提示。

动作则要区分“进入”“退出”和“转移中”。进入 LOITER 时记录当前位置作为悬停目标点、把水平位置控制器从速度环切换为位置环;退出 LOITER 时清除位置积分项、避免残留积分导致姿态抖动;转移过程中点亮对应模式指示灯、写入一条飞行日志。这些动作看似琐碎,却是状态机从“能跑”到“可靠”的关键。

常见错误:把守卫条件写成“事件发生就切换”。例如只判断“模式开关拨到定点档”就切 LOITER,而不检查 GPS 质量,会导致飞机在定位不可用时进入定点模式,出现漂移甚至失控。守卫条件必须覆盖所有影响该模式安全运行的前提。

五、用状态机描述飞行模式的三个原则

  • 状态互斥且完备:任意时刻飞机只能处于一个飞行模式;所有可能的飞行阶段都应被某个状态覆盖,不能出现“无状态可归”的悬空区间。
  • 转移有明确触发与守卫:每条转移都要能回答“什么事件触发”“什么条件允许”“切换时做什么”,三者缺一不可。
  • 安全转移优先:涉及返航、降落、上锁等保护性转移,应设置最高优先级,并允许其绕过部分普通守卫条件(但仍需满足最低安全前提,如返航前确认 Home 点有效)。