tanek 发表于 2014-12-20 16:18:06

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

本帖最后由 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添加到工程中参与编译。
目录树结构

        | ---- msgmap.h
        | ---- app_cfg.h
        | ----        
        |                | ---- ooc.h
        |                | --- app_type.h
        |                | ----
        |                                | ---- t_queue.h
        |                                | ---- template.h                                                                               
      | ----
                        | ----        msgmap.c       
                        | ---- msgmap.h
                        | ---- app_cfg.g
                        | ----                        
                                        | ---- 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_HANDLEserial_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);
    returntrue;
}
现在动态消息地图已经配置好了,再使用前通过cmd_register(s_tUserMSGMap,UBOUND(s_tUserMSGMap));进行注册。
        在对msg_map_search的使用时将他进行了二次封装,当msg_map_search执行到fsm_rt_cpl状态时调用他的消息
处理函数。
staticfsm_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();
    }
}


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

tanek 发表于 2014-12-20 16:18:57

坐等大家来拍砖啊。。。

tdchenke 发表于 2014-12-20 16:48:15

学习了   自己C语言水平还很烂得多看书学习

chenguanghua 发表于 2014-12-20 17:01:00

傻孩子工作室出品的?

my_avr 发表于 2014-12-20 17:48:00

先标记一下,大概看了下,应该是个酷贴,晚上仔细研究研究

Vampireyifeng 发表于 2014-12-20 17:49:04

好好学学,一直用的都只是消息循环队列搞的,

ijlc1314 发表于 2014-12-20 20:13:32

支持,回头得好好看看

tanek 发表于 2014-12-21 00:25:29

chenguanghua 发表于 2014-12-20 17:01
傻孩子工作室出品的?

对,傻孩子老师教的

tanek 发表于 2014-12-21 00:26:01

Vampireyifeng 发表于 2014-12-20 17:49
好好学学,一直用的都只是消息循环队列搞的,

欢迎讨论

永恒520 发表于 2014-12-21 03:39:06

好好学习一下…

ijlc1314 发表于 2014-12-21 10:22:57

以前使用过消息字的方式。通过枚举定义消息字,一个消息字对应一个函数指针。不过没做到楼主这么规范

使用消息的方式,去实现比如类似于长按键(长按N秒后执行),感觉好像有点别扭

yikuang 发表于 2014-12-21 22:10:03

学习下,谢谢!

霸气侧漏 发表于 2014-12-21 22:14:44

支持一个,厉害了

yiming988 发表于 2014-12-21 22:35:10

为什么“CheckSringUseMsgMap”函数是首字母大写,“msg_apple_handler”函数是下划线+全小写呢?

bird777 发表于 2014-12-21 22:35:57

先标识下,明天上班看

tanek 发表于 2014-12-21 22:50:16

yiming988 发表于 2014-12-21 22:35
为什么“CheckSringUseMsgMap”函数是首字母大写,“msg_apple_handler”函数是下划线+全小写呢? ...

大意了。抛开此贴不说,编码规范本身就是一个习惯

yikuang 发表于 2014-12-23 09:26:05



疯子,不是说true不一定等于1吗

tanek 发表于 2014-12-23 10:37:06

yikuang 发表于 2014-12-23 09:26
疯子,不是说true不一定等于1吗

typedef struct{
    bool bAutoReset;
    bool bIsSet;
}event_t;

这里定义bAutoReset;类型错了,应该是

typedef struct{
    even_model_t bAutoReset;
    bool bIsSet;
}event_t;

neverlic 发表于 2014-12-23 16:24:05

傻孩子图书工作室作品"",最近偶感软件架构及软件思想方面有些欠缺,问下傻孩子的新书 有出版时间表了没?

yikuang 发表于 2014-12-23 16:41:34

软件架构及软件思想方面有些欠缺

例如呢,说说你的建议和想法?

wangkx1990 发表于 2014-12-23 17:01:14

啊,终于找到疯子了!
俺芯创{:lol:}
另,yikuang是?

Gorgon_Meducer 发表于 2014-12-23 17:13:57

yikuang 发表于 2014-12-23 16:41
例如呢,说说你的建议和想法?

人家是偶感自己有欠缺……

yikuang 发表于 2014-12-23 17:18:39

wangkx1990 发表于 2014-12-23 17:01
啊,终于找到疯子了!
俺芯创
另,yikuang是?

不再犹豫呢{:lol:} {:hug:}

yikuang 发表于 2014-12-23 17:21:16

Gorgon_Meducer 发表于 2014-12-23 17:13
人家是偶感自己有欠缺……

OMG,看错了!

tanek 发表于 2014-12-23 19:07:00

本帖最后由 tanek 于 2014-12-23 19:11 编辑

neverlic 发表于 2014-12-23 16:24
傻孩子图书工作室作品"",最近偶感软件架构及软件思想方面有些欠缺,问下傻孩子的新书 有出版时间表了没? ...

跑这里来催斑竹的书来了,不过问得好!斑竹你的书什么时候出啊。

tanek 发表于 2014-12-23 19:10:25

wangkx1990 发表于 2014-12-23 17:01
啊,终于找到疯子了!
俺芯创
另,yikuang是?

我是四哥

wangkx1990 发表于 2014-12-23 19:17:41

tanek 发表于 2014-12-23 19:10
我是四哥

那为什么他叫你疯子啊,再说四哥你不是y打头,然后你的qq吗,难道你不敢拿出来秀了,犯大错了{:lol:}

Gorgon_Meducer 发表于 2014-12-23 19:32:19

水军……

tanek 发表于 2014-12-23 20:34:30

wangkx1990 发表于 2014-12-23 19:17
那为什么他叫你疯子啊,再说四哥你不是y打头,然后你的qq吗,难道你不敢拿出来秀了,犯大错了...

时而四哥,时而疯子,我们是------疯四哥组合

sblpp 发表于 2014-12-23 21:16:37

绝对牛X啊!徘徊在牛A和牛C之间!

相由心生 发表于 2014-12-24 00:47:12

有点像contiki.....

tanek 发表于 2014-12-24 10:07:18

相由心生 发表于 2014-12-24 00:47
有点像contiki.....

哪里像呢。你说的这个没玩过

yiming988 发表于 2014-12-25 10:06:27

tanek 发表于 2014-12-21 22:50
大意了。抛开此贴不说,编码规范本身就是一个习惯

扯犊子这个习惯不好

tanek 发表于 2014-12-25 10:27:31

yiming988 发表于 2014-12-25 10:06
扯犊子这个习惯不好

恩。是的不好的习惯,所以说大意了

315936392 发表于 2014-12-25 11:19:47

好好研究系

皮爱了西 发表于 2014-12-25 23:25:54

真心看不懂啊

tanek 发表于 2014-12-26 09:19:08

皮爱了西 发表于 2014-12-25 23:25
真心看不懂啊

哪里不懂。是代码觉得复杂看不懂还是文档说明表述不清楚呢

皮爱了西 发表于 2014-12-29 23:33:00

tanek 发表于 2014-12-26 09:19
哪里不懂。是代码觉得复杂看不懂还是文档说明表述不清楚呢

俺水平不够,暂时理解不了这个模块的思路。

GZZXB 发表于 2015-1-6 21:07:15

傻孩子的东东的确够严谨,但是我在想在小rom mcu里面比如2krom的片子里怎么用。随随便便一个kei模块,一个消息地图框架就干掉2krom了吧。{:lol:}

Gorgon_Meducer 发表于 2015-1-7 10:15:59

GZZXB 发表于 2015-1-6 21:07
傻孩子的东东的确够严谨,但是我在想在小rom mcu里面比如2krom的片子里怎么用。随随便便一个kei模块,一个 ...

又是一个想当然的家伙,实际编译下就知道了。并不大。

tanek 发表于 2015-1-7 10:50:03

GZZXB 发表于 2015-1-6 21:07
傻孩子的东东的确够严谨,但是我在想在小rom mcu里面比如2krom的片子里怎么用。随随便便一个kei模块,一个 ...

你说的占用2K ROM应该是你编译帖子提供的demo工程后看到的数字吧。

Gorgon_Meducer 发表于 2015-1-7 11:00:36

tanek 发表于 2015-1-7 10:50
你说的占用2K ROM应该是你编译帖子提供的demo工程后看到的数字吧。

我估计他没编译……

foxpro2005 发表于 2015-1-31 16:01:39

有个地方感觉有点怪...

catwill 发表于 2015-4-3 16:35:55

呵呵,楼上应该是逗号表达式吧……
但是我相信他的本意是分号~

wangle315065 发表于 2015-4-16 11:27:08

命令泵。。。。。。

foxpro2005 发表于 2015-4-23 11:03:55

catwill 发表于 2015-4-3 16:35
呵呵,楼上应该是逗号表达式吧……
但是我相信他的本意是分号~

呵呵, 是的, 可能是LZ没注意....{:lol:}

wenqingli 发表于 2015-8-24 14:57:29

谢谢 mark再看

wenqingli 发表于 2015-8-24 15:06:25

跟消息队列有什么区别?

Gorgon_Meducer 发表于 2015-8-25 11:05:01

wenqingli 发表于 2015-8-24 15:06
跟消息队列有什么区别?

45楼答案。

gujiamao_love 发表于 2017-7-22 15:34:12

wangle315065 发表于 2015-4-16 11:27
命令泵。。。。。。

我没有搜索到你说的这个概念?
麻烦告知下 谢谢!{:smile:}

gujiamao_love 发表于 2017-8-10 09:43:42

catwill 发表于 2015-4-3 16:35
呵呵,楼上应该是逗号表达式吧……
但是我相信他的本意是分号~

逗号表达式挺好的哇
页: [1]
查看完整版本: [分享][交流]发布一个消息地图的模块