|
本帖最后由 Gorgon_Meducer 于 2014-12-20 17:07 编辑
傻孩子图书工作室作品
所谓消息地图就是根据不同的状态来执行对应的处理程序,这一技术成为消息地图。例如我们平时使用的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
然后我们需要配置消息系统,这里我们采用静态配置--所谓静态配置是在编译的阶段对模块的配置,
- #define INSERT_MSG_MAP_FUNC_EXRERN \
- extern bool msg_apple_handler(const msg_t *ptMSG); \
- extern bool msg_orange_handler(const msg_t *ptMSG); \
- extern bool msg_hello_handler(const msg_t *ptMSG);
- #define INSERT_MSG_MAP_CMD {"apple", &msg_apple_handler}, \
- {"orange", &msg_orange_handler}, \
- {"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进行删除。
首先定义消息地图以及消息处理函数:
- bool msg_use2_handler(const msg_t *ptMSG);
- bool msg_use1_handler(const msg_t *ptMSG);
- static msg_t s_tUserMSGMap[] = {
- {"use1", &msg_use1_handler},
- {"use2", &msg_use2_handler},
- };
复制代码
这样消息地图就定义好了,而消息地图的处理函数由你自己编写,就我们的任务而言消息地图的相应函数只需要做一件事,那
就是置位事件,例如 msg_apple_handler的功能是置位事件s_tEventApple:
- bool msg_apple_handler(const msg_t *ptMSG)
- {
- if(NULL == ptMSG) {
- return false ;
- }
-
- SET_EVENT(&s_tEventApple);
- return true;
- }
复制代码
现在动态消息地图已经配置好了,再使用前通过cmd_register(s_tUserMSGMap,UBOUND(s_tUserMSGMap));进行注册。
在对msg_map_search的使用时将他进行了二次封装,当msg_map_search执行到fsm_rt_cpl状态时调用他的消息
处理函数。
- static fsm_rt_t CheckSringUseMsgMap(void)
- {
- const msg_t *ptMsg = NULL;
- if(fsm_rt_cpl == msg_map_search(&ptMsg)) {
- ptMsg->fnHandler(ptMsg);
- }
-
- return fsm_rt_on_going;
- }
复制代码
现在消息地图部分已经OK,使用的时候调用CheckSringUseMsgMap就可以了。现在我们来实现task_a、task_b、task_c
这三个进程是输出进程,他们的功能是等待事件触发,事件触发后执行事件的相应-----输出字符串。现在我们定义事件
- static event_t s_tEventApple;
- static event_t s_tEventOrange;
- static event_t s_tEventWorld;
复制代码
然后进行初始化,初始化完成后就可以使用了:
- INIT_EVENT(&s_tEventApple,false,MANUAL);
- INIT_EVENT(&s_tEventOrange,false,MANUAL);
- INIT_EVENT(&s_tEventWorld,false,MANUAL);
复制代码
然后编写task_a的进程函数
- #define TASK_A_FSM_RESET() do {s_tState = TASK_A_START;} while(0)
- static fsm_rt_t task_a(void)
- {
- static enum {
- TASK_A_START = 0,
- TASK_A_WAIT_EVENT,
- TASK_A_PRINT
- }s_tState = TASK_A_START;
-
- switch(s_tState) {
- case TASK_A_START:
- s_tState = TASK_A_WAIT_EVENT;
- //break;
-
- case TASK_A_WAIT_EVENT:
- if(WAIT_EVENT(&s_tEventApple)){
- s_tState = TASK_A_PRINT;
- }
- break;
-
- case TASK_A_PRINT:
- if(fsm_rt_cpl == print_apple()){
- RESET_EVENT(&s_tEventApple);
- TASK_A_FSM_RESET();
- return fsm_rt_cpl;
- }
- break;
- }
-
- return fsm_rt_on_going;
- }
复制代码- #define PRINT_APPLE_RESET_FSM() do { s_tState = PRINT_APPLE_START; } while(0)
- static fsm_rt_t print_apple(void)
- {
- static enum {
- PRINT_APPLE_START = 0,
- PRINT_APPLE_INIT,
- PRINT_APPLE_SEND
- }s_tState = PRINT_APPLE_START;
-
- static uint8_t *s_pchString = (uint8_t *)"apple\r\n";
- static print_str_t s_tPrintStruct;
-
- switch(s_tState) {
- case PRINT_APPLE_START:
- s_tState = PRINT_APPLE_INIT;
- //break;
-
- case PRINT_APPLE_INIT:
- if(INIT_SRT_OUTPUT(&s_tPrintStruct,s_pchString)){
- s_tState = PRINT_APPLE_SEND;
- }else {
- return fsm_rt_err;
- }
- break;
-
- case PRINT_APPLE_SEND:
- if(fsm_rt_cpl == print_string(&s_tPrintStruct)){
- PRINT_APPLE_RESET_FSM();
- return fsm_rt_cpl;
- }
- break;
- }
-
- return fsm_rt_on_going;
- }
复制代码
这里很清楚的可以看到该进程的处理过程,等待事件s_tEventApple触发,然后调用输出 print_apple,而 子状态机 print_apple
就是调用 print_string将输出的内容放到输出队列。其他的两个进程以此编写,这里不在赘述。
下面我们队输入输出字节流的进程进行说明(stream_in_out),在这个进程中我们用到了队列,而队列的功能代码通过
宏进行插入:DEF_QUEUE(MsgMapQueue,uint8_t,uint8_t,ATOM_ACESS);这样我们就可以使用队列了,首先定义两个输入、输
出的队列:
- QUEUE(MsgMapQueue) g_tFIFOin;
- QUEUE(MsgMapQueue) g_tFIFOout;
复制代码
字节流的接口和发送很简单参考如下代码。
- #define SERIAL_IN_TASK_FSM_RESET() do {s_tState = SERIAL_IN_TASK_START;} while(0)
- static fsm_rt_t serial_in_task(void)
- {
- static uint8_t s_chByte = 0;
- static enum {
- SERIAL_IN_TASK_START = 0,
- SERIAL_IN_TASK_READ
- }s_tState = SERIAL_IN_TASK_START;
-
- switch(s_tState) {
- case SERIAL_IN_TASK_START:
- s_tState = SERIAL_IN_TASK_READ;
- //breka;
- case SERIAL_IN_TASK_READ:
- if(serial_in(&s_chByte)){
- ENQUEUE(MsgMapQueue,&g_tFIFOin,s_chByte);
- SERIAL_IN_TASK_FSM_RESET();
- return fsm_rt_cpl;
- }
- break;
- }
-
- return fsm_rt_on_going;
- }
- #define SERIAL_OUT_TASK_FSM_RESET() do {s_tState = SERIAL_OUT_TASK_START;} while(0)
- static fsm_rt_t serial_out_task(void)
- {
- static uint8_t s_chByte = 0;
- static enum {
- SERIAL_OUT_TASK_START = 0,
- SERIAL_OUT_TASK_READ_QUE,
- SERIAL_OUT_TASK_OUTPUT
- }s_tState = SERIAL_OUT_TASK_START;
-
- switch(s_tState) {
- case SERIAL_OUT_TASK_START:
- s_tState = SERIAL_OUT_TASK_READ_QUE;
- //breka;
- case SERIAL_OUT_TASK_READ_QUE:
- if(DEQUEUE(MsgMapQueue,&g_tFIFOout,&s_chByte)){
- s_tState = SERIAL_OUT_TASK_OUTPUT;
- }
- break;
-
- case SERIAL_OUT_TASK_OUTPUT:
- if(serial_out(s_chByte)) {
- SERIAL_OUT_TASK_FSM_RESET();
- return fsm_rt_cpl;
- }
- break;
- }
-
- return fsm_rt_on_going;
- }
复制代码
现在各个进程都已经准备完毕,就剩下我们进行调用了,main函数如下
- int main(void)
- {
- system_init();
-
- INIT_EVENT(&s_tEventApple,false,MANUAL);
- INIT_EVENT(&s_tEventOrange,false,MANUAL);
- INIT_EVENT(&s_tEventWorld,false,MANUAL);
-
- QUEUE_INIT(MsgMapQueue,&g_tFIFOin,s_tBuf, UBOUND(s_tBuf));
- QUEUE_INIT(MsgMapQueue,&g_tFIFOout,s_tPiPeBuf, UBOUND(s_tPiPeBuf));
-
- cmd_register(s_tUserMSGMap,UBOUND(s_tUserMSGMap));
-
- while(1) {
- task_a();
- task_b();
- task_c();
- CheckSringUseMsgMap();
- stream_in_out();
- }
- }
复制代码
个人水平有限,欢迎大家拍砖,盖房娶媳妇。
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?注册
x
阿莫论坛20周年了!感谢大家的支持与爱护!!
知道什么是神吗?其实神本来也是人,只不过神做了人做不到的事情 所以才成了神。 (头文字D, 杜汶泽)
|