所谓消息地图就是根据不同的状态来执行对应的处理程序,这一技术成为消息地图。例如我们平时使用的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\r\n";
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 }

 

 

 

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

 

 posted on 2015-08-19 17:01  ~疯子~  阅读(614)  评论(0编辑  收藏  举报