原文地址: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 */