百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

嵌入式开发工程师必备:有限状态机

bigegpt 2025-01-05 15:49 47 浏览



作者 | Peter Galan


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


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


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


有限状态机理论


根据有限状态机理论,你可能会想起两种类型的FSM 表示,一种是Mealy 有限状态机,另一种是Moore 有限状态机。这两类FSM 都处理三组变量,一组输入变量X(k), 一组内部状态U(k)和一组输出变量Y(k)。这两类FSM 使用相同的转换函数δ 来映射U x X →


Mealy 和Moore FSM 的区别在于用于输出状态的第二个映射函数。Mealy 的输出状态映射函数λ 表示U x X → Y 映射,而Mooreλ 表示一个更简单的映射,从U → Y。从实现的角度来看,虽然Mo o re FSM 可能更简单,但Meal y FSM 也有其优势。例如, 与内部状态的数量相比,它可以有更多或不同的输出状态,而在Mo o re FSM 中,每个内部状态都与一个输出状态相关联。


通用的Mealy FSM 可用下面的表格来表示:

其中u k 列表示当前的内部状态,x k 行表示当前的输入状态。表格显示下一个内部状态(δ 映射)和相应的输出状态(λ 映射)。




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


每个EDFA 使用一个或两个(更常见的两级EDFA)激光泵,将能量提供给EDFA 内的特殊光纤线圈。通过放大器,该能量从上一个节点传输到下一个节点的光信号(光子) 放大。当连接E D FA 和其它节点的光纤断开并且检测到信号丢失(LO S)时,就会出现问题。这种情况必须立即处理,激光功率必须降低或完全切断。其它情况也需要立即关注。整个安全序列是E D FA 固件实施的一部分。


FSM 的基本实施


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


如果查看上面的代码,您可以看到条件(事件标志组合)如何在满足条件的情况下, 将c u r r State 变量从初始、禁用状态移动/ 更改为下一个状态。每次这样的内部状态变化,都会触发一个特定动作,在某些情况下, 输出状态会触发多个动作。


到目前为止一切都还正常进行,但随着整个过程变得更加复杂,i f- e l s e 决策语句也变得更加复杂(而且嵌套更深)。然后,如果需要修改任何if-else 决策语句,它可能会影响诸多其它语句。接下来,整个处理功能将需要反复测试。两级E D FA 需要两个类似的FSM 功能,再加上另一个控制A D FA 放大器的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() 的“状态图”。它处理ST T 类型的数据。这些数据被定义为S_FSM_STT_T 类型,并作为FSM_sstKernel ()函数的参数。由于FSM_ s st Ke r n e l()需要修改至少一个数据项,因此必须将其作为指向S _ FSM_ STT_ 类型数据的引用/ 指针。




FSM_sstKernel()所做的事情非常简单。它在S _ FSM_ R OW_ T 类型的行(数组元素)中“循环”, 并查找presentState 与当前内部状态currentState 相同的行。如果找到这样的行,将调用其对应的事件测试函数,并将它们的返回值“a n d - e d”放在一起,以确定是否满足了移动到下一个内部状态的条件。但有个限制,只能将两个逻辑值“and-ed”加在一起。如果需要更多个,则需要通过另一个F _ FSM_ EVENT_T 类型的事件,来扩展S_FSM_ROW_T 数据结构。如果内部状态之间的转换,必须将条件/ 事件“or-ed”放在在一起,那又如何处理呢?每个事件(或它们“and-ed”的组合)必须在单独的、但在其它方面相同的行中提供。


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


如果我想使用FS M _ s st Ke r n e l() 来处理更多ST T 行数截然不同的FSM,该怎么办?必须定义ROW_MAX 常量,以满足最长的STT,但最好的解决方案是用行的链接列表替换行数组。然后,每个S_FSM_STT_T 型数据, 将使用一个最佳内存空间。然而,一些简单的嵌入式系统,不支持动态分配内存空间。


现在,在FS M _ s st Ke r n e l()中取消对这两个p r i n tf()语句的注释,可以看到FSM 是如何从当前状态发展到下一个状态的。这显然不适用于嵌入式系统, 但整个代码可能会在P C 机上进行测试, 例如在Microsoft Visual S tudio 中。


完成和编译


一旦FSM _ exa m p l e . h 和 FSM _ example.c 完成后(甚至可以编译它们并创建对应的.o b j 文件),就可以将它们添加到应用程序中。在另一个源文件中,需要创建STT 表。这意味着首先定义ST T 的每一行,最后定义STT 本身。然后可以调用FS M _ s st Ke r n e l()函数。您很可能会在F/W 应用程序的main()函数中, 将其作为后台任务之一调用。您可以定义更多这样类似S_FSM_STT_T 变量,并调用FSM_sstKernel()来引用它们。


为了更好地可视化, 请在MS V i s u a l Studio 下的PC 上编写以下源文件:

在编译和运行程序( 以及FSM_ example.h 和FSM_example.cpp)时,如果FSM_sstKernel()中的两个printf() 语句并未注释掉,您应该会看到如图2 所示的结果。


关键概念:

■检查有限状态机的基本实现。

■考察有限状态机实现的改进。


思考一下:

如果通过一种不同的方法来实施有限状态机?

相关推荐

ActiveAndroid使用(对象化数据库)

配置模块的build.gradlerepositories{mavenCentral()mavenLocal()maven{url"https://oss.sonatype.org/conte...

AndroidStudio下的依赖管理(android app依赖外部jar包)

在开发中用第三方库是很常见的事,如何在AndroidStudio下管理这些依赖呢?这就是这篇文章的目的。目录Maven/Ivy仓库依赖Module依赖aar文件依赖jar文件依赖例子完整代码一、Mav...

Android Studio之gradle的配置与介绍

1、gradle的简单介绍Gradle是可以用于Android开发的新一代的BuildSystem,也是AndroidStudio默认的build工具。其实Gradle脚本是基于一种JVM语言—...

Android中的run-as命令带来的安全问题

一、前言最近一周比较忙,没时间写东西了,今天继续开始我们今天的话题:run-as命令,在上周的开发中,遇到一个问题,就是在使用run-as命令的时候出现了一个错误,不过当时因为工作进度的问题,这问题就...

Android系统级深入开发——input驱动程序

1、Input驱动程序是Linux输入设备的驱动程序,分成游戏杆(joystick)、鼠标(mouse和mice)和事件设备(Eventqueue)3种驱动程序。其中事件驱动程序是目前通用的驱动程序...

Android项目中如何用好构建神器Gradle?

CSDN移动将持续为您优选移动开发的精华内容,共同探讨移动开发的技术热点话题,涵盖移动应用、开发工具、移动游戏及引擎、智能硬件、物联网等方方面面。如果您想投稿、参与内容翻译工作,或寻求近匠报道,请发送...

Android Studio自定义文件类头(android studio自定义标题栏)

--简书作者谢恩铭转载请注明出处今天给大家介绍一个很简单的"小"技巧。平时,我们在AndroidStudio中开发Android时,总免不了要创建新的文件,也许是Java文件,也许是C...

C语言#include头文件真的是插入代码吗?

若文章对您有帮助,欢迎关注程序员小迷。助您在编程路上越走越好!编译器理论和实作既是又不是。从编译器理论理解,#include头文件"相当于"插入了头文件的代码,以供源代码引用(宏定...

Android 系统核心机制binder(03)binder C++层实现

本章关键点总结&说明:这里主要关注BinderC++部分即可,看到,也是本章节的核心内容,主要就是以C++封装的框架为主来解读binder。之前主要针对于底层驱动binder的数据交互以及...

Java对象序列化与反序列化的那些事

Java对象序列化与反序列化的那些事在Java的世界里,对象序列化和反序列化就像一对孪生兄弟,它们共同构成了Java对象存储和传输的基础。如果你曾经尝试将对象保存到文件中,或者在网络中传输对象,那么你...

Java对象序列化剖析(java 对象序列化)

对象序列化的目的1)希望将Java对象持久化在文件中2)将Java对象用于网络传输实现方式如果希望一个类的对象可以被序列化/反序列化,那该类必须实现java.io.Serializable接口或jav...

C++模板 - 16(SFINAE)(c++模板编程)

C++支持函数重载,同一个函数名,只要它的签名不一样,可以声明若干个版本(这个特性也是必须的,不然构造函数就只能有一个了)。现在函数的重载集合中又加入了新的成员-函数模板,事情就变得越发有趣起来,...

NewtoSoft.Json相关使用技巧(newtosoft.json相关使用技巧有哪些)

  本篇将为大家介绍Newtonsoft.Json的一些高级用法,可以修改很少的代码解决上述问题。Newtonsoft.Json介绍  在做开发的时候,很多数据交换都是以json格式传输的。而使用Js...

C#调用DeepSeek API(c#调用deepseek api 流式输出)

一、官方网站二、DeepSeek测试DeepSeek三大适用模式:基础模型(V3)、深度思考(R1)、联网搜索。基础模型(V3)深度思考(R1)联网搜索三、C#调用DeepSeekAPI核心代码//...

.NET性能系列文章二:Newtonsoft.Json vs System.Text.Json

微软终于追上了?图片来自GlennCarstens-Peters[1]Unsplash[2]欢迎来到.NET性能系列的另一章。这个系列的特点是对.NET世界中许多不同的主题进行研究、基准和比较...