simple fsm狀態機模板應用筆記(二)——如何使用simple fsm

原文地址:https://www.amobbs.com/thread-5668532-1-1.html

如何使用

1. 如何定義一個狀態機

語法:

simple_fsm( <狀態機名稱>,
    def_params(
        參數列表                
    )
)

例子:

/*! fsm used to output specified string */
simple_fsm( print_string,
    def_params(
        const char *pchStr;        //!< point to the target string
        uint16_t hwIndex;          //!< current index
        uint16_t hwLength;        //!< claimed length of the target string, it is used to prevent buffer overflow 
    )
)

這裏,實際上我們爲目標狀態機控制塊定義了一個專用的類型,可以用fsm()對這個狀態機加以引用。需要說明的是,這個類型本質上是一個掩碼結構體,也就是說你無法通過這個類型直接訪問控制塊的成員變量。這也是它安全的地方——當然,防君子不防小人。

語法:

fsm(<狀態機名稱>)

例子:

static fsm( print_string ) s_fsmPrintString;     //! 定義了一個本地的狀態機控制塊

2. 如何extern一個狀態機

很多時候,我們的狀態機會作爲一個模塊,提供給別的.c文件來使用(直接調用或者作爲子狀態機被調用,那麼這種情況下應該如何處理呢?

語法:

extern_simple_fsm( <狀態機名稱>,
    def_params(
        參數列表                
    )
)

例子:
在某個頭文件中寫入如下的內容:

#include "simple_fsm.h"
 
...
 
/*! fsm used to output specified string */
extern_simple_fsm( print_string,
    def_params(
        const char *pchStr;        //!< point to the target string
        uint16_t hwIndex;          //!< current index
        uint16_t hwLength;        //!< claimed length of the target string, it is used to prevent buffer overflow 
    )
)

3. 如何實現一個狀態機控制塊的初始化函數

很多複雜的狀態機其服務本身是需要初始化的,簡單說就是它的控制塊在狀態機使用前,必須進行初始化,這類初始化是通過用戶自定義的初始化函數來實現的,那麼如何編寫這類初始化函數呢?


[注意] 無論狀態機多簡單,初始化函數都不能省略。
原因很簡單,這樣寫出來的代碼兼容性最好。有的說,控制塊如果你不初始化,就是自動放到ZI段去了,編譯器會自動幫你初始化爲0。即便如此,這也是不妥的,原因如下:
a. 不能依賴編譯器,因爲ANSI-C並沒有規定不初始化的變量一定會被自動初始化爲0
b. 如果控制塊是來自堆,就沒有人幫你初始化狀態機控制塊了,別忘控制塊裏至少還有一個狀態變量
c. 作爲子狀態機使用的時候,爲了節省空間,不同時運行的子狀態機可以用union共享同一塊Memory,這種情況下,狀態機使用前不初始化問題很嚴重。


語法:

fsm_initialiser( <狀態機名稱>,
    args(           
        <狀態機初始化函數的形參列表,參數用逗號隔開,如果真的沒有形參,可以省略該部分>
        /* 注意,即便沒有形參,你也是需要initialiser來初始化狀態機的 */
    ))
 
    init_body (
        <初始化函數的函數體,用普通C語言語法即可>
        /* 如果初始化過程中發生了任何錯誤需要放棄初始化並立即退出,使用 abort_init() */
    )

例子:

fsm_initialiser( print_string,
    args(           
        const char *pchString, uint16_t hwSize
    ))
 
    init_body (
        if (NULL == pchString || 0 == hwSize) {
            abort_init();                                       //!< illegal parameter
        } else if (strlen(pchString) < hwSize) {
            abort_init();                                       //!< buffer overflow
        }
 
        this.pchStr = pchString;  
        this.hwLength = hwSize;
    )

4. 如何extern一個狀態機初始化函數

當一個狀態機包含初始化函數時,如果要把該狀態機提供給別的.c使用,我們還需要把對應的初始化函數也extern出去。
當你使用 extern_fsm_initialiser 的時候,我們的宏木板還會自動定義一個函數原型,這樣,你就可以 用這個函數圓形去定義指向 當前初始化函數 的函數指針。函數原型的名稱如下:
<狀態機名稱>_init_fn
語法:

extern_simple_fsm_initialiser( <狀態機名稱>,
    args(           
        <狀態機初始化函數的形參列表,參數用逗號隔開,如果真的沒有形參,可以省略該部分>
        /* 注意,即便沒有形參,你也是需要initialiser來初始化狀態機的 */
    ))

例子:
在某個頭文件中寫入如下的內容:

#include "simple_fsm.h"
 
...
 
/*! fsm used to output specified string */
extern_simple_fsm_initialiser( print_string,
    args(           
        const char *pchString, uint16_t hwSize
    ))
 
...

這裏,系統順便定義了一個函數原型,print_string_init_fn,你可以用print_string_init_fn 直接定義函數指針:

 print_string_init_fn *fnInit = &print_string_init;   //!< <狀態機名稱>_init  就是初始化函數的函數名。

5. 如何初始化一個狀態機

對於一個需要初始化的狀態機,我們應該如何對它進行初始化呢?
語法:

init_fsm(   <狀態機名稱>, <目標狀態機控制塊的地址>, 
    args( 
        <狀態機初始化函數的實參列表,參數用逗號隔開,如果沒有實參,可以省略該部分> 
     ));
 
該函數的返回值是地址:
     NULL          初始化過程中出錯
     ! NULL   <目標狀態機控制塊的地址>

例子:

//! 定義了一個狀態機控制塊
static fsm(print_string)  s_fsmPrintString;
 
#define DEMO_STRING   "Hello FSM World!\r\n"
 
    if (NULL == init_fsm(    print_string, & s_fsmPrintString,
        args( 
            DEMO_STRING,                      //!< target string    
            sizeof(DEMO_STRING) - 1))) {      //!< String Length
         /* failed to initialize the FSM, put error handling code here */
     }
 

6. 如何實現一個狀態機

語法:

fsm_implementation(  <狀態機名稱>, 
        args( <狀態機的形參列表,參數用逗號隔開,如果沒有形參,可以省略這部分> )
    )
    def_states( <列舉所有狀態機狀態,用逗號隔開,確保狀態機的入口狀態列在第一的位置> )               
 
    <局部變量列表>
 
    body (
         on_start(  
             <狀態機復位後第一次運行時,運行且只運行一次的代碼,通常放一些狀態機內部的初始化代碼,如果無所事事,可以省略這個部分>
         )  
      
        <狀態機所有的狀態實現>
    )

例子:

fsm_implementation(  print_string )
    def_states( CHECK_LENGTH, OUTPUT_CHAR )               
 
    body (
         on_start(  
             this.hwIndex = 0;         //!< reset index
         )  
      
        ...
    )

7. 如何extern一個狀態機函數

當你使用 extern_fsm_implementation 的時候,我們的宏木板還會自動定義一個函數原型,這樣,你就可以用這個函數圓形去定義指向 當前初始化函數 的函數指針。函數原型的名稱如下:
<狀態機名稱>_fn
語法:

extern_fsm_implementation(  <狀態機名稱>, 
        args( <狀態機的形參列表,參數用逗號隔開,如果沒有形參,可以省略這部分> )
    )

例子:
在某個頭文件中寫入如下的內容:

#include "ooc.h"
#include "simple_fsm.h"
 
...
 
extern_fsm_implementation(  print_string );
 
...
 

這裏,系統順便定義了一個函數原型,print_string_fn,你可以用print_string_fn 直接定義函數指針:

print_string_fn *fnFSM = &print_string;   //!< <狀態機名稱> 就是狀態機函數的名稱。

8. 如何實現一個狀態

狀態必須在body()內實現,具體形式如下:
語法:

  state( <狀態名稱>,
 
         <狀態實現代碼,C語言實現>
 
         fsm_on_going();
     )

在實現狀態的過程中,狀態的切換要通過 transfer_to() 來實現,它將立即終止當前狀態代碼的執行,並跳轉到目標狀態中,其語法如下:

transfer_to( <目標狀態的名稱> )

有些時候,我們只希望更新狀態機的狀態,而並不希望立即終止當前狀態機的執行,則可以用update_state_to() 來實現。通常update_state_to() 配合 “省缺狀態結尾處的fsm_on_going()” 來直接 fall-through 到緊隨着當前狀態的下一個狀態來執行,這實際上是利用switch的fall-through特性來實現某些情況下的狀態機性能提升。其語法如下:

 update_state_to( <目標狀態的名稱> )

實際上 transfer_to() 等效於以下的組合:

    update_state_to( <目標狀態> )
    fsm_on_going();

狀態實現的時候,如果需要更新狀態機的返回值,則可以使用下列方式:

     fsm_on_going()                         立即終止當前狀態,並讓狀態機返回fsm_rt_on_going;
     fsm_cpl()                                  立即終止當前狀態,復位狀態機,並讓狀態機返回fsm_rt_cpl;
     fsm_reset()                               僅復位狀態機,不影響狀態機返回值(通常配合fsm_on_going()fsm_report() 使用)
     fsm_report( <任意負數> )          立即終止當前狀態,並返回錯誤碼(任意小於等於fsm_rt_err)的值

例子:

fsm_implementation(  print_string )
    def_states( CHECK_LENGTH, OUTPUT_CHAR )               
 
    body (
         on_start(  
             this.hwIndex = 0;         //!< reset index
         )  
      
        state ( CHECK_LENGTH,
            if ( this.hwIndex >= this.hwLength ) {
                 fsm_cpl();               
            }
            update_state_to ( OUTPUT_CHAR );              //! deliberately ignore the following fsm_on_going() in order to fall through to next state
            // fsm_on_going();        
        )
 
        state ( OUTPUT_CHAR,
             if (SERIAL_OUT( this.pchStr[ this.hwIndex ] )) {
                   this.hwIndex++;
                   transfer_to ( CHECK_LENGTH );
             }
 
             fsm_on_going();
        )
    )

9. 如何調用一個狀態機

狀態機(包括子狀態機)的調用方式是一樣的,假設狀態機已經被初始化過了,那麼可以使用下面的方法進行調用(放在超級循環裏面,或者放在某個狀態裏面是一樣的):
語法:

   call_fsm ( <狀態機名稱>, <狀態機控制塊的地址>
        args( <狀態機的實參列表,參數用逗號隔開。如果沒有實參,可以省略該部分> )
    )
 
該函數的返回值是狀態機的運行狀態 fsm_rt_t:
     fsm_rt_err                狀態機出現了意料之外的,且自身無法處理的錯誤,例如無效的參數
     fsm_rt_on_going           狀態機正在執行
     fsm_rt_cpl                狀態機已經完成

例子:

    static fsm(print_string) s_fsmPrintSting;
 
    void main(void)
    {
         ...
         while(1) {
             ...
             if (fsm_rt_cpl == call_fsm( print_string, &s_fsmPrintString )) {
                  /* fsm is complete, do something here */
             }
         }
    }

10. 如何前置聲明一個狀態機

有些時候,在我們正式通過 simple_fsm 宏定義一個狀態機之前,當前狀態機就要被其它(當前狀態機)所依賴的關鍵類型所引用,比如,定義指向當前狀態機的指針啊,函數指針啊,之類的——簡而言之,前置引用的問題如何解決呢?
語法:

declare_simple_fsm( <狀態機名稱> )

例子:
在某個頭文件中寫入如下的內容:

nclude "simple_fsm.h"
 
 
declare_simple_fsm(print_string);
extern_fsm_implementation(print_string);
extern_simple_fsm_initialiser( print_string,
    args(           
        const char *pchString, uint16_t hwSize
    ));
 
typedef struct {
    fsm(print_string) *ptThis;          //!< a pointer points to fsm obj
    print_string_fn *fnTask;             //!< a function pointer, point to fsm function
    print_string_init_fn *fnInit;       //!< a function pinter, points to initialisation function
} vtable_t;

/*! fsm used to output specified string */
simple_fsm( print_string,
    def_params(
        vtable_t Methods;
        const char *pchStr;        //!< point to the target string
        uint16_t hwIndex;          //!< current index
        uint16_t hwLength;        //!< claimed length of the target string, it is used to prevent buffer overflow 
    )
)

一個簡單的例子

這裏,我們展示了一個簡單的狀態機例子,用於週期性的通過串口輸出“hello”。我們可以看到,這個例子裏定義了兩個狀態機,print_hello 用於打印字符串,並調用另外一個子狀態機delay_1s用於實現一個差不離的延時(代碼裏用了一個隨便寫的常數10000,領會精神就好)。

print_hello 狀態機的結構相當簡單,前半部分是字符串的輸出——簡單粗暴的爲每一個字符分配 一個狀態;後半部分演示了子狀態機的調用方式:首先對子狀態機進行初始化(如果這個子狀態機確實需要這個步驟);緊接着是通過一個專門的狀態來進行子狀態機調用。我們通過子狀態機的返回值來了解子狀態機的狀態——正在進行(on going),完成(cpl )還是發生了什麼錯誤(返回值爲負數)

/***************************************************************************
*   Copyright(C)2009-2017 by Gorgon Meducer<[email protected]> *
*                                                                         *
*   This program is free software; you can redistribute it and/or modify  *
*   it under the terms of the GNU Lesser General Public License as        *
*   published by the Free Software Foundation; either version 2 of the    *
*   License, or (at your option) any later version.                       *
*                                                                         *
*   This program is distributed in the hope that it will be useful,       *
*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
*   GNU General Public License for more details.                          *
*                                                                         *
*   You should have received a copy of the GNU Lesser General Public      *
*   License along with this program; if not, write to the                 *
*   Free Software Foundation, Inc.,                                       *
*   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
***************************************************************************/
 
/*============================ INCLUDES ======================================*/
#include ".\app_cfg.h"
 
/*============================ MACROS ========================================*/
/*============================ MACROFIED FUNCTIONS ===========================*/   
 
#ifndef SERIAL_OUT
#define SERIAL_OUT(__BYTE)      serial_out(__BYTE)
#endif
 
/*============================ TYPES =========================================*/
 
/*! \brief you can use simple fsm at any where you want with little cost. 
           E.g.
            
*! \brief function that output a char with none-block manner
*! \param chByte target char
*! \retval true the target char has been put into the output buffer 
*! \retval false service is busy
*/
extern bool serial_out(uint8_t chByte);
 
 
 
/*============================ GLOBAL VARIABLES ==============================*/
/*============================ LOCAL VARIABLES ===============================*/
/*============================ PROTOTYPES ====================================*/
 
/*! /brief define fsm delay_1s
*!        list all the parameters
*/
simple_fsm( delay_1s,
    
    /* define all the parameters used in the fsm */ 
    def_params(
        uint32_t wCounter;                  //!< a uint32_t counter
    )
)
 
/*! /brief define fsm print_hello
*!        list all the parameters
*/
simple_fsm( print_hello,
    def_params(
        fsm(delay_1s) fsmDelay;             //!< sub fsm delay_1s
    )
)
 
/*============================ IMPLEMENTATION ================================*/
 
 
 
/*! /brief define the fsm initialiser for FSML delay_1s
*! /param wCounter  an uint32_t value for the delay count
*/
fsm_initialiser(delay_1s,               //!< define initialiser for fsm: delay_1s
    /*! list all the parameters required by this initialiser */
    args(           
        uint32_t wCounter               //!< delay count
    ))
 
    /*! the body of this initialiser */
    init_body (
        this.wCounter = wCounter;       //!< initialiser the fsm paramter
    )
/* End of the fsm initialiser */
 
 
/*! /brief Implement the fsm: delay_1s
*         This fsm only contains one state.
*/
fsm_implementation(  delay_1s)
    def_states(DELAY_1S)                //!< list all the states used in the FSM
 
    /* the body of the FSM: delay_1s */
    body (
        state(  DELAY_1S,               //!< state: DELAY_1s
            if (!this.wCounter) {
                fsm_cpl();              //!< FSM is completed
            }
            this.wCounter--;
            fsm_on_going();             //!< on-going
        )
    )
/* End of fsm implementation */
 
 
fsm_initialiser(print_hello)
    init_body ()
 
/*! /brief Implement the fsm: delay_1s
*         This fsm only contains one state.
*/
fsm_implementation(print_hello)
 
    /*! list all the states used in the FSM */
    def_states(PRINT_H, PRINT_E, PRINT_L, PRINT_L_2, PRINT_O, DELAY)
 
    body(
//! the on_start block are called once and only once on the entry point of a FSM
//        on_start(
//            /* add fsm parameter initialisation code here */
//        )
 
        state(PRINT_H,
            if (SERIAL_OUT('H')) {
                transfer_to(PRINT_E);   //!< transfer to state PRINT_E
            }
            fsm_on_going();             //!< on going
        )
        
        state(PRINT_E,
            if (SERIAL_OUT('e')) {
                transfer_to(PRINT_L);
            }
            fsm_on_going();
        )
 
        state(PRINT_L,
            if (SERIAL_OUT('l')) {
                transfer_to(PRINT_L_2);
            }
            fsm_on_going();
        )
 
        state(PRINT_L_2,
            if (SERIAL_OUT('l')) {
                transfer_to(PRINT_O);
            }
            fsm_on_going();
        )
 
        state(PRINT_O,
            if (!SERIAL_OUT('o')) {
                fsm_on_going();
            }
 
            //! initialize the internal sub fsm
            init_fsm(   delay_1s,           //!< FSM: delay_1s
                        &(this.fsmDelay),   //!< the fsm control block
                        args(10000));       //!< pass parameters to the initialiser
 
            //! update the state to DELAY without yield, so it will fall-through to the following state directly
            update_state_to(DELAY);
        )
 
 
        state(DELAY,
            /*! call the sub fsm */
            if (fsm_rt_cpl == call_fsm(delay_1s, &(this.fsmDelay))) {
                fsm_cpl();
            }
            fsm_on_going();
        )
    )
 
/* EOF */
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章