• [嵌入式][分享][交流]发布一个消息地图的模块


      所谓消息地图就是根据不同的状态来执行对应的处理程序,这一技术成为消息地图。例如我们平时使用的if、else语句switch、case
    语句都是消息地图的一种实现方式,而这个模块采用的是函数指针的方式来实现消息地图。采用全状态机开发消息可以进行动态、静态的
    配置。消息地图的技术来源于傻孩子老师的<深入浅出AVR单片机>,具体的细节请参考此书。

    废话少说,下面直接进入正题,讲解一下怎么移植这个模块。

    1、准备工作
            首先我们要有一个硬件平台,具有一个串口的最新系统即可,为了体现代码的平台无关性请 参考文档《平台搭建》来搭建平台。
    这样我们有了一个共同讨论的基础。(我的示例工程采用的是STM32神州III的开发板)。

    2、解压缩
            将下载下来的“MsgMapService”解压缩,模块的目录结构如下所示,文件夹msgmap是服务实现的具体的代码,而utilities文件夹
    的内容是改模块依赖的一些宏以及队列的模板,msgmap.h是调用该模块的接口头文件,app_cfg.h是该模块的配置头文件,使用该模
    块的时候对模块的依赖进行配置。
            解压后将“MsgMapService”文件夹整个拷贝到你的工程中,将msgmap.c 和 checkstring.c添加到工程中参与编译。
    目录树结构
    [MsgMapService]
            | ---- msgmap.h
            | ---- app_cfg.h
            | ---- [utilities]       
            |                | ---- ooc.h
            |                | --- app_type.h
            |                | ---- [template]
            |                                | ---- t_queue.h
            |                                | ---- template.h                                                                               
            | ---- [msgmap]
                            | ----        msgmap.c       
                            | ---- msgmap.h
                            | ---- app_cfg.g
                            | ---- [checkstring]                       
                                            | ---- checkstring.c
                                            | ---- checkstring.h
                                            | ---- app_cfg.h       

    3、配置模块
            该模块是通过读取队列的字节流,而消息地图是有用户进行的配置,这里可以采用动态的配置和静态的配置两种方式。
    首先该模块依赖队列,我在配置文件中插入一条宏:EXTERN_QUEUE(MsgMapQueue,uint8_t,uint8_t);MsgMapQueue是定义
    的队列的名称,队列的使用方法见t_queue.h.我们将数据接收队列用tFIFOin命名,用宏进行插入。
    #define CHECK_BYTE_QUEUE     g_tFIFOin
            然后我们需要配置消息系统,这里我们采用静态配置--所谓静态配置是在编译的阶段对模块的配置,

    1 #define INSERT_MSG_MAP_FUNC_EXRERN                                          
    2     extern bool msg_apple_handler(const msg_t *ptMSG);                      
    3     extern bool msg_orange_handler(const msg_t *ptMSG);                     
    4     extern bool msg_hello_handler(const msg_t *ptMSG);
    5 
    6 #define INSERT_MSG_MAP_CMD  {"apple", &msg_apple_handler},                  
    7                             {"orange", &msg_orange_handler},                
    8                             {"hello", &msg_hello_handler},

    这两条宏就实现了消息地图的静态配置,msg_apple_handler、msg_orange_handler、msg_hello_handler是消息处理函数,
    而字符串就是消息了。
            消息地图还有一个依赖,就是我们的字符输出函数。即为平台里的serial_out函数,这里我们用宏来进行插入
    #define SERIAL_OUT_HANDLE  serial_out。
    现在我们的模块的基本的使用配置就完成了,接下来我们看看如何调用。

    4、模块的使用
            现在我们消息地图来完成一个任务,通过这个任务来介绍这个模块的具体的调用的方法。我们要完成的这个任务的功能
    是“芝麻开门”,就是我通过超级终端进行字符输入,然后该任务对输入的字符进行相应,不同的字符串对应不同的相应,例如
    我们输入hello的时候向我们输出world,就好比我们操作系统的命令行一样,你输入一个命令,操作系统给出一个响应,下面
    看看这个任务怎么实现。
            在模块配置的环节我们介绍了消息地图的静态配置,现在我们继续介绍消息地图的另一种配置------动态配置,所谓动态
    配置就是消息地图在运行的工程中可以通过cmd_register进行注册,通过cmd_unregister进行删除。
            首先定义消息地图以及消息处理函数:

    1 bool msg_use2_handler(const msg_t *ptMSG);
    2 bool msg_use1_handler(const msg_t *ptMSG);
    3 
    4 static msg_t s_tUserMSGMap[] = {
    5     {"use1", &msg_use1_handler},
    6     {"use2", &msg_use2_handler},
    7 };

    现在动态消息地图已经配置好了,再使用前通过cmd_register(s_tUserMSGMap,UBOUND(s_tUserMSGMap));进行注册。
            在对msg_map_search的使用时将他进行了二次封装,当msg_map_search执行到fsm_rt_cpl状态时调用他的消息
    处理函数。

     1 static  fsm_rt_t CheckSringUseMsgMap(void)
     2 {
     3     const msg_t *ptMsg = NULL;
     4 
     5     if(fsm_rt_cpl == msg_map_search(&ptMsg)) {
     6         ptMsg->fnHandler(ptMsg);
     7     }
     8         
     9     return fsm_rt_on_going;
    10 }

    现在消息地图部分已经OK,使用的时候调用CheckSringUseMsgMap就可以了。现在我们来实现task_a、task_b、task_c
    这三个进程是输出进程,他们的功能是等待事件触发,事件触发后执行事件的相应-----输出字符串。现在我们定义事件

    static event_t s_tEventApple;
    static event_t s_tEventOrange;
    static event_t s_tEventWorld;

    然后进行初始化,初始化完成后就可以使用了:

    1     INIT_EVENT(&s_tEventApple,false,MANUAL);
    2     INIT_EVENT(&s_tEventOrange,false,MANUAL);
    3     INIT_EVENT(&s_tEventWorld,false,MANUAL);

    然后编写task_a的进程函数

     1 #define TASK_A_FSM_RESET() do {s_tState = TASK_A_START;} while(0)
     2 static fsm_rt_t task_a(void)
     3 {
     4     static enum {
     5         TASK_A_START                    = 0,
     6         TASK_A_WAIT_EVENT,
     7         TASK_A_PRINT
     8     }s_tState = TASK_A_START;
     9     
    10     switch(s_tState) {
    11         case TASK_A_START:
    12             s_tState = TASK_A_WAIT_EVENT;
    13             //break;
    14         
    15         case TASK_A_WAIT_EVENT:
    16             if(WAIT_EVENT(&s_tEventApple)){
    17                 s_tState = TASK_A_PRINT;
    18             }
    19             break;
    20           
    21         case TASK_A_PRINT:
    22             if(fsm_rt_cpl == print_apple()){
    23                 RESET_EVENT(&s_tEventApple);
    24                 TASK_A_FSM_RESET();
    25                 return fsm_rt_cpl; 
    26             }
    27             break;               
    28     }
    29     
    30     return fsm_rt_on_going;
    31 }
     1 #define PRINT_APPLE_RESET_FSM() do { s_tState = PRINT_APPLE_START; } while(0)
     2 static fsm_rt_t print_apple(void)
     3 {
     4     static enum {
     5         PRINT_APPLE_START = 0,
     6         PRINT_APPLE_INIT,
     7         PRINT_APPLE_SEND
     8     }s_tState = PRINT_APPLE_START;
     9     
    10     static uint8_t *s_pchString = (uint8_t *)"apple
    ";
    11     static print_str_t s_tPrintStruct;
    12     
    13     switch(s_tState) {
    14         case PRINT_APPLE_START:
    15             s_tState = PRINT_APPLE_INIT;
    16             //break;
    17             
    18         case PRINT_APPLE_INIT:
    19             if(INIT_SRT_OUTPUT(&s_tPrintStruct,s_pchString)){
    20                 s_tState = PRINT_APPLE_SEND;
    21             }else {
    22                 return fsm_rt_err;
    23             }
    24             break;
    25         
    26         case PRINT_APPLE_SEND:
    27             if(fsm_rt_cpl == print_string(&s_tPrintStruct)){
    28                 PRINT_APPLE_RESET_FSM();
    29                 return fsm_rt_cpl;
    30             } 
    31             break;
    32     }
    33         
    34     return fsm_rt_on_going;
    35 }

    这里很清楚的可以看到该进程的处理过程,等待事件s_tEventApple触发,然后调用输出 print_apple,而 子状态机 print_apple
    就是调用 print_string将输出的内容放到输出队列。其他的两个进程以此编写,这里不在赘述。

            下面我们队输入输出字节流的进程进行说明(stream_in_out),在这个进程中我们用到了队列,而队列的功能代码通过
    宏进行插入:DEF_QUEUE(MsgMapQueue,uint8_t,uint8_t,ATOM_ACESS);这样我们就可以使用队列了,首先定义两个输入、输
    出的队列:

    1 QUEUE(MsgMapQueue) g_tFIFOin;
    2 QUEUE(MsgMapQueue) g_tFIFOout;

    字节流的接口和发送很简单参考如下代码。

     1 #define SERIAL_IN_TASK_FSM_RESET() do {s_tState = SERIAL_IN_TASK_START;} while(0)
     2 static fsm_rt_t serial_in_task(void)
     3 {
     4     static uint8_t s_chByte = 0;
     5     static enum {
     6         SERIAL_IN_TASK_START = 0,
     7         SERIAL_IN_TASK_READ
     8     }s_tState = SERIAL_IN_TASK_START;
     9     
    10     switch(s_tState) {
    11         case SERIAL_IN_TASK_START:
    12             s_tState = SERIAL_IN_TASK_READ;
    13             //breka;
    14         case SERIAL_IN_TASK_READ:
    15             if(serial_in(&s_chByte)){
    16                 ENQUEUE(MsgMapQueue,&g_tFIFOin,s_chByte);
    17                 SERIAL_IN_TASK_FSM_RESET();
    18                 return fsm_rt_cpl;
    19             }
    20             break;
    21     }
    22     
    23     return fsm_rt_on_going;
    24 }
    25 
    26 #define SERIAL_OUT_TASK_FSM_RESET() do {s_tState = SERIAL_OUT_TASK_START;} while(0)
    27 static fsm_rt_t serial_out_task(void)
    28 {
    29     static uint8_t s_chByte = 0;
    30     static enum {
    31         SERIAL_OUT_TASK_START = 0,
    32         SERIAL_OUT_TASK_READ_QUE,
    33         SERIAL_OUT_TASK_OUTPUT
    34     }s_tState = SERIAL_OUT_TASK_START;
    35     
    36     switch(s_tState) {
    37         case SERIAL_OUT_TASK_START:
    38             s_tState = SERIAL_OUT_TASK_READ_QUE;
    39             //breka;
    40         case SERIAL_OUT_TASK_READ_QUE:
    41             if(DEQUEUE(MsgMapQueue,&g_tFIFOout,&s_chByte)){
    42                 s_tState = SERIAL_OUT_TASK_OUTPUT;
    43             }
    44             break;        
    45             
    46         case SERIAL_OUT_TASK_OUTPUT:
    47             if(serial_out(s_chByte)) {
    48                 SERIAL_OUT_TASK_FSM_RESET();
    49                 return fsm_rt_cpl;
    50             }
    51             break;
    52     }
    53     
    54     return fsm_rt_on_going;
    55 }

    现在各个进程都已经准备完毕,就剩下我们进行调用了,main函数如下

     1 int main(void)
     2 {
     3     system_init();
     4     
     5     INIT_EVENT(&s_tEventApple,false,MANUAL);
     6     INIT_EVENT(&s_tEventOrange,false,MANUAL);
     7     INIT_EVENT(&s_tEventWorld,false,MANUAL);
     8     
     9     QUEUE_INIT(MsgMapQueue,&g_tFIFOin,s_tBuf, UBOUND(s_tBuf)); 
    10     QUEUE_INIT(MsgMapQueue,&g_tFIFOout,s_tPiPeBuf, UBOUND(s_tPiPeBuf)); 
    11    
    12     cmd_register(s_tUserMSGMap,UBOUND(s_tUserMSGMap));
    13     
    14     while(1) {
    15         task_a();
    16         task_b();
    17         task_c();
    18         CheckSringUseMsgMap();
    19         stream_in_out();
    20     }
    21 }

    个人水平有限,欢迎大家拍砖,盖房娶媳妇。

  • 相关阅读:
    java之元数据(metadata)
    悲观锁(Pessimistic Locking)和乐观锁
    新建了springboot项目在包下右键创建class时无class选项
    idea创建一个springboot项目
    处理百万级以上的数据提高查询速度的方法
    写入文件
    WCf客户端测试
    WCF客户端代理
    WCF之Windows宿主(可安装成服务自动并启动)
    戴上耳机,全世界都是你的
  • 原文地址:https://www.cnblogs.com/zhaoli/p/4742744.html
Copyright © 2020-2023  润新知