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
};

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

2.2 状态结构

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

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

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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]);
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

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);

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

初始化状态对象,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 */
}

 
 
  • 1
  • 2
  • 3
  • 4

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();
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

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]);

 
 
  • 1
  • 2
  • 3
  • 4
  • 5

基本事件的定义

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 }
};

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

4. 小结

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

  • QFsm简单,容易通过C语言来实现
  • 将状态中事件触发的动作放在状态函数中实现,具备合适的函数粒度,不向普通二维状态表一样,函数粒度太小
  • 状态的扩展是方便有效的,不需要改变状态结构
  • 使用一个函数指针来表示状态,RAM资源占用少
  • 需要枚举事件,不需要枚举状态,不是层次式的状态,但是也可以很好的扩展为层次式的状态机
    m状态机的设计时十分巧妙的,通过函数指针来表达一个状态。提供了两个非常简单的接口,接口中操作的状态和事件都是父状态下定义的结构,所有的应用层状态和事件在应用层代码中定义,接口中我们只关心状态是否发生迁移,而不用关心状态中的事件处理。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
QEP(Quadrature Encoder Pulse)电路是用于读取旋转编码器的脉冲信号的电路,通常在电控制系统中使用。下面是一个简单的Verilog代码示例,用于实现一个基本的QEP电路: ```verilog module qep ( input clk, input rst, input a, input b, output reg dir, output reg [7:0] count ); reg [1:0] state; always @(posedge clk, posedge rst) begin if (rst) begin state <= 2'b00; count <= 8'h00; dir <= 1'b0; end else begin case (state) 2'b00: begin if (a && !b) begin state <= 2'b01; end else if (!a && b) begin state <= 2'b10; end end 2'b01: begin if (!a && !b) begin count <= count + 8'h01; dir <= 1'b0; state <= 2'b00; end else if (a && b) begin state <= 2'b11; end end 2'b10: begin if (!a && !b) begin count <= count - 8'h01; dir <= 1'b1; state <= 2'b00; end else if (a && b) begin state <= 2'b11; end end 2'b11: begin if (a && !b) begin state <= 2'b01; end else if (!a && b) begin state <= 2'b10; end end endcase end end endmodule ``` 这个Verilog模块接受四个输入信号:时钟信号(clk)、复位信号(rst)、A相信号(a)和B相信号(b)。它还生成两个输出信号:方向信号(dir)和计数器值(count)。 该代码使用一个有限状态来实现QEP电路。状态有四个状态,分别对应于A相和B相的四个可能的状态。 当A相和B相的状态变化时,状态会根据当前状态和新状态来确定方向,并更新计数器值。最终,输出信号将被更新,以反映旋转方向和计数器值。 请注意,这只是一个基本的QEP电路实现,具体实现方法可能会因电类型和系统要求而有所不同。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值