状态机基础:状态、事件、转移、动作的定义与关系
有限状态机(Finite State Machine, FSM)是嵌入式系统、通信协议、用户界面和业务逻辑建模中最基础也最核心的形式化工具。要真正掌握层次状态机(HSM)和复杂系统建模,首先必须透彻理解FSM的四个基本要素:状态(State)、事件(Event)、转移(Transition)和动作(Action)。这四个要素并非孤立存在,而是通过“当前状态 + 事件 → 转移条件 → 执行动作 → 进入新状态”这一因果链紧密耦合。本章将从定义出发,厘清四者的边界与联系,并通过一个电梯控制器的案例,展示如何将抽象概念落地为可实现的模型。
上图展示了四个要素之间的基本关系:状态是系统在某一时刻的稳定存在形式;事件是促使系统做出反应的外部或内部刺激;转移定义了系统在特定事件下从一个状态切换到另一个状态的路径;动作则是转移发生时或状态进入/退出时执行的具体行为。下面我们逐一深入剖析。
2.1 状态:系统行为的稳定片段
核心原理:状态是有限状态机中最基本的构成单元,它表示系统在某一时刻所处的“模式”或“条件”。在UML状态图中,状态通常用圆角矩形表示,内部可以包含三个标准行为:entry(进入动作)、do(活动动作)和exit(退出动作)。状态具有“记忆性”——它决定了系统对后续事件的响应方式。例如,一个串口接收模块在“等待起始位”状态和“接收数据位”状态下,对同一个“时钟沿”事件的处理逻辑完全不同。
判断方法:识别状态时,应遵循三个原则:互斥性(系统在任一时刻只能处于一个状态)、完整性(所有可能的情况都应被某个状态覆盖)、稳定性(状态应代表一段可辨识的、持续一定时间的行为模式)。如果两个“状态”在行为上没有本质区别,就应该合并;如果某个状态内部存在明显不同的子行为,则考虑引入层次状态机。
| 状态类型 | 说明 | 典型示例 |
|---|---|---|
| 初始状态 | 系统启动后自动进入的状态 | 上电后的“自检”状态 |
| 中间状态 | 处理过程中的稳定阶段 | 通信协议中的“等待ACK” |
| 终止状态 | 流程结束或系统停止的状态 | “关机完成”状态 |
| 历史状态 | 记录上次退出时的子状态(HSM特性) | 中断恢复后回到之前的运行模式 |
2.2 事件:驱动状态转移的触发器
核心原理:事件是发生在某一时刻、能够被状态机识别并响应的刺激。事件可以是外部的(按键按下、网络数据包到达、传感器中断)或内部的(定时器超时、计算完成、错误标志置位)。事件具有瞬时性——它不像状态那样持续存在,而是在某个时间点发生并触发一次处理。在建模时,事件通常用带箭头的转移线上的标签表示,格式为“事件名[守卫条件]/动作”。
操作流程:定义事件时,建议遵循以下步骤:
- 枚举所有刺激源:列出系统可能接收到的所有输入,包括硬件中断、软件消息、时间条件。
- 抽象事件类型:将具体的物理信号抽象为语义事件,例如“GPIO上升沿”抽象为“用户按下确认键”。
- 区分事件与条件:事件是“发生了什么”,条件是“当前是否满足要求”。例如“温度超过80℃”是事件,“温度传感器已校准”是条件。
- 定义事件参数:事件可以携带数据,如“收到数据帧”事件应包含帧长度和内容。
2.3 转移:状态之间的路径与守卫
核心原理:转移定义了状态机如何响应事件。一个完整的转移包含五个要素:源状态、触发事件、守卫条件(Guard)、动作(Action)和目标状态。转移的语义是:当系统处于源状态时,如果触发事件发生,并且守卫条件为真,则执行动作并进入目标状态。守卫条件是可选的,但它是实现复杂逻辑的关键——它允许同一个事件在不同条件下导向不同的状态。
判断方法:设计转移时,需要检查以下问题:
- 每个状态对每个可能的事件是否都有明确的响应?(要么转移,要么忽略)
- 守卫条件是否完备?是否存在两个转移的守卫条件同时为真的情况?
- 是否存在不可达状态或死锁状态?
- 转移动作是否应该放在源状态的exit、转移本身还是目标状态的entry中?
// 转移语法示例(类UML伪代码)
state IDLE:
on BUTTON_PRESSED [is_door_closed] / start_motor() -> RUNNING
on BUTTON_PRESSED [is_door_open] / beep_warning() -> IDLE
on TIMEOUT / -> IDLE
2.4 动作:状态机行为的执行者
核心原理:动作是状态机对外部世界产生影响的执行步骤。动作分为三类:转移动作(在转移过程中执行,如发送数据、启动定时器)、入口动作(进入状态时执行,如初始化变量)、出口动作(离开状态时执行,如关闭外设)。动作是“无持续时间的”——它被视为瞬时完成,如果需要持续行为,应使用状态的do活动。
操作流程:决定动作放置位置时,遵循以下优先级:
- 如果动作只在该转移路径上需要,放在转移动作中。
- 如果动作在进入该状态的任何情况下都需要,放在入口动作中。
- 如果动作在离开该状态的任何情况下都需要,放在出口动作中。
- 如果动作需要持续执行直到被中断,放在
do活动中。
| 动作类型 | 执行时机 | 典型用途 | 注意事项 |
|---|---|---|---|
| entry | 进入状态瞬间 | 初始化局部变量、开启LED | 不能依赖转移事件参数 |
| do | 状态持续期间 | 电机运转、数据采集 | 需要支持中断/取消机制 |
| exit | 离开状态瞬间 | 关闭外设、保存上下文 | 不能访问转移目标状态信息 |
| 转移动作 | 转移过程中 | 发送消息、记录日志 | 可以访问事件参数和守卫条件结果 |
2.5 案例拆解:电梯控制器中的四要素
场景与约束:设计一个单电梯控制器,要求响应楼层呼叫和轿厢内选层。系统有3个楼层,电梯状态包括:静止待机(IDLE)、上行(UP)、下行(DOWN)、开门(DOOR_OPEN)。约束条件:电梯门关闭后才能移动;到达目标楼层后自动开门;超时未操作则自动关门。
初始现象与目标:初始状态为IDLE,电梯停在一层。目标是构建一个可运行的FSM模型,能够处理以下事件:FLOOR_CALL(n)(楼层呼叫)、CABIN_SELECT(n)(轿厢选层)、ARRIVED(到达楼层)、DOOR_TIMEOUT(开门超时)。
分析判断过程:首先识别状态:IDLE、UP、DOWN、DOOR_OPEN。然后定义关键事件:FLOOR_CALL、CABIN_SELECT、ARRIVED、DOOR_TIMEOUT。转移设计如下:
- IDLE状态收到
FLOOR_CALL(n)且n>当前层 → 执行start_motor(UP)→ 进入UP状态。 - UP状态收到
ARRIVED且当前层=目标层 → 执行stop_motor()、open_door()→ 进入DOOR_OPEN状态。 - DOOR_OPEN状态收到
DOOR_TIMEOUT→ 执行close_door()→ 进入IDLE状态。 - UP状态收到
FLOOR_CALL(n)且n>当前层 → 更新目标层(转移动作),不改变状态。
实际操作步骤:使用状态转移表实现该模型,伪代码如下:
switch(current_state) {
case IDLE:
if (event == FLOOR_CALL) {
target_floor = event.floor;
if (target_floor > current_floor) {
start_motor(UP);
current_state = UP;
} else if (target_floor < current_floor) {
start_motor(DOWN);
current_state = DOWN;
}
}
break;
case UP:
if (event == ARRIVED) {
stop_motor();
open_door();
current_state = DOOR_OPEN;
} else if (event == FLOOR_CALL && event.floor > current_floor) {
target_floor = event.floor; // 转移动作:更新目标
}
break;
case DOOR_OPEN:
if (event == DOOR_TIMEOUT) {
close_door();
current_state = IDLE;
}
break;
}
结果对比与复盘:该模型成功处理了连续呼叫、中途更新目标等场景。但发现一个问题:如果电梯在UP状态时收到一个低于当前层的呼叫,该事件被忽略,这可能导致用户等待过久。改进方案:引入“待处理队列”或使用层次状态机,在父状态中统一处理“新呼叫”事件,根据方向优先级决定是否改变运行方向。这个案例说明,基础FSM适合处理简单逻辑,但面对复杂交互时,需要结合队列机制或HSM的“历史状态”和“守卫条件”来增强表达能力。