经典状态机的层次式实现

经典状态机的层次式实现
作者: yugen 提交日期:2008-8-28 12:08:00  正常 | 分类: | 访问量:2195

   在Miro Samek Ph.D.所著的《嵌入式系统的微模块化程序设计――实用程序状态图C/C++实现》中提到了三种经典状态机的实现方法。这三种方法都是非层次式的。通过独立的子状态变量来记录每个状态的子状态和在进入/退出动作中实现子状态的初始化/清除来实现经典状态机的层次式。
  例子,一个状态机的部分状态图:
  

  
   在上面的状态图中,顶层有两个状态:STATE_MEASURE和STATE_PASSWD,状态机初始化为STATE_MEASURE状态。而STATE_MEASURE还有三个子状态STATE_MEASURE_NORMAL、STATE_MEASURE_MIN_SUCCESS、STATE_MEASURE_MIN。
  使用FSMState变量来记录顶层状态的状态,而使用StateMeasureState来记录STATE_MEASURE_NORMAL状态的子状态。有多少个状态有子状态,就要有多少个变量还记录这些子状态。
   状态之间的切换是通过事件来切换的。图中只有一个事件,即EVKey事件(按键事件)。EVKey事件有一个KeyValue参数,指示按键的键值。在有子状态的顶层状态的处理器中,先检查事件是不是发送给自己的,若是,则直接处理;不是的话,则转发到子状态处理器处理。例如STATE_MEASURE状态下按下KEY_SET键处理方法。进入和退出事件是隐含的,在进入和退出每个状态时,都会产生(但不一定会有实际动作函数执行),另一个用处就是,初始化子状态。
   对于状态机的使用者来说,所要做的就是检测事件-->发送事件到状态机(调用函数FSMDispatch(FSMState, (EVENT*)&Event);)。不需要关心状态机的内部状态及子状态的问题。
  
  实现代码:
  
  /******************************************************************************
  *事件类型代码定义,定义各种类型的事件代号
  ******************************************************************************/
  typedef enum _EVENT_ID
  {
   EVENT_ID_ENTRY, //进入事件,必须要有,用来初始化子状态
   EVENT_ID_EXIT, //退出事件,保留
   EVENT_ID_KEY //按键事件
  //添加其他事件类型
  } EVENT_ID;
  
  /******************************************************************************
  *事件定义,用于定义具体的事件,EVENT是一个基类事件,其他事件结构的第一个
  *成员必须为EVENT_ID EventID;以能实现事件之间的强制转换
  ******************************************************************************/
  typedef struct _EVENT
  {
   EVENT_ID EventID; //事件类型
  } EVENT;
  
  /******************************************************************************
  *常量事件定义,包括进入事件和退出事件
  ******************************************************************************/
  ///
  /// 进入事件定义
  ///
  const EVENT EventEntry = 
  {
   EVENT_ID_ENTRY,
  };
  
  ///
  /// 退出事件定义
  ///
  const EVENT EventExit =
  {
   EVENT_ID_EXIT,
  };
  
  /******************************************************************************
  *按键事件定义,声明此类型的变量时,必须将EventID设置为EVENT_ID_KEY
  ******************************************************************************/
  typedef struct _EVENT_KEY
  {
   EVENT_ID EventID; //EventID为第一个成员,以能实现和EVENT转换
   uint8 KeyValue; //按键值
  }EVENT_KEY;
  
  /******************************************************************************
  *状态处理器指针定义
  ******************************************************************************/
  typedef void (*FSM_STATE)(const EVENT *e);
  
  /******************************************************************************
  *状态机变量定义
  ******************************************************************************/
  FSM_STATE FSMState= NULL; //最高层状态机当前状态
  FSM_STATE StateMeasureState = NULL; //用于记录STATE_MEASURE状态的子状态
  
  /******************************************************************************
  *状态机处理器函数声明,以State前缀
  ******************************************************************************/
  void StateMeasure(const EVENT *e);
  void StateMeasureNormal(const EVENT *e); //为STATE_MEASURE子状态STATE_MEASURE_NORMAL的子状态处理器
  void StateMeasureMin(const EVENT *e);
  void StateMeausreMinSuccess(const EVENT *e);
  void StatePasswd(const EVENT *e);
  
  /******************************************************************************
  *状态机的动作函数声明,以Act前缀
  ******************************************************************************/
  void ActDispMainClear(void);
  void ActSetMeasureMode(uint32 Mode);
  //下略
  
  ///
  /// 状态机转换到新的状态
  ///
  /// 要转换状态的状态机(即状态机变量)
  /// 新的状态(传递的是新的状态处理器函数)
  void FSMTran(FSM_STATE *FSM, FSM_STATE target)
  {
   (*FSM)(&EventExit); //发送退出事件到上个状态处理器,以执行一些清除动作
   *FSM = target;
   (*FSM)(&EventEntry); //发送进入事件到新的状态处理器,若状态有子状态的话,将执行子状态初始化
  }
  
  ///
  /// 将事件分发到状态机。因为高层状态和子层状态有不同的状态机变量,所以需要一个参数来指明事件分发到那个状态机。
  ///
  /// 接收分发事件的目标状态机
  /// 要分发的事件
  void FSMDispatch(FSM_STATE FSM, const EVENT *e)
  {
   FSM(e);
  }
  
  ///
  /// 状态机初始化,顶层状态的初始化
  ///
  void FSMInit(void)
  {
   FSMState = StateMeasure; //状态机的初始状态是STATE_MEASURE
   FSMDispatch(FSMState, &EventEntry);
  }
  
  ///
  /// STATE_MEASURE状态处理
  ///
  /// 当前状态下要处理的事件
  void StateMeasure(const EVENT *e)
  {
   EVENT_KEY *EventKey;
  
   switch(e->EventID)
   {
   case EVENT_ID_ENTRY:
   ActSetMeasureMode(MEASURE_NORMAL);
   StateMeasureState = StateMeasureNormal; //首次进入STATE_MEASURE时,初始化子状态为STATE_MEASURE_NORMAL;
   FSMDispatch(StateMeasureState, e);
   break;
   case EVENT_ID_EXIT:
   if(StateMeasureState != NULL) //转发到子状态处理器处理
   {
   FSMDispatch(StateMeasureState, e);
   }
   break;
   case EVENT_ID_KEY:
   EventKey = (EVENT_KEY*)e; //事件转换成按键事件
   if(EventKey->KeyValue == KEY_SET) //这里处理关键,顶层状态处理器先捕获事件,判断是不是发送到顶层的,若是则直接处理,不是,则转发到子状态处理器处理。
   {
   ActSetMeasureMode(STOP_MEASURE);
   ActDispMainClear();
   ActResetPasswd();
   ActDispPasswd(InputPasswd, PasswdPos);
   FSMTran(&FSMState, StatePasswd); //顶层状态转换,转换到STATE_PASSWD状态
   }
   else
   {
   FSMDispatch(StateMeasureState, e); //交给子状态机处理
   }
   break;
   }
  }
  
  ///
  /// STATE_MEASURE_NORMAL状态处理
  ///
  /// 当前状态下要处理的事件
  void StateMeasureNormal(const EVENT *e)
  {
   EVENT_KEY *EventKey;
  
   switch(e->EventID)
   {
   case EVENT_ID_KEY:
   EventKey = (EVENT_KEY*)e;
   if(EventKey->KeyValue == KEY_SET)
   {
   ActSetMeasureMode(STOP_MEASURE);
   ActResetPasswd();
   ActDispPasswd(InputPasswd, 0);
   FSMTran(&FSMState, StatePasswd);
   }
   else if(EventKey->KeyValue == KEY_MIN)
   {
   ActSetMeasureMode(MEASURE_MIN);
   FSMTran(&StateMeasureState, StateMeasureMin);
   }
   break;
   }
  }
  
  ///
  /// STATE_MEASURE_MIN_SUCCESS状态处理
  ///
  /// 当前状态下要处理的事件
  void StateMeasureMinSuccess(const EVENT *e)
  {
   //EVENT_KEY *EventKey;
  
   switch(e->EventID)
   {
   case EVENT_ID_KEY: //任意键
   ActSetMeasureMode(MEASURE_NORMAL);
   FSMTran(&StateMeasureState, StateMeasureNormal);
   break;
   }
  }
  
  ///
  /// STATE_MEASURE_MIN状态处理
  ///
  /// 当前状态下要处理的事件
  void StateMeasureMin(const EVENT *e)
  {
   EVENT_KEY *EventKey;
  
   switch(e->EventID)
   {
   case EVENT_ID_KEY:
   EventKey = (EVENT_KEY*)e;
   if(EventKey->KeyValue == KEY_CANCEL)
   {
   ActSetMeasureMode(MEASURE_NORMAL);
   FSMTran(&StateMeasureState, StateMeasureNormal);
   }
   else if(EventKey->KeyValue == KEY_MIN)
   {
   ActSetMeasureMode(MEASURE_MIN_OK);
   FSMTran(&StateMeasureState, StateMeasureMinSuccess);
   }
   break;
   }
  }
  
   在《嵌入式系统的微模块化程序设计――实用程序状态图C/C++实现》中提到了Ian Horrocks提出的一种方法也是使用独立的变量来表示层次,我没看过那篇文章,不知道是不是与此类似。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
状态模是GoF23个模中最常用的之一,这篇小文不打算涉及方方面面的内容,只想在状态模的高效运用方面谈一下自己的心得体会。   状态模是用来设计状态机的,因此下面的叙述中将它们等同理解。有关状态机设计方面的书籍,我这里隆重推荐一本:《Practical Statecharts in C/C++ Quantum Programming for Embedded Systems》,中文名叫做《嵌入系统的微模块化程序设计-实用状态图C/C++实现》,北航出版的,作者是Miro Samek博士,长期从事嵌入实时系统的开发,具有丰富的经验。如果你想对状态机领域进行比较深入的研究,这本书绝对不容错过。   让我们先来看看比较“古老”的状态机实现,假设你还是用C语言。一般而言,我们用得到状态机系统都可以称为事件(消息)驱动系统,系统往往处于某个状态,等待外部的激励。这些激励可以是外部的事件、定时器超时等等,系统收到这些事件后,进行相应的处理,然后跃迁到新的状态(状态也可能不变)继续等待下一个激励的到来,最后直到相应的事务处理完毕为止。   典型的状态机实现中需要考虑几个要素:状态、消息(及其内容)、消息处理函数以及系统上下文等。系统处于某个状态,收到某个消息后,解析出消息内容,然后调用相应的消息处理函数进行处理,而消息处理函数往往会用到状态机的上下文数据,消息处理完毕系统会跃迁到新的状态。   典型代码大致如下:   switch (state)   {    case STATE1:    switch (msg)    {    case MSG1:    HandleMsg1(msgpara,context);    nextstate(STATE2);    break;    case MSG2:    HandleMsg2(msgpara,context);    nextstate(STATE3);    break;    /*......*/    }    case STATE2:    switch (msg)    {    case MSG3:    HandleMsg3(msgpara,context);    nextstate(STATE3);    break;    /*......*/    }    /*......*/   }   可以看到这就是所谓的平面状态机,特点就是先枚举状态,然后再枚举消息,如果找不到的话,就将消息丢弃。   为了使状态机更高效的运行,这里有几个小技巧,稍为总结一下。   (1)把接收概率大的消息放在前面   把同一个状态下最有可能收到的消息放在前面。一个状态下可能要处理很多消息,这视乎你状态划分的粒度大小。每个消息收到的机会并不是均等的,有些消息系统收到的概率很大,有些很小,因此把接收概率大的消息放在前面,这样可以减少case消息时的比较次数,相应的执行效率就提高了。对于一个状态机的运行而言,这样的节省当然微乎其微,但假如你的系统同时运行成千上万个这种状态机时,那么就有必要考虑一下这种优化了。   (2)查表法   第(1)种方法再怎么优化,也需要枚举状态和消息,假如能把这方面的开销变成零,那么效率自然可以进一步提升。我们可以想象把消息处理函数指针放在一个二维数组(表)中,其中一维代表状态,另外一维代表消息序号,那么通过p[state][msg]就可以定位到当前状态下当前消息的处理函数。对一些简单的应用,甚至可以把新状态也存放在这张二维表中,这样的好处是用户不需要显示调用状态跃迁函数。当然对于一些状态有不同执行路径的情况,状态的跃迁可能就要放在消息处理函数之中。   (3)消息先分段再查表   一般而言,一个状态机的状态数目不会很多,当然接收的消息数目也是有限的。但一般来说,消息是不连续的,这样应用查表法可能内存的开销就比较大,尤其是消息序号比较稀疏的时候,内存更加浪费。   在一般的嵌入软件开发中,我发现往往可以将消息进行归类分段,比方说一个接口的消息定义成一段。这样虽然消息不连续,但通过分段后可以将消息放在一个较紧凑的内存空间中,在这个空间里再运用查表法,就有可能达到效率和空间开销的平衡。注意,我是说有可能,并不是一定,这取决于具体情况。系统收到消息后,先判断消息处于哪个分段,然后调用p[state][msg - offset]来进行处理

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值