在嵌入式系统、游戏开发和协议解析等领域,状态机是一种高效且常用的设计模式。它通过将系统的行为划分为不同的状态,并根据输入事件在这些状态之间进行转换,简化了复杂系统的设计和维护。本文将介绍一个由 happyDom 开发的 C 语言状态机框架,并演示如何使用它来构建一个简单的状态机。
框架概述
happyDom/stateMachineC 是一个轻量级的 C 语言状态机框架,适用于嵌入式环境。它提供了定义状态、事件和状态转换的功能,支持有限状态机(FSM)的创建和管理。该框架的主要特点包括:
-
简洁易用:直观的 API 设计,便于集成和使用。
-
高性能:经过优化,适合资源受限的嵌入式环境。
-
可扩展性:支持动态添加状态和事件,灵活应对复杂场景。
快速入门
- 设计状态跳转图
在本示例中,我们实现一个简单的开关机控制状态机。状态机的设计如下图所示:
- 克隆仓库
首先,使用 Git 克隆状态机框架的源代码
git clone git@gitee.com:DyyYq/stateMachineC.git
- 包含头文件
在项目中包含状态机框架的头文件:
#include "stateMachine.h"
- 定义状态
定义枚举变量表示状态机的各个状态。在此示例中,我们设定了以下 4 个状态:
-
stPreOff:预关机状态
-
stOff:关机状态
-
stPreOn:预开机状态
-
stOn:开机状态
enum myStates {
stPreOff = 0, // 预关机状态
stOff, // 关机状态
stPreOn, // 预开机状态
stOn, // 开机状态
stCnt // 状态机的状态数量
};
- 定义跳转事件
状态机根据事件进行状态转换。在本示例中,我们只定义一个事件,即用户按下按键 s 时触发状态转换。事件处理函数如下:
// 状态机跳转事件:检测是否按下 's' 键
smEventResult_t keyPressed(smUnit_t *pSt) {
if (pSt->roundCounter > 3 && _kbhit()) {
if ('s' == _getch()) {
return go;
}
}
return aWait;
}
为了判断预开机和预关机动作是否完成,我们添加了另一个事件判定:
// 判定预开机和预关机动作是否完成,简化处理为 3 秒后动作完成
smEventResult_t preActionCmplt(smUnit_t *pSt) {
if (pSt->roundCounter >= 3) {
return go;
}
return aWait;
}
- 定义状态动作
为每个状态定义动作。每个状态的具体行为如下:
-
stOff 状态时,不执行任何操作。
-
stPreOn 状态时,打印“加载用户配置中”信息。
-
stPreOn 状态下,开机过程持续 3 秒。
-
stOn 状态时,持续显示开机时长。
-
stPreOff 状态时,打印“保存用户配置中”信息。
-
stPreOff 状态下,关机过程持续 3 秒。
// stPreOff 状态的 Entry 动作
void actionEntry_preOff(smUnit_t *pSt) {
printf("保存用户配置中,请稍候!\n");
}
// stPreOff 状态的 Do 动作
void actionDo_preOff(smUnit_t *pSt) {
if (pSt->roundCounter < 3) {
printf("%d\n", 3 - pSt->roundCounter);
} else if (pSt->roundCounter == 3) {
printf("用户配置保存完成\n");
}
}
// stPreOn 状态的 Entry 动作
void actionEntry_preOn(smUnit_t *pSt) {
printf("加载用户配置中,请稍候!\n");
}
// stPreOn 状态的 Do 动作
void actionDo_preOn(smUnit_t *pSt) {
if (pSt->roundCounter < 3) {
printf("%d\n", 3 - pSt->roundCounter);
} else if (pSt->roundCounter == 3) {
printf("用户配置加载完成\n");
}
}
// stOn 状态的 Do 动作
void actionDo_on(smUnit_t *pSt) {
printf("已经开机 %ds\n", pSt->roundCounter);
}
- 初始化状态机并注册事件
定义并初始化状态机变量,将状态动作和跳转事件注册到状态机中。最终,通过每秒轮询运行状态机。
int main(int argc, char const *argv[]) {
// 定义状态机变量
stateMachine_t demoSM;
// 初始化状态机,指定状态数量,指定默认状态
fsm_init(&demoSM, stCnt, stOff);
// 为各状态注册状态动作
demoSM.actionSignUp(&demoSM, stPreOff, actionEntry_preOff, actionDo_preOff, NULL);
demoSM.actionSignUp(&demoSM, stPreOn, actionEntry_preOn, actionDo_preOn, NULL);
demoSM.actionSignUp(&demoSM, stOn, NULL, actionDo_on, NULL);
// 注册状态跳转事件
demoSM.eventSingUp(&demoSM, stOff, stPreOn, keyPressed);
demoSM.eventSingUp(&demoSM, stPreOn, stOn, preActionCmplt);
demoSM.eventSingUp(&demoSM, stOn, stPreOff, keyPressed);
demoSM.eventSingUp(&demoSM, stPreOff, stOff, preActionCmplt);
// 运行状态机
while (1) {
demoSM.run(&demoSM);
Sleep(1000); // 每秒钟轮询一次
}
}
运行效果
以下是状态机运行时的效果示意: