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

你可能感兴趣的:(编程思想)