天天看點

量子架構(1)

量子架構簡稱QP,是一種狀态機架構,實作了有限狀态機FSM和層次狀态機HSM,目前官方僅有C和C++語言的實作。對于PC端和web端的開發,這個架構有種英雄無用武之地的感覺,但在嵌入式領域這個架構徹底颠覆了我的認識。

提到這個架構,不能不提架構作者Miro Samek的書《Pratical UML Statecharts In C/C++》。這本書既可以看作是狀态機方面的著作,也能看作QP的教程,強烈推薦對狀态機感興趣的人讀之。

閑話不說,直接用一個簡單例子來說說這個架構的使用。當然,這個架構在使用之前是需要根據具體平台和系統做一些簡單的移植工作的,這部分會在之後描述。

**************************************

任務:基于狀态機控制一個LED燈閃爍

(1)采用基于數值變量的狀态機

#define LED_SHORT_DELAY        1000

typedef enum
{
    LedState_Off = 0,
    LedState_On
} LedState;

typedef enum
{
    LedSignal_TurnOff = 0,
    LedSignal_TurnOn
} LedSignal;

LedState led_state = LedState_Off;
LedSignal led_sig;

void Led_On(void)
{
    printf("Led is ON.\r\n");
}

void Led_Off(void)
{
    printf("Led is OFF.\r\n");
}

void Led_Control(LedSignal sig)
{
    switch (led_state)
    {
    case LedState_Off:
        if (LedSignal_TurnOn == sig)
        {
            led_state = LedState_On;
            Led_On();
        }
        break;

    case LedState_On:
        if (LedSignal_TurnOff == sig)
        {
            led_state = LedState_Off;
            Led_Off();
        }
        break;
    }
}

void Led_Blink(void)
{
    int delay = LED_SHORT_DELAY;
    
    led_sig = LedSignal_TurnOff;
    while (1)
    {
        int delay = LED_SHORT_DELAY;
        
        led_sig = !led_sig;
        Led_Control(led_sig);
        while(delay--);
    }
}      

狀态機的實作集中在Led_Control這個函數中。它包含了狀态led_state、外部信号(事件)sig這兩個狀态機基本元素。

(2)基于QP的狀态機實作

定義狀态

static QState Led_StateInitial(QFsm* fsm, QEvt* e);
static QState Led_StateOn(QFsm* fsm, QEvt* e);
static QState Led_StateOff(QFsm* fsm, QEvt* e);      

可以看出QP下的狀态是函數指針?對,沒錯,QP下的狀态是通過函數指針來表示的。在這裡,引入了第一個問題,采用什麼形式儲存目前狀态好?(采用流水賬的形式探讨問題)

a. 變量形式

比如(1)中的led_state變量用于儲存目前狀态,然後通過switch-case分支對各種外部signal進行判斷分别處理。這種方式簡單易懂,實作起來也是非常easy。但如果系統複雜一些,n個狀态變量,每個狀态變量分别對應 a_i(i = 1, …, n) 種狀态。在最壞情況下,n個狀态互相嵌套,你的switch-case将有a_n*a_(n-1)*…*a_1層。在工業現場,随便複雜一點的應用都會有十幾層的switch-case和if-else吧。不要說讓别人維護你的代碼,編這種程式的你也會叫苦不疊。

b. 狀态表(state table)

較為流行的是采用二維表,以狀态集為行,signal事件為列。如(1)中的Led_Control狀态機可表示為:

LedSignal_TurnOff LedSignal_TurnOn
LedState_Off \

Led_On(); // action

LedState_On; // next state

LedState_On

Led_Off(); // action

LedState_Off; // next state

采用狀态表的方式使得狀态機執行效率得到很大提高(O(1)),避免了分支判斷。其缺點也在上表中顯而易見,對于一些狀态,某些signal是沒有意義的,但仍需在狀态表中列出。C語言自身不支援hash,構造出來的狀态表通常是一個稀疏矩陣,浪費有限的存儲空間。當後期由于需求變更需要調整狀态表時,很容易造成遺漏或錯誤。如果想實作層次狀态機,可以想像狀态表這種實作方式既繁瑣且易出錯。當然,對于簡單的應用,狀态表仍是很好的一個選擇。

c. object-oriented狀态設計模式

這種方式充分利用面向對象設計思想,設計一個抽象類定義所有signal的處理接口,然後由不同state去繼承該類,并實作自身關心的事件處理接口以覆寫基類的相應接口(多态)。同樣以Led_Control為例,設計一個抽象類(文法不規範)

class Led;
class LedState
{
public:
    virtual void OnLedSignal_TurnOn(Led* contex) {};
    virtual void OnLedSignal_TurnOff(Led* contex) {};
}

class LedOnState : public  LedState
{
public:
    void OnLedSignal_TurnOff(Led* contex) { Led_Off(); contex->Tran(&contex->stateOff) }
}

class LedOffState: public LedState
{
public:
    void OnLedSignal_TurnOn(Led* contex) { Led_On(); contex->Tran(&contex->stateOn) }
}

class Led:
{
private:
    LedState* m_state;
    static LedOnState stateOn;
    static LedOffState stateOff;
    void Tran(LedState* state)    { m_state = state; }
    
public:
    void OnLedSignal_TurnOn(void)    { m_state->OnLedSignal_TurnOn(this) }    // 響應'開'信号
    void OnLedSignal_TurnOff(void)    { m_state->OnLedSignal_TurnOff(this) }    // 響應'關'信号
    void Init(void)    { Tran(stateOff) };    // 初始狀态
    
    friend class LedOnState;
    friend class LedOffState;
}      

LedState是抽象基類,包含了所有外部signal(LedSignal_TurnOff、LedSignal_TurnOn)的預設處理接口。LedOnState繼承于LedState,實作了它自身關心的LedSignal_TurnOff信号處理方法。LedOffState同樣繼承于LedState,實作了LedSignal_TurnOn信号處理方法。

Led這個類是狀态機,包含了兩個狀态,分别是LedOnState和LedOffState的兩個執行個體。這個狀态機有一個重要的成員變量m_state(LedState*類型)用于儲存目前狀态,而Tran成員函數用于切換狀态。當這個狀态機響應外部signal時,直接調用目前狀态相應的信号處理方法即可。例如,Led狀态機Init()後其狀态為m_state = &stateOff(LedOffState*),當收到LedSignal_TurnOff信号時,調用自身響應函數Led:OnLedSignal_TurnOff()時,有m_state->OnLedSignal_TurnOff() <—> LedOffState::OnLedSignal_TurnOff(Led*)。而LedOffState類的OnLedSignal_TurnOff繼承于LedState,函數為空,是以什麼也不做。當收到LedSignal_TurnOn信号時,類似的可以知道其相當于調用了LedOffState::OnLedSignal_TurnOn(Led*)。而該函數執行兩個動作:Led_TurnOn();同時調用Tran将狀态機的目前狀态m_state切換至&stateOn(LedOnState*)。

從上面這個Led例子可看出,基于OO思想的狀态機極大程度依賴于像C++這種語言的面向對象特性。好處有很多:

  • 各自的狀态可以獨立處理各自感興趣的外部信号;
  • 狀态遷移十分高效,隻需改變指針所指内容;
  • 信号分發性能也非常可觀,可保證O(1)複雜度;

盡管有如上優點,但在實際應用時,需要枚舉全部外部信号的處理方法且最大化狀态基類的接口數量,這個工作量其實不小,由Led代碼也可看出一些端倪。

d. QEP有限狀态機

顧名思義,将上述3種思想取長補短再加上QP作者個人原創思想提出的一種狀态機。在QP架構中,非層次有限狀态機定義為QFsm。雖說在QFsm的C語言實作中,不過是用struct封裝了其成員變量和函數,沒有類的真正概念,但了解起來也就是類,是以就以QFsm類稱謂之。

量子架構(1)

QFsm含有一個成員變量State儲存目前狀态,這一點與方式c一緻。隻是這裡的QFsm狀态機中的狀态都是函數指針,具備QState QStateHandler(void* me, QEvent* e)形式。也就是說,當有外部事件e: QEvent産生時,QFsm的處理很簡單,即調用函數state(me, e)。依然以本文開始時的LED示例來說,它有二個狀态:

static QState Led_StateOn(QFsm* fsm, QEvt* e);
static QState Led_StateOff(QFsm* fsm, QEvt* e);      

還有一個Led_StateInitial是每個QFsm狀态機必須具有的一個初始化狀态,它隻負責将狀态遷移到預設狀态。如LED預設為關狀态,那麼有:

static QState Led_StateInitial(QFsm* fsm, QEvt* e)
{
    Q_TRAN(&Led_StateOff);
}      

再看Led的兩個狀态下對外部事件的處理

static QState Led_StateOn(QFsm* fsm, QEvt* e)
{
    switch (e->sig)
    {
    case Q_ENTER_SIG:    // 進入該狀态時執行動作
        return Q_HANDLED;
    
    case Q_EXIT_SIG:    // 退出該狀态時執行動作
        return Q_HANDLED;
        
    case LedSignal_TurnOff:    // '關'信号
        Led_Off();
       return Q_TRAN(&Led_StateOff);
    }
    return Q_IGNORED();
}

static QState Led_StateOff(QFsm* fsm, QEvt* e)
{
    switch (e->sig)
    {
    case Q_ENTER_SIG:    // 進入該狀态時執行動作
        return Q_HANDLED;
    
    case Q_EXIT_SIG:    // 退出該狀态時執行動作
        return Q_HANDLED;
        
    case LedSignal_TurnOn:    // '開'信号
        Led_On();
       return Q_TRAN(&Led_StateOn);
    }
    return Q_IGNORED();
}      

是否能感覺出,有了QP架構的支援,狀态機的使用顯得清晰明了。同時QP下的狀态都具有Enter和Exit動作,這與UML規範下的狀态機完全一緻。由于這系列日志隻關注應用,不會對UML規範和QP狀态機有何不同做對比,感興趣的同道請仔細研讀QP的那本教程。

雖然QP在Led這個例子上面的應用及其簡單,但也算麻雀雖小,五髒俱全。引出了一組不認識的宏:

Q_ENTER_SIG
Q_EXIT_SIG
Q_HANDLED
Q_IGNORED
Q_TRAN      

不光如此,還有QFsm狀态之間的切換是怎麼實作的,有心的同道肯定會想到怎麼你沒有講QFsm的dispatch和Init是怎麼回事,是不是狀态的切換跟dispatch(分發)有關。這些内容将在這個系列的後續日志中逐一記錄。

如果QP僅僅做到QFsm這個程度,根本不值得我如此推崇,當涉及到層次狀态機,活動對象這樣的概念時,QP架構才真正的讓人感覺到了什麼是強大。調了衆人的胃口,實屬不該。按捺不住的同道請點選下面的連結進入QP官網,立刻下載下傳考察其應用價值。那本教程我也給出百度網盤外鍊以作參考(畢竟是盜版,可不敢太張揚)。

原文釋出時間為:2017年01月17日

本文作者:robert_cai

本文來源:

部落格園

,如需轉載請聯系原作者。

繼續閱讀