C语言_有限状态机(FSM)

article/2025/9/29 17:43:30

C语言_有限状态机(Finite State Machine)

基本介绍

许多小型或复杂的应用程序都使用有限状态机 (FSM),C 语言中的有限状态机是嵌入式系统的流行设计模式之一,有限状态机使开发变得容易和顺利。

有很多设备使用事件基态,如咖啡机、自动售货机、POS 设备、门锁系统等。一些 POS 设备使用事件表,在事件表中使用事件处理程序注册事件,通过相关条件触发事件的执行。
在这里插入图片描述

本文中,使用C语言创建一个简易的ATM状态机。 ATM 机的状态可以通过即将发生的事件进行更改。ATM状态机包含以下几个状态:

  • Idle State
  • Card Inserted State
  • Pin entered State
  • Option Selected State
  • Amount Entered State

初始化时,ATM处于闲置状态,之后进入插卡状态,插卡处理完成之后需要用户输入密码,输入密码之后进行相关选项的操作,设置选项完成之后输入金额。创建一个状态机,按照以下步骤进行:

  • Gather the information which the user wants.
  • Analyze the all gather information and sketch the state transition diagram.
  • create a code skeleton of the state machine.
  • Make sure the transition (changing state) work properly
  • Implement all the required information in the code skeleton of the state machine.
  • Test the implemented state machine.

收集需求——>分析需求绘制状态图——>创建代码框架——>确认状态转换是否正确——>编写状态的具体动作——>测试状态机。

实现方法

在C语言中,有两种常用的方式实现基于事件的状态机:一种是通过嵌套的switch case(或者if else)实现,一种是look up table(查表)实现。

look up table 查表法实现有限状态机

结构体数组创建有限状态机是一种比较优雅的方式。状态机的状态和事件封装在一个结构中,并在适当的状态和事件上调用函数指针(事件处理程序),程序的可读性比较好。

#include <stdio.h>
//Different state of ATM machine
typedef enum
{Idle_State,Card_Inserted_State,Pin_Eentered_State,Option_Selected_State,Amount_Entered_State,last_State
} eSystemState;
//Different type events
typedef enum
{Card_Insert_Event,Pin_Enter_Event,Option_Selection_Event,Amount_Enter_Event,Amount_Dispatch_Event,last_Event
} eSystemEvent;
//typedef of function pointer
typedef eSystemState(*pfEventHandler)(void);
//structure of state and event with event handler
typedef struct
{eSystemState eStateMachine;eSystemEvent eStateMachineEvent;pfEventHandler pfStateMachineEvnentHandler;
} sStateMachine;
//function call to dispatch the amount and return the ideal state
eSystemState AmountDispatchHandler(void)
{return Idle_State;
}
//function call to Enter amount and return amount entered state
eSystemState EnterAmountHandler(void)
{return Amount_Entered_State;
}
//function call to option select and return the option selected state
eSystemState OptionSelectionHandler(void)
{return Option_Selected_State;
}
//function call to enter the pin and return pin entered state
eSystemState EnterPinHandler(void)
{return Pin_Eentered_State;
}
//function call to processing track data and return card inserted state
eSystemState InsertCardHandler(void)
{return Card_Inserted_State;
}
//Initialize array of structure with states and event with proper handler
sStateMachine asStateMachine[] =
{{ Idle_State,				Card_Insert_Event,			InsertCardHandler },{ Card_Inserted_State,		Pin_Enter_Event,			EnterPinHandler },{ Pin_Eentered_State,		Option_Selection_Event,		OptionSelectionHandler },{ Option_Selected_State,	Amount_Enter_Event,			EnterAmountHandler },{ Amount_Entered_State,		Amount_Dispatch_Event,		AmountDispatchHandler }
};
//main function
int main(int argc, char *argv[])
{eSystemState eNextState = Idle_State;while (1){//Api read the eventeSystemEvent eNewEvent = read_event();if ((eNextState < last_State) && (eNewEvent < last_Event) && 		(asStateMachine[eNextState].eStateMachineEvent == eNewEvent) && (asStateMachine[eNextState].pfStateMachineEvnentHandler != NULL)){// function call as per the state and event and return the next state of the finite state machineeNextState = (*asStateMachine[eNextState].pfStateMachineEvnentHandler)();}else{//Invalid}}return 0;
}

状态机结构体中包含状态,事件编号,执行状态的动作。执行动作将返回对应的状态值。填写状态机的结构体数组,状态、触发事件和即将执行的动作。状态机运行时,初始化状态机,实践中,通常从外部API读入实践触发动作的执行。执行状态机中的动作,返回下一个状态的状态编号。

switch case实现有限状态机

这是实现状态机的最简单方法。使用 if-else 或 switch case 来检查状态并触发事件。如果状态和触发事件的组合匹配,则执行事件处理程序并更新下一个状态。这取决于检查第一个状态或事件的要求。 在下面的示例代码中,首先验证状态,然后检查触发的事件。

#include <stdio.h>
//Different state of ATM machine
typedef enum
{Idle_State,Card_Inserted_State,Pin_Eentered_State,Option_Selected_State,Amount_Entered_State,
} eSystemState;
//Different type events
typedef enum
{Card_Insert_Event,Pin_Enter_Event,Option_Selection_Event,Amount_Enter_Event,Amount_Dispatch_Event
} eSystemEvent;
//Prototype of eventhandlers
eSystemState AmountDispatchHandler(void)
{return Idle_State;
}
eSystemState EnterAmountHandler(void)
{return Amount_Entered_State;
}
eSystemState OptionSelectionHandler(void)
{return Option_Selected_State;
}
eSystemState EnterPinHandler(void)
{return Pin_Eentered_State;
}
eSystemState InsertCardHandler(void)
{return Card_Inserted_State;
}
int main(int argc, char *argv[])
{eSystemState eNextState = Idle_State;eSystemEvent eNewEvent;while (1){//Read system EventseSystemEvent eNewEvent = ReadEvent();switch (eNextState){case Idle_State:{if (Card_Insert_Event == eNewEvent){eNextState = InsertCardHandler();}}break;case Card_Inserted_State:{if (Pin_Enter_Event == eNewEvent){eNextState = EnterPinHandler();}}break;case Pin_Eentered_State:{if (Option_Selection_Event == eNewEvent){eNextState = OptionSelectionHandler();}}break;case Option_Selected_State:{if (Amount_Enter_Event == eNewEvent){eNextState = EnterAmountHandler();}}break;case Amount_Entered_State:{if (Amount_Dispatch_Event == eNewEvent){eNextState = AmountDispatchHandler();}}break;default:break;}}return 0;
}

使用switch case 和if else实现上面的状态切换和动作执行,导致程序很长,不利于后期的维护和修改。可否对上面的程序进行优化,避免这种嵌套的结构呢?

使用二维数组指针精简switch case嵌套结构

答案是肯定得,我们在上面的程序中,首先是用switch case进行了状态的判断,然后使用if else判断是否有事件触发动作。那么相当于这段代码包含两层判断,第一层大的状态判断后面跟随着一层判断。基于这个认知,自然就想到,可否使用一个二维数组,行代表一层判断,列代表这一层判断下面的子状态判断呢!

下面,使用一个二维数组替代以上switch case的嵌套,实现代码的精简:

# include <stdio.h>
typedef enum
{Idle_State,Card_Inserted_State,Pin_Eentered_State,Option_Selected_State,Amount_Entered_State,last_State
} eSystemState;
//Different type events
typedef enum
{Card_Insert_Event,Pin_Enter_Event,Option_Selection_Event,Amount_Enter_Event,Amount_Dispatch_Event,last_Event
} eSystemEvent;
//typedef of 2d array
typedef eSystemState (*const afEventHandler[last_State][last_Event])(void);
//function call to dispatch the amount and return the ideal state
eSystemState AmountDispatchHandler(void)
{return Idle_State;
}
//function call to Enter amount and return amount enetered state
eSystemState EnterAmountHandler(void)
{return Amount_Entered_State;
}
//function call to option select and return the option selected state
eSystemState OptionSelectionHandler(void)
{return Option_Selected_State;
}
//function call to enter the pin and return pin entered state
eSystemState EnterPinHandler(void)
{return Pin_Eentered_State;
}
//function call to processing track data and return card inserted state
eSystemState InsertCardHandler(void)
{return Card_Inserted_State;
}
int main(int argc, char *argv[])
{eSystemState eNextState = Idle_State;eSystemEvent eNewEvent;// Table to define valid states and event of finite state machinestatic afEventHandler StateMachine ={[Idle_State] = { [Card_Insert_Event] = InsertCardHandler },[Card_Inserted_State] = { [Pin_Enter_Event] = EnterPinHandler },[Pin_Eentered_State] = { [Option_Selection_Event] = OptionSelectionHandler },[Option_Selected_State] = { [Amount_Enter_Event] = EnterAmountHandler },[Amount_Entered_State] = { [Amount_Dispatch_Event] = AmountDispatchHandler },};while (1){// assume api to read the next eventeSystemEvent eNewEvent = ReadEvent();//Check NULL pointer and array boundaryif ((eNextState < last_State) && (eNewEvent < last_Event) && StateMachine[eNextState][eNewEvent] != NULL){// function call as per the state and event and return the next state of the finite state machineeNextState = (*StateMachine[eNextState][eNewEvent])();}else{//Invalid}}return 0;
}

在上面这段代码中,使用typedef定义函数指针,返回值正是执行动作返回的状态值。二维数组返回event,是一个执行动作的过程。这样的话,在有限状态机的实现中,定义行为state,列里面我们放置event,每一列对应一个action,就可以实现根据前一次的state和event,执行当前与之对应的action,而返回当前执行动作的state,使状态机正确转移到下一个状态。

在上面的实现方法中,一个嵌套的 switch case 替换为一个指向函数的指针数组。 精简了代码,但是同时也并降低代码的可读性。 当状态很多,触发动作的条件也很多时,会造成内存大量的浪费,因为在二维数组中,每一行里面并非所有的event都是这一行的state必须的。

以上是状态机的常见实现方法,针对嵌入式的手写代码编程,常常希望有一种简单又不需要占用太多资源的方法,并且能够有效复用。这里推荐一种基于事件驱动的有限状态机框架,QEP框架,使用函数指针映射为状态,将状态机的描述进行标准化处理,接口简单,占用资源极少。

QEP有限状态机框架

博主会长期更新关于有限状态机设计的方法与思想,欢迎关注。

reference:
https://aticleworld.com/state-machine-using-c/


http://chatgpt.dhexx.cn/article/cPKmkEfY.shtml

相关文章

Unity字体展示下载

Unity字体种类展示 这是字体包里面的图片,是不是很多种字体. 下载链接: https://download.csdn.net/download/qq_42603590/12001130 这是下载字体包的地方,很便宜.没有积分的可以留言,我发给你 有时候可能回复的不是很快(抱拳了) 喜欢的话点个赞,关注一下再走吧,谢谢

Unity 之 官网下载地址,方便各个版本的 Unity 安装包下载

Unity 之 官网下载地址&#xff0c;方便各个版本的 Unity 安装包下载 目录 Unity 之 官网下载地址&#xff0c;方便各个版本的 Unity 安装包下载 一、简单介绍 二、各个版本下载入口网址 一、简单介绍 在 Unity 的下载地址现在不是很好找&#xff0c;这里保存一下 Unity 各…

Unity入门之路0-Unity下载安装以及版本选择

文章目录 下载链接Unity Hub和Unity的关系UnityHub下载(Win)两者比较 Unity版本选择许可证问题 下载链接 一定不要百度或者去垃圾网站下载盗版网站 &#xff0c;Unity是正版免费的&#xff0c;官方很关注使用者的感受&#xff0c;所以下载官网的就没问题。 https://unity.cn/re…

UnityHub下载缓存位置

一、下载Unity各版本的编辑器 C:\Users\XXX\AppData\Local\Temp\unityhub-xxx-xxx-xxx-xxx 我电脑是 C:\Users\Administrator\AppData\Local\Temp\unityhub-xxxx-xxxx 如果你不需要备份安装包&#xff0c;那么这个缓存的文件夹&#xff0c;就与你无关了&#xff0c;因为安装完…

使用UnityHub下载任意版本Unity

目录 方法一 使用链接方法二 官网下载(适用于2018.4.23及以上版本) unityHub上只能下载官方指定的版本,很多其他版本不能下载,下面介绍的是在unityHub下载任意版本的方法 方法一 使用链接 举例: 2019.2.11f1版本的unity----> unityhub://2019.2.11f1/5f859a4cfee5 格式 unit…

Unity给游戏对象贴图、从官网下载资源、导入导出

1、新建项目、在项目场景中创建几何对象并修改参数 在层级“”中创建一个立方体&#xff08;3D对象&#xff09;&#xff0c;同理也创建一个球体 创建好的立方体会显示在场景视图中 &#xff08;从场景视图或层级视图中&#xff09;选中几何体&#xff0c;选择场景视图中竖排工…

unity下载网页所有图片

用unity的c#脚本批量下载网页上的所有图片 1、将网页的html保存到本地 在网页上鼠标右击另存为如下图所示 保存html文件 2、通过截取<img“”>获取图片存储的地址 经过两个步骤之后就可以开始着手敲代码了 代码 html下载的本地地址和要保存的图片地址 //保存在本地ht…

Unity下载方法(超详细)

一、进入官网&#xff0c;点击[下载Unity]&#xff0c;点击右上角的小人头像&#xff0c;点击[创建Unity ID](创建ID的方法你点进去按照它要求你的一步一步做就行啦)。 二、创建完Unity ID并登录(或已有Unity ID并登录)后&#xff0c;下拉网页&#xff0c;点击[下载Unity Hub]&…

Unity 改变下载资源商店中资源默认路径的方法

Unity 改变下载资源商店中资源默认路径的方法 Unity资源商店中免费资源可以被我们很好的使用&#xff0c;尤其对于暂时还不会自己设计资源的创作者。但是&#xff0c;unity默认是将资源商店的下载路径设置在了C:\Users\操作系统当前用户\AppData\Roaming\Unity\Asset Store-5.x…

Unity 各版本下载方法

开发Unity的&#xff0c;获取不同版本Unity版本和了解Unity最新动态很重要&#xff0c;现在更新迭代很频繁&#xff0c;在开发时&#xff0c;不论遇到项目升级&#xff0c;还是插件要求&#xff0c;还是老项目运行&#xff0c;总是在多个版本间切换。 是不是经常遇到&#xff0…

Unity3d_NGUI和UGUI的学习

由于之前刚入门的时候&#xff0c;应Unity3d整体发展的要求我们自学了UGUI(相对来说UGUI比NGUI做得更好一些&#xff0c;后面会有2者对比)&#xff0c;但是后来公司要求使用NGUI&#xff0c;所以我这边把之前学习UGUI&#xff08;不全&#xff0c;当时资源有限&#xff09;和NG…

NGUI与UGUI的区别及其优缺点

UIGUI与BGUI 的区别 首先说一下NGUI NGUI是严格遵循KISS原则并用C#编写的Unity&#xff08;适用于专业版和免费版&#xff09;插件&#xff0c;提供强大的UI系统和事件通知框架。其代码简洁&#xff0c;多数类少于200行代码。这意味着程序员可以很容易地扩展NGUI的功能或调节已…

NGUI的使用教程与实例

原文地址&#xff1a;http://www.tasharen.com/?page_id185 NGUI下载地址&#xff1a;点我传送 NGUI教程&#xff1a;步骤1-Scene 1.创建一个新的场景&#xff08;New Scene&#xff09;。 2.选择并删除场景里的MainCamera。 3.在NGUI菜单下选择Create a New UI&#xff0c…

Unity NGUI 插件 简介

文章目录 Unity NGUI一. NGUI基础1.1 导入NGUI插件1.2 基本UI资源1.3 制作UI图集1.4 制作UI字体1.5 UIRoot、UIPanel 和 UICamera 组件1.6 Depth (深度) 二. 核心组件2.1 UISprite (精灵)2.2 UILabel (标签)2.3 UITexture (纹理)2.4 UIButton (按钮)2.5 UISlider (进度条)2.6 U…

Unity3D种UGUI与NGUI的对比差别(2)

层级管理概念UGUI采用Hierarchy排序的方式&#xff0c;替代了NGUI中的Depth排序。更精准的说&#xff0c;NGUI的排序是通过Depth、Z值、RenderQueue共同影响的&#xff0c;整体规则过于复杂&#xff1b;而UGUI采用的排序比较简单&#xff0c;在Canvas内部元素采用Hierarchy方式…

NGUI插件] 收录最全的NGUI示例文档中文教程(更新版本3.0.5)

NGUI3.0.X官网示例中文讲解 本站NGUI各个版本插件下载地址 NGUI 2.7.0中文字体的使用 官方网站上的控件说明翻译: NGUI控件说明(中文) UIWidget NGUI控件说明(中文) UIRoot NGUI控件说明(中文) UIPanel NGUI控件说明(中文) UICamera NGUI控件说明(中文) UIStretch NGUI控件…

NGUI 3.5教程(一)安装NGUI 3.5.8

写在前面&#xff1a; 网上找的NGUI教程&#xff0c;都是基于2.x版本的。为了能配合教程学着做&#xff0c;我也是下载了各种NGUI 2.x版本。但是在导入的时候&#xff0c;或多或少都报错&#xff08;我用的Unity 的版本是4.3.2&#xff09;。无奈之下&#xff0c;只好下载了最…

[Unity3D]事半功倍:界面插件NGUI的使用教程与实例

原文地址&#xff1a;http://www.tasharen.com/?page_id185 NGUI下载地址&#xff1a;点我传送 NGUI教程&#xff1a;步骤1-Scene 1.创建一个新的场景&#xff08;New Scene&#xff09;。 2.选择并删除场景里的MainCamera。 3.在NGUI菜单下选择Create a New UI&#xff0c…

GUI之安装 一、打开NGUI的官方网站

GUI之安装 一、打开NGUI的官方网站 http://www.tasharen.com/?page_id140 二、往下拖网页找到免费版本的下载地址并下载 三、在Unity3D工程中导入所下载的unitypackage 点击Unity3D左上角的菜单Assets->ImportPackage->CustomPackage 在弹出的选择窗口中选择之前下载的…

NGUI -- UILabel

注意&#xff1a;本文翻译时NGUI版本已经更新到3.0.7 原文地址&#xff1a;http://www.tasharen.com/forum/index.php?topic6706 总览 UILabel是能用于显示文本的挂件。 所有Label需要一个字体才能正常工作。这个字体可以是动态的&#xff08;Dynamic&#xff09;&#xff0…