搜索
bottom↓
回复: 120

[分享][交流][2019-03-19 Update] 整理了一个简洁的状态机模板

  [复制链接]

出0入296汤圆

发表于 2017-2-12 15:46:06 | 显示全部楼层 |阅读模式
本帖最后由 Gorgon_Meducer 于 2019-9-5 22:42 编辑


说在前面的话

      好久没有整理代码了,最近一直在做ARMv8-M系统安全设计相关的研究,虽然忙,但不代表我对
自己无聊的爱好——整理一些好玩的代码模板,或者说语法糖——失去了兴趣。人总是会变的,一段
时间过去以后,发现过去写的代码真心看着“心累”——宏一律大写看着辣眼睛,比如以前写的状态机
脚本,所有做“状态机脚本语法辅助”的宏都是大写,看着果然还是不舒服。这次,我修正了一下自己的
编码风格:
    “所有宏和枚举都是大写除非对应的宏或者枚举用于辅助脚本语法”,比如后面你们看到的那个例子。
所有的状态机关键字都小写了,是不是舒服很多?

    如果只是换个格式,那未免也显得太没诚意了,这次的新模板具有以下特性:

    - 针对ARM架构进行效率上的优化
    - 为每一个状态机提供一个控制块,用于参数封装,并且每个控制块在内部都用掩码结构体进行私
      有化保护
    - 状态机模板可以独立存在,实现上更简洁



首先,我们来说说这次的模板在效率上作了什么优化?是什么原理?
   
    ARM的Thumb指令集有一个特点:所有的寻址都是间接寻址,尤其是对变量的访问,通常都要借助
一个寄存器来保存变量的地址,例如下面的语句:

  1.     LDR   r0, =<某个数组的基地址>  ; 步骤1: 这是一个汇编伪代码,将某个数组的基地址复制到r0中,汇编器可以识别这种语法
  2.     LDR   r1,  [r0]                ; 步骤2:r0里保存的是一个uint32_t变量的地址,我们把它读出来保存到r1里面
  3.     LDR   r2,  [r0, #4]          ; 步骤3:读取uint32_t 数组的第二个word
复制代码

   这种方式实际上对面向对结构体的访问非常友好,而且如果你仔细观察你会发现:
    1. 如果你访问的是一个静态变量或者全局变量,那么生成的汇编包含“步骤1”和“步骤2”
    2. 如果你访问的是一个数组,那么一定会包含“步骤1”,然后每个元素的访问都对应一个步骤,也就是“步骤2”、“步骤三”
    3. 你会发现,无论是单个静态变量的访问,还是批量数组或者结构体的访问,“步骤1”——也就是加载基地址的过程都是省不掉的。
        在这种情况下,数组和结构体元素的访问共享同一个步骤一,这就比单个变量的访问要节省很多。

    举一个例子:总有人问,外设寄存器是单独定义成类似全局变量的形式好,还是用结构体访问的形式好?根据上面的描述,答案
    就很清楚了。同样的,普通的switch状态机,横竖要包含一个静态的状态变量,另外还有若干静态的参数,那么

    “为什么不把状态变量和状态机用到的静态变量打包成一个结构体——也就是状态机控制块呢?”
   
    实际上,根据上面的分析,哪怕这个状态机控制块只包含一个状态变量,它也不会比直接使用状态变量的方式增加更多的开销,
    相反,如果这个控制块包含更多其他的变量,我们就赚了!所以,我在模板上加入了以下的内容:


  1. #define __def_simple_fsm(__FSM_TYPE, ...)                               \
  2.         DECLARE_CLASS(__FSM_TYPE)                                   \
  3.         DEF_CLASS(__FSM_TYPE)                                       \
  4.             uint_fast8_t chState;                                   \
  5.             __VA_ARGS__                                             \
  6.         END_DEF_CLASS(__FSM_TYPE)

  7. #define def_simple_fsm(__NAME, ...)                                     \
  8.         __def_simple_fsm(fsm(__NAME), __VA_ARGS__)
复制代码

    可以看到,状态机控制块至少包含了一个状态变量 chState,而用状态机要用到的其它变量可以通过 "..." 对应的__VA_ARGS__
    加入到结构体中来。例如,一个用于延时的状态机delay_1s需要一个计数器,我们可以写成如下的形式:


  1. def_simple_fsm( delay_1s,
  2.    
  3.     /* define all the parameters used in the fsm */
  4.    uint32_t wCounter;                  //!< a uint32_t counter
  5. )
复制代码


    这里,delay_1s 是状态机的名字,uint32_t wCounter; 是我们定义的参数(可以定义更多的参数)。显然,这两个东西放在一起
让人有点不知所措,所以我们增加了一个语法的辅助宏:

  1.     #define def_params(...)         __VA_ARGS__
复制代码

    借助它,我们写出来的代码即便没有注释,也好懂多了:

  1. def_simple_fsm( delay_1s,
  2.     def_params(
  3.         uint32_t wCounter;                  
  4.     )
  5. )
复制代码


    那么,实现状态机的时候,我们如何访问控制块里面的成员变量呢?这就要看看状态机的实现宏了:

  1. #define implement_fsm(__NAME, ...)                                              \
  2.     fsm_rt_t __NAME( fsm(__NAME) *ptFSM __VA_ARGS__ )                           \
  3.     {                                                                           \
  4.         class(fsm_##__NAME##_t) *ptThis = (class(fsm_##__NAME##_t) *)ptFSM;     \
  5.         if (NULL == ptThis) {                                                   \
  6.             return fsm_rt_err;                                                  \
  7.         }  

  8. #define body(...)                                                               \
  9.         switch (ptThis->chState) {                                              \
  10.             case 0:                                                             \
  11.                 ptThis->chState++;                                              \
  12.             __VA_ARGS__                                                         \
  13.         }                                                                       \
  14.                                                                                 \
  15.         return fsm_rt_on_going;                                                 \
  16.     }
复制代码

    这里我们可以发现, implement_fsm() 和 body() 是配对使用的。你也许已经猜到了,状态机的具体
    实现代码是写在body的括号里的。具体可以看后面的例子,这里我们继续来讨论状态机控制块成员变量
    的访问。
    implement_fsm() 实际上规定了状态机的函数原形,它包含了一个指向状态机控制块的指针ptFSM,而
    这个指针随后就被还原为原始形式(控制块默认情况下实际上是一个掩码结构体,所以要访问内部成员
    必须要还原为原始形式):ptThis实际上就指向了我们实际使用的控制块,通过这个结构体指针,我们
    就可以轻松的访问任何的成员变量。但到这里,不要急,为了让代码更好看一点,我们引入了一个专门
    的辅助宏:

  1. #ifndef this
  2. #   define this    (*ptThis)
  3. #endif
复制代码

    借助这一语法糖,我们可以毫无代价的在body()内部通过 "this." 的方式访问成员变量,例如:

  1. implement_fsm (  delay_1s)              
  2.     def_states(DELAY_1S)

  3.     body (
  4.         state(  DELAY_1S ) {               
  5.             if (0 == this.wCounter) {
  6.                 fsm_cpl();              
  7.             }
  8.             this.wCounter--;        
  9.         }
  10.     )

复制代码

    如果我们的状态机要作为一个字模块提供给外部使用怎么办呢?别着急,这里有一个简单的宏,你
    可以放在头文件里面提供给别的.c文件来引用:

  1. #define __extern_simple_fsm(__NAME, __FSM_TYPE, ...)                \
  2.         declare_class(__FSM_TYPE)                                   \
  3.         extern_class(__FSM_TYPE)                                    \
  4.             uint_fast8_t chState;                                   \
  5.             __VA_ARGS__                                             \
  6.         end_extern_class(__FSM_TYPE)                                \
  7.         extern fsm_rt_t __NAME( __FSM_TYPE *ptThis __VA_ARGS__ );

  8. #define extern_simple_fsm(__NAME, ...)                              \
  9.         __extern_simple_fsm(__NAME, fsm(__NAME), __VA_ARGS__)  
复制代码

    比如,我们要把delay_1s作为一个字状态机提供出去,我们可以在头文件里这么写:

  1. extern_simple_fsm( delay_1s,
  2.     def_params(
  3.         uint32_t wCounter;                  
  4.     )
  5. )
复制代码

    好吧,我承认,其实就是把定义的部分又抄了一遍并加了一个extern_的前缀,简单吧?通过上面的
    宏定义,容易发现,因为使用了掩码结构体的形式,所以使用者是无法直接访问控制块内的成员变量
    的。

    至此,控制块定义、使用和优化的部分我们就解释完毕了。如果你有任何疑问,欢迎跟贴讨论。


最后谈谈设计思维和哲学

    这个状态机模板从发布第一个版本到小范围试用已经过去大半年了,其间,我被问得最多的问题是:
“你这已经不是C语言了”、“你实际上是制作了另外一个状态机脚本语言语法”、“为什么要做一个四不像
的东西呢?”、“这个模板本质上和protoThread一样,你为什么要重复发明轮子呢?” 针对这些大家感兴
趣的问题,如果我不从设计思维的角度给出答案,这个模板是很难让人接受的。下面我就以上问题,
从设计思维上给出一个系统的答案:

       首先,C语言原生态就不支持状态机,用C语言实现的状态机,本质上只是一种模拟。这跟C语言并
不原生态支持面向对象,如果真的要大量使用面向对象进行编程,最好的办法是使用C++,而不使用
OOPC去模拟是一样的——为什么呢?因为程序设计要专注于“应用逻辑的实现”本身,应该尽量避免被
“某种技术”分心——对需要大量使用面向对象技术进行开发的程序来说,应用逻辑是我们应该更多关心的,
而使用C模拟OO则是需要避免的。
        同样的问题发生在状态机上,C语言不仅不支持状态机,甚至我们模拟状态机的技术本身也相当复
杂、混乱。不像面向对象有C++,状态机的开发并没有一种语言与C具有传承关系(别说verlog,谢谢,
有本事你去找个verlog编译器,编译出来的机器码主流MCU都能运行的)。这可怎么办呢?回到我们的
目的本身:

程序设计要专注于“应用逻辑的实现”本身,应该尽量避免被“某种技术”分心

        为了达到这个目的,一个可行的方案就是想方设法构造一种基于C语言的“脚本语言”,使得状态机的
开发者得以关注“状态机应用逻辑的实现”,而不必关心“状态机具体是如何使用C语言进行构造的”。也就是
说,从一开始我们建立这个模板的目的就是要构造一种 状态机专用 的脚本语言,使得这种语言可以极大
的简化状态机的开发和表达。这种脚本语言根本就不用“看起来是C语言”,因为它从一开始就不是C语言。
        另一方面,新的脚本语言在使用时,应该能“无缝”的与其它C语言代码(函数)融合在一起,这表现
于:状态机的调用、参数传递、基本类似C的函数调用。简而言之,新的脚本语言:

设计的时候看起来是状态机,使用的时候看起来就像C语言

这与C++设计的时候是面向对象,使用的时候(可以)看起来就像C语言是类似的。基于上述思想,我们得以
“狡辩说”:现在的状态机模板导致的结果是一个对C很友好的状态机脚本语言,而不是一个用C实现的“四
不像”——当然,这对一部分人来说“其狡辩的本质是不随个人意志转移而改变的”  : p。

        针对和protoThread技术原理类似的问题,其实如果你真的使用过protoThread就会发现,这两个
模板在出发点上就是截然相反的:

- protoThread 试图让人产生“我是在使用RTOS进行线程开发”的错觉,它极力隐藏的是它“状态机的本质”
- simple fsm 从一开始,就让开发人员明确知道“我是在开发状态机”

足可见,虽然技术原理相同,但思维不同,最终使用的设计哲学也大相径庭。

        最后,一个决定性的因素说明 simple fsm 不是一个简单的模板而是一个“新的(基于C的)脚本语
言”,即simple fsm 使用了面向对象技术来封装状态机,这就从根本上决定了它不只是一种设计状态机的
方式,而是一整套面向对象状态机设计的哲学,比如:

    - 一个状态机就是一个类
    - 状态机函数只是这个类的一个方法
    - 状态机所要用到的变量都作为成员变量封装在类中(每个状态机都有自己的上下文)
    - 状态机及其数据被封装在一起,且对外界提供私有化保护(掩码结构体实现的private)
    - 状态机类是可以多实例的
    - 每个状态机从一开始就是一个任务(有自己的上下文——注意,这里的上下文是一个广义的概念,
       并不局限于stack)
    - 支持面向对象开发带来的种种好处
    - 支持面向接口开发(注意,面向接口开发不是面向对象的专利)

        综上所述:使用simple fsm开发的时候,我们只关心状态机如何设计,这也是为什么写出来的
                          代码  从字面上看  更像状态机而不是C语言;而调用状态机的时候,又对C语言很
                          友好——这当然是个优点。另外,如果你并不知道如何设计状态机,也不喜欢,那
                          么推荐你用protoThread或者干脆RTOS,因为你用simple fsm就要清楚你写的就是
                          TMD状态机!

     欢迎大家踊跃讨论,拍砖。
                                                                                          —— 傻孩子   吐槽于 2017-10-14日夜


如何使用


1. 如何定义一个状态机

    语法:


  1. //! 前置声明状态机
  2. declare_simple_fsm( <状态机名称> )

  3. //! 状态机类型定义
  4. def_simple_fsm( <状态机名称>,
  5.     def_params(
  6.         参数列表               
  7.     )
  8. )
复制代码


    例子:


  1. declare_simple_fsm(print_string)

  2. /*! fsm used to output specified string */
  3. def_simple_fsm( print_string,
  4.     def_params(
  5.         const char *pchStr;        //!< point to the target string
  6.         uint16_t hwIndex;          //!< current index
  7.         uint16_t hwLength;        //!< claimed length of the target string, it is used to prevent buffer overflow
  8.     )
  9. )
复制代码


     这里,实际上我们为目标状态机控制块定义了一个专用的类型,可以用fsm()对这个状态机加以引用。需要说明
的是,这个类型本质上是一个掩码结构体,也就是说你无法通过这个类型直接访问控制块的成员变量。这也是它安
全的地方——当然,防君子不防小人。
     语法:

  1.      fsm(<状态机名称>)
复制代码

     例子:

  1.      static fsm( print_string ) s_fsmPrintString;     //! 定义了一个本地的状态机控制块
复制代码




2. 如何extern一个状态机
         很多时候,我们的状态机会作为一个模块,提供给别的.c文件来使用(直接调用或者作为子状态机被
    调用,那么这种情况下应该如何处理呢?

    语法:

  1. extern_simple_fsm( <状态机名称>,
  2.     def_params(
  3.         参数列表               
  4.     )
  5. )
复制代码


    例子:

        在某个头文件中写入如下的内容:

  1. #include "simple_fsm.h"

  2. ...

  3. /*! fsm used to output specified string */
  4. extern_simple_fsm( print_string,
  5.     def_params(
  6.         const char *pchStr;        //!< point to the target string
  7.         uint16_t hwIndex;          //!< current index
  8.         uint16_t hwLength;        //!< claimed length of the target string, it is used to prevent buffer overflow
  9.     )
  10. )
复制代码




3. 如何实现一个状态机控制块的初始化函数
    很多复杂的状态机其服务本身是需要初始化的,简单说就是它的控制块在状态机使用前,必须进行
初始化,这类初始化是通过用户自定义的初始化函数来实现的,那么如何编写这类初始化函数呢?


[注意] 无论状态机多简单,初始化函数都不能省略。
    原因很简单,这样写出来的代码兼容性最好。有的说,控制块如果你不初始化,就是自动放到ZI段去了,
    编译器会自动帮你初始化为0。即便如此,这也是不妥的,原因如下:
    a. 不能依赖编译器,因为ANSI-C并没有规定不初始化的变量一定会被自动初始化为0
    b. 如果控制块是来自堆,就没有人帮你初始化状态机控制块了,别忘控制块里至少还有一个状态变量
    c. 作为子状态机使用的时候,为了节省空间,不同时运行的子状态机可以用union共享同一块Memory,
        这种情况下,状态机使用前不初始化问题很严重。



    语法:

  1. fsm_initialiser( <状态机名称>,
  2.     args(           
  3.         <状态机初始化函数的形参列表,参数用逗号隔开,如果真的没有形参,可以省略该部分>
  4.         /* 注意,即便没有形参,你也是需要initialiser来初始化状态机的 */
  5.     ))

  6.     init_body (
  7.         <初始化函数的函数体,用普通C语言语法即可>
  8.         /* 如果初始化过程中发生了任何错误需要放弃初始化并立即退出,使用 abort_init() */
  9.     )
复制代码


    例子:


  1. fsm_initialiser( print_string,
  2.     args(           
  3.         const char *pchString, uint16_t hwSize
  4.     ))

  5.     init_body (
  6.         if (NULL == pchString || 0 == hwSize) {
  7.             abort_init();                                       //!< illegal parameter
  8.         } else if (strlen(pchString) < hwSize) {
  9.             abort_init();                                       //!< buffer overflow
  10.         }

  11.         this.pchStr = pchString;  
  12.         this.hwLength = hwSize;
  13.     )
复制代码




4. 如何extern一个状态机初始化函数
     当一个状态机包含初始化函数时,如果要把该状态机提供给别的.c使用,我们还需要把对应的初
始化函数也extern出去。

    当你使用 extern_fsm_initialiser 的时候,我们的宏木板还会自动定义一个函数原型,这样,你就可以
    用这个函数圆形去定义指向 当前初始化函数 的函数指针。函数原型的名称如下:

   <状态机名称>_init_fn

    语法:

  1. extern_simple_fsm_initialiser( <状态机名称>,
  2.     args(           
  3.         <状态机初始化函数的形参列表,参数用逗号隔开,如果真的没有形参,可以省略该部分>
  4.         /* 注意,即便没有形参,你也是需要initialiser来初始化状态机的 */
  5.     ))
复制代码


    例子:
        在某个头文件中写入如下的内容:

  1. #include "simple_fsm.h"

  2. ...

  3. /*! fsm used to output specified string */
  4. extern_simple_fsm_initialiser( print_string,
  5.     args(           
  6.         const char *pchString, uint16_t hwSize
  7.     ))

  8. ...

复制代码

这里,系统顺便定义了一个函数原型,print_string_init_fn,你可以用print_string_init_fn 直接定义函数指针:

  1.     print_string_init_fn *fnInit = &print_string_init;   //!< <状态机名称>_init  就是初始化函数的函数名。
复制代码




5. 如何初始化一个状态机
    对于一个需要初始化的状态机,我们应该如何对它进行初始化呢?

    语法:

  1. init_fsm(   <状态机名称>, <目标状态机控制块的地址>,
  2.     args(
  3.         <状态机初始化函数的实参列表,参数用逗号隔开,如果没有实参,可以省略该部分>
  4.      ));

  5. 该函数的返回值是地址:
  6.      NULL          初始化过程中出错
  7.      ! NULL   <目标状态机控制块的地址>

复制代码


     例子:

  1. //! 定义了一个状态机控制块
  2. static fsm(print_string)  s_fsmPrintString;

  3. #define DEMO_STRING   "Hello FSM World!\r\n"

  4.     if (NULL == init_fsm(    print_string, & s_fsmPrintString,
  5.         args(
  6.             DEMO_STRING,                      //!< target string   
  7.             sizeof(DEMO_STRING) - 1))) {      //!< String Length
  8.          /* failed to initialize the FSM, put error handling code here */
  9.      }

复制代码




6. 如何实现一个状态机
    这里,我们提供两种语法:语法1和语法2。语法1的写法会将整个状态机作为黑盒子封装起来(会
阻止用户进行单步调试——因为无法下断点)。这非常适合模块调试好以后,作为一个黑盒子发布。
语法2更类似C函数,同时也支持调试——非常推荐在状态机的开发阶段使用。

    语法1:

  1. implement_fsm(  <状态机名称> )
  2.     def_states( <列举所有状态机状态,用逗号隔开,确保状态机的入口状态列在第一的位置> )

  3.     body (
  4.         
  5.          on_start(  
  6.              <状态机复位后第一次运行时,运行且只运行一次的代码,通常放一些状态机内部的初始化代码,如果无所事事,可以省略这个部分>
  7.          )  
  8.       
  9.         <状态机所有的状态实现>
  10.     )
复制代码


    例子:

  1. implement_fsm(  print_string )
  2.     def_states( CHECK_LENGTH, OUTPUT_CHAR )  

  3.     body (
  4.          on_start(  
  5.              this.hwIndex = 0;         //!< reset index
  6.          )  
  7.       
  8.         ...
  9.     )
复制代码



语法2:

  1. implement_fsm(  <状态机名称> )
  2. {
  3.     def_states( <列举所有状态机状态,用逗号隔开,确保状态机的入口状态列在第一的位置> )

  4.     body_begin();
  5.         
  6.     on_start(  
  7.              <状态机复位后第一次运行时,运行且只运行一次的代码,通常放一些状态机内部的初始化代码,如果无所事事,可以省略这个部分>
  8.     )  
  9.       
  10.     <状态机所有的状态实现>

  11.     body_end();
  12. }
复制代码


    例子:

  1. implement_fsm(  print_string )
  2. {
  3.     def_states( CHECK_LENGTH, OUTPUT_CHAR )  

  4.     body_begin();

  5.     on_start(  
  6.         this.hwIndex = 0;         //!< reset index
  7.     )  
  8.    
  9.      ...

  10.     body_end();
  11. }
复制代码





7. 如何extern一个状态机函数

    当你使用 extern_fsm_implementation 的时候,我们的宏木板还会自动定义一个函数原型,这样,你就可以
    用这个函数圆形去定义指向 当前初始化函数 的函数指针。函数原型的名称如下:

   <状态机名称>_fn


    语法:

  1. extern_fsm_implementation(  <状态机名称> )
复制代码



    例子:
        在某个头文件中写入如下的内容:

  1. #include "ooc.h"
  2. #include "simple_fsm.h"

  3. ...

  4. extern_fsm_implementation(  print_string );

  5. ...

复制代码


这里,系统顺便定义了一个函数原型,print_string_fn,你可以用print_string_fn 直接定义函数指针:

  1.     print_string_fn *fnFSM = &print_string;   //!< <状态机名称> 就是状态机函数的名称。
复制代码





8. 如何实现一个状态
     状态必须在body()内实现,具体形式如下:

    语法:

  1.      state( <状态名称>,
  2.         <本状态内使用的局部变量列表>
  3.          <状态实现代码,C语言实现>
  4.      )
复制代码


或者:


  1.      state( <状态名称>){
  2.         <局部变量列表>
  3.          <状态实现代码,C语言实现>
  4.      }
复制代码


   需要强调的是,如果状态中没有任何状态切换或者更新的操作,当前state里的代码将被重复执行(相
当于默认自反)。


    在实现状态的过程中,状态的切换要通过 transfer_to() 来实现,它将更新状态为目标状态并释放CPU
(也就是类似RTOS中的yield),等待下一个协作运行的机会,其语法如下:

  1.      transfer_to( <目标状态的名称> )
复制代码


    有些时候,我们只希望跳转到目标状态继续执行,而并不希望立即释放CPU(yield),则可以用
update_state_to() 来实现。update_state_to()其实类似于C语言里的goto。其语法如下:

  1.      update_state_to( <目标状态的名称> )
复制代码


     状态实现的时候,如果需要更新状态机的返回值,则可以使用下列方式:

  1.      fsm_on_going()                         立即释放CPU(yield),并让状态机返回fsm_rt_on_going;
  2.      fsm_cpl()                                  立即释放CPU(yield),复位状态机,并让状态机返回fsm_rt_cpl;
  3.      fsm_reset()                               仅复位状态机状态、不释放CPU,不影响状态机返回值(通常配合fsm_on_going()
  4.                                                     和fsm_report() 使用)
  5.      fsm_report( <任意负数> )         立即释放CPU(yield),并返回错误码(任意小于等于fsm_rt_err)的值
复制代码


     例子:

  1. implement_fsm(  print_string )            
  2.     def_states( CHECK_LENGTH, OUTPUT_CHAR )

  3.     body (
  4.          

  5.          on_start(  
  6.              this.hwIndex = 0;         //!< reset index
  7.          )  
  8.       
  9.         state ( CHECK_LENGTH) {
  10.             if ( this.hwIndex >= this.hwLength ) {
  11.                  fsm_cpl();               
  12.             }
  13.             update_state_to ( OUTPUT_CHAR );              //! transfer to OUTPUT_CHAR immediately without yield      
  14.         }

  15.         state ( OUTPUT_CHAR ) {
  16.              if (SERIAL_OUT( this.pchStr[ this.hwIndex ] )) {
  17.                    this.hwIndex++;
  18.                    transfer_to ( CHECK_LENGTH );
  19.              }
  20.         }
  21.     )
复制代码


9. 如何调用一个状态机
     状态机(包括子状态机)的调用方式是一样的,假设状态机已经被初始化过了,那么可以使用
下面的方法进行调用(放在超级循环里面,或者放在某个状态里面是一样的):

    语法:

  1.     call_fsm ( <状态机名称>, <状态机控制块的地址> )
  2.     )

  3. 该函数的返回值是状态机的运行状态 fsm_rt_t:
  4.      fsm_rt_err                状态机出现了意料之外的,且自身无法处理的错误,例如无效的参数
  5.      fsm_rt_on_going           状态机正在执行
  6.      fsm_rt_cpl                状态机已经完成
复制代码


    例子:

  1.     static fsm(print_string) s_fsmPrintSting;

  2.     void main(void)
  3.     {
  4.          ...
  5.          while(1) {
  6.              ...
  7.              if (fsm_rt_cpl == call_fsm( print_string, &s_fsmPrintString )) {
  8.                   /* fsm is complete, do something here */
  9.              }
  10.          }
  11.     }
复制代码




10. 如何前置声明一个状态机
         有些时候,在我们正式通过 simple_fsm 宏定义一个状态机之前,当前状态机就要被其它(当前状态机)所依赖的关键类型所引用,
比如,定义指向当前状态机的指针啊,函数指针啊,之类的——简而言之,前置引用的问题如何解决呢?

    语法:

  1. declare_simple_fsm( <状态机名称> )
复制代码


    例子:

        在某个头文件中写入如下的内容:

  1. #include "simple_fsm.h"


  2. declare_simple_fsm(print_string);
  3. extern_fsm_implementation(print_string);
  4. extern_simple_fsm_initialiser( print_string,
  5.     args(           
  6.         const char *pchString, uint16_t hwSize
  7.     ));

  8. typedef struct {
  9.     fsm(print_string) *ptThis;          //!< a pointer points to fsm obj
  10.     print_string_fn *fnTask;             //!< a function pointer, point to fsm function
  11.     print_string_init_fn *fnInit;       //!< a function pinter, points to initialisation function
  12. } vtable_t;


  13. /*! fsm used to output specified string */
  14. extern_simple_fsm( print_string,
  15.     def_params(
  16.         vtable_t Methods;
  17.         const char *pchStr;        //!< point to the target string
  18.         uint16_t hwIndex;          //!< current index
  19.         uint16_t hwLength;        //!< claimed length of the target string, it is used to prevent buffer overflow
  20.     )
  21. )
复制代码



一个简单的例子


     这里,我们展示了一个简单的状态机例子,用于周期性的通过串口输出“hello”。我们可以看到,
这个例子里定义了两个状态机,print_hello 用于打印字符串,并调用另外一个子状态机delay_1s用于
实现一个差不离的延时(代码里用了一个随便写的常数10000,领会精神就好)。

     print_hello 状态机的结构相当简单,前半部分是字符串的输出——简单粗暴的为每一个字符分配
一个状态;后半部分演示了子状态机的调用方式:首先对子状态机进行初始化(如果这个子状态机确
实需要这个步骤);紧接着是通过一个专门的状态来进行子状态机调用。我们通过子状态机的返回值
来了解子状态机的状态——正在进行(on going),完成(cpl )还是发生了什么错误(返回值为负数)



  1. /***************************************************************************
  2. *   Copyright(C)2009-2017 by Gorgon Meducer<Embedded_zhuoran@hotmail.com> *
  3. *                                                                         *
  4. *   This program is free software; you can redistribute it and/or modify  *
  5. *   it under the terms of the GNU Lesser General Public License as        *
  6. *   published by the Free Software Foundation; either version 2 of the    *
  7. *   License, or (at your option) any later version.                       *
  8. *                                                                         *
  9. *   This program is distributed in the hope that it will be useful,       *
  10. *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
  11. *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
  12. *   GNU General Public License for more details.                          *
  13. *                                                                         *
  14. *   You should have received a copy of the GNU Lesser General Public      *
  15. *   License along with this program; if not, write to the                 *
  16. *   Free Software Foundation, Inc.,                                       *
  17. *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
  18. ***************************************************************************/

  19. /*============================ INCLUDES ======================================*/
  20. #include ".\app_cfg.h"

  21. /*============================ MACROS ========================================*/
  22. /*============================ MACROFIED FUNCTIONS ===========================*/   

  23. #ifndef SERIAL_OUT
  24. #define SERIAL_OUT(__BYTE)      serial_out(__BYTE)
  25. #endif

  26. /*============================ TYPES =========================================*/

  27. /*! \brief you can use simple fsm at any where you want with little cost.
  28.            E.g.
  29.             
  30. *! \brief function that output a char with none-block manner
  31. *! \param chByte target char
  32. *! \retval true the target char has been put into the output buffer
  33. *! \retval false service is busy
  34. */
  35. extern bool serial_out(uint8_t chByte);



  36. /*============================ GLOBAL VARIABLES ==============================*/
  37. /*============================ LOCAL VARIABLES ===============================*/
  38. /*============================ PROTOTYPES ====================================*/

  39. declare_simple_fsm(delay_1s)
  40. declare_simple_fsm(print_hello)

  41. /*! /brief define fsm delay_1s
  42. *!        list all the parameters
  43. */
  44. def_simple_fsm( delay_1s,
  45.    
  46.     /* define all the parameters used in the fsm */
  47.     def_params(
  48.         uint32_t wCounter;                  //!< a uint32_t counter
  49.     )
  50. )

  51. /*! /brief define fsm print_hello
  52. *!        list all the parameters
  53. */
  54. def_simple_fsm( print_hello,
  55.     def_params(
  56.         fsm(delay_1s) fsmDelay;             //!< sub fsm delay_1s
  57.     )
  58. )

  59. /*============================ IMPLEMENTATION ================================*/



  60. /*! /brief define the fsm initialiser for FSML delay_1s
  61. *! /param wCounter  an uint32_t value for the delay count
  62. */
  63. fsm_initialiser(delay_1s,               //!< define initialiser for fsm: delay_1s
  64.     /*! list all the parameters required by this initialiser */
  65.     args(           
  66.         uint32_t wCounter               //!< delay count
  67.     ))

  68.     /*! the body of this initialiser */
  69.     init_body (
  70.         this.wCounter = wCounter;       //!< initialiser the fsm paramter
  71.     )
  72. /* End of the fsm initialiser */


  73. /*! /brief Implement the fsm: delay_1s
  74. *         This fsm only contains one state.
  75. */
  76. implement_fsm(  delay_1s)
  77.     def_states(DELAY_1S)                //!< list all the states used in the FSM

  78.     /* the body of the FSM: delay_1s */
  79.     body (
  80.         

  81.         state(  DELAY_1S) {            
  82.             if (!this.wCounter) {
  83.                 fsm_cpl();              //!< FSM is completed
  84.             }
  85.             this.wCounter--;
  86.         }
  87.     )
  88. /* End of fsm implementation */


  89. fsm_initialiser(print_hello)
  90.     init_body ()

  91. /*! /brief Implement the fsm: delay_1s
  92. *         This fsm only contains one state.
  93. */
  94. implement_fsm(print_hello)
  95.     /*! list all the states used in the FSM */
  96.     def_states(PRINT_H, PRINT_E, PRINT_L, PRINT_L_2, PRINT_O, DELAY)

  97.     body(
  98. //! the on_start block are called once and only once on the entry point of a FSM
  99. //        on_start(
  100. //            /* add fsm parameter initialisation code here */
  101. //        )

  102.         state(PRINT_H) {
  103.             if (SERIAL_OUT('H')) {
  104.                 transfer_to(PRINT_E);   //!< transfer to state PRINT_E
  105.             }
  106.         }
  107.         
  108.         state(PRINT_E) {
  109.             if (SERIAL_OUT('e')) {
  110.                 transfer_to(PRINT_L);
  111.             }
  112.         }

  113.         state(PRINT_L) {
  114.             if (SERIAL_OUT('l')) {
  115.                 transfer_to(PRINT_L_2);
  116.             }
  117.         }

  118.         state(PRINT_L_2) {
  119.             if (SERIAL_OUT('l')) {
  120.                 transfer_to(PRINT_O);
  121.             }
  122.         }

  123.         state(PRINT_O) {
  124.             if (!SERIAL_OUT('o')) {
  125.                 fsm_on_going();
  126.             }

  127.             //! initialize the internal sub fsm
  128.             init_fsm(   delay_1s,           //!< FSM: delay_1s
  129.                         &(this.fsmDelay),   //!< the fsm control block
  130.                         args(10000));       //!< pass parameters to the initialiser

  131.             //! update the state to DELAY without yield, so it will fall-through to the following state directly
  132.             update_state_to(DELAY);
  133.         }


  134.         state(DELAY) {
  135.             /*! call the sub fsm */
  136.             if (fsm_rt_cpl == call_fsm(delay_1s, &(this.fsmDelay))) {
  137.                 fsm_cpl();
  138.             }
  139.         }
  140.     )

  141. /* EOF */
复制代码




相关下载


    一站式头文件支持(请直接copy utilities 文件夹到你的文件中,然后包含 里面的compiler.h, ooc.h 和 simple_fsm.h 即可使用


相关文章


    a. [交流][微知识]模块的封装(一):C语言类的封装
    b. [交流][微知识]模块的封装(二):C语言类的继承和派生
    c. [交流][微知识]模块的封装(三):无伤大雅的形式主义


最新更新

    a. 在最新的vsf中加入了对simple_fsm的支持(https://github.com/vsfteam/vsf/b ... e/main_fsm_simple.c
    b. 在vsf中,simple_fsm做了以下改进
        - 删除了对掩码结构体的依赖
        - 简化了部分用于语法糖的宏
        - 利用vsf_kernel进行优化——随着子状态机调用深度的加深,状态机任务栈的消耗保持不变。这实际上是结合了状态机“函数指针实现法”和“switch实现法”的优点做出的一个重大改进。
        - 可以和vsf的其它任务形式试用统一的方式进行通信。这包括vsf_eda,vsf_task, vsf_pt和vsf_thread。


注意:请勿使用下方附件中的ooc.rar和simple_fsm.zip。这是历史遗留的老版本,不知道为什么,无法通过编辑帖子的方法来删除。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2017-2-12 17:13:39 | 显示全部楼层
谢谢大侠

出0入0汤圆

发表于 2017-2-12 17:22:52 来自手机 | 显示全部楼层
先收藏,在看,老师,那个队列的那个帖子更新了么

出0入0汤圆

发表于 2017-2-12 17:53:11 来自手机 | 显示全部楼层
帮顶。老师又发贴了。

出0入296汤圆

 楼主| 发表于 2017-2-12 17:54:03 | 显示全部楼层
hongjie0216 发表于 2017-2-12 17:22
先收藏,在看,老师,那个队列的那个帖子更新了么

最近都会陆续更新

出5入0汤圆

发表于 2017-2-12 18:23:32 来自手机 | 显示全部楼层
我靠,我以为系统时间错乱了呢。好久不见

出0入0汤圆

发表于 2017-2-12 18:33:26 | 显示全部楼层
先收藏了,跟老师学习学习,谢谢!

出0入147汤圆

发表于 2017-2-12 19:08:30 | 显示全部楼层
不错,虽说简洁,但功能已经很完善,也发一个我自己封装的简易版状态机

  1.   FSM_DEFINE(FSM_STATE_INIT);           //定义一个状态机实例,初始状态为FSM_STATE_INIT

  2.   FSM_WAIT();                           //状态机等待一定时间

  3.   FSM_START                             //开始进行调度
  4.   {
  5.     STATE_DEFINE(FSM_STATE_INIT)        //定义状态FSM_STATE_INIT
  6.     {
  7.       STATE_ENTER                       //进入状态FSM_STATE_INIT
  8.       {

  9.       }
  10.       STATE_DURING                      //处于状态FSM_STATE_INIT
  11.       {
  12.         // 处理状态FSM_STATE_INIT事务
  13.         STATE_SWITCH(FSM_STATE_IDLE);   //切换至新状态
  14.       }
  15.       STATE_EXIT                        //切换状态前执行退出状态FSM_STATE_INIT操作
  16.       {
  17.         STATE_DELAY(10);                //延时10个节拍后进入新状态
  18.       }
  19.     }STATE_END                          //状态FSM_STATE_INIT结束
  20.     STATE_DEFINE(FSM_STATE_IDLE)        //定义状态FSM_STATE_IDLE
  21.     {
  22.       STATE_ENTER                       //进入状态FSM_STATE_IDLE
  23.       {

  24.       }
  25.       STATE_DURING                      //处于状态FSM_STATE_IDLE
  26.       {
  27.         // 处理状态FSM_STATE_IDLE事务
  28.       }
  29.       STATE_EXIT                        //切换状态前执行退出状态FSM_STATE_IDLE操作
  30.       {
  31.         STATE_DELAY(10);                //延时10个节拍后进入新状态
  32.       }
  33.     }STATE_END                          //状态FSM_STATE_IDLE结束   
  34.   }FSM_DEFAULT(FSM_STATE_INIT)          //定义默认状态
复制代码

出0入0汤圆

发表于 2017-2-12 19:30:22 | 显示全部楼层
极好。。。。很久之前状态机就是楼主的帖子入门的

出0入0汤圆

发表于 2017-2-12 19:45:51 来自手机 | 显示全部楼层
楼主好久没出现了!

出0入0汤圆

发表于 2017-2-12 20:29:08 | 显示全部楼层
必须支持!状态机编程是单片机重要思想

出0入0汤圆

发表于 2017-2-12 20:59:17 | 显示全部楼层
向大师学习FSM。谢谢!

出0入8汤圆

发表于 2017-2-12 21:02:42 来自手机 | 显示全部楼层
关注一下,楼主确实好久没出现了,我还以为金盆洗手了。

出100入0汤圆

发表于 2017-2-12 21:09:53 来自手机 | 显示全部楼层
先mark再慢慢看,楼主好久没出现了…

出0入0汤圆

发表于 2017-2-12 21:21:01 | 显示全部楼层
顶一下老师           

出0入0汤圆

发表于 2017-2-12 21:41:30 | 显示全部楼层
mark 状态机,有实例还是比较好的,很强!

出0入0汤圆

发表于 2017-2-12 21:57:00 | 显示全部楼层


顶一下老师

出0入0汤圆

发表于 2017-2-12 22:01:08 | 显示全部楼层
膜拜下大神,论坛里为数不多的技术大牛

出0入0汤圆

发表于 2017-2-13 07:29:55 | 显示全部楼层
顶你
看到标题以为时间错乱了!

出0入0汤圆

发表于 2017-2-13 09:58:50 | 显示全部楼层
顶一下老师!

出0入0汤圆

发表于 2017-2-13 09:59:26 | 显示全部楼层
y574924080 发表于 2017-2-12 17:53
帮顶。老师又发贴了。

Y师兄啊,好久不冒泡哈。

出0入0汤圆

发表于 2017-2-13 10:28:53 | 显示全部楼层
凌晨一点 发表于 2017-2-12 20:29
必须支持!状态机编程是单片机重要思想

状态机不单单是在mcu领域应用,玩的溜的话,在很多数据处理的时候都可以应用。

出0入0汤圆

发表于 2017-2-13 10:44:26 | 显示全部楼层
请问下,老师,如果按照原来的状态机模式,是否有改进,即不经过封装的?

出0入296汤圆

 楼主| 发表于 2017-2-13 17:28:38 | 显示全部楼层
卢台长 发表于 2017-2-13 10:44
请问下,老师,如果按照原来的状态机模式,是否有改进,即不经过封装的? ...

这次封装其实在执行效率上是有改进的——仅对ARM架构来说是这样。我会在帖子正文做出解释。

出0入296汤圆

 楼主| 发表于 2017-2-13 18:20:16 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2017-2-13 18:21 编辑
dreampet 发表于 2017-2-12 19:08
不错,虽说简洁,但功能已经很完善,也发一个我自己封装的简易版状态机

...


感觉我坑了你……你也是一堆大写的宏,哈哈哈。
说说我自己的感受,其实每个状态的ENTER和EXIT不必每个状态都有……太拘禁。并不是说QP的东西都是好的。

出0入0汤圆

发表于 2017-2-13 20:34:02 | 显示全部楼层
感谢分享

出0入147汤圆

发表于 2017-2-13 20:47:40 来自手机 | 显示全部楼层
Gorgon_Meducer 发表于 2017-2-13 18:20
感觉我坑了你……你也是一堆大写的宏,哈哈哈。
说说我自己的感受,其实每个状态的ENTER和EXIT不必每个状 ...

呵呵,大写宏这个毛病应该是改不过来了, 这个状态机,确实只有DURING 是必须的,ENTER 和EXIT 可以按需增减,最近一年都在用Simulink的模型生成代码,也就参考它的Stateflow封装了一个。

出0入0汤圆

发表于 2017-2-13 20:50:49 | 显示全部楼层
不错,比以前的顺眼多了

出0入24汤圆

发表于 2017-2-13 22:10:50 来自手机 | 显示全部楼层
学习学习

出0入0汤圆

发表于 2017-2-13 23:29:23 | 显示全部楼层

学习学习,一直关注着楼主呢

出0入0汤圆

发表于 2017-2-13 23:34:31 | 显示全部楼层
很期待后期的...

出0入0汤圆

发表于 2017-2-14 17:57:29 | 显示全部楼层
搜藏,感谢!

出0入0汤圆

发表于 2017-2-15 15:03:37 | 显示全部楼层
向老师学习

出0入0汤圆

发表于 2017-2-16 09:36:33 | 显示全部楼层
用在51这样的资源有限的单片机里是不是比较合适?

出0入296汤圆

 楼主| 发表于 2017-2-19 14:44:26 | 显示全部楼层
genhao2 发表于 2017-2-16 09:36
用在51这样的资源有限的单片机里是不是比较合适?

适合小资源的MCU,但说实话,感觉并不特别的适合51——只能说不会更差吧…… 毕竟51的寻址方式很蛋疼。

出0入296汤圆

 楼主| 发表于 2017-2-19 17:34:10 | 显示全部楼层
使用教程更新完毕,里面一步一步的带着大家做了一个实现通用字符串输出的状态机,欢迎拍砖。

出0入0汤圆

发表于 2017-2-21 12:23:52 | 显示全部楼层
谢谢大侠~

出0入0汤圆

发表于 2017-2-21 12:40:41 | 显示全部楼层
收藏,学习中

出0入0汤圆

发表于 2017-2-21 13:13:45 | 显示全部楼层
挺好的。赞 !              

出0入0汤圆

发表于 2017-2-21 16:58:34 | 显示全部楼层
顶一下,收藏

出0入296汤圆

 楼主| 发表于 2017-2-23 10:50:53 | 显示全部楼层
有没有什么使用上的疑问?
有没有什么技术上的问题?
欢迎交流。有问必答。

出0入0汤圆

发表于 2017-2-23 22:52:10 | 显示全部楼层
简单分析下为什么C在大量使用宏时,理解代码比较困难。

C的预处理器基本上是接近图灵完备的,我们可以用预处理的语句构造一些宏,这些宏可以(可能)构造出来一台虚拟机 M1,这台虚拟机M1有可能是图灵完备的,当然不完备也没关系。理解这个意思就可以了。

然后我们可以使用构造的宏和C语句在这台虚拟机M1上实现功能,即:程序员->M1->C->物理机器。

在这台虚拟机M1上编程,有一个基本的缺陷:这台虚拟机M1很难调试,因为你很难在M1上再开发出一套编译调试工具出来。因此在它上面完成稍微复杂的工作会较为困难。

当这个项目的读者要理解代码时,他需要先掌握这台虚拟机M1的构造。而虚拟机M1的构造是宏的作者自己有意无意间定义的,换言之,这台虚拟机M1是私有的,有可能宏作者自己也不太清楚这台虚拟机M1是什么。他在M1的层次读代码,但在尝试用C的层次理解。M1的存在,导致了阅读理解维护这类项目的困难。

而直接使用C语句实现功能,是在某个物理机器上编程,这个机器是有明确定义的,调试工具和手段很多,相对那台私家定制M1,代码逻辑容易理解,即:程序员->C->物理机器。

理论上在M1可以再构建出一个虚拟机M2,也可无限递归这个过程:程序员->Mn....->M1->C->物理机器,汇编,微码之类的层次这里就略过了。

出0入0汤圆

发表于 2017-2-24 14:47:36 | 显示全部楼层
从坛子里看到了不少状态机的写法,有没有具体的例子呢,总感觉无从下手

出0入0汤圆

发表于 2017-2-24 15:52:37 | 显示全部楼层
本帖最后由 3DA502 于 2017-2-24 16:19 编辑

非常非常的标准化,期待能用上

目前就会 tSys.StateNumber = next

感觉就是把一个if switch就能说明白的C语言基础操作,用层层打包,变成了C++

出0入0汤圆

发表于 2017-2-24 15:59:00 来自手机 | 显示全部楼层
慢慢看太长了!!!

出0入0汤圆

发表于 2017-2-24 15:59:19 来自手机 | 显示全部楼层
慢慢看太长了!!!

出0入0汤圆

发表于 2017-2-24 17:30:37 | 显示全部楼层
学习了。不过宏是小写的看起来像是一个语句,但在实际调用宏的时候不需要加分号,这个有点不适应哈哈。

前辈对宏的使用炉火纯青,看前辈用了不少C99特性,最近用的一款单片机的IDE不支持C99,有点难过,不然可以用上试试了。

出0入0汤圆

发表于 2017-2-24 17:40:51 | 显示全部楼层
看您在ooc.h中对一下这几个函数做了声明,这几个函数是什么作用呢?

  1. /*! \brief initialize event
  2. *! \param ptEvent target event
  3. *! \return the address of event item
  4. */
  5. extern DELEGATE *delegate_init(DELEGATE *ptEvent);

  6. /*! \brief initialize event handler item
  7. *! \param ptHandler the target event handler item
  8. *! \param fnRoutine event handler routine
  9. *! \param pArg handler extra arguments
  10. *! \return the address of event handler item
  11. */
  12. extern DELEGATE_HANDLE *delegate_handler_init(
  13.     DELEGATE_HANDLE *ptHandler, DELEGATE_HANDLE_FUNC *fnRoutine, void *pArg);

  14. /*! \brief register event handler to specified event
  15. *! \param ptEvent target event
  16. *! \param ptHandler target event handler
  17. *! \return access result
  18. */
  19. extern gsf_err_t register_delegate_handler(DELEGATE *ptEvent, DELEGATE_HANDLE *ptHandler);

  20. /*! \brief unregister a specified event handler
  21. *! \param ptEvent target event
  22. *! \param ptHandler target event handler
  23. *! \return access result
  24. */
  25. extern gsf_err_t unregister_delegate_handler( DELEGATE *ptEvent, DELEGATE_HANDLE *ptHandler);

  26. /*! \brief raise target event
  27. *! \param ptEvent the target event
  28. *! \param pArg event argument
  29. *! \return access result
  30. */
  31. extern fsm_rt_t invoke_delegate( DELEGATE *ptEvent, void *pParam);
复制代码

出0入0汤圆

发表于 2017-2-24 18:01:16 | 显示全部楼层
顶 大湿!   大师出品必是精品。

出0入296汤圆

 楼主| 发表于 2017-2-25 12:15:00 | 显示全部楼层
int 发表于 2017-2-24 17:40
看您在ooc.h中对一下这几个函数做了声明,这几个函数是什么作用呢?

这些是模拟C#的delegate。还有一个配套的.c。跟这个帖子无关,你们可以删掉。

出0入296汤圆

 楼主| 发表于 2017-2-25 21:27:20 | 显示全部楼层
- 2017-2-25 更新
   
    更新稳定版本。修正ooc.h在IAR下报类型不匹配的bug
    更新simple_fsm。增加一个fsm_implementation() 和 extern_fsm_implementation() 宏。更新楼主位的教程。

出100入101汤圆

发表于 2017-2-26 09:29:44 | 显示全部楼层
请教LZ,ARMv8-M系统安全设计相关的研究,这是工作,还是兴趣?是针对特定单片机,还是通用?

出0入296汤圆

 楼主| 发表于 2017-2-26 15:52:49 | 显示全部楼层
fengyunyu 发表于 2017-2-26 09:29
请教LZ,ARMv8-M系统安全设计相关的研究,这是工作,还是兴趣?是针对特定单片机,还是通用? ...

工作。内容有通用的部分也有专用的部分

出0入0汤圆

发表于 2017-2-26 23:18:22 | 显示全部楼层
难得看到一次老师,不知道您最近忙什么呢

出0入4汤圆

发表于 2017-2-26 23:40:14 | 显示全部楼层
大师  膜拜   引路人

出0入0汤圆

发表于 2017-2-28 13:45:13 | 显示全部楼层
老师,关于Cricital, Mailbox,Event  没有更改,还用之前的是么?

出0入0汤圆

发表于 2017-2-28 15:50:36 | 显示全部楼层
本来上这个论坛,基本就是逛逛水坛,看看诸位电工都怎么讨得好生活。
惊闻王Sir更新了,赶紧过来捧场!以后可以来逛技术坛了。

出0入296汤圆

 楼主| 发表于 2017-2-28 20:21:02 来自手机 | 显示全部楼层
end2000 发表于 2017-2-23 22:52
简单分析下为什么C在大量使用宏时,理解代码比较困难。

C的预处理器基本上是接近图灵完备的,我们可以用预 ...

问题就出在你不信任宏的作者。宏在一种情况下是有进步意义的:宏固化的逻辑是可靠的。你要信任他。不要去搞懂他的原理。你按照使用说明去用就好。宏之所以被这么诟病,是因为你能看到他的底层实现,想想但凡看不到底层实现的东西,大家就回归本真——考虑怎么用了。动态链接库,没有源代码,大家用的好好的,只看接口。C#系统控件,没有源代码,大家就只关心使用了。至于你说的这个对中间逻辑的信任问题,不管你信不信任,用宏和用其它黑盒子是一样的代码质量问题——事不关心,关心则乱。
在心理上过了黑盒子这一关,好处谁用谁知道——如果你问我,用宏有没有缺点!有!必须有!就是你不知道他封装了什么,不好调试。欢迎不要使用。也欢迎冒着风险体验。如人饮水冷暖自知。

出0入296汤圆

 楼主| 发表于 2017-2-28 20:24:08 来自手机 | 显示全部楼层
hongjie0216 发表于 2017-2-28 13:45
老师,关于Cricital, Mailbox,Event  没有更改,还用之前的是么?

暂时没有

出0入296汤圆

 楼主| 发表于 2017-2-28 20:25:52 来自手机 | 显示全部楼层
fancyboy 发表于 2017-2-26 23:18
难得看到一次老师,不知道您最近忙什么呢

做安全相关的研究

出0入296汤圆

 楼主| 发表于 2017-2-28 20:27:39 来自手机 | 显示全部楼层
int 发表于 2017-2-24 17:30
学习了。不过宏是小写的看起来像是一个语句,但在实际调用宏的时候不需要加分号,这个有点不适应哈哈。

前 ...

最好还是加上!

出0入296汤圆

 楼主| 发表于 2017-2-28 20:29:04 来自手机 | 显示全部楼层
3DA502 发表于 2017-2-24 15:52
非常非常的标准化,期待能用上

目前就会 tSys.StateNumber = next

不是为了模仿c++,而是为了简化开发。形式主义只有在无伤大雅,不会造成额外负担得情况下才有意义

出0入0汤圆

发表于 2017-3-3 13:21:59 | 显示全部楼层
老师 ,发现两处错误,

this_pchString改为this.pchStr,否则编译报错

引号不是英文字符,编译报错

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入296汤圆

 楼主| 发表于 2017-3-5 09:27:16 来自手机 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2017-3-5 09:33 编辑
hongjie0216 发表于 2017-3-3 13:21
老师 ,发现两处错误,

this_pchString改为this.pchStr,否则编译报错


非常感谢指出!!!
语法介绍里面的例子,我没有测试过……不好意思哈。范例代码的例子是编译测试过的

问题已修正

出0入0汤圆

发表于 2017-3-5 15:24:43 | 显示全部楼层
感谢分享 , 学习了

出0入296汤圆

 楼主| 发表于 2017-3-8 12:06:10 | 显示全部楼层
大家可以分享一些用这个状态机模板写的代码来交流下。

出0入0汤圆

发表于 2017-3-15 06:42:15 | 显示全部楼层
老师其实是欢迎大家来找茬

出0入0汤圆

发表于 2017-3-17 18:01:30 | 显示全部楼层
本帖最后由 卢台长 于 2017-3-17 18:02 编辑

  1. implement_fsm(  delay_1s)
  2.     def_states(DELAY_1S)               

  3.     body (
  4.         state(  DELAY_1S,               
  5.             if (this.wCounter) {    //应该是 if(!this.wCounter)
  6.                 fsm_cpl();              
  7.             }
  8.             this.wCounter--;
  9.             fsm_on_going();            
  10.         )
  11.     )

复制代码

老师,忘加了一个!符号

出0入0汤圆

发表于 2017-3-20 10:18:07 | 显示全部楼层
先顶再看,估计得学习一下,才能看的懂了。

出0入296汤圆

 楼主| 发表于 2017-3-20 10:26:47 | 显示全部楼层
卢台长 发表于 2017-3-17 18:01
老师,忘加了一个!符号

这是钓鱼的,真正认真看的人才能发现。你是第三个发现的。恭喜你。

出0入0汤圆

发表于 2017-3-20 11:59:50 | 显示全部楼层
必须要顶起来~~

出0入0汤圆

发表于 2017-3-29 23:52:20 | 显示全部楼层
感谢分享,希望有比较复杂的应用实例,才能更深入体会状态机的应用。

出0入0汤圆

发表于 2017-3-30 01:10:33 来自手机 | 显示全部楼层
armv8-m有trustzone了。tee可以玩很多东西。本人也是安全从业人员。:)

出0入0汤圆

发表于 2017-3-30 08:49:26 | 显示全部楼层
虽然不是很懂,但是感觉老师写的挺好的,有机会用一下会更能理解

出0入0汤圆

发表于 2017-3-30 09:18:55 | 显示全部楼层
end2000 发表于 2017-2-23 22:52
简单分析下为什么C在大量使用宏时,理解代码比较困难。

C的预处理器基本上是接近图灵完备的,我们可以用预 ...

还有一点,宏用的不是标准的C语法,看起来怪怪的,总觉得有错,多个逗号,少个分号的

出0入0汤圆

发表于 2017-4-1 23:15:34 | 显示全部楼层
simple_fsm.h里面有个数据类型uint_fast8_t,是在哪里定义的,我看c没有这个关键字呀。

出0入0汤圆

发表于 2017-4-1 23:20:19 来自手机 | 显示全部楼层
说真的!乍一看不容易董

出0入0汤圆

发表于 2017-4-4 10:04:22 | 显示全部楼层
顶一下老师

出0入0汤圆

发表于 2017-4-6 11:47:05 | 显示全部楼层
已经领悟到了老师的精髓 有时间我上传一个 使用完整版的例程

出0入0汤圆

发表于 2017-4-9 22:18:27 来自手机 | 显示全部楼层
非常好的编程思维模式,还不是太懂,值得深入研究。

出0入0汤圆

发表于 2017-4-11 00:20:14 | 显示全部楼层
谢谢,学习!!

出0入0汤圆

发表于 2017-4-11 09:19:24 | 显示全部楼层
请教老师,下面这几个是在哪里有定义?
uint_fast8_t
int_fast8_t
uint_fast16_t
int_fast16_t

出0入0汤圆

发表于 2017-4-11 15:30:18 | 显示全部楼层
#define END_DEF_CLASS(__NAME, ...)              \
    };                                          \
    union __NAME {                              \
        __VA_ARGS__                             \
        uint_fast8_t __NAME##__chMask[(sizeof(__##__NAME) + sizeof(uint_fast8_t) - 1) / sizeof(uint_fast8_t)];\
    };
这里联合里定义一个数组__NAME##__chMask,起什么作用?

出0入296汤圆

 楼主| 发表于 2017-5-16 05:13:50 | 显示全部楼层
lzchuo 发表于 2017-4-11 09:19
请教老师,下面这几个是在哪里有定义?
uint_fast8_t
int_fast8_t

关于uint_fastx_t的原理,使用方法和应用场合,我在另外一个帖子  [微知识]整整整型  里面有详细介绍。

出0入296汤圆

 楼主| 发表于 2017-5-16 05:15:33 | 显示全部楼层
lzchuo 发表于 2017-4-11 15:30
#define END_DEF_CLASS(__NAME, ...)              \
    };                                          \
...

这是掩码结构体,在我另外一个帖子 [微知识]模块的封装(一):C语言类的封装  有原理性的介绍。
最近这个头文件,我做了重大的更新,还请关注。

出0入296汤圆

 楼主| 发表于 2017-5-16 05:39:18 | 显示全部楼层
本帖最后由 Gorgon_Meducer 于 2017-5-16 05:42 编辑

2017-5-16更新
    a. 增加了对函数指针的支持
    b. 增加了simple_fsm 和 ooc.h 的宏兼容性,解决了多重宏嵌套时预先编译的错误,例如
        CLASS( fsm(demo_fsm) )    被预编译成   __fsm(demo_fsm) 导致无法识别fsm()宏
    c. 更新了ooc.h 和 simple_fsm.h 加入了必要的注释
    d. 更新了楼主位的使用说明,加入了对 initialiser 以及 状态函数,函数原型的说明(如何
       使用函数圆形来定义对应的指针)

出0入0汤圆

发表于 2017-5-16 17:33:00 | 显示全部楼层
Gorgon_Meducer 发表于 2017-5-16 05:39
2017-5-16更新
    a. 增加了对函数指针的支持
    b. 增加了simple_fsm 和 ooc.h 的宏兼容性,解决了多重 ...

建议您将代码部署到github上,感觉会方便一些

出0入0汤圆

发表于 2017-5-17 21:26:48 | 显示全部楼层
谢谢大师!先mark再慢慢看,每次看都有收获!

出0入0汤圆

发表于 2017-5-17 22:02:41 | 显示全部楼层
有没有实际项目的应用实例呢?

出0入0汤圆

发表于 2017-5-18 08:37:53 | 显示全部楼层
Gorgon_Meducer 发表于 2017-5-16 05:15
这是掩码结构体,在我另外一个帖子  [微知识]模块的封装(一):C语言类的封装  有原理性的介绍。
最近这 ...

好的,多谢老师,看来要仔细研究下

出0入0汤圆

发表于 2017-5-29 21:29:06 | 显示全部楼层
感谢分享,学习了。

出0入0汤圆

发表于 2017-5-30 14:21:56 来自手机 | 显示全部楼层
我是来催书的^_^

出140入115汤圆

发表于 2017-6-3 08:42:40 | 显示全部楼层
KEIL 编译不通过,真心不会用,能给个工程模板吗?

出0入296汤圆

 楼主| 发表于 2017-6-30 23:47:17 | 显示全部楼层
yanyanyan168 发表于 2017-6-3 08:42
KEIL 编译不通过,真心不会用,能给个工程模板吗?

你可以给我你的工程,我帮你看下问题在哪里。

出0入296汤圆

 楼主| 发表于 2017-9-5 00:10:23 | 显示全部楼层
2017-04-09 更新
    - 更新simple_fsm.h模板,加入对 状态机前置引用的支持。使用说明请参照楼主位第10条。

出0入0汤圆

发表于 2017-9-7 23:19:30 | 显示全部楼层
谢谢傻孩子老师。真是大好人,

出0入0汤圆

发表于 2017-9-8 08:42:30 | 显示全部楼层
谢谢, 有空学习

出0入0汤圆

发表于 2017-9-8 11:24:32 | 显示全部楼层
Gorgon_Meducer 发表于 2017-9-5 00:10
2017-04-09 更新
    - 更新simple_fsm.h模板,加入对 状态机前置引用的支持。使用说明请参照楼主位第10 ...

加入ooc.h的时候,编译提示找不到gsf_err_t定义,请问是在那个文件定义的?

出0入0汤圆

发表于 2017-9-8 11:50:40 | 显示全部楼层
还有一个问题是,把原来的oopc.h换成ooc.h后,key_queue_t编译通不过了!

出0入0汤圆

发表于 2017-9-8 13:24:55 | 显示全部楼层
感谢 ,,学习状态鸡。。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-19 20:18

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表