所谓消息地图就是根据不同的状态来执行对应的处理程序,这一技术成为消息地图。例如我们平时使用的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 }
个人水平有限,欢迎大家拍砖,盖房娶媳妇。