当前位置:首页 > 嵌入式 > 嵌入式大杂烩
[导读]星标「嵌入式大杂烩」,一起进步!来源:https://blog.csdn.net/qq_36969440/article/details/110387716状态机基本术语现态:是指当前所处的状态。条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。动...

星标嵌入式大杂烩」,一起进步!

编程学习笔记,同时,公众号内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!" data-from="0">来源:https://blog.csdn.net/qq_36969440/article/details/110387716

状态机基本术语

现态:是指当前所处的状态。条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。

动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。

传统有限状态机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        {
            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 , 
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: 驱动电源

在工业自动化蓬勃发展的当下,工业电机作为核心动力设备,其驱动电源的性能直接关系到整个系统的稳定性和可靠性。其中,反电动势抑制与过流保护是驱动电源设计中至关重要的两个环节,集成化方案的设计成为提升电机驱动性能的关键。

关键字: 工业电机 驱动电源

LED 驱动电源作为 LED 照明系统的 “心脏”,其稳定性直接决定了整个照明设备的使用寿命。然而,在实际应用中,LED 驱动电源易损坏的问题却十分常见,不仅增加了维护成本,还影响了用户体验。要解决这一问题,需从设计、生...

关键字: 驱动电源 照明系统 散热

根据LED驱动电源的公式,电感内电流波动大小和电感值成反比,输出纹波和输出电容值成反比。所以加大电感值和输出电容值可以减小纹波。

关键字: LED 设计 驱动电源

电动汽车(EV)作为新能源汽车的重要代表,正逐渐成为全球汽车产业的重要发展方向。电动汽车的核心技术之一是电机驱动控制系统,而绝缘栅双极型晶体管(IGBT)作为电机驱动系统中的关键元件,其性能直接影响到电动汽车的动力性能和...

关键字: 电动汽车 新能源 驱动电源

在现代城市建设中,街道及停车场照明作为基础设施的重要组成部分,其质量和效率直接关系到城市的公共安全、居民生活质量和能源利用效率。随着科技的进步,高亮度白光发光二极管(LED)因其独特的优势逐渐取代传统光源,成为大功率区域...

关键字: 发光二极管 驱动电源 LED

LED通用照明设计工程师会遇到许多挑战,如功率密度、功率因数校正(PFC)、空间受限和可靠性等。

关键字: LED 驱动电源 功率因数校正

在LED照明技术日益普及的今天,LED驱动电源的电磁干扰(EMI)问题成为了一个不可忽视的挑战。电磁干扰不仅会影响LED灯具的正常工作,还可能对周围电子设备造成不利影响,甚至引发系统故障。因此,采取有效的硬件措施来解决L...

关键字: LED照明技术 电磁干扰 驱动电源

开关电源具有效率高的特性,而且开关电源的变压器体积比串联稳压型电源的要小得多,电源电路比较整洁,整机重量也有所下降,所以,现在的LED驱动电源

关键字: LED 驱动电源 开关电源

LED驱动电源是把电源供应转换为特定的电压电流以驱动LED发光的电压转换器,通常情况下:LED驱动电源的输入包括高压工频交流(即市电)、低压直流、高压直流、低压高频交流(如电子变压器的输出)等。

关键字: LED 隧道灯 驱动电源
关闭