State Pattern in Embedded Software Architecture Design

Follow the + star public account and never miss exciting content

cf039b586e1874a2d49a92a310ae7591.gif

Source | Embedded Electronics

Arrangement & Typesetting | Embedded Application Research Institute

State machine basic terminology

  • Current state : refers to the current state.

  • Condition : Also known as "event", when a condition is met, an action will be triggered or a state transition will be performed.

  • Action : Action performed after the condition is met. After the action is executed, it can be moved to a new state or remain in the original state. Actions are not required. When the conditions are met, you can directly migrate to the new state without performing any action.

  • Secondary state : The new state to be moved to after the conditions are met. "Secondary state" is relative to the "present state". Once the "secondary state" is activated, it transforms into a new "present state".

22fe7ec580fa6fd36cfc24bfe5418f50.png

Traditional finite state machine Fsm implementation method

da71c2366e20899fb5db54fa2c586330.png

As shown in the figure, it is a timing counter. The counter has two states, one is the setting state and the other is the timing state.

Set status

  • "+" "-" buttons to set the initial countdown

  • When the count value setting is completed, click the confirmation button to start the timing, that is, switch to the timing state.

Timing status

  • Press "+" "-" to enter the password. "+" means 1, "-" means input 0. The password has 4 digits in total.

  • Confirm key: Only when the entered password is equal to the default password, pressing the confirm key can stop the timing, otherwise the timing will directly reach zero and relevant operations will be performed.

Nested switches

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

bc2cbe812118e5310926c5fd53998c9e.png

advantage

Simple, the code is coherent and easy to understand

shortcoming

  • When the number of states or events increases, the code state function needs to be changed frequently, and the code amount of the state event processing function will continue to increase.

  • The state machine is not encapsulated and has poor portability.

  • There is no implementation of state entry and exit operations. Entry and exit are particularly important in state machines:

  • Entry event: It will only be triggered once when you first enter. Its main function is to perform necessary initialization of the state.

  • Exit event: It will only be triggered once when the state switches. Its main function is to clear the intermediate parameters generated by the state and provide a clean environment for the next entry.

status table

Two-dimensional state transition table

State machines can be divided into states and events. State transitions are driven by events, so state transitions can be represented by a two-dimensional table.

10b8ff0efe899b97f909fc0283a0e5dd.png

(*)  Conversion to setting only occurs when (code == defuse_code).

/*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);
}
  • advantage

    • Each state is relatively independent for users. Adding events and states does not require modifying previously existing state event functions.

    • The state machine can be encapsulated and has better portability.

Safe conversion of function pointers. Using the following features, users can extend state machines and events with private attributes and use a unified basic state machine interface.

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

  • The most obvious shortcoming is that the function granularity is too small. One state and one event will generate a function. When there are many states and events, the processing functions will increase quickly, and the logic will be scattered when reading the code.

  • The entry and exit actions are not implemented.

One-dimensional state transition table

ff174258d416f60820a2d445608c21b7.png

Implementation principle:

d056a867a17b6f340c12ae34794b2cb2.png

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 状态相关*/
  • advantage

    • Each state is relatively independent for users. Adding events and states does not require modifying previously existing state event functions.

    • Implemented state entry and exit

    • Easy to design based on the state transition diagram (the state transition diagram lists the possible transitions of each state, which is the transition table here)

    • The implementation is flexible and can implement complex logic, such as the last state and adding monitoring conditions to reduce the number of events. Achieve non-complete event drive

  • shortcoming

    • The function granularity is smaller (smaller than two-dimensional and grows slower). It can be seen that each state requires at least 3 functions, and all conversion relationships need to be listed.

QP embedded real-time framework

Features

  • event-driven programming

    • Hollywood Principle: Different from traditional sequential programming methods such as "super loop", or traditional RTOS tasks. Most modern event-driven systems are structured according to the Hollywood principle, (Don't call me; I'll call you.)

  • object-oriented

    • Classes and single inheritance

ae889861d885cedc1f3327b5a9a32e59.png

  • tool

    • QM  : A software that describes state machines through UML class diagrams and can automatically generate C code

df70b58ad6d1b5d263c5bac9e9c3f737.png

    • QS software tracking tool

6c36e97ecf4bb938196041c36fedd932.png

cda53e363ee2cf736b48dbafd5d92d7d.png

QEP implements finite state machine Fsm

  • accomplish

a255a96034d03da20f14efa83482e8cd.png

/* 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();
}
  • advantage

    • Adopt object-oriented design method and have good portability

    • Implemented entry and exit actions

    • Appropriate granularity, and the granularity of events is controllable

    • By changing the pointer when switching states, it is efficient

    • Extensible to become a hierarchical state machine

  • shortcoming

    • The definition of events and the control of event granularity are the biggest difficulties in design. For example, when the serial port receives a frame of data, the update of these variables is treated as a separate event, or the serial port receives data as an event. Or the display screen. If this programming method is used, how to design events.

Introduction to QP implementation of hierarchical state machine Hsm

4cc21e7275da56673348c49150a87568.png

initialization:

456aa95c8f2900c0cc8ade520533f300.png

Implementation of initialization hierarchical state machine: During initialization, the state selected by the user is always the lowest state. As shown in the figure above, after we turn on the calculator, we should enter the starting state.

This involves a problem. There is a state switching path from the initial top to begin. When we set the state to begin, how to search for this path becomes the key (only by knowing the path can we enter the begin correctly. To execute Entry and exit events for transition states in the path)

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

23759703e7bffd70628d4f1051b9ca66.png

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 */
                 ....

The composition of QP real-time framework

b8767b1cee08967c898016bfc7e137e9.png

1c6a3737b06e3ba722c99f0cedf62af8.png

Memory management

Using a memory pool, for low-performance MCUs, the memory is extremely limited. The introduction of memory management is mainly in the entire architecture, using events as the main means of task communication, and events have parameters. The same type of event may be triggered multiple times. After the event processing is completed, the event needs to be cleared. Static events cannot be used, so it is necessary to create memory pools for different events.

For memory pools with different block sizes, what needs to be considered is the alignment of the starting address of each block. When initializing the memory pool, we divide the memory pool based on blocksize+header size. Assuming a 2-byte structure, if divided by 2, assuming the mcu is 4-byte aligned, then half of the structure's starting address will not be aligned. In this case, space needs to be reserved for each block to ensure that each block Alignment.

aeebd263e2a01dad910ba7208564d47b.png

event queue

  • Each active object maintains an event queue. Events are derived from basic events. Different types of events only need to add their basic event members to the queue of the active object. Finally, they can be removed through a forced conversion. Get additional parameters.

96089c0aa6fd104b955df5b5a3ed4f3a.png

event dispatch

  • Direct event sending

    • QActive_postLIFO()

  • Issue subscription event sending

    • The vertical axis represents signals (the base class of events)

    • Active objects support 64 priorities, and each active object is required to have a unique priority.

    • The priority bit is used to indicate which active objects are subscribed to an event, and after the event is triggered, the event is dispatched to the active objects according to the priority.

c5aa6920fc8e3da56757d8c85f06a528.png

timed event

  • Unordered linked list

7bc2a0cf58b965b18a52a6a61ddd3c4c.png

  • Cooperative Scheduler QV

e5a3a0a8ba0c779d40a0d7f06d9a7590.png

  • Preemptible scheduler QK

Introduction to QP nano

  • Full support for hierarchical state nesting, including guaranteed entry/exit actions for any state transition topology up to 4 levels of state nesting

  • Supports up to 8 concurrently executing, deterministic, thread-safe event queue active objects57

  • Supports one byte wide (255 signals) signals, and a scalable parameter, which can be configured to 0 (no parameters), 1, 2 or 4 bytes

  • Direct event dispatch mechanism using first-in-first-out FIFO queuing strategy

  • Each active object has a one-time time event (timer) with a configurable dynamic range of 0 (no time event), 1, 2 or 4 bytes

  • Built-in cooperative vanilla kernel

  • Built-in preemptible RTC kernel named QK-nano

  • A low-power architecture with an idle callback function to easily implement power-saving modes.

  • The code is prepared for non-standard extensions of C compilers for popular low-end CPU architectures (e.g., allocation of constant objects in code space, reentrant functions, etc.)

  • Assertion-based error handling strategy

  • coding style

b7a8e9283dd21930aa65654fffac5deb.png

c5eab36d1b283d1fdb1cf5ba22e0e1be.png

cacdfc6b7be4e6ce236b309b0680c219.png

5ffa207468c897fafcdf2f7583715cab.png

9fff78fef20639776a5cc9517bb07650.png

Statement: The material of this article comes from the Internet, and the copyright belongs to the original author. If there is any copyright issue with the work, please contact me to delete it.

------------ END ------------

e85b7f64bfd7002b4b82c4658fa3c8ec.gif

●Column "Embedded Tools "

●Column "Embedded Development"

●Column "Keil Tutorial"

●Embedded column selected tutorials

Follow the official account and reply " Add Group " to join the technical exchange group according to the rules, and reply " 1024 " to view more content.

ed27e2e3578f5a23fbdd39ec2f557dce.jpeg

6bd65b7210957543e8187da13c615b61.png

Click " Read the original text " to view more sharing.

Guess you like

Origin blog.csdn.net/ybhuangfugui/article/details/133326526
Recommended