以下内容转载自安富莱电子: http://forum.armfly.com/forum.php

前面几个章节主要给大家讲解了任务间的同步和资源共享机制,本章节为大家讲解任务间的通信机制
消息邮箱,RTX 的消息邮箱其实就是消息队列,注意和 uCOS-II 中的消息邮箱区分开,uCOS-II 的消息邮
箱只能实现一个数据的传递。这里的消息邮箱可以实现多个数据的传递。

消息邮箱的概念及其作用
RTX 的消息邮箱实际上就是消息队列,通过内核提供的服务,任务或中断服务子程序可以将一个消息
(注意,RTX 消息邮箱传递的是消息的地址而不是实际的数据)放入到消息队列。同样,一个或者多个任
务可以通过内核服务从消息队列中得到消息。通常,先进入消息队列的消息先传给任务,也就是说,任务
先得到的是最先进入到消息队列的消息,即先进先出的原则(FIFO)。
也许有不理解的初学者会问采用消息邮箱多麻烦,搞个全局数组不是更简单,其实不然。在裸机编程
时,使用全局数组的确比较方便,但是在加上 RTOS 后就是另一种情况了。 使用全局数组相比消息邮箱主
要有如下四个问题:
 使用消息邮箱可以让 RTOS 内核有效的管理任务,全局数组是无法做到的,任务的超时等机制需要用
户自己去实现。
 使用了全局数组就要防止多任务的访问冲突,使用消息邮箱已经处理好了这个问题。 用户无需担心。
 使用消息邮箱可以有效的解决中断服务程序跟任务之间消息传递的问题。
 FIFO 机制更有利于数据的处理。

RTX 任务间消息邮箱的实现
任务间消息邮箱的实现是指各个任务之间使用消息邮箱实现任务间的通信。 下面我们通过如下的框图
来说明一下 RTX 消息邮箱的实现,让大家有一个形象的认识。

运行条件:
 创建消息邮箱,可以存放 10 个消息。
 创建 2 个任务 Task1 和 Task2,任务 Task1 向消息邮箱放数据地址,任务 Task2 从消息邮箱取数据地址。
 RTX 的消息读取和存放仅支持 FIFO 方式。
运行过程主要有以下两种情况:
 任务 Task1 向消息邮箱放数据地址,任务 Task2 从消息邮箱取数据地址,如果放数据地址的速度快
于取数据的速度,那么会出现消息邮箱存放满的情况,RTX 的消息存放函数 os_mbx_send 支持超时
等待,可以设置超时等待,直到有位置可以存放消息放或者设置时间超时。
 任务 Task1 向消息邮箱放数据地址,任务 Task2 从消息邮箱取数据地址,如果放数据地址的速度慢
于取数据的速度,那么会出现消息邮箱为空的情况,RTX 的消息获取函数 os_mbx_wait 支持超时等
待,可以设置超时等待,直到消息邮箱中有消息放或者设置时间超时。
上面就是一个简单的 RTX 任务间消息邮箱通信过程。

RTX 中断方式消息邮箱的实现
RTX 中断方式消息邮箱的实现是指中断函数和 RTX 任务之间使用消息邮箱。 下面我们通过如下的框
图来说明一下 RTX 消息邮箱的实现,让大家有一个形象的认识。

 创建 1 个任务 Task1 和一个串口接收中断。
 RTX 的消息读取和存放仅支持 FIFO 方式。
运行过程主要有以下两种情况:
 中断服务程序向消息邮箱放数据地址,任务 Task1 从消息邮箱取数据地址,如果放数据地址的速度快
于取数据的速度,那么会出现消息邮箱存放满的情况。由于中断服务程序里面的消息邮箱发送函数
isr_mbx_send 不支持超时设置,所有发送前要通过函数 isr_mbx_check 检测邮箱是否满。
 中断服务程序向消息邮箱放数据地址,任务 Task1 从消息邮箱取数据地址,如果放数据地址的速度慢
于取数据的速度,那么会出现消息邮箱存为空的情况。在 RTX 的任务中可以通过函数 os_mbx_wait
获取消息,因为此函数可以设置超时等待,直到消息邮箱中有消息存放或者设置时间超时。
上面就是一个简单 RTX 消息邮箱通信过程。 实际应用中,中断方式的消息机制切记注意以下四个问题:
 中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
 实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在
任务中实现消息处理,这样可以有效的保证中断服务程序的实时响应。同时此任务也需要设置为高优
先级,以便退出中断函数后任务可以得到及时执行。
 中断服务程序中一定要调用专用于中断的消息邮箱函数 isr_mbx_send,isr_mbx_receive 和
isr_mbx_check。
 在 RTX 操作系统中实现中断函数和裸机编程是一样的。
 另外强烈推荐用户将 Cortex-M3 内核的 STM32F103 和 Cortex-M4 内核的 STM32F407 的
NVIC 优先级分组设置为 4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这样中断
优先级的管理将非常方便。
 用户要在 RTX 多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。

消息邮箱 API 函数
使用如下 8 个函数可以实现 RTX 消息邮箱:
 os_mbx_check
 os_mbx_declare
 os_mbx_init
 os_mbx_send
 os_mbx_wait
 isr_mbx_check
 isr_mbx_receive
 isr_mbx_send

函数 os_mbx_declare
函数原型:
#define os_mbx_declare( \
name, \ /* 消息邮箱名 */
cnt ) \ /* 消息个数 */
U32 name [4 + cnt]
函数描述:
函数 os_mbx_declare 定义了消息邮箱的大小和消息邮箱名,用于消息邮箱的初始化。 其实就是通过宏定
义的方式定义了一个数组 U32 类型的数组 name[4 + cnt]。 定义成 32 位数组是因为 CM3/CM4 是 32 位
机,地址也就是 32 位的,从而指针变量也就是固定的 32 位变量。 RTX 的消息邮箱传递的是变量的地址,
而不是变量的内容,所以要将邮箱定义成 32 位数组。
 第 1 个参数表示定义的消息邮箱名。
 第 2 个参数是邮箱支持的消息个数。
一般的应用中,消息邮箱支持 20 个消息就够了。

os_mbx_declare (mailbox, 10);

函数 os_mbx_init
函数原型:
void os_mbx_init (
OS_ID mailbox, /* 消息邮箱的 ID 标识 */
U16 mbx_size ); /* 邮箱大小,单位字节 */
函数描述:
函数 os_mbx_init 用于消息邮箱的初始化。其实就是将函数 os_mbx_declare 定义的数组进行初始化用于
消息邮箱的 FIFO。
 第 1 个参数填写消息邮箱的 ID 标识,即函数 os_mbx_declare 第一个参数。
 第 2 个参数填写函数 os_mbx_declare 定义的邮箱大小,单位字节。

函数 os_mbx_send
函数原型:
OS_RESULT os_mbx_send (
OS_ID mailbox, /* 消息邮箱 ID 标识 */
void* message_ptr, /* 消息指针,即数据的地址 */
U16 timeout ); /* 超时时间设置 */
函数描述:
函数 os_mbx_send 用于向消息邮箱存放数据指针,或者说数据地址。 如果消息邮箱已经满了,调用此函
数的任务将被挂起,等待消息邮箱可用,直到消息邮箱有空间可用或者超时时间溢出才会返回。
 第 1 个参数填写消息邮箱的 ID 标识,即函数 os_mbx_declare 第一个参数。
 第 2 个参数填写消息指针,即数据的地址。

 第 3 个参数表示设置的等待时间,范围 0-0xFFFF,当参数设置为 0-0xFFFE 时,表示等待这么多个
时钟节拍,参数设置为 0xFFFF 时表示无限等待直到消息邮箱有可用的空间。
 返回值 OS_R_OK,表示消息指针成功放到消息邮箱中。
返回值 OS_R_TMO,表示消息邮箱已经满了,在设置的超时时间范围内也没有等到可用的空间。
使用这个函数要注意以下问题:
1. 使用此函数前一定要调用函数 os_mbx_init 进行初始化。
2. 此函数用于往消息邮箱放消息指针,另一个函数 os_mbx_wait 用于从消息邮箱取出消息指针,取出后
相应的位置也就释放出来了,方便下次存放数据。

函数 os_mbx_wait
函数原型:
OS_RESULT os_mbx_wait (
OS_ID mailbox, /* 消息邮箱的 ID 标识 */
void** message, /* 存放消息指针的变量地址 */
U16 timeout ); /* 超时时间设置 */
函数描述:
函数 os_mbx_wait 用于从消息邮箱中获取消息。如果消息邮箱为空,调用此函数的任务将被挂起,直到
消息邮箱有消息可用或是设置的超时时间溢出才会返回。
 第 1 个参数填写消息邮箱的 ID 标识,即函数 os_mbx_declare 第一个参数。
 第 2 个参数填写从消息邮箱中获取消息后,存放消息的指针变量。
 第 3 个参数表示设置的等待时间,范围 0-0xFFFF,当参数设置为 0-0xFFFE 时,表示等待这么多个
时钟节拍,参数设置为 0xFFFF 时表示无限等待直到消息邮箱有消息。
 返回值 OS_R_OK,表示从消息邮箱中有消息,立即从消息邮箱中获得消息,无需等待。。
返回值 OS_R_TMO,表示消息邮箱为空,在设置的超时时间范围内也没有等到消息。
返回值 OS_R_MBX,表示在设置的超时时间范围内收到消息。

使用这个函数要注意以下问题:
1. 使用此函数前一定要调用函数 os_mbx_init 进行初始化。
2. 此函数用于往消息邮箱放消息指针,另一个函数 os_mbx_wait 用于从消息邮箱取出消息指针,取出后
相应的位置也就释放出来了,方便下次存放数据。

这里对函数 os_mbx_wait 的第二个参数再做一下解释。
 定义一个指针变量 uint8_t *pMsg,含义:pMsg 是指向 uint8_t 型变量的指针变量。
 指针变量 pMsg(注意,这里说的是 pMsg,不是*pMsg,前面没有*号)在内存中也是要占空间的,
对于 CM3/CM4 内核来说是占用 4 个字节。指针变量 pMsg 里面存储的就是从消息邮箱中获取的消
息指针,或者说消息地址。 从而*pMsg 就是 pMsg 所指向存储单元的实际内容。
 由于函数 os_mbx_wait 第二个参数是 void **message,有两级指针,所以填写的时候要填写成
os_mbx_wait(&mailbox, (void *)&pMsg, usMaxBlockTime)。 &pMsg 就是表示指针变量在内存
所在的地址。
 这里再说说 void 指针类型,使用 void 指针类型可定义一个指针变量,但不指定它是指向哪一种类型
的数据。 用户在使用这种类型指针变量时可以用来指向一个抽象的类型的数据,在将它的值赋值给另
一个指针变量时要进行强制类型转换使之适合于被赋值的变量的类型。 比如:
char *p1;
void *p2;
p1 = (char *)p2
如果细心的同学会发现,邮箱发送函数 os_mbx_send 和接收函数 os_mbx_wait 消息指针传递都是
用的 void 指针类型,这样就给消息数据传递带来了极大的方便,方便在哪里了呢?这样的话使用消
息邮箱传递任意数据类型变量都变的十分方便,用户只需做一下强制类型转换即可。

函数 isr_mbx_check
函数原型:
OS_RESULT isr_mbx_check (
OS_ID mailbox ); /*消息邮箱的 ID 标识*/
函数描述:
函数 isr_mbx_check 用来检测消息邮箱剩余空间可以存储的消息个数。 建议配合函数 isr_mbx_send 一起
使用。
 第 1 个参数填写消息邮箱的 ID 标识,即函数 os_mbx_declare 第一个参数。

 函数返回消息邮箱剩余空间可以存储的消息个数。
使用这个函数要注意以下问题:
1. 使用此函数前一定要调用函数 os_mbx_init 进行初始化。
2. 此函数只能在中断服务程序中调用。

函数 isr_mbx_send
函数原型:
void isr_mbx_send (
OS_ID mailbox, /*消息邮箱的 ID 标识*/
void* message_ptr ); /* 消息指针,即数据的地址*/
函数描述:
函数 isr_mbx_send 用于向消息邮箱存放数据指针,或者说数据地址。 如果消息邮箱已经满了,再次调用
此函数会造成消息邮箱溢出。 所以调用此函数前,强烈建议调用函数 isr_mbx_check 进行检测,检测是否
还有空间可用。
 第 1 个参数填写消息邮箱的 ID 标识,即函数 os_mbx_declare 第一个参数。
 第 2 个参数填写消息指针,即数据的地址。
使用这个函数要注意以下问题:
1. 使用此函数前一定要调用函数 os_mbx_init 进行初始化。
2. 为了防止消息邮箱溢出,强烈建议调用此函数前,先调用函数 isr_mbx_check 进行检测,检测是否还
有空间可用。

实战演习场:

__task void AppTaskMsgPro(void)
{ uint8_t *pMsg;
OS_RESULT xResult;
const uint16_t usMaxBlockTime = ; /* 延迟周期 */ while()
{
xResult = os_mbx_wait(&mailbox, (void **)&pMsg, usMaxBlockTime); switch (xResult)
{
/* 无需等待接受到消息邮箱数据 */
case OS_R_OK:
printf("无需等待接受到消息邮箱数据,pMsg = %d\r\n", *pMsg);
break; /* 消息邮箱空,usMaxBlockTime等待时间从消息邮箱内获得数据 */
case OS_R_MBX:
printf("消息邮箱空,usMaxBlockTime等待时间从消息邮箱内获得数据,pMsg = %d\r\n", *pMsg);
break; /* 超时 */
case OS_R_TMO:
macBeep_TOGGLE();
break; /* 其他值不处理 */
default:
break;
}
}

串口输出:

其他测试:(极其重要)

上面试一个发送任务,一个接收任务,现在我想一个发送,两个接收任务,看RTX怎么反馈:

经过测试,在一个发送,两个接收的时候,如果发送速度,慢于接收速度,即我每次发送之后,都立马被接收了,此时,我的两个接收任务,优先级高的会一直接收,优先级低的不能接收到消息,使用的是按键模拟,每次按键就发送消息,这样接收的时候只会在高优先级的任务接收;

再测试,我把两个接收任务的优先级设置成一样,这样的话,会出现交替接收我的发送消息:

而且注意,创建优先级相同的任务时,先创建的先接收,后创建的后接收:把上面图片的任务创建顺序改变之后如下(之前是LED先创建,MsgPro后创建):

    HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro,             /* 任务函数 */
, /* 任务优先级 */
&AppTaskMsgProStk, /* 任务栈 */
sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */
HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任务函数 */
, /* 任务优先级 */
&AppTaskLEDStk, /* 任务栈 */
sizeof(AppTaskLEDStk)); /* 任务栈大小,单位字节数 */

输出如下,这样就是先打印AppTaskMsgPro:

再行测试:发送消息采取一直发送,不等待:

接收的时候,有两个任务去接收,但是只有高优先级的任务能够接收到:

如果把接收消息的两个任务改成优先级相同的(前提是使能了时间轮转片调度),就会交替出现:

Summary:

总结一下RTX的消息邮箱,其实就是一个存储数据的结构,类似我们裸机编程的全局数组,但是消息邮箱支持FIFO。

我们项目中,如果需要交互类似数组这样的数据,就选用消息邮箱。并且,尽量一个任务发送,另一个任务接收,单对单的,比较试用也更便于程序员自己的逻辑管理。

RTX——第16章 消息邮箱的更多相关文章

  1. Linux就这个范儿 第16章 谁都可以从头再来--从头开始编译一套Linux系统 nsswitch.conf配置文件

    Linux就这个范儿 第16章 谁都可以从头再来--从头开始编译一套Linux系统  nsswitch.conf配置文件 朋友们,今天我对你们说,在此时此刻,我们虽然遭受种种困难和挫折,我仍然有一个梦 ...

  2. 【RL-TCPnet网络教程】第16章 UDP用户数据报协议基础知识

    第16章      UDP用户数据报协议基础知识 本章节为大家讲解UDP(User Datagram Protocol,用户数据报协议),需要大家对UDP有个基础的认识,方便后面章节UDP实战操作. ...

  3. springboot深入浅出系列(16章97节)-看了都说好

    章节目录 第一章 spring boot 2.x基础及概念入门 1.1.spring boot 产生的背景及其优势 1.2.spring boot 2.x 新特性说明 1.3.helloworld及项 ...

  4. javascript权威指南第16章 HTML5脚本编程

    <!DOCTYPE html> <html> <head> <script type="text/javascript" src=&quo ...

  5. 第16章 List集合的总结和遍历

    第16章 List集合的总结和遍历 1.重构设计 根据Vector类,ArrayList类,和LinkedList类所具有的存储特点以及拥有的方法入手,发现共性往上抽取. 共同特点: 1.允许元素重复 ...

  6. ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配

    第16章讲的是分页机制和动态页面分配的问题,说实话这个一开始接触是会把人绕晕的,但是这个的确太重要了,有了分页机制内存管理就变得很简单,而且能直接实现平坦模式. ★PART1:Intel X86基础分 ...

  7. LPTHW 笨方法学习python 16章

    根据16章的内容作了一些扩展. 比如,判断文件如果存在,就在文件后追加,如不存在则创建. 同时借鉴了shell命令中类似 cat <<EOF > test的方法,提示用户输入一个结尾 ...

  8. java JDK8 学习笔记——第16章 整合数据库

    第十六章 整合数据库 16.1 JDBC入门 16.1.1 JDBC简介 1.JDBC是java联机数据库的标准规范.它定义了一组标准类与接口,标准API中的接口会有数据库厂商操作,称为JDBC驱动程 ...

  9. 《深入Java虚拟机学习笔记》- 第16章 控制流

    <深入Java虚拟机学习笔记>- 第16章 控制流

随机推荐

  1. jQuery知识集锦

      CreateTime--2017年2月16日14:00:22Author:MarydonjQuery知识集锦1.empty()与remove()的区别 <select id="ty ...

  2. JNI 在命令行窗口输入字符,不显所输入字符,显指定的掩饰符

    //JNI-命令行窗口输入字符,显掩饰符.txt /*  目标:在命令行窗口输入字符,不显所输入字符,显指定的掩饰符  作者:tangshancheng@21cn.com*/ 1.KeyBoard.j ...

  3. JPA实体继承实体的映射策略

    注:这里所说的实体指的是@Entity注解的类 继承映射使用@Inheritance来注解.它的strategy属性的取值由枚举InheritanceType来定义(包含SINGLE_TABLE.TA ...

  4. genymotion安装(unknown generic error)及配置在Android studio环境中

    /*转载请注明出处.本文地址:http://write.blog.csdn.net/postedit/44261371*/ genymotion模拟器的长处我就不阐述了,一个字:快!! .如今来说一下 ...

  5. Foundations of Machine Learning: Boosting

    Foundations of Machine Learning: Boosting Boosting是属于自适应基函数(Adaptive basis-function Model(ABM))中的一种模 ...

  6. OGG_GoldenGate数据迁移三进程Extract / Dump / Relicat(案例)

    2014-03-04 Created By BaoXinjian

  7. android自带theme

    在网上搜了一下,android自带theme如下: •android:theme="@android:style/Theme.Dialog"   将一个Activity显示为对话框 ...

  8. scikit-learn 入门

    简介: scikit-learn是一个基于NumPy.SciPy.Matplotlib的开源机器学习工具包.採用Python语言编写.主要涵盖分类. 回归和聚类等算法,比如knn.SVM.逻辑回归.朴 ...

  9. enumerate的简单使用

    l = [11,22,33,55,"ss","zz"] for i,v in enumerate(l): print(i,v) #打印结果: # 0 11 # ...

  10. Spring Boot 2.0官方文档之 Actuator(转)

    执行器(Actuator)的定义 执行器是一个制造业术语,指的是用于移动或控制东西的一个机械装置,一个很小的改变就能让执行器产生大量的运动. An actuator is a manufacturin ...