有限状态机基础:FSM的定义、状态、事件、转移、动作等核心要素,以及状态转移表与状态图的表示方法
有限状态机(Finite State Machine, FSM)是嵌入式系统、通信协议、用户界面逻辑和硬件控制器设计中最基础的建模工具。它描述了一个系统在有限个明确状态之间,如何响应外部或内部事件而进行迁移,并在迁移过程中执行特定动作。理解FSM的核心要素与两种标准表示方法(状态转移表与状态图),是后续学习层次状态机(HSM)的基石。本章将系统拆解FSM的五大核心要素,并深入讲解状态转移表与状态图的构建规则与工程实践。
上图展示了FSM的核心要素与两种表示方法之间的逻辑关系。在工程实践中,状态转移表与状态图并非互斥,而是互补的:状态图用于设计阶段的直观推演与评审,状态转移表则用于实现阶段的逻辑核对与自动化验证。下面我们将逐一深入剖析。
2.1 FSM的核心要素:状态、事件、转移、动作
一个完整的FSM模型由五个基本要素构成,它们共同定义了系统的全部行为边界。理解这些要素的精确含义,是避免状态爆炸和逻辑混乱的前提。
2.1.1 状态(State)
状态是系统在某一时刻所处的稳定情形,它由一组内部变量(如寄存器值、计数器、标志位)的特定组合来表征。状态必须是互斥的(同一时刻只能处于一个状态)且穷尽的(所有可能情形都被覆盖)。在建模时,状态应具有明确的语义,例如:IDLE(空闲)、READY(就绪)、BUSY(忙)、ERROR(错误)。状态的数量应保持最小化,避免将“子状态”或“条件分支”错误地建模为独立状态。
2.1.2 事件(Event)
事件是触发状态转移的瞬时信号或条件。事件可以是外部的(如按键按下、数据帧到达、中断触发),也可以是内部的(如定时器超时、计算完成)。事件通常携带参数(如按键的键值、数据的长度),但事件本身不包含时间持续性。在FSM中,事件是“输入”,它驱动状态机从一个状态迁移到另一个状态。需要特别区分“事件”与“条件”:事件是“发生了什么”,而条件(Guard)是“在什么情况下允许转移”。
2.1.3 转移(Transition)
转移定义了状态机在特定事件触发下,从当前状态(源状态)切换到下一个状态(目标状态)的路径。一个完整的转移由五元组表示:源状态 + 触发事件 + 守卫条件 + 动作 + 目标状态。转移必须满足确定性:在任意状态下,对于同一事件,最多只能有一个转移被激活(即守卫条件互斥)。如果多个转移同时满足条件,则会产生竞争,这是设计中的严重错误。
2.1.4 动作(Action)
动作是在状态转移过程中执行的具体操作,它是FSM的输出。动作分为三类:进入动作(Entry Action,进入某状态时执行)、退出动作(Exit Action,离开某状态时执行)、转移动作(Transition Action,在转移过程中执行)。动作可以是赋值、调用函数、发送消息、置位/清零引脚等。动作的执行时机至关重要:进入动作在目标状态生效前执行,退出动作在源状态失效后执行。
2.1.5 初始状态与终止状态
FSM必须有一个初始状态(用实心圆点指向),它是系统复位或上电后进入的第一个状态。终止状态(用同心圆表示)是可选的,用于表示流程结束。在嵌入式系统中,FSM通常设计为循环运行,即没有终止状态,而是通过回到初始状态来形成闭环。
2.2 状态转移表:结构化逻辑的精确表达
状态转移表是一种用二维表格形式描述FSM行为的方法。它特别适合用于逻辑验证、代码自动生成和文档化。表格的行代表当前状态,列代表触发事件,单元格内填写“目标状态/动作”。
2.2.1 构建步骤
- 列出所有状态:包括初始状态和所有可能的稳定状态。
- 列出所有事件:包括外部输入和内部定时/信号事件。
- 填充转移关系:对于每个“状态+事件”组合,确定目标状态和要执行的动作。如果该组合不发生转移(即事件被忽略),则标记为“-”或“无转移”。
- 检查完备性与冲突:确保每个状态下的每个事件都有明确的处理(转移或忽略),且不存在两个冲突的转移。
2.2.2 状态转移表示例
以一个简单的自动售货机控制器为例(仅接受1元硬币,商品价格2元),其状态转移表如下:
| 当前状态 | 事件:投币(1元) | 事件:取消 | 事件:出货完成 |
|---|---|---|---|
| IDLE(空闲) | 累计金额=1 / 进入WAIT_1 | -(忽略) | -(不可能) |
| WAIT_1(等待1元) | 累计金额=2 / 进入RELEASE | 退还1元 / 进入IDLE | -(不可能) |
| RELEASE(出货) | -(忽略) | -(忽略) | 累计金额=0 / 进入IDLE |
上表中,累计金额=1是动作,进入WAIT_1是目标状态。这种表格形式可以轻松转换为C语言中的switch-case结构或查表法实现。
2.3 状态图:可视化建模与沟通利器
状态图(State Diagram)是UML中用于描述状态机的图形化工具。它比状态转移表更直观,特别适合表达复杂的分支、循环和并发逻辑。状态图的基本元素包括:圆角矩形(状态)、实心圆(初始状态)、带箭头的连线(转移)、以及标注在连线上的“事件[守卫条件]/动作”。
2.3.1 状态图绘制规范
- 状态节点:使用圆角矩形,内部标注状态名称。可增加内部区域标注进入动作(entry/)和退出动作(exit/)。
- 初始状态:使用实心圆点,并有一条无事件标注的箭头指向初始状态。
- 转移箭头:使用带箭头的实线,从源状态指向目标状态。标注格式为:
事件(参数) [守卫条件] / 动作。其中守卫条件和动作可省略。 - 自转移:如果事件触发后状态不变,则画一个从状态指向自身的箭头。
2.3.2 状态图与状态转移表的对比
| 维度 | 状态转移表 | 状态图 |
|---|---|---|
| 可读性 | 适合机器阅读和逻辑核对 | 适合人类理解和设计评审 |
| 复杂逻辑表达 | 表格会迅速膨胀,难以表达嵌套 | 可以清晰表达层次和并发(配合HSM) |
| 错误检查 | 容易检查遗漏(空单元格) | 容易检查死循环和不可达状态 |
| 代码生成 | 可直接映射为查表法或switch-case | 需要借助工具(如Stateflow)转换 |
2.4 实战案例:串口数据帧解析器
下面通过一个串口接收状态机的设计案例,完整演示从需求到状态图、再到状态转移表的全过程。该案例是嵌入式开发中最常见的场景之一。
需求与约束:假设我们需要解析一个简单的数据帧,帧格式为:0xAA (帧头) + LEN (1字节长度) + DATA (LEN字节) + CRC (1字节校验)。串口每次中断接收1字节。要求状态机能够识别帧头、接收长度、累积数据、校验CRC,并在校验失败时丢弃并重新同步。
状态定义:
SYNC:等待帧头(0xAA)。GET_LEN:接收长度字节。GET_DATA:接收数据字节,需要计数。GET_CRC:接收校验字节。DONE:帧接收完成,处理数据。
关键事件:RX_BYTE(b)(收到字节b)、CRC_OK(校验通过)、CRC_FAIL(校验失败)。
状态图逻辑描述:在SYNC状态,若收到0xAA,则转移到GET_LEN;否则停留在SYNC。在GET_LEN状态,保存长度并转移到GET_DATA。在GET_DATA状态,每收到一字节存入缓冲区,计数器减1,当计数器为0时转移到GET_CRC。在GET_CRC状态,收到字节后计算CRC,若通过则进入DONE,否则回到SYNC。在DONE状态,处理完数据后无条件回到SYNC。
状态转移表(部分):
| 当前状态 | 事件:RX_BYTE(b) | 事件:CRC_OK / CRC_FAIL |
|---|---|---|
| SYNC | [b==0xAA] / 进入GET_LEN;[b!=0xAA] / 忽略 | - |
| GET_LEN | 保存LEN=b / 进入GET_DATA | - |
| GET_DATA | 保存数据,计数减1;[计数==0] / 进入GET_CRC | - |
| GET_CRC | 计算CRC / 进入DONE 或 回到SYNC | CRC_OK / 进入DONE;CRC_FAIL / 进入SYNC |
| DONE | 处理数据 / 进入SYNC | - |
分析判断过程:在GET_DATA状态,我们使用了一个内部计数器变量。这个计数器不是状态,而是状态内部的数据。如果错误地将计数器的每个值都建模为状态,会导致状态爆炸。这是初学者常犯的错误。另外,在GET_CRC状态,我们通过计算CRC的结果来决定是进入DONE还是回到SYNC,这体现了“动作”与“转移”的配合。
结果与复盘:该状态机在实现时,使用switch(state)加case分支,每个case内处理事件。通过状态转移表,我们可以轻松验证所有分支是否覆盖完整。实际测试中,该设计能正确解析连续数据流,且在CRC错误后能快速重新同步到帧头。复盘经验:状态机的健壮性取决于对异常事件(如帧头丢失、长度超限)的处理,务必在转移表中显式列出所有可能的事件组合。
2.5 总结与复盘
本章详细阐述了FSM的五大核心要素:状态、事件、转移、动作以及初始/终止状态。状态转移表以其结构化的特性,成为逻辑验证和代码生成的可靠依据;状态图以其直观性,成为设计沟通和复杂逻辑推演的首选工具。两者结合使用,可以构建出既严谨又易于维护的嵌入式系统模型。
在实际项目中,建议采用“先画状态图,再填状态转移表,最后编码”的流程。状态图帮助团队对齐设计意图,状态转移表帮助工程师逐行核对逻辑,编码则是对表格的机械翻译。掌握FSM基础后,下一阶段将面临状态爆炸和复用性差的挑战,这正是引入层次状态机(HSM)的动机所在。