嵌入式系统的有限状态机

获取有关使用C编程语言为嵌入式系统进行有限状态机编程的帮助。

通过彼得•加 2021年10月4日
提供:Peter Galan,一位退休的控制软件工程师

学习目标

  • 研究有限状态机(FSM)的基本实现。
  • 看看FSM的改进实现。
  • 参见FSM的编译代码和支持图表。

有限状态机(FSM)的一个定义是,FSM是描述特定(逻辑)过程的一系列事件和动作的数学模型。在工程实践中,你可以发现无数这样的过程,从一个非常简单的通过几个按钮控制电动机的过程开始,到一个非常复杂的FSM系统监控一个非常复杂的制造过程或航天飞机。

我曾为广泛应用于光通信的掺铒光纤放大器(EDFA)设计过固件。其中一个固件部分必须处理EDFA的安全问题,如果设计不当,EDFA可能会使用对人体有害的强激光。看起来是一个常规的FSM代码设计,以一个非常复杂、难以遵循和维护的漫长过程结束。需要一种不同的方式来实现FSM实现,这可能会引起嵌入式代码设计人员的兴趣。

FSM理论

从有限自动机的理论中,你可能还记得两种类型的FSM表示,一个是Mealy有限状态机,一个是Moore有限状态机。两种类型的FSM都基于三组变量,一组输入变量,X (k),一组内部状态,U (k)和一组输出变量,Y (k).两种类型的FSM使用相同的转换函数,δ,用于映射U x x→U。Mealy FSM和Moore FSM的区别在于用于输出状态的第二个映射函数。Mealy输出状态映射函数,λ,代表U x x→Y而摩尔的λ表示一个更简单的映射U→Y.从实现的角度来看,Moore FSM可能更简单,而Mealy FSM有一定的优势。例如,它可以拥有比内部状态数更多或不同的输出状态,而在Moore FSM中,每个内部状态都与一个输出状态相关联。

一个通用的Mealy FSM可以用下表表示:

在哪里uk列表示当前的内部状态,和xk行表示当前输入状态。和表显示了下一个内部状态(δ映射)和相应的输出状态(λ映射)。

图1显示了FSM的另一种更常见的表示形式——状态图。你可以看到的是一个完整的一级EDFA安全序列状态图。

图1:一级EDFA安全状态图。提供:Peter Galan,一位退休的控制软件工程师

图1:一级EDFA安全状态图。提供:Peter Galan,一位退休的控制软件工程师

每个EDFA使用一个或两个(对于更常见的两级EDFA)激光泵浦,这些泵浦为放置在EDFA内部的特殊光纤线圈提供能量。这种能量放大光信号(光子)流经放大器从前一个节点到下一个节点。当连接EDFA与其他节点的光纤损坏并且检测到信号丢失(LOS)时,就会出现这种问题。这种情况必须立即处理,必须降低激光功率或完全关闭。其他情况也需要立即关注。整个安全序列被实现为EDFA固件的一个片段。

FSM的基本实现

对于这种有限状态机(通常对于嵌入式控制软件)实现来说,最好的编程语言是什么?是的,C语言。还有很多其他更现代的,面向对象编程语言,但是C语言就像是嵌入式系统的“母语”。一个有经验的嵌入式软件设计者如何开始用C语言实现FSM呢?它很可能以这样的函数开始:

如果您查看上面的代码,您可以看到条件(事件标志组合)如果满足,将如何导致将currState变量从初始状态DISABLED移动/更改到下一个状态和下一个状态。对于每一个这样的内部状态更改,都会触发一个特定的操作-输出状态,在某些情况下,会触发多个操作。

到目前为止还不错,但是随着整个过程变得更加复杂,if - else决策语句也变得更加复杂(嵌套也更深)。然后,如果需要修改任何if - else决策语句,它可能会影响许多剩余的语句。接下来,整个处理功能需要一次又一次的测试。两级EDFA将需要两个这样的(相似,但不完全相同)FSM函数加上另一个管理ADFA放大器的FSM函数,这将花费大量的精力。

改进FSM的实现

有限状态机还有另一种表示方式。它是通过一个状态转换表(STT)实现的。STT是Mealy FSM描述的另一种形式。这样的表通常有四列和所需的行。的转换现状下一个状态.属性触发转换事件(一个或多个事件标志的组合)。的行动描述与转换关联的所有操作。例如,这是一个STT(只是它的片段),对应于图中所示的状态图。

为什么这样的表示比状态图更好呢?因为它可以更优雅地实现。下面,看看如何在C语言中实现它。

有经验的软件设计人员会将FSM代码分成几个源文件,并从FSM_example.h头文件开始,该头文件将包含特殊类型定义和所有函数原型。下面是这样一个头文件的好例子。

对于经验较少的程序员来说,前两个定义可能值得解释一下。每个函数都定义了一个特定函数指针的名称。在c#中,这些定义等同于使用delegate关键字的定义。它们是C编程中非常强大的工具,允许C程序编写得像使用面向对象的编程语言(如c#)一样优雅。

下面是第二个源文件,其中完整实现了之前定义的所有函数原型。

除了最后一个函数,其他函数都应该很清楚。一种类型是F_FSM_EVENT_T类型的函数,它们测试某些系统状态(标志)并返回true或false值。注意像IsLosON()和IsLosOFF()这样的“配对”函数。在这种特殊情况下,只有一个真正测试了通过硬件(H/W)报告状态/标志的输入信号丢失。如果检测到信号丢失,该函数返回true,否则返回false。第二个函数只是一个包装器,它调用第一个函数并返回一个负的结果。

这里定义的第二种函数类型是F_FSM_ACTION_T类型。这些功能是“动作”功能,它们控制(打开或关闭)一个所需的内部/外部设备的微控制器或一些H/W电路,这整个嵌入式系统由的。

无论FSM是如何实现的,这两种类型的函数都是必需的,例如“状态图”过程或STT实现。现在,FSM_sstKernel()函数是一些新的东西。它取代了“状态图”实现函数FSM_stage1()。它对stt类型的数据起作用。这些数据被定义为S_FSM_STT_T类型,它们作为FSM_sstKernel()函数的参数提供。它们必须作为S_FSM_STT_T类型数据的引用/指针提供,因为FSM_sstKernel()将修改至少一个数据项。

FSM_sstKernel()所做的事情非常简单。它“循环”遍历S_FSM_ROW_T类型的行(数组元素),并寻找presentState与当前内部状态currentState相同的行。如果找到这样的行,将调用它的事件测试函数,并将它们的返回值“and-ed”放在一起,以确定是否满足移动到下一个内部状态的条件。有两个逻辑值“and-ed”在一起的限制。如果需要更多,则使用另一个f_fsm_event_t类型事件扩展S_FSM_ROW_T数据结构。对于内部状态之间的转换,必须一起“or-ed”的条件/事件呢?每个事件(或者它们的“and-ed”组合)必须在单独的、但在其他方面相同的行中提供。

然后FSM_sstKernel()继续(如果满足瞬态条件)启动行数据中找到的所有操作。最后,它将行的nextState复制到stt的currentState。

如果我想使用FSM_sstKernel()更多的具有完全不同数量的STT行fsm怎么办?必须定义ROW_MAX常数以满足最长STT,但是最好的解决方案是用行链表替换行数组。然后,每个s_fsm_stt_t类型的数据将使用最佳内存空间。但是,一些简单的嵌入式系统不允许使用动态分配的内存空间。

现在,通过取消注释FSM_sstKernel()中的这两个printf()语句,可以看到FSM如何从当前/当前状态进一步发展到下一个状态。这显然不能在嵌入式系统中工作,但是整个代码可以在PC上测试,例如在Microsoft Visual Studio中。

完成和编译

一旦FSM_example.h和FSM_example.c完成(你甚至可以编译它们并创建它们的.obj文件),你就可以将它们添加到你的应用程序中。在另一个源文件中需要创建STT表。它意味着首先定义STT的每一行,最后定义STT本身。然后可以调用FSM_sstKernel()函数。你很可能会在F/W应用程序的main()函数中调用它作为后台任务之一。你可以定义更多这样的S_FSM_STT_T变量,并调用FSM_sstKernel()来引用它们。

为了更好的可视化,在MS Visual Studio下在PC上编写以下源文件:

提供:Peter Galan,一位退休的控制软件工程师

提供:Peter Galan,一位退休的控制软件工程师

当你编译并运行这个程序(连同FSM_example.h和FSM_example.cpp)时,在FSM_sstKernel()中没有注释的这两个printf()语句,你应该会看到如下截图所示的结果:

第一阶段EDFA安全状态图仿真。提供:Peter Galan,一位退休的控制软件工程师

第一阶段EDFA安全状态图仿真。提供:Peter Galan,一位退休的控制软件工程师

彼得•加是一位退休的控制软件工程师。由内容经理马克·霍斯克编辑,控制工程CFE媒体和技术mhoske@cfemedia.com

更多的答案

关键词:有限状态机,FSM,嵌入式系统编程

考虑一下这个

如果你的控制系统依靠几十年的控制方法,你能得到几十年的结果吗?


作者简介:Peter Galan是一名(已退休)控制系统设计师,在电子、控制系统和软件设计方面拥有丰富的经验。他曾在ZTS, GE, Husky,北电,JDSU,艾默生(加拿大和美国)等多家公司工作,此前在科希塞(前捷克斯洛伐克)的技术大学工作。他拥有布拉格捷克技术大学自动化控制系统博士学位和应用控制论硕士学位。