状态机初识

  有限状态机FSM思想广泛应用于硬件控制电路设计,也是软件上常用的一种处理方法(软件上称为FMM消息机)。它把复杂的控制逻辑分解成有限个稳定状态,在每个状态上判断事件,变连续处理为离散数字处理,符合计算机的工作特点。同时,因为有限状态机具有有限个状态,所以可以在实际的工程上实现。但这并不意味着其只能进行有限次的处理,相反,有限状态机是闭环系统,有限但是无穷,可以用有限的状态,处理无穷的事件。

  有限状态机的工作原理如1所示,发生事件(event)后,根据当前状态(curState),决定执行的动作(action)并设置下一个状态号(nextState)。

    

    

    

  图2为一个状态机实例的状态转移图,它的含义是:

  • 在s0状态,如果发生e0事件,那么就执行a0动作,并保持状态不变;
  • 如果发生e1事件,那么就执行a1动作,并将状态转移到s1态;
  • 如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;
  • 在s1状态,如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;
  • 在s2状态,如果发生e0事件,那么就执行a0动作,并将状态转移到s0态。

  有限状态机不仅能够用状态转移图表示,还可以用二维的表格代表。一般将当前状态号写在横行上,将事件写在纵列上,如表1所示。其中“--”表示空(不执行动作,也不进行状态转移),“an/sn”表示执行动作an,同时将下一状态设置为sn。表1和图2表示的含义是完全相同的。

  观察表1可知,状态机可以用两种方法实现:竖着写(在状态中判断事件)和横着写(在事件中判断状态)。这两种实现在本质上是完全等效的,但在实际操作中,效果却截然不同。

  竖着写(在状态中判断事件)C代码片段:

    

 1 cur_state = nxt_state;
 2 switch(cur_state) //在当前状态中判断事件
 3 {
 4     case s0: //在s0状态
 5         if(e0_event) //如果发生e0事件,那么就执行a0动作,并保持状态不变;
 6         {
 7         //执行a0动作;
 8         //nxt_state = s0;  //因为状态号是自身,所以可以删除此句,以提高运行速度。
 9         }
10         else if(e1_event) //如果发生e1事件,那么就执行a1动作,并将状态转移到s1态;
11         {
12             //执行a1动作;
13             nxt_state = s1;
14         }
15         else if(e2_event) //如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;
16         {
17             //执行a2动作;
18             nxt_state = s2;
19         }
20         else
21         {
22             break;
23         }
24
25     case s1: //在s1状态
26         if(e2_event) //如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;
27         {
28             //执行a2动作;
29          nxt_state = s2;
30         }
31         else
32         {
33       break;
34         }
35
36     case s2: //在s2状态
37         if(e0_event)  //如果发生e0事件,那么就执行a0动作,并将状态转移到s0态;
38         {
39             //执行a0动作;
40             nxt_state = s0;
41         }
42 }

  横着写(在事件中判断状态)C代码片段:

  

 1 //e0事件发生时,执行的函数
 2 void e0_event_function(int * nxt_state)
 3 {
 4     int cur_state;
 5     cur_state = *nxt_state;
 6     switch(cur_state)
 7     {
 8         case s0: //观察表1,在e0事件发生时,s1处为空
 9         case s2: //执行a0动作;
10         *nxt_state = s0;
11     }
12 }
13
14 //e1事件发生时,执行的函数
15 void e1_event_function(int * nxt_state)
16 {
17     int cur_state;
18     cur_state = *nxt_state;
19     switch(cur_state)
20     {
21         case s0: //观察表1,在e1事件发生时,s1和s2处为空
22             //执行a1动作;
23             *nxt_state = s1;
24     }
25 }
26
27 //e2事件发生时,执行的函数
28 void e2_event_function(int * nxt_state)
29 {
30     int cur_state;
31     cur_state = *nxt_state;
32     switch(cur_state)
33     {
34         case s0: //观察表1,在e2事件发生时,s2处为空
35         case s1:
36             //执行a2动作;
37             *nxt_state = s2;
38     }
39 }

  上面横竖两种写法的代码片段,实现的功能完全相同,但是,横着写的效果明显好于竖着写的效果。理由如下:

  1. 竖着写隐含了优先级排序(其实各个事件是同优先级的),排在前面的事件判断将毫无疑问地优先于排在后面的事件判断,这种if/else if写法上的限制将破坏事件间原有的关系。而横着写不存在此问题。

  2. 由于处在每个状态时的事件数目不一致,而且事件发生的时间是随机的,无法预先确定,导致竖着写沦落为顺序查询方式,结构上的缺陷使得大量时间被浪费。对于横着写,在某个时间点,状态是唯一确定的,在事件里查找状态只要使用switch语句,能一步定位到相应的状态,延迟时间可以预先准确估算。而且在事件发生时,调用事件函数,在函数里查找唯一确定的状态,并根据其执行动作和状态转移的思路清晰简洁,效率高,富有美感。

  总之,我个人认为,在软件里写状态机使用横着写的方法比较妥帖。

  竖着写的方法也不是完全不能使用,在一些小项目里,逻辑不太复杂,功能精简,同时为了节约内存耗费,竖着写的方法也不失为一种合适的选择。

  在FPGA类硬件设计中,以状态为中心实现控制电路状态机(竖着写)似乎是唯一的选择,因为硬件不太可能靠事件驱动(横着写)。不过在FPGA里有一个全局时钟,在每次上升沿时进行状态切换,使得竖着写的效率并不低。虽然在硬件里竖着写也要使用if/else if这类查询语句,但他们映射到硬件上是组合逻辑,查询只会引起门级延迟(ns量级),而且硬件是真正并行工作的,这样竖着写在硬件里就没有负面影响。因此,在硬件设计里,使用竖着写的方式成为必然的选择。这也是为什么很多搞硬件的工程师在设计软件状态机时下意识的只使用竖着写方式的原因,思维定式罢了。

时间: 2025-01-02 01:35:06

状态机初识的相关文章

capwap学习笔记——初识capwap(四)

2.5.7 CAPWAP传输机制 WTP和AC之间使用标准的UDP客户端/服务器模式来建立通讯. CAPWAP协议支持UDP和UDP-Lite [RFC3828]. ¢ 在IPv4上,CAPWAP控制和数据通道使用UDP.此时CAPWAP报文中的UDP校验和必须设置为0.AC上的CAPWAP控制报文端口为UDP众所周知端口5246,数据报文端口为UDP众所周知端口5247 ,WTP可以随意选择CAPWAP控制和数据端口. ¢ 在IPv6上,CAPWAP控制通道一般使用UDP,而数据通道可以使用U

capwap学习笔记——初识capwap(二)

2.5.1 AC发现机制 WTP使用AC发现机制来得知哪些AC是可用的,决定最佳的AC来建立CAPWAP连接. WTP的发现过程是可选的.如果在WTP上静态配置了AC,那么WTP并不需要完成AC的发现过程. WTP首先发送一个 Discovery Request message给受限的广播地址,或者CAPWAP的多播地址(224.0.1.140),或者是预配置的AC的单播地址.在IPV6网络中,由于广播并不存在,因此使用"All ACs multicast address" (FF0X

capwap学习笔记——初识capwap(三)

2.5.6 CAPWAP状态机详解 2.5.6.1 Start to Idle 这个状态变迁发生在设备初始化完成. ¢  WTP: 开启CAPWAP状态机. ¢  AC:  开启CAPWAP状态机. 2.5.6.2 Idle to Discovery 这个状态变迁发生是为了支持CAPWAP发现进程. ¢   WTP: WTP进入发现状态是为了优先去传输第一个Discovery Request message.在进入这个状态之前,WTP设置发现DiscoveryInterval timer,将Di

初识Python,望君多多关照

在学习Python之前,我们接触过数据结构和网页制作.前者让我们学习如何把C语言运用的更加整齐规范,而后者让我们亲身学习如何运用所学,制作一个静态网页.通过这些课程的学习,让我对C语言产生了比较大的压力,以至于对编程.对这学期的Python课程都有一种如临大敌的感觉. 但是真的学习了这门课程,体会了编码过程中的一些固定运用方法和套路之后,也许过程中对这门课程隐隐约约产生了一点点朦胧的感觉,仿佛他也并没有想象中的那么困难,起码现在的学习让我认为,他可能没有C语言那么繁琐和麻烦.当然,以一个初学者的

初识数组排序!!!!

<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>初识数组排序</title> <!--调试成功--> <style type="text/css"> *{ padding:0; margin: 0; } li,ul{ list-style: none; } #p

初识操作系统和linux

初识操作系统和linux 1.计算机系统由硬件系统和软件系统两大部分组成:是一种能接收和存储信息,并按照存储在其内部的程序对海量数据进行自动.高速地处理,然后把处理结果输出的现代化智能电子设备. 2.世界上第一台计算机是1946年诞生在美国宾州大学. 3.冯·诺依曼体系结构:1946年数学家冯·诺依曼于提出计算机硬件系统由运算器.控制器.存储器.输入设备.输出设备.摩根定律:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,性能也将提升一倍.现在计算机技术进本很难遵

JAVA 初识类加载机制 第13节

JAVA 初识类加载机制 第13节 从这章开始,我们就进入虚拟机类加载机制的学习了.那么什么是类加载呢?当我们写完一个Java类的时候,并不是直接就可以运行的,它还要编译成.class文件,再由虚拟机解释给当前的操作系统去执行.这些过程都是我们看不见的,我们能看见的也就是一个.class文件.既然虚拟机要解释这些.class文件给当前的操作系统听,那么他怎么获得这些.class文件呢?虚拟机获得这些.class文件的过程就是类加载了. 所以,总结来说就是:虚拟机将.class文件从磁盘或者其他地

初识React

原文地址:北云软件-初识React 专注于UI 在MVC分层设计模式中,react常被拿来实现视图层(V).React不依赖于技术栈的其他部分,因此可以方便的在现有项目中尝试用它来实现一个小特性. 虚拟DOM React从DOM中抽象出来,给出一种更简洁的编程模型,且性能表现更好.能够通过NodeJS实现服务端渲染,通过React Native开发原生app. 数据流React实现单向.响应式数据流,减少boilerplate且比传统数据绑定更容易理解. 简洁的组件React的组件都实现了一个r

泛型的几种类型以及初识webform

今天学习的可以分为两类吧,但是学习的都是比较抽象的,不太容易掌握吧.首先我们大部分时间学习了泛型,泛型的委托,泛型接口以及枚举器,迭代器,扩展方法:最后简单的认识了webform,实现了一个简单的功能. 一.泛型 定义:泛型(generic)可以软糖多个类型共享一组代码,泛型允许我们声明类型参数化.可以用不同的类型进行实例化,说白了,就是可以用类型占位符,创建具体类型致命的真实概念.C#中提供了五种泛型,类,结构,接口,委托和方法.下面举例说明可能更容易理解, class MyStack<T>