精华内容
下载资源
问答
  • 状态机编程
    千次阅读
    2019-03-19 13:29:34

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

     

     
     

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

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

            cur_state = nxt_state;     

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

     

    1. 横着写(在事件中判断状态)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语句,能一步定位到相应的状态,延迟时间可以预先准确估算。而且在事件发生时,调用事件函数,在函数里查找唯一确定的状态,并根据其执行动作和状态转移的思路清晰简洁,效率高,富有美感。

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

     

    参考:https://blog.csdn.net/V__KING__/article/details/71740492

    更多相关内容
  • 状态机编程介绍

    2018-12-28 16:25:28
    状态机的概念;状态机的要素;状态迁移图(STD);状态迁移表;用状态机思路实现一个时钟程序
  • 采用Verilog语言编程可以简化有限状态机设计过程,并优化硬件资源配置。本方案首先介绍了利用Verilog设计有限状态机的流程和不同方式,其次从电路的容错性、延时、面积等因素进行考量,着重对编码方式进行比较,最后根据...
  • 有限状态机编程

    2018-12-21 10:22:35
    有限状态机C语言编程有限状态机C语言编程有限状态机C语言编程
  • 比如模块化编程,框架式编程,状态机编程等等,都是一种好的框架。 今天说的就是状态机编程,由于篇幅较长,大家慢慢欣赏。那么状态机是一个这样的东东?状态机(state machine)有5个要素,分别是状态(state)、迁移...

    关注、星标公众号,直达精彩内容

    来源:玩转嵌入式

    作者:Alicedodo

    摘要:不知道大家有没有这样一种感觉,就是感觉自己玩单片机还可以,各个功能模块也都会驱动,但是如果让你完整的写一套代码,却无逻辑与框架可言,上来就是开始写!东抄抄写抄抄。说明编程还处于比较低的水平,那么如何才能提高自己的编程水平呢?学会一种好的编程框架或者一种编程思想,可能会受用终生!比如模块化编程,框架式编程,状态机编程等等,都是一种好的框架。


    今天说的就是状态机编程,由于篇幅较长,大家慢慢欣赏。那么状态机是一个这样的东东?状态机(state machine)有5个要素,分别是状态(state)、迁移(transition)、事件(event)、动作(action)、条件(guard)。

    什么是状态机?

    状态机是一个这样的东东:状态机(state machine)有 5 个要素,分别是状态(state)、迁移(transition)、事件(event)、动作(action)、条件(guard)。

    状态:一个系统在某一时刻所存在的稳定的工作情况,系统在整个工作周期中可能有多个状态。例如一部电动机共有正转、反转、停转这 3 种状态。

    一个状态机需要在状态集合中选取一个状态作为初始状态。

    迁移:系统从一个状态转移到另一个状态的过程称作迁移,迁移不是自动发生的,需要外界对系统施加影响。停转的电动机自己不会转起来,让它转起来必须上电。

    事件:某一时刻发生的对系统有意义的事情,状态机之所以发生状态迁移,就是因为出现了事件。对电动机来讲,加正电压、加负电压、断电就是事件

    动作:在状态机的迁移过程中,状态机会做出一些其它的行为,这些行为就是动作,动作是状态机对事件的响应。给停转的电动机加正电压,电动机由停转状态迁移到正转状态,同时会启动电机,这个启动过程可以看做是动作,也就是对上电事件的响应。

    条件:状态机对事件并不是有求必应的,有了事件,状态机还要满足一定的条件才能发生状态迁移。还是以停转状态的电动机为例,虽然合闸上电了,但是如果供电线路有问题的话,电动机还是不能转起来。

    只谈概念太空洞了,上一个小例子:一单片机、一按键、俩 LED 灯(记为L1和L2)、一人, 足矣!

    规则描述:

    1、L1L2状态转换顺序OFF/OFF--->ON/OFF--->ON/ON--->OFF/ON--->OFF/OFF

    2、通过按键控制L1L2的状态,每次状态转换需连续按键5

    3、L1L2的初始状态OFF/OFF

    图1

    下面这段程序是根据功能要求写成的代码。

    程序清单List1:

    void main(void)
    {
     sys_init();
     led_off(LED1);
     led_off(LED2);
     g_stFSM.u8LedStat = LS_OFFOFF;
     g_stFSM.u8KeyCnt = 0;
     while(1)
     {
      if(test_key()==TRUE)
      {
       fsm_active();
      }
      else
      {
       ; /*idle code*/
      }
     }
    }
    void fsm_active(void)
    {
     if(g_stFSM.u8KeyCnt > 3) /*击键是否满 5 次*/
     {
      switch(g_stFSM.u8LedStat)
      {
       case LS_OFFOFF:
        led_on(LED1); /*输出动作*/
        g_stFSM.u8KeyCnt = 0;
        g_stFSM.u8LedStat = LS_ONOFF; /*状态迁移*/
        break;
       case LS_ONOFF:
        led_on(LED2); /*输出动作*/
        g_stFSM.u8KeyCnt = 0;
        g_stFSM.u8LedStat = LS_ONON; /*状态迁移*/
        break;
       case LS_ONON:
        led_off(LED1); /*输出动作*/
        g_stFSM.u8KeyCnt = 0;
        g_stFSM.u8LedStat = LS_OFFON; /*状态迁移*/
        break;
       case LS_OFFON:
        led_off(LED2); /*输出动作*/
        g_stFSM.u8KeyCnt = 0;
        g_stFSM.u8LedStat = LS_OFFOFF; /*状态迁移*/
        break;
       default: /*非法状态*/
        led_off(LED1);
        led_off(LED2);
        g_stFSM.u8KeyCnt = 0;
        g_stFSM.u8LedStat = LS_OFFOFF; /*恢复初始状态*/
        break;
      }
     }
     else
     {
      g_stFSM.u8KeyCnt++; /*状态不迁移,仅记录击键次数*/
     }
    }
    

    实际上在状态机编程中,正确的顺序应该是先有状态转换图,后有程序,程序应该是根据设计好的状态图写出来的。不过考虑到有些童鞋会觉得代码要比转换图来得亲切,我就先把程序放在前头了。

    这张状态转换图是用UML(统一建模语言)的语法元素画出来的,语法不是很标准,但拿来解释问题足够了。

    图2按键控制流水灯状态转换图

    圆角矩形代表状态机的各个状态,里面标注着状态的名称。

    带箭头的直线或弧线代表状态迁移,起于初态,止于次态。

    图中的文字内容是对迁移的说明,格式是:事件[条件]/动作列表(后两项可选)。

    “事件[条件]/动作列表”要说明的意思是:如果在某个状态下发生了“事件”,并且状态机

    满足“[条件]”,那么就要执行此次状态转移,同时要产生一系列“动作”,以响应事件。在这个例子里,我用“KEY”表示击键事件。

    图中有一个黑色实心圆点,表示状态机在工作之前所处的一种不可知的状态,在运行之前状态机必须强制地由这个状态迁移到初始状态,这个迁移可以有动作列表(如图1所示),但不需要事件触发。

    图中还有一个包含黑色实心圆点的圆圈,表示状态机生命周期的结束,这个例子中的状态机生生不息,所以没有状态指向该圆圈。

    关于这个状态转换图就不多说了,相信大家结合着上面的代码能很容易看明白。现在我们再聊一聊程序清单List1。

    先看一下fsm_active()这个函数,g_stFSM.u8KeyCnt = 0;这个语句在switch—case里共出现了 5 次,前 4 次是作为各个状态迁移的动作出现的。从代码简化提高效率的角度来看,我们完全可以把这 5 次合并为 1 次放在 switch—case 语句之前,两者的效果是完全一样的,代码里之所以这样啰嗦,是为了清晰地表明每次状态迁移中所有的动作细节,这种方式和图2的状态转换图所要表达的意图是完全一致的。

    再看一下g_stFSM这个状态机结构体变量,它有两个成员:u8LedStat和 u8KeyCnt。用这个结构体来做状态机好像有点儿啰嗦,我们能不能只用一个像 u8LedStat 这样的整型变量来做状态机呢?

    当然可以!我们把图 2中的这 4 个状态各自拆分成 5 个小状态,这样用 20 个状态同样能实现这个状态机,而且只需要一个 unsigned char 型的变量就足够了,每次击键都会引发状态迁移, 每迁移 5 次就能改变一次 LED 灯的状态,从外面看两种方法的效果完全一样。

    假设我把功能要求改一下,把连续击键5次改变L1L2的状态改为连续击键100次才能改变L1L2的状态。这样的话第二种方法需要4X100=400个状态!而且函数fsm_active()中的switch—case语句里要有400个case,这样的程序还有法儿写么?!

    同样的功能改动,如果用g_stFSM这个结构体来实现状态机的话,函数fsm_active()只需要将if(g_stFSM.u8KeyCnt>3)改为if(g_stFSM.u8KeyCnt > 98)就可以了!

    g_stFSM结构体的两个成员中,u8LedStat可以看作是质变因子,相当于主变量;u8KeyCnt可以看作是量变因子,相当于辅助变量。量变因子的逐步积累会引发质变因子的变化。

    g_stFSM这样的状态机被称作Extended State Machine,我不知道业内正规的中文术语怎么讲,只好把英文词组搬过来了。

    2、状态机编程的优点

    说了这么多,大家大概明白状态机到底是个什么东西了,也知道状态机化的程序大体怎么写了,那么单片机的程序用状态机的方法来写有什么好处呢?

    (1)提高CPU使用效率

    话说我只要见到满篇都是delay_ms()的程序就会蛋疼,动辄十几个ms几十个ms的软件延时是对CPU资源的巨大浪费,宝贵的CPU机时都浪费在了NOP指令上。那种为了等待一个管脚电平跳变或者一个串口数据而岿然不动的程序也让我非常纠结,如果事件一直不发生,你要等到世界末日么?

    把程序状态机化,这种情况就会明显改观,程序只需要用全局变量记录下工作状态,就可以转头去干别的工作了,当然忙完那些活儿之后要再看看工作状态有没有变化。只要目标事件(定时未到、电平没跳变、串口数据没收完)还没发生,工作状态就不会改变,程序就一直重复着“查询—干别的—查询—干别的”这样的循环,这样CPU就闲不下来了。在程序清单 List3 中,if{}else{}语句里else下的内容(代码中没有添加,只是加了一条/*idle code*/的注释示意)就是上文所说的“别的工作” 。

    这种处理方法的实质就是在程序等待事件的过程中间隔性地插入一些有意义的工作,好让CPU不是一直无谓地等待。

    (2) 逻辑完备性

    我觉得逻辑完备性是状态机编程最大的优点

    不知道大家有没有用C语言写过计算器的小程序,我很早以前写过,写出来一测试,那个惨不忍睹啊!当我规规矩矩的输入算式的时候,程序可以得到正确的计算结果,但要是故意输入数字和运算符号的随意组合,程序总是得出莫名其妙的结果。

    后来我试着思维模拟一下程序的工作过程,正确的算式思路清晰,流程顺畅,可要碰上了不规矩的式子,走着走着我就晕菜了,那么多的标志位,那么多的变量,变来变去,最后直接分析不下去了。

    很久之后我认识了状态机,才恍然明白,当时的程序是有逻辑漏洞的。如果把这个计算器程序当做是一个反应式系统,那么一个数字或者运算符就可以看做一个事件,一个算式就是一组事件组合。对于一个逻辑完备的反应式系统,不管什么样的事件组合,系统都能正确处理事件,而且系统自身的工作状态也一直处在可知可控的状态中。反过来,如果一个系统的逻辑功能不完备,在某些特定事件组合的驱动下,系统就会进入一个不可知不可控的状态,与设计者的意图相悖。

    状态机就能解决逻辑完备性的问题。

    状态机是一种以系统状态为中心,以事件为变量的设计方法,它专注于各个状态的特点以及状态之间相互转换的关系。状态的转换恰恰是事件引起的,那么在研究某个具体状态的时候,我们自然而然地会考虑任何一个事件对这个状态有什么样的影响。这样,每一个状态中发生的每一个事件都会在我们的考虑之中,也就不会留下逻辑漏洞。

    这样说也许大家会觉得太空洞,实践出真知,某天如果你真的要设计一个逻辑复杂的程序,

    我保证你会说:哇!状态机真的很好用哎!

    (3)程序结构清晰

    用状态机写出来的程序的结构是非常清晰的。

    程序员最痛苦的事儿莫过于读别人写的代码。如果代码不是很规范,而且手里还没有流程图,读代码会让人晕了又晕,只有顺着程序一遍又一遍的看,很多遍之后才能隐约地明白程序大体的工作过程。有流程图会好一点,但是如果程序比较大,流程图也不会画得多详细,很多细节上的过程还是要从代码中理解。

    相比之下,用状态机写的程序要好很多,拿一张标准的UML状态转换图,再配上一些简明的文字说明,程序中的各个要素一览无余。程序中有哪些状态,会发生哪些事件,状态机如何响应,响应之后跳转到哪个状态,这些都十分明朗,甚至许多动作细节都能从状态转换图中找到。可以毫不夸张的说,有了UML状态转换图,程序流程图写都不用写。

    套用一句广告词:谁用谁知道!

    果子哥制作

    说明:文章转载自一篇pdf文档,作者Alicedodo,旨在分享技术知识,如有侵权请联系删除!

    ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

    推荐阅读:

    嵌入式编程专辑Linux 学习专辑C/C++编程专辑
    Qt进阶学习专辑
    

    关注我的微信公众号,回复“加群”按规则加入技术交流群。


    点击“阅读原文”查看更多分享。

    展开全文
  • 嵌入式状态机编程-QP状态机框架与常见状态机方法

    千次阅读 多人点赞 2020-11-30 14:33:59
    状态机基本术语 现态:是指当前所处的状态。 条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧...

    状态机基本术语

    • 现态:是指当前所处的状态。
    • 条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
    • 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
    • 次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
      在这里插入图片描述

    传统有限状态机Fsm实现方法

    在这里插入图片描述

    如图,是一个定时计数器,计数器存在两种状态,一种为设置状态,一种为计时状态

    设置状态

    • “+” “-” 按键对初始倒计时进行设置
    • 当计数值设置完成,点击确认键启动计时 ,即切换到计时状态

    计时状态

    • 按下“+” “-” 会进行密码的输入“+”表示1 ,“-”表示输入0 ,密码共有4位
    • 确认键:只有输入的密码等于默认密码,按确认键才能停止计时,否则计时直接到零,并执行相关操作

    嵌套switch

          /***************************************
          1.列出所有的状态
          ***************************************/
          typedef enum{
              SETTING,
              TIMING
          }STATE_TYPE;
          /***************************************
          2.列出所有的事件
          ***************************************/
          typedef enum{
            	UP_EVT,
              DOWN_EVT,
              ARM_EVT,
              TICK_EVT
          }EVENT_TYPE;
          /***************************************
          3.定义和状态机相关结构
          ***************************************/
          struct  bomb
          {
              uint8_t state;
              uint8_t timeout;
              uint8_t code;
              uint8_t defuse_code;
          }bomb1;
          /***************************************
          4.初始化状态机
          ***************************************/
          void bomb1_init(void)
          {
              bomb1.state = SETTING;
              bomb1.defuse_code = 6;    //0110 
          }
          /***************************************
          5. 状态机事件派发
          ***************************************/
          void bomb1_fsm_dispatch(EVENT_TYPE evt ,void* param)
          {
              switch(bomb1.state)
              {
                  case SETTING:
                  {
                      switch(evt)
                      {
                          case UP_EVT:    // "+"   按键按下事件
                            if(bomb1.timeout< 60)  ++bomb1.timeout;
                              bsp_display(bomb1.timeout);
                          break;
                          case DOWN_EVT:  // "-"   按键按下事件
                              if(bomb1.timeout > 0)  --bomb1.timeout;
                              bsp_display(bomb1.timeout);
                          break;
                          case ARM_EVT:   // "确认" 按键按下事件
                              bomb1.state = TIMING;
                              bomb1.code  = 0;
                          break;
                      }
                  } break; 
                  case TIMING:
                  {
                      switch(evt)
                      {
                          case UP_EVT:    // "+"   按键按下事件
                             bomb1.code = (bomb1.code <<1) |0x01;
                          break;
                          case DOWN_EVT:  // "-"   按键按下事件
                              bomb1.code = (bomb1.code <<1); 
                          break;
                          case ARM_EVT:   // "确认" 按键按下事件
                              if(bomb1.code == bomb1.defuse_code){
                                  bomb1.state = SETTING;
                              }
                              else{
                              	bsp_display("bomb!")
                              }
                          break;
                          case TICK_EVT:
                              if(bomb1.timeout)
                              {
                                  --bomb1.timeout;
                                  bsp_display(bomb1.timeout);
                              }
                              if(bomb1.timeout == 0)
                              {
                                  bsp_display("bomb!")
                              }
                          break;
                      }   
                  }break;
              }
          }
    

    在这里插入图片描述

    • 优点
      • 简单,代码阅读连贯,容易理解
    • 缺点
      • 当状态或事件增多时,代码状态函数需要经常改动,状态事件处理函数会代码量会不断增加
      • 状态机没有进行封装,移植性差。
      • 没有实现状态的进入和退出的操作。进入和退出在状态机中尤为重要
        • 进入事件: 只会在刚进入时触发一次,主要作用是对状态进行必要的初始化
        • 退出事件:只会在状态切换时触发一次 ,主要的作用是清除状态产生的中间参数,为下次进入提供干净环境

    状态表

    二维状态转换表

    状态机可以分为状态和事件 ,状态的跃迁都是受事件驱动的,因此可以通过一个二维表格来表示状态的跃迁。
    在这里插入图片描述
    (*) 仅当( code == defuse_code) 时才发生到setting 的转换。

          /*1.列出所有的状态*/
          enum
          {
              SETTING,
              TIMING,
              MAX_STATE
          };
          /*2.列出所有的事件*/
          enum
          {
              UP_EVT,
              DOWN_EVT,
              ARM_EVT,
              TICK_EVT,
              MAX_EVT
          };
          
          /*3.定义状态表*/
          typedef void (*fp_state)(EVT_TYPE evt , void* param);
          static  const fp_state  bomb2_table[MAX_STATE][MAX_EVENT] =
          {
              {setting_UP , setting_DOWN , setting_ARM , null},
              {setting_UP , setting_DOWN , setting_ARM , timing_TICK}
          };
          
          struct bomb_t
          {
              const fp_state const *state_table; /* the State-Table */
              uint8_t state; /* the current active state */
              
              uint8_t timeout;
              uint8_t code;
              uint8_t defuse_code;
          };
          struct bomb bomb2=
          {
              .state_table = bomb2_table;
          }
          void bomb2_init(void)
          {
              bomb2.defuse_code = 6; // 0110
              bomb2.state = SETTING;
          }
          
          void bomb2_dispatch(EVT_TYPE evt , void* param)
          {
              fp_state  s = NULL;
              if(evt > MAX_EVT)
              {
                  LOG("EVT type error!");
                  return;
              }
              s = bomb2.state_table[bomb2.state * MAX_EVT + evt];
              if(s != NULL)
              {
                  s(evt , param);
              }
          }
          /*列出所有的状态对应的事件处理函数*/
          void setting_UP(EVT_TYPE evt, void* param)
          {
              if(bomb1.timeout< 60)  ++bomb1.timeout;
              bsp_display(bomb1.timeout);
          }
    
    • 优点
      • 各个状态面向用户相对独立,增加事件和状态不需要去修改先前已存在的状态事件函数。

      • 可将状态机进行封装,有较好的移植性
        函数指针的安全转换 , 利用下面的特性,用户可以扩展带有私有属性的状态机和事件而使用统一的基础状态机接口
        typedef void (*Tran)(struct StateTableTag *me, Event const *e);
        /
        ||
        void Bomb2_setting_ARM (Bomb2 *me, Event const *e);

        typedef struct Bomb
        {
           struct StateTableTag *me;  //必须为第一个成员
            uint8_t private;
        }
        
    • 缺点
      • 函数粒度太小是最明显的一个缺点,一个状态和一个事件就会产生一个函数,当状态和事件较多时,处理函数将增加很快,在阅读代码时,逻辑分散。
    • 没有实现进入退出动作。

    一维状态转换表

    在这里插入图片描述
    实现原理:
    在这里插入图片描述

     typedef void (*fp_action)(EVT_TYPE evt,void* param);
        
        /*转换表基础结构*/
        struct tran_evt_t
        {
          	EVT_TYPE evt;
            uint8_t next_state;
        };
        /*状态的描述*/
        struct  fsm_state_t
        {
            fp_action  enter_action;      //进入动作
            fp_action  exit_action;		 //退出动作
            fp_action  action;           
            
            tran_evt_t* tran;    //转换表
            uint8_t     tran_nb; //转换表的大小
            const char* name;
        }
        /*状态表本体*/
        #define  ARRAY(x)   x,sizeof(x)/sizeof(x[0])
        const struct  fsm_state_t  state_table[]=
        {
            {setting_enter , setting_exit , setting_action , ARRAY(set_tran_evt),"setting" },
            {timing_enter , timing_exit , timing_action , ARRAY(time_tran_evt),"timing" }
        };
        
        /*构建一个状态机*/
        struct fsm
        {
            const struct state_t * state_table; /* the State-Table */
            uint8_t cur_state;                      /* the current active state */
            
            uint8_t timeout;
            uint8_t code;
            uint8_t defuse_code;
        }bomb3;
        
        /*初始化状态机*/
        void  bomb3_init(void)
        {
            bomb3.state_table = state_table;  //指向状态表
            bomb3.cur_state = setting;
            bomb3.defuse_code = 8; //1000
        }
        /*状态机事件派发*/
        void  fsm_dispatch(EVT_TYPE evt , void* param)
        {
            tran_evt_t* p_tran = NULL;
            
            /*获取当前状态的转换表*/
            p_tran = bomb3.state_table[bomb3.cur_state]->tran;
            
            /*判断所有可能的转换是否与当前触发的事件匹配*/
            for(uint8_t i=0;i<x;i++)
            {
                if(p_tran[i]->evt == evt)//事件会触发转换
                {
                    if(NULL != bomb3.state_table[bomb3.cur_state].exit_action){
                		bomb3.state_table[bomb3.cur_state].exit_action(NULL);  //执行退出动作
                	}
                    if(bomb3.state_table[_tran[i]->next_state].enter_action){
                       bomb3.state_table[_tran[i]->next_state].enter_action(NULL);//执行进入动作
                    }
                    /*更新当前状态*/
                    bomb3.cur_state = p_tran[i]->next_state;
                }
                else
                {
                     bomb3.state_table[bomb3.cur_state].action(evt,param);
                }
            }
        }
        /*************************************************************************
        setting状态相关
        ************************************************************************/
        void setting_enter(EVT_TYPE evt , void* param)
        {
            
        }
        void setting_exit(EVT_TYPE evt , void* param)
        {
            
        }
        void setting_action(EVT_TYPE evt , void* param)
        {
            
        }
        tran_evt_t set_tran_evt[]=
        {
            {ARM , timing},
        }
        /*timing 状态相关*/
    
    • 优点
      • 各个状态面向用户相对独立,增加事件和状态不需要去修改先前已存在的状态事件函数。
      • 实现了状态的进入和退出
      • 容易根据状态跃迁图来设计 (状态跃迁图列出了每个状态的跃迁可能,也就是这里的转换表)
      • 实现灵活,可实现复杂逻辑,如上一次状态,增加监护条件来减少事件的数量。可实现非完全事件驱动
    • 缺点
      • 函数粒度较小(比二维小且增长慢),可以看到,每一个状态需要至少3个函数,还需要列出所有的转换关系。

    QP嵌入式实时框架

    特点

    • 事件驱动型编程

      • 好莱坞原则:和传统的顺序式编程方法例如“超级循环”,或传统
        的RTOS 的任务不同。绝大多数的现代事件驱动型系统根据好莱坞原则被构造,
        (Don’t call me; I’ll call you.)
    • 面向对象

      • 类和单一继承
        在这里插入图片描述
    • 工具

      • QM :一个通过UML类图来描述状态机的软件,并且可以自动生成C代码

        在这里插入图片描述

      • QS软件追踪工具
        在这里插入图片描述
        在这里插入图片描述

    QEP实现有限状态机Fsm

    • 实现
      在这里插入图片描述
        /* qevent.h ----------------------------------------------------------------*/
          typedef struct QEventTag 
          {  
           	QSignal sig; 			 
          	uint8_t dynamic_;  
          } QEvent;
          /* qep.h -------------------------------------------------------------------*/
          typedef uint8_t QState; /* status returned from a state-handler function */
          typedef QState (*QStateHandler) (void *me, QEvent const *e); /* argument list */
          typedef struct QFsmTag		 /* Finite State Machine */
          { 
           	QStateHandler state;     /* current active state */
          }QFsm;
          
          #define QFsm_ctor(me_, initial_) ((me_)->state = (initial_))
          void QFsm_init (QFsm *me, QEvent const *e);
          void QFsm_dispatch(QFsm *me, QEvent const *e);
          
          #define Q_RET_HANDLED ((QState)0)
          #define Q_RET_IGNORED ((QState)1)
          #define Q_RET_TRAN ((QState)2)
          #define Q_HANDLED() (Q_RET_HANDLED)
          #define Q_IGNORED() (Q_RET_IGNORED)
          
           #define Q_TRAN(target_) (((QFsm *)me)->state = (QStateHandler)   (target_),Q_RET_TRAN)
          
          enum QReservedSignals
          {
              Q_ENTRY_SIG = 1, 
           	Q_EXIT_SIG, 
           	Q_INIT_SIG, 
           	Q_USER_SIG 
          };
          
          /* file qfsm_ini.c ---------------------------------------------------------*/
          #include "qep_port.h" /* the port of the QEP event processor */
          #include "qassert.h" /* embedded systems-friendly assertions */
          void QFsm_init(QFsm *me, QEvent const *e) 
          {
              (*me->state)(me, e); /* execute the top-most initial transition */
          	/* enter the target */
           	(void)(*me->state)(me , &QEP_reservedEvt_[Q_ENTRY_SIG]);
          }
          /* file qfsm_dis.c ---------------------------------------------------------*/
          void QFsm_dispatch(QFsm *me, QEvent const *e)
          {
              QStateHandler s = me->state; /* save the current state */
           	QState r = (*s)(me, e); /* call the event handler */
           	if (r == Q_RET_TRAN)  /* transition taken? */
              {
            		(void)(*s)(me, &QEP_reservedEvt_[Q_EXIT_SIG]); /* exit the source */
            		(void)(*me->state)(me, &QEP_reservedEvt_[Q_ENTRY_SIG]);/*enter target*/
          	}
          }
      实现上面定时器例子
          #include "qep_port.h" /* the port of the QEP event processor */
          #include "bsp.h" /* board support package */
          
          enum BombSignals /* all signals for the Bomb FSM */
          { 
              UP_SIG = Q_USER_SIG,
              DOWN_SIG,
              ARM_SIG,
              TICK_SIG
          };
          typedef struct TickEvtTag 
          {
            QEvent super;      /* derive from the QEvent structure */
            uint8_t fine_time; /* the fine 1/10 s counter */
          }TickEvt;
          
          typedef struct Bomb4Tag 
          {
           	QFsm super; 	 /* derive from QFsm */
           	uint8_t timeout; /* number of seconds till explosion */
            	uint8_t code;    /* currently entered code to disarm the bomb */
            	uint8_t defuse;  /* secret defuse code to disarm the bomb */
          } Bomb4;
          
          void Bomb4_ctor (Bomb4 *me, uint8_t defuse);
          QState Bomb4_initial(Bomb4 *me, QEvent const *e);
          QState Bomb4_setting(Bomb4 *me, QEvent const *e);
          QState Bomb4_timing (Bomb4 *me, QEvent const *e);
          /*--------------------------------------------------------------------------*/
          /* the initial value of the timeout */
          #define INIT_TIMEOUT 10
          /*..........................................................................*/
          void Bomb4_ctor(Bomb4 *me, uint8_t defuse) {
          	QFsm_ctor_(&me->super, (QStateHandler)&Bomb4_initial);
           	me->defuse = defuse; /* the defuse code is assigned at instantiation */
          }
          /*..........................................................................*/
          QState Bomb4_initial(Bomb4 *me, QEvent const *e) {
          	(void)e;
          	me->timeout = INIT_TIMEOUT;
          	return Q_TRAN(&Bomb4_setting);
          }
          /*..........................................................................*/
          QState Bomb4_setting(Bomb4 *me, QEvent const *e) {
          	switch (e->sig){
          		case UP_SIG:{
          			if (me->timeout < 60) {
          				++me->timeout;
          				BSP_display(me->timeout);
          			}
                      return Q_HANDLED();
          		}
          		case DOWN_SIG: {
          			if (me->timeout > 1) {
          				--me->timeout;
          				BSP_display(me->timeout);
          			}
          			return Q_HANDLED();
          		}
          		case ARM_SIG: {
          			return Q_TRAN(&Bomb4_timing); /* transition to "timing" */
          		}
          	}
          	return Q_IGNORED();
          }
          /*..........................................................................*/
          void Bomb4_timing(Bomb4 *me, QEvent const *e) {
          	switch (e->sig) {
          		case Q_ENTRY_SIG: {
          			me->code = 0; /* clear the defuse code */
          			return Q_HANDLED();
                  }
          		case UP_SIG: {
          			me->code <<= 1;
          			me->code |= 1;
          			return Q_HANDLED();
                  }
          		case DOWN_SIG: {
          			me->code <<= 1;
          			return Q_HANDLED();
          		}
          		case ARM_SIG: {
          			if (me->code == me->defuse) {
          				return Q_TRAN(&Bomb4_setting);
          			}
          			return Q_HANDLED();
          		}
          		case TICK_SIG: {
          			if (((TickEvt const *)e)->fine_time == 0) {
          				--me->timeout;
          				BSP_display(me->timeout);
          				if (me->timeout == 0) {
          				BSP_boom(); /* destroy the bomb */
          				}
          			}
          			return Q_HANDLED();
          		}
          	}
          	return Q_IGNORED();
          }
    
    • 优点
      • 采用面向对象的设计方法,很好的移植性
      • 实现了进入退出动作
      • 合适的粒度,且事件的粒度可控
      • 状态切换时通过改变指针,效率高
      • 可扩展成为层次状态机
    • 缺点
      • 对事件的定义以及事件粒度的控制是设计的最大难点,如串口接收到一帧数据,这些变量的更新单独作为某个事件,还是串口收到数据作为一个事件。再或者显示屏,如果使用此种编程方式,如何设计事件。

    QP 实现层次状态机 Hsm简介

    在这里插入图片描述

    初始化
    在这里插入图片描述

    初始化层次状态机的实现:在初始化时,用户所选取的状态永远是最底层的状态,如上图,我们在计算器开机后,应该进入的是开始状态,这就涉及到一个问题,由最初top(顶状态)到begin 是有一条状态切换路径的,当我们设置状态为begin如何搜索这条路径成为关键(知道了路径才能正确的进入begin,要执行路径中过渡状态的进入和退出事件)

    void QHsm_init(QHsm *me, QEvent const *e) 
        {
        	Q_ALLEGE((*me->state)(me, e) == Q_RET_TRAN);
            t = (QStateHandler)&QHsm_top; /* HSM starts in the top state */
         	do { /* drill into the target... */
        		QStateHandler path[QEP_MAX_NEST_DEPTH_];
         		int8_t ip = (int8_t)0; /* transition entry path index */
         		path[0] = me->state; /* 这里的状态为begin */
                
                /*通过执行空信号,从底层状态找到顶状态的路径*/
         	 	(void)QEP_TRIG_(me->state, QEP_EMPTY_SIG_);
         	 	while (me->state != t) {
          			path[++ip] = me->state;
        			(void)QEP_TRIG_(me->state, QEP_EMPTY_SIG_);
        		}
                /*切换为begin*/
         		me->state = path[0]; /* restore the target of the initial tran. */
        		/* 钻到最底层的状态,执行路径中的所有进入事件 */
          		Q_ASSERT(ip < (int8_t)QEP_MAX_NEST_DEPTH_);
        		do { /* retrace the entry path in reverse (desired) order... */
          				QEP_ENTER_(path[ip]); /* enter path[ip] */
         		} while ((--ip) >= (int8_t)0);
                
          		t = path[0]; /* current state becomes the new source */
          	} while (QEP_TRIG_(t, Q_INIT_SIG) == Q_RET_TRAN);
         	me->state = t;
        }
    

    状态切换
    在这里插入图片描述

     /*.................................................................*/
        QState result(Calc *me, QEvent const *e) 
        {
            switch (e->sig) 
            {you
                case ENTER_SIG:{
                    break;
                }
                case EXIT_SIG:{
                	break;
                }
            	case C_SIG: 
                {
            		printf("clear");    
                    return Q_HANDLED();
                }
                case B_SIG:
                {  
                    return Q_TRAN(&begin);
                }
        	}
        	return Q_SUPER(&reday);
        }
        /*.ready为result和begin的超状态................................................*/
        QState ready(Calc *me, QEvent const *e) 
        {
            switch (e->sig) 
            {
                case ENTER_SIG:{
                    break;
                }
                case EXIT_SIG:{
                	break;
                }
                case OPER_SIG:
                {  
                    return Q_TRAN(&opEntered);
                }
        	}
        	return Q_SUPER(&on);
        }
    
    
    
        void QHsm_dispatch(QHsm *me, QEvent const *e) 
        {
            QStateHandler path[QEP_MAX_NEST_DEPTH_];
        	QStateHandler s;
        	QStateHandler t;
        	QState r;
        	t = me->state; 				/* save the current state */
        	do { 						/* process the event hierarchically... */
        		s = me->state;
        		r = (*s)(me, e); 		/* invoke state handler s */
        	} while (r == Q_RET_SUPER); //当前状态不能处理事件 ,直到找到能处理事件的状态
            
        	if (r == Q_RET_TRAN) { 				/* transition taken? */
        		int8_t ip = (int8_t)(-1); 		/* transition entry path index */
        		int8_t iq;					 	/* helper transition entry path index */
        		path[0] = me->state; 			/* save the target of the transition */
           		path[1] = t;
        		while (t != s) { 		/* exit current state to transition source s... */
        			if (QEP_TRIG_(t, Q_EXIT_SIG) == Q_RET_HANDLED) {/*exit handled? */
        				(void)QEP_TRIG_(t, QEP_EMPTY_SIG_); /* find superstate of t */
        			}
        			t = me->state; 		/* me->state holds the superstate */
        		}
        	 . . .
        	}
        	me->state = t; 				/* set new state or restore the current state */
        }
    

    在这里插入图片描述

    	t = path[0]; /* target of the transition */
            if (s == t) { /* (a) check source==target (transition to self) */
                 QEP_EXIT_(s) /* exit the source */
                 ip = (int8_t)0; /* enter the target */
             }
             else {
                 (void)QEP_TRIG_(t, QEP_EMPTY_SIG_); /* superstate of target */
                 t = me->state;
                 if (s == t) { /* (b) check source==target->super */
                      ip = (int8_t)0; /* enter the target */
             	  }
                 else {
                     (void)QEP_TRIG_(s, QEP_EMPTY_SIG_); /* superstate of src */
                     /* (c) check source->super==target->super */
                     if(me->state == t) {
                         QEP_EXIT_(s) /* exit the source */
                         ip = (int8_t)0; /* enter the target */
                      }
                      else {
                           /* (d) check source->super==target */
                           if (me->state == path[0]) {
                              QEP_EXIT_(s) /* exit the source */
                           }
                           else { /* (e) check rest of source==target->super->super..
                               * and store the entry path along the way */
                            ....
    
    

    QP实时框架的组成

    在这里插入图片描述
    在这里插入图片描述

    - 内存管理
    使用内存池,对于低性能mcu,内存极为有限,引入内存管理主要是整个架构中,是以事件作为主要的任务通信手段,且事件是带参数的,可能相同类型的事件会多次触发,而事件处理完成后,需要清除事件,无法使用静态的事件,因此是有必要为不同事件创建内存池的。
    对于不同块大小的内存池,需要考虑的是每个块的起始地址对齐问题。在进行内存池初始化时,我们是根据blocksize+header大小来进行划分内存池的。假设一个2字节的结构,如果以2来进行划分,假设mcu 4字节对齐,那么将有一半的结构起始地址无法对齐,这时需要为每个块预留空间,保证每个块的对齐。
    在这里插入图片描述

    - 事件队列

    • 每一个活动对象维护一个事件队列,事件都是由基础事件派生的,不同类型的事件只需要将其基础事件成员添加到活动对象的队列中即可,最终在取出的时候通过一个强制转换便能获得附加的参数。

    在这里插入图片描述

    - 事件派发

    • 直接事件发送
      • QActive_postLIFO()
    • 发行订阅事件发送
      • 竖轴表示信号(为事件的基类)
      • 活动对象支持64个优先级,每一个活动对象要求拥有唯一优先级
      • 通过优先级的bit位来表示某个事件被哪些活动对象订阅,并在事件触发后根据优先级为活动对象派发事件。
        在这里插入图片描述

    - 定时事件

    • 非有序链表
      在这里插入图片描述

    • 合作式调度器QV
      在这里插入图片描述

    • 可抢占式调度器QK

    QP nano的简介

    • 完全支持层次式状态嵌套,包括在最多4 层状态嵌套情况下,对任何状态转换拓扑的可保
      证的进入/ 退出动作
    • 支持高达8 个并发执行的,可确定的,线程安全的事件队列的活动对象57
    • 支持一个字节宽( 255 个信号)的信号,和一个可伸缩的参数,它可被配置成0 (没有参
      数), 1 , 2 或4 字节
    • 使用先进先出FIFO排队策略的直接事件派发机制
    • 每个活动对象有一个一次性时间事件(定时器),它的可配置动态范围是0(没有时间事
      件) , 1 , 2 或4 字节
    • 内建的合作式vanilla 内核
    • 内建的名为QK-nano 的可抢占型RTC内核(见第六章6.3.8节)
    • 带有空闲回调函数的低功耗架构,用来方便的实现节省功耗模式。
    • 在代码里为流行的低端CPU架构的C编译器的非标准扩展进行了准备(例如,在代码空
      间分配常数对象,可重入函数,等等)
    • 基于断言的错误处理策略
    • 代码风格
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    参考资料

    链接:https://pan.baidu.com/s/18LQhr7qumRSQvHqQgE4UnA
    提取码:qqqq

    http://www.state-machine.com/ (QP官网)

    展开全文
  • 状态机编程的流水灯,stm32f103zet,编程思路很好,供大家学习使用
  • C语言状态机编程思想

    2020-12-29 11:21:59
    关注、星标公众号,直达精彩内容文章来源:头条-嵌入式在左C语言在右链接:https://www.toutiao.com/i6843028812112855564/有限状态机概念有限状态机...

    关注、星标公众号,直达精彩内容

    文章来源:头条-嵌入式在左C语言在右

    链接:https://www.toutiao.com/i6843028812112855564/

    有限状态机概念

    有限状态机是一种概念思想,把复杂的控制逻辑分解成有限个稳定状态,组成闭环系统,通过事件触发,让状态机按设定的顺序处理事务。单片机C语言的状态机编程,是利用条件选择语句(switchcase或者ifelse)切换状态,通过改变状态机状态,让程序按设定的顺序执行。

    有限状态机由有限的状态和相互之间的转移构成,在任何时候只能处于给定数目的状态中的一个。当接收到一个输入事件时,状态机产生一个输出,同时也可能伴随着状态的转移。状态机的原理如下:在当前状态下,发生某个事件后转移到下一个状态,然后决定执行的功能动作。可参考如下示意图:

    应用举例

    要想使用状态机思想进行编程,需要将任务分解成有限个稳定状态。

    这里以常见的按键动作进行举例说明:

    上图为按键典型的动作图,可以分解为四个状态,分别为:

    状态1 = 按键弹起、

    状态2 = 前沿抖动、

    状态3 = 按键按下、

    状态4 = 后沿抖动。

    有限状态机的C代码实现如下:

    if (定时器 >= 10ms) //10ms是典型消抖时间
    {
       switch (按键状态)
       {
         case 按键弹起状态:
          if (IO读取为低电平) 按键状态=前沿抖动; 
         break;
         case 前沿抖动状态:
          if (IO读取为低电平) 按键状态=按键按下; 
         break;
         case 按键按下状态:
          if (IO读取为高电平) 按键状态=后沿抖动; 
         break;
         case 后沿抖动状态:
          if (IO读取为高电平) 按键状态=按键弹起; 
         break;
    default:按键状态=按键弹起;
       }
    }
    

    状态机编程建议

    巧妙的使用结构体和枚举一方面可以便于扩展和维护状态机的状态和事件,另一方面可提高程序的可读性。假设有3种状态(状态数可以随意增加),状态枚举如下:

    typedef enum {
      state_1=1,
      state_2,
      state_3
    }State;
    

    假设有5个事件(也可以随意增加),事件枚举如下:

    typedef enum{
      event_1=1,
      event_2,
      event_3,
      event_4,
      event_5
    }Event;
    

    定义一个结构体描述如下:

    typedef struct {
      State curState;      //当前状态
      Event eventId;      //事件
      State nextState;   //下一个状态
      Action action;     //动作功能
    }StateEvent;
    

    根据具体的应用场景调整StateEvent,并赋予相应的动作功能,整体的基本流程如下:当前状态->有事件触发->跳到下一个状态->具体的动作功能

    总结

    状态机应用很广泛,也可以锻炼逻辑思维,LoRa消息推送也常采用状态机的思想, 实际上状态机涉及的知识点很多,本篇文章只是简要的介绍了下单片机C语言的状态机编程思想,在日后的开发设计中,需要不断的总结经验并灵活应用。

    -END-

    整理文章为传播相关技术,版权归原作者所有 |

    | 如有侵权,请联系删除 |

    往期好文合集

    来,看看这20个常用的宏定义!

    干货 | 深度剖析C语言的main函数

    扯淡!C语言怎么可能被淘汰呢?

      最 后  

     

    若觉得文章不错,转发分享,也是我们继续更新的动力。

    5T资源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,PCB、FPGA、DSP、labview、单片机、等等

    在公众号内回复「更多资源」,即可免费获取,期待你的关注~

    展开全文
  • 单片机C语言的状态机编程,是利用条件选择语句(switch – case)切换状态,通过函数内部指令改变状态机状态,让程序按设定的顺序执行。 二、应用说明 1、要想使用状态机进行编程,需要将任务分解成有限个稳定状态...
  • JTAG协议入门及状态机编程 。。。。。。。。。。。。
  • C语言入门--状态机编程

    千次阅读 2018-05-08 19:09:08
    状态机的好处不用多说,自己百度去,但传统的编程模式,无论是C语言,或是硬件FPGA的Verilog都是采用switch-case结构,硬件的还好说,是并行的,但如果是C语言实现状态机则可能需要对每个case进行判断,状态少比如几...
  • 【C语言】状态机编程

    千次阅读 2018-07-06 15:43:10
    状态机原理:有限状态机的工作原理如图1所示,发生...这无意是最直观的方式,使用一堆条件判断,会编程的人都可以做到,对简单小巧的状态机来说最合适,但是毫无疑问,这样的方式比较原始,对庞大的状态机难以维护。...
  • 状态机编程思想及实例

    千次阅读 2018-06-29 17:30:40
    状态机编程思想状态机是软件编程中的一个重要概念。状态机将程序的行为划分为若干个状态,对于每一个状态规定其行为和可能的状态转换关系。状态机的状态即可以由其内部定义的状态转换关系改变,也可由外部操作改变,...
  • 嵌入式编程状态机

    千次阅读 2021-11-02 00:09:44
    2、状态机编程的优点(1)提高CPU使用效率(2) 逻辑完备性(3)程序结构清晰3、状态机的三种实现方法switch—case 法表格驱动法函数指针法小节 摘要:不知道大家有没有这样一种感觉,就是感觉自己玩单片机还可以,各个...
  • 状态机编程实例及适用范围

    万次阅读 2014-05-25 21:35:02
    状态机编程思想小议
  • FPGA的状态机编程方法

    2010-05-11 12:23:19
    关于FPGA状态机编程方法的讲解,适合学习FPGA的用户使用
  • 主要描述用状态机思维实现一些程序的控制算法。 包括1、基本概念; 2、状态机图种的模型元素; 3、制定状态机图中的动作和事件; 4、组合状态; 5、实例
  • 状态机 编程 c语言

    2011-08-17 12:51:40
    状态机编程 举例讲解状态机编程方法 是以C语言为范例的
  • 4中流行的有限状态机C语言编程方法。非常简单易学
  • 有限状态机游戏编程

    2014-05-07 11:04:31
    1.人物类的实现(以Mao类为例) 2.状态类的实现(以MaoOwnedStates为例) 3.消息系统的添加(以CommandorOwnedStates为例)
  • C语言实现的状态机

    热门讨论 2014-06-29 22:58:08
    C状态机,switch-case版;C状态机,查表法。
  • 好东东要分享键盘输入接口与状态机编程,通用I/O数字输入接口
  • 记一次代码重构--状态机编程

    千次阅读 2017-08-05 14:54:00
    状态机编程是一种非常常用的编程手段,本文将结合一次实际开发过程中的重构案例来跟大家一起学习这种编程方法。
  • STM32串口通信协议和状态机模式程序,亲测能用。内有详细的讲解文档,很好的学习资料。
  • 状态机的C语言编程

    2012-11-09 10:23:56
    有限状态机的实现方式、状态机的两种写法+实例、有限状态机自动机
  • 搞PLC编程多年,一直不知道状态机,学习matlab后,发现状态机编程异常方便,过去很多编程时的疑惑豁然开朗起来。 下面是用状态机描述的控制任务。 这个状态机较简单,那如何在STL中把它描述出来呢? 这里我们选择用...
  • 说到单片机编程,不得不说到状态机状态机做为软件编程的主要架构已经在各种语言中应用,当然包括C语言,在一个思路清晰而且高效的程序中,必然有状态机的身影浮现。灵活的应用状态机不仅是程序更高效,而且可读性...
  • 51单片机实现状态机

    2012-03-21 14:51:26
    一份不错的学习单片机状态机的ppt,应该是一份老师上课用的课件吧
  • 状态机编程技巧:状态表与函数表

    千次阅读 2013-10-31 10:55:19
    没有swith case那样可以直接能通过程序代码看到各 状态 的跳转与 状态机 的执行步骤, 但这种 状态表与函数表 实现的 状态机。代码简洁,无遗漏状态,经典! 简单实例: #include char str[128] = " ./a.out ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 350,289
精华内容 140,115
关键字:

状态机编程

友情链接: dlaod622.zip