QEP_FSM有限状态机框架

QEP_FSM有限状态机框架

1. 有限状态机框架介绍

有限状态机在C语言或者C++中的典型实现方式主要包含以下几种:

  • 嵌套的switch语句

  • 状态表

  • 面向对象的设计模式

  • 基于以上方法的结合变体

典型的实现方法可以参考这篇博客,对嵌套switch和状态表的设计做了详细介绍:
C语言_有限状态机FSM: link

本文介绍一种新的有限状态机的实现方法QEP,是一种基于事件的状态处理方式,QEP是QP框架的一个组件,这里简单介绍一下QP框架。

QP(Quantum Platform, 量子平台,简称QP,)是一个用于实时嵌入式系统的软件框架,QP是轻量级的、开源的、基于层次式状态机的、事件驱动的平台。

QP由一个合乎UML规范的事件处理器(QEP),一个可移植的事件驱动实时框架(QF),一个小型化的运行至完成的内核(QK)和一个软件跟踪系统(QS)组成。

在这里插入图片描述

其中,QEP(Quantum Event Processor)是一个合乎UML规范的事件处理器。它使得UML状态机的直接编码(使用UML状态图)成为可能,并能生成高度可维护的C/C++代码。每一个状态机元素都精确无歧义地对应到唯一的代码片段。QEP完全支持层次化状态嵌套,这方便了子状态机的复用而无需重复进行编码。

2. QEP实现原理

QEP结合了状态表的设计方法,但是状态表的设计有一个缺陷,状态表通常是一个二维数组,数组的行和列表示状态和触发事件,数组元素则是事件处理的函数指针,当状态和事件比较多是,将需要大量的函数实现事件的处理。QEP则每个状态需要一个函数,函数中采用switch case结构处理事件。

QEP的创新点在于把状态映射为函数指针,QEP状态机提供给了标准接口init()和dispatch()。其中的核心结构是QFsm,是一个抽象类,不需要直接实例化,而是在创建实例状态的时候继承QFsm父类,派生出实际需要的状态,其中可以添加需要的状态变量。例如本例中,Bomb实例继承了QFsm类,但是有自己的状态便令timeout, code等。QEP事件处理器的结构如下:

QEP事件处理器的结构

2.1 QFsm结构

QStateHandlerQFsm中的核心结构,使用一个函数指针来表示状态,函数指针所指向的函数里面使用一个switch结构处理该状态下所有的事件,处理完事件返回一个状态值,已处理/忽略/需要状态迁移。

Q_TRAN宏中直接在需要状态迁移时改变了实例状态(myBomb)即状态函数指针,并且返回了一个状态值Q_RET_TRAN,dispatch根据这个返回的值判断是否进行状态切换,或者状态不需要切换继续执行当前状态。

这个宏的定义是有技巧的,是一个逗号表达式,return执行了逗号之前的内容,返回了逗号之后的值

这里定义的基本事件,是每一个状态必须有的

typedef  U8  QState;

typedef  QState  (*QStateHandler)(void *me, QEvent const *e);

typedef  struct  QFsmTag{
    QStateHandler  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) 

#define  QEP_EMPTY_SIG 0

enum  QReservedSignals{
    Q_ENTRY_SIG = 1,
    Q_EXIT_SIG,
    Q_INIT_SIG,
    Q_USER_SIG
};

2.2 状态结构

QFsm状态机不包含层次状态机,不需要处理事件中的dynamic_,这是事件的父类结构,子类事件结构在第一个元素继承这个父类结构

typedef  U8  QSignal;

typedef struct QEventTag
{
    QSignal sig;
    U8      dynamic_;
}QEvent;

2.3 QFsm结构对外提供的方法

QFsm提供标准接口init()和dispatch()两个接口,QFsm_dispatch接口判断当前状态时保持还是需要迁移,并且执行状态函数指针,检查是否有事件触发。如果状态不迁移,直接执行状态函数,如果迁移,则执行前一个状态的退出动作和当前状态的进入动作。

void  QFsm_init(QFsm *me, QEvent const *e)
{
    (*me->state)(me, e);
    (void)(*me->state)(me, &(QEP_reservedEvt_[Q_ENTRY_SIG]));
}

void  QFsm_dispatch(QFsm *me, QEvent const *e)
{
    QStateHandler s = me->state;

    QState r = (*s)(me, e);

    if (r == Q_RET_TRAN)
    {
        (void)(*s)(me, &QEP_reservedEvt_[Q_EXIT_SIG]);
        (void)(*me->state)(me, &QEP_reservedEvt_[Q_ENTRY_SIG]);
    }
}

3. QFsm的使用

3.1 继承父类QFsm实例化

这里事件信号从Q_USER_SIG开始,因为状态默认有进入/退出/初始化,避免覆盖了这些信号。有三个要点:

  • 事件继承QEvent
  • 状态继承QFsm
  • 状态指针的定义全部在应用层代码中
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 */
    U8 fine_time; /* the fine 1/10 s counter */
} TickEvt;

typedef struct BombTag {
    QFsm super; /* derive from QFsm */
    U8 timeout; /* number of seconds till explosion */
    U8 code;    /* currently entered code to disarm the bomb */
    U8 defuse;  /* secret defuse code to disarm the bomb */
} Bomb;

void Bomb_ctor(Bomb *me, U8 defuse);
QState Bomb_initial(Bomb *me, QEvent const *e);
QState Bomb_setting(Bomb *me, QEvent const *e);
QState Bomb_timing(Bomb *me, QEvent const *e);

初始化状态对象,Bomb_ctor用于构建和父类的状态关系:

void Bomb_ctor(Bomb *me, U8 defuse) {
    QFsm_ctor(&me->super, (QStateHandler)&Bomb_initial);
    me->defuse = defuse;                /* the defuse code is assigned at instantiation */
}

3.2 状态函数的定义

QFsm的核心思想即在于使用一个函数指针来表示状态,本例中有三个状态,初始化/设置状态/定时状态

/*....................................................................*/
QState Bomb_initial(Bomb *me, QEvent const *e) {
    (void)e;
    me->timeout = INIT_TIMEOUT;
    return Q_TRAN(&Bomb_setting);
}

/*....................................................................*/
QState Bomb_setting(Bomb *me, QEvent const *e) 
{
    switch (e->sig) 
    {
        case UP_SIG: 
        {
            if (me->timeout < 60) {
            ++me->timeout;
            printf("timeout is %d:\n", me->timeout);
        }
        return Q_HANDLED();
        }
        case DOWN_SIG: 
        {
            if (me->timeout > 1) {
            --me->timeout;
            printf("timeout is %d:\n", me->timeout);
            }
            return Q_HANDLED();
        } 
        case ARM_SIG: 
        {
            return Q_TRAN(&Bomb_timing);    /* transition to "timing" */
        }
        default:
        {
            return Q_IGNORED();
        }
    }
    return Q_IGNORED();
}


/*....................................................................*/
QState Bomb_timing(Bomb *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(&Bomb_setting);
        }
        return Q_HANDLED();
        }
        case TICK_SIG: 
        {
            if (((TickEvt const *)e)->fine_time == 0) {
            --me->timeout;
            printf("timeout is %d:\n", me->timeout);
            if (me->timeout == 0) {
            printf("boom boom boom!\n");
            }
            }
            return Q_HANDLED();
        }
    }
    return Q_IGNORED();
}

3.3 注意事项

QFsm_dispatch接口中使用了QEP_reservedEvt_, 定义了基本事件,进入/退出/初始化

//如果状态需要进行初始化,状态迁移后执行,需要注意的是s是dispatch中的局部变量,状态更新时更新的是me,此时s是没有更新的
(void)(*s)(me, &QEP_reservedEvt_[Q_EXIT_SIG]);
//这两句合在一起,先执行前一个状态退出时的动作,然后执行状态迁移之后的进入动作
(void)(*s)(me, &QEP_reservedEvt_[Q_EXIT_SIG]);
(void)(*me->state)(me, &QEP_reservedEvt_[Q_ENTRY_SIG]);

基本事件的定义

QEvent const QEP_reservedEvt_[4] = {
    { (QSignal)QEP_EMPTY_SIG, (U8)0 },
    { (QSignal)Q_ENTRY_SIG, (U8)0 }, 
    { (QSignal)Q_EXIT_SIG, (U8)0 },
    { (QSignal)Q_INIT_SIG, (U8)0 }
};

4. 小结

QFsm状态机的设计时十分巧妙的,通过函数指针来表达一个状态。提供了两个非常简单的接口,接口中操作的状态和事件都是父状态下定义的结构,所有的应用层状态和事件在应用层代码中定义,接口中我们只关心状态是否发生迁移,而不用关心状态中的事件处理。

  • QFsm简单,容易通过C语言来实现
  • 将状态中事件触发的动作放在状态函数中实现,具备合适的函数粒度,不向普通二维状态表一样,函数粒度太小
  • 状态的扩展是方便有效的,不需要改变状态结构
  • 使用一个函数指针来表示状态,RAM资源占用少
  • 需要枚举事件,不需要枚举状态,不是层次式的状态,但是也可以很好的扩展为层次式的状态机
    m状态机的设计时十分巧妙的,通过函数指针来表达一个状态。提供了两个非常简单的接口,接口中操作的状态和事件都是父状态下定义的结构,所有的应用层状态和事件在应用层代码中定义,接口中我们只关心状态是否发生迁移,而不用关心状态中的事件处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值