第16章 消息邮箱
前面几个章节主要给大家讲解了任务间的同步和资源共享机制,本章节为大家讲解任务间的通信机制消息邮箱,RTX的消息邮箱其实就是消息队列,注意和uCOS-II中的消息邮箱区分开,uCOS-II的消息邮箱只能实现一个数据的传递。这里的消息邮箱可以实现多个数据的传递。
本章教程配套的例子含Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407。
16.1 消息邮箱
16.2 消息邮箱API函数
16.3 实验例程说明(任务间通信)
16.4 实验例程说明(中断方式通信)
16.5 总结
16.1 消息邮箱
16.1.1 消息邮箱的概念及其作用
RTX的消息邮箱实际上就是消息队列,通过内核提供的服务,任务或中断服务子程序可以将一个消息(注意,RTX消息邮箱传递的是消息的地址而不是实际的数据)放入到消息队列。同样,一个或者多个任务可以通过内核服务从消息队列中得到消息。通常,先进入消息队列的消息先传给任务,也就是说,任务先得到的是最先进入到消息队列的消息,即先进先出的原则(FIFO)。
也许有不理解的初学者会问采用消息邮箱多麻烦,搞个全局数组不是更简单,其实不然。在裸机编程时,使用全局数组的确比较方便,但是在加上RTOS后就是另一种情况了。使用全局数组相比消息邮箱主要有如下四个问题:
(1)使用消息邮箱可以让RTOS内核有效的管理任务,全局数组是无法做到的,任务的超时等机制需要用户自己去实现。
(2)使用了全局数组就要防止多任务的访问冲突,使用消息邮箱已经处理好了这个问题。用户无需担心。
(3)使用消息邮箱可以有效的解决中断服务程序跟任务之间消息传递的问题。
(4)FIFO机制更有利于数据的处理。
16.1.2 RTX任务间消息邮箱的实现
任务间消息邮箱的实现是指各个任务之间使用消息邮箱实现任务间的通信。下面我们通过如下的框图来说明一下RTX消息邮箱的实现,让大家有一个形象的认识。
运行条件:
1. 创建消息邮箱,可以存放10个消息。
2. 创建2个任务Task1和Task2,任务Task1向消息邮箱放数据地址,任务Task2从消息邮箱取数据地址。
3. RTX的消息读取和存放仅支持FIFO方式。
运行过程主要有以下两种情况:
(1)任务Task1 向消息邮箱放数据地址,任务Task2从消息邮箱取数据地址,如果放数据地址的速度快于取数据的速度,那么会出现消息邮箱存放满的情况,RTX的消息存放函数os_mbx_send支持超时等待,可以设置超时等待,直到有位置可以存放消息放或者设置时间超时。
(2)任务Task1 向消息邮箱放数据地址,任务Task2从消息邮箱取数据地址,如果放数据地址的速度慢于取数据的速度,那么会出现消息邮箱为空的情况,RTX的消息获取函数os_mbx_wait支持超时等待,可以设置超时等待,直到消息邮箱中有消息放或者设置时间超时。
上面就是一个简单的RTX任务间消息邮箱通信过程。
16.1.3 RTX中断方式消息邮箱的实现
RTX中断方式消息邮箱的实现是指中断函数和RTX任务之间使用消息邮箱。下面我们通过如下的框图来说明一下RTX消息邮箱的实现,让大家有一个形象的认识。
运行条件:
1. 创建消息邮箱,可以存放10个消息。
2. 创建1个任务Task1和一个串口接收中断。
3. RTX的消息读取和存放仅支持FIFO方式。
运行过程主要有以下两种情况:
(1)中断服务程序向消息邮箱放数据地址,任务Task1从消息邮箱取数据地址,如果放数据地址的速度快于取数据的速度,那么会出现消息邮箱存放满的情况。由于中断服务程序里面的消息邮箱发送函数isr_mbx_send不支持超时设置,所有发送前要通过函数isr_mbx_check检测邮箱是否满。
(2)中断服务程序向消息邮箱放数据地址,任务Task1从消息邮箱取数据地址,如果放数据地址的速度慢于取数据的速度,那么会出现消息邮箱存为空的情况。在RTX的任务中可以通过函数os_mbx_wait获取消息,因为此函数可以设置超时等待,直到消息邮箱中有消息存放或者设置时间超时。
上面就是一个简单RTX消息邮箱通信过程。实际应用中,中断方式的消息机制切记注意以下四个问题:
1. 中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
2. 实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在任务中实现消息处理,这样可以有效的保证中断服务程序的实时响应。同时此任务也需要设置为高优先级,以便退出中断函数后任务可以得到及时执行。
3. 中断服务程序中一定要调用专用于中断的消息邮箱函数isr_mbx_send,isr_mbx_receive和isr_mbx_check
4. 在RTX操作系统中实现中断函数和裸机编程是一样的。
(1)另外强烈推荐用户将Cortex-M3内核的STM32F103和Cortex-M4内核的STM32F407的NVIC优先级分组设置为4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这样中断优先级的管理将非常方便。
(2)用户要在RTX多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。
下一个,然后仔细研究研究!
16.2 消息邮箱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
关于这8个函数的讲解及其使用方法可以看教程第3章3.3小节里面说的参考资料rlarm.chm文件
这里我们重点的说一下函数os_mbx_declare,os_mbx_init,os_mbx_send,os_mbx_wait,isr_mbx_check和isr_mbx_send。
16.2.1 函数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)第1个参数表示定义的消息邮箱名。
(2)第2个参数是邮箱支持的消息个数。
一般的应用中,消息邮箱支持20个消息就够了。
使用举例:
- #include <rtl.h>
-
- /* 声明一个支持10个消息的消息邮箱 */
- os_mbx_declare (mailbox, 10);
16.2.2 函数os_mbx_init
函数原型:
- void os_mbx_init (
- OS_ID mailbox, /* 消息邮箱的ID标识 */
- U16 mbx_size ); /* 邮箱大小,单位字节 */
函数描述:
函数os_mbx_init用于消息邮箱的初始化。其实就是将函数os_mbx_declare定义的数组进行初始化用于消息邮箱的FIFO。
(1)第1个参数填写消息邮箱的ID标识,即函数os_mbx_declare第一个参数。
(2)第2个参数填写函数os_mbx_declare定义的邮箱大小,单位字节。
使用举例:
- #include <rtl.h>
-
- /* 声明一个支持10个消息的消息邮箱 */
- os_mbx_declare (mailbox, 10);
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppObjCreate
- * 功能说明: 创建任务通信机制
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppObjCreate (void)
- {
- /* 创建消息邮箱 */
- os_mbx_init (&mailbox, sizeof(mailbox));
- }
16.2.3 函数os_mbx_send
函数原型:
- OS_RESULT os_mbx_send (
- OS_ID mailbox, /* 消息邮箱ID标识 */
- void* message_ptr, /* 消息指针,即数据的地址 */
- U16 timeout ); /* 超时时间设置 */
函数描述:
函数os_mbx_send用于向消息邮箱存放数据指针,或者说数据地址。如果消息邮箱已经满了,调用此函数的任务将被挂起,等待消息邮箱可用,直到消息邮箱有空间可用或者超时时间溢出才会返回。
(1)第1个参数填写消息邮箱的ID标识,即函数os_mbx_declare第一个参数。
(2)第2个参数填写消息指针,即数据的地址。
(3)第3个参数表示设置的等待时间,范围0-0xFFFF,当参数设置为0-0xFFFE时,表示等待这么多个时钟节拍,参数设置为0xFFFF时表示无限等待直到消息邮箱有可用的空间。
(4)返回值OS_R_OK,表示消息指针成功放到消息邮箱中。
返回值OS_R_TMO,表示消息邮箱已经满了,在设置的超时时间范围内也没有等到可用的空间。
使用这个函数要注意以下问题:
1. 使用此函数前一定要调用函数os_mbx_init进行初始化。
2. 此函数用于往消息邮箱放消息指针,另一个函数os_mbx_wait用于从消息邮箱取出消息指针,取出后相应的位置也就释放出来了,方便下次存放数据。
使用举例:
- #include <rtl.h>
-
- /* 声明一个支持10个消息的消息邮箱 */
- os_mbx_declare (mailbox, 10);
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppObjCreate
- * 功能说明: 创建任务通信机制
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppObjCreate (void)
- {
- /* 创建消息邮箱 */
- os_mbx_init (&mailbox, sizeof(mailbox));
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskUserIF
- * 功能说明: 按键消息处理
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反)
- *********************************************************************************************************
- */
- __task void AppTaskUserIF(void)
- {
- uint8_t ucMsg = 0;
- uint8_t ucKeyCode;
-
- while(1)
- {
- ucKeyCode = bsp_GetKey();
-
- if (ucKeyCode != KEY_NONE)
- {
- switch (ucKeyCode)
- {
- /* K1键按下,打印调试说明 */
- case KEY_DOWN_K1:
- printf("K1键按下,使用MDK中自带的RTX调试组件,请务必使用MDK4.74版本进行调试\r\n");
- break;
-
- /* K2键按下,向消息邮箱发送数据 */
- case KEY_DOWN_K2:
- ucMsg++;
-
- /* 向消息邮箱发数据,如果消息邮箱满了,等待100个时钟节拍 */
- if(os_mbx_send (&mailbox, &ucMsg, 100) != OS_R_OK)
- {
- /* 发送失败,即使等待了100个时钟节拍 */
- printf("K2键按下,向消息邮箱发送数据失败,即使等待了100个时钟节拍\r\n");
- }
- else
- {
- /* 发送成功 */
- printf("K2键按下,向消息邮箱发送数据成功\r\n");
- }
-
- break;
-
- /* 其他的键值不处理 */
- default:
- break;
- }
- }
-
- os_dly_wait(20);
- }
- }
16.2.4 函数os_mbx_wait
函数原型:
- OS_RESULT os_mbx_wait (
- OS_ID mailbox, /* 消息邮箱的ID标识 */
- void** message, /* 存放消息指针的变量地址 */
- U16 timeout ); /* 超时时间设置 */
函数描述:
函数os_mbx_wait用于从消息邮箱中获取消息。如果消息邮箱为空,调用此函数的任务将被挂起,直到消息邮箱有消息可用或是设置的超时时间溢出才会返回。
(1)第1个参数填写消息邮箱的ID标识,即函数os_mbx_declare第一个参数。
(2)第2个参数填写从消息邮箱中获取消息后,存放消息的指针变量。
(3)第3个参数表示设置的等待时间,范围0-0xFFFF,当参数设置为0-0xFFFE时,表示等待这么多个时钟节拍,参数设置为0xFFFF时表示无限等待直到消息邮箱有消息。
(4)返回值OS_R_OK,表示从消息邮箱中有消息,立即从消息邮箱中获得消息,无需等待。
返回值OS_R_TMO,表示消息邮箱为空,在设置的超时时间范围内也没有等到消息。
返回值OS_R_MBX,表示在设置的超时时间范围内收到消息。
使用这个函数要注意以下问题:
1. 使用此函数前一定要调用函数os_mbx_init进行初始化。
2. 此函数用于往消息邮箱放消息指针,另一个函数os_mbx_wait用于从消息邮箱取出消息指针,取出后相应的位置也就释放出来了,方便下次存放数据。
使用举例:
- #include <rtl.h>
-
- /* 声明一个支持10个消息的消息邮箱 */
- os_mbx_declare (mailbox, 10);
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppObjCreate
- * 功能说明: 创建任务通信机制
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppObjCreate (void)
- {
- /* 创建消息邮箱 */
- os_mbx_init (&mailbox, sizeof(mailbox));
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskMsgPro
- * 功能说明: 消息处理,等待任务AppTaskUserIF发来的消息邮箱数据
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 3
- *********************************************************************************************************
- */
- __task void AppTaskMsgPro(void)
- {
- uint8_t *pMsg;
- OS_RESULT xResult;
- const uint16_t usMaxBlockTime = 200; /* 延迟周期 */
-
- while(1)
- {
- 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:
- bsp_LedToggle(1);
- bsp_LedToggle(4);
- break;
-
- /* 其他值不处理 */
- default:
- break;
- }
- }
- }
这里对函数os_mbx_wait的第二个参数再做一下解释。
(1)定义一个指针变量uint8_t *pMsg,含义:pMsg是指向uint8_t型变量的指针变量。
(2)指针变量pMsg(注意,这里说的是pMsg,不是*pMsg,前面没有*号)在内存中也是要占空间的,对于CM3/CM4内核来说是占用4个字节。指针变量pMsg里面存储的就是从消息邮箱中获取的消息指针,或者说消息地址。从而*pMsg就是pMsg所指向存储单元的实际内容。
(3)由于函数os_mbx_wait第二个参数是void **message,有两级指针,所以填写的时候要填写成os_mbx_wait(&mailbox, (void *)&pMsg, usMaxBlockTime)。&pMsg就是表示指针变量在内存所在的地址。
(4)这里再说说void 指针类型,使用void指针类型可定义一个指针变量,但不指定它是指向哪一种类型的数据。用户在使用这种类型指针变量时可以用来指向一个抽象的类型的数据,在将它的值赋值给另一个指针变量时要进行强制类型转换使之适合于被赋值的变量的类型。比如:
char *p1;
void *p2;
p1 = (char *)p2
如果细心的同学会发现,邮箱发送函数os_mbx_send和接收函数os_mbx_wait消息指针传递都是用的void指针类型,这样就给消息数据传递带来了极大的方便,方便在哪里了呢?这样的话使用消息邮箱传递任意数据类型变量都变的十分方便,用户只需做一下强制类型转换即可。
16.2.5 函数isr_mbx_check
函数原型:
- OS_RESULT isr_mbx_check (
- OS_ID mailbox ); /*消息邮箱的ID标识*/
函数描述:
函数isr_mbx_check用来检测消息邮箱剩余空间可以存储的消息个数。建议配合函数isr_mbx_send一起使用。
(1)第1个参数填写消息邮箱的ID标识,即函数os_mbx_declare第一个参数。
(2)函数返回消息邮箱剩余空间可以存储的消息个数。
使用这个函数要注意以下问题:
1. 使用此函数前一定要调用函数os_mbx_init进行初始化。
2. 此函数只能在中断服务程序中调用。
使用举例:
在中断服务程序中仅发一个消息
- #include <rtl.h>
-
- /* 声明一个支持10个消息的消息邮箱 */
- os_mbx_declare (mailbox, 10);
-
- static void AppObjCreate (void)
- {
- /* 创建消息邮箱 */
- os_mbx_init (&mailbox, sizeof(mailbox));
- }
-
- void EXTI0_IRQHandler (void){
- ..
- if (isr_mbx_check (&mailbox) != 0) {
- isr_mbx_send (&mailbox, msg);
- }
- ..
- }
在中断服务程序中发多个消息
- #include <rtl.h>
-
- /* 声明一个支持10个消息的消息邮箱 */
- os_mbx_declare (mailbox, 10);
-
- static void AppObjCreate (void)
- {
- /* 创建消息邮箱 */
- os_mbx_init (&mailbox, sizeof(mailbox));
- }
-
- void EXTI0_IRQHandler (void){
- int i,free;
- ..
- free = isr_mbx_check (&mailbox1);
- for (i = 0; i < 16; i++) {
- if (free > 0) {
- free--;
- isr_mbx_send (&mailbox1, msg);
- }
- }
- ..
-
- }
16.2.6 函数isr_mbx_send
函数原型:
- void isr_mbx_send (
- OS_ID mailbox, /*消息邮箱的ID标识*/
- void* message_ptr ); /* 消息指针,即数据的地址*/
函数描述:
函数isr_mbx_send用于向消息邮箱存放数据指针,或者说数据地址。如果消息邮箱已经满了,再次调用此函数会造成消息邮箱溢出。所以调用此函数前,强烈建议调用函数isr_mbx_check进行检测,检测是否还有空间可用。
(1)第1个参数填写消息邮箱的ID标识,即函数os_mbx_declare第一个参数。
(2)第2个参数填写消息指针,即数据的地址。
使用这个函数要注意以下问题:
1. 使用此函数前一定要调用函数os_mbx_init进行初始化。
2. 为了防止消息邮箱溢出,强烈建议调用此函数前,先调用函数isr_mbx_check进行检测,检测是否还有空间可用。
使用举例:
在中断服务程序中仅发一个消息
- #include <rtl.h>
-
- /* 声明一个支持10个消息的消息邮箱 */
- os_mbx_declare (mailbox, 10);
-
- static void AppObjCreate (void)
- {
- /* 创建消息邮箱 */
- os_mbx_init (&mailbox, sizeof(mailbox));
- }
-
- void EXTI0_IRQHandler (void){
- ..
- if (isr_mbx_check (&mailbox) != 0) {
- isr_mbx_send (&mailbox, msg);
- }
- ..
- }
在中断服务程序中发多个消息
- #include <rtl.h>
-
- /* 声明一个支持10个消息的消息邮箱 */
- os_mbx_declare (mailbox, 10);
-
- static void AppObjCreate (void)
- {
- /* 创建消息邮箱 */
- os_mbx_init (&mailbox, sizeof(mailbox));
- }
-
- void EXTI0_IRQHandler (void){
- int i,free;
- ..
- free = isr_mbx_check (&mailbox1);
- for (i = 0; i < 16; i++) {
- if (free > 0) {
- free--;
- isr_mbx_send (&mailbox1, msg);
- }
- }
- ..
-
- }
16.3 实验例程说明(任务间通信)
16.3.1 STM32F103开发板实验
配套例子:
V4-414_RTX实验_消息邮箱
实验目的:
1. 学习RTX的消息邮箱。
2. RTX的消息邮箱等同于uCOS的消息队列,uCOS的消息邮箱仅支持一个消息的收发。
实验内容:
1. K1按键按下,串口打印。
2. K2键按下,向消息邮箱发送数据。任务AppTaskMsgPro接收到消息后进行消息处理。
3. 各个任务实现的功能如下:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,等待任务AppTaskUserIF发来的消息邮箱数据。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
RTX配置:
RTX配置向导详情如下:
Task Configuration
Number of concurrent running tasks
允许创建4个任务,实际创建了如下四个任务
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,等待任务AppTaskUserIF发来的消息邮箱数据。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
Number of tasks with user-provided stack
创建的4个任务都是采用自定义堆栈方式。
RTX任务调试信息:
程序设计:
任务栈大小分配:
staticuint64_t AppTaskUserIFStk[512/8]; /* 任务栈 */
staticuint64_t AppTaskLEDStk[256/8]; /* 任务栈 */
staticuint64_t AppTaskMsgProStk[512/8]; /* 任务栈 */
staticuint64_t AppTaskStartStk[512/8]; /* 任务栈 */
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数,浮点运算和uint64_t类型数据运算会出问题。
系统栈大小分配:
RTX初始化:
- /*
- *********************************************************************************************************
- * 函 数 名: main
- * 功能说明: 标准c程序入口。
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- int main (void)
- {
- /* 初始化外设 */
- bsp_Init();
-
- /* 创建启动任务 */
- os_sys_init_user (AppTaskStart, /* 任务函数 */
- 4, /* 任务优先级 */
- &AppTaskStartStk, /* 任务栈 */
- sizeof(AppTaskStartStk)); /* 任务栈大小,单位字节数 */
- while(1);
- }
RTX任务创建:
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskCreate
- * 功能说明: 创建应用任务
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppTaskCreate (void)
- {
- HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF, /* 任务函数 */
- 1, /* 任务优先级 */
- &AppTaskUserIFStk, /* 任务栈 */
- sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */
-
- HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任务函数 */
- 2, /* 任务优先级 */
- &AppTaskLEDStk, /* 任务栈 */
- sizeof(AppTaskLEDStk)); /* 任务栈大小,单位字节数 */
-
- HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro, /* 任务函数 */
- 3, /* 任务优先级 */
- &AppTaskMsgProStk, /* 任务栈 */
- sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */
- }
创建消息邮箱:
- /* 声明一个支持10个消息的消息邮箱 */
- os_mbx_declare (mailbox, 10);
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppObjCreate
- * 功能说明: 创建任务通信机制
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppObjCreate (void)
- {
- /* 创建消息邮箱 */
- os_mbx_init (&mailbox, sizeof(mailbox));
-
- }
四个RTX任务的实现:
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskUserIF
- * 功能说明: 按键消息处理
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反)
- *********************************************************************************************************
- */
- __task void AppTaskUserIF(void)
- {
- uint8_t ucMsg = 0;
- uint8_t ucKeyCode;
-
- while(1)
- {
- ucKeyCode = bsp_GetKey();
-
- if (ucKeyCode != KEY_NONE)
- {
- switch (ucKeyCode)
- {
- /* K1键按下,打印调试说明 */
- case KEY_DOWN_K1:
- printf("K1键按下,使用MDK中自带的RTX调试组件,请务必使用MDK4.74版本进行调试\r\n");
- break;
-
- /* K2键按下,向消息邮箱发送数据 */
- case KEY_DOWN_K2:
- ucMsg++;
-
- /* 向消息邮箱发数据,如果消息邮箱满了,等待100个时钟节拍 */
- if(os_mbx_send (&mailbox, &ucMsg, 100) != OS_R_OK)
- {
- /* 发送失败,即使等待了100个时钟节拍 */
- printf("K2键按下,向消息邮箱发送数据失败,即使等待了100个时钟节拍\r\n");
- }
- else
- {
- /* 发送成功 */
- printf("K2键按下,向消息邮箱发送数据成功\r\n");
- }
-
- break;
-
- /* 其他的键值不处理 */
- default:
- break;
- }
- }
-
- os_dly_wait(20);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskLED
- * 功能说明: LED闪烁。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskLED(void)
- {
- const uint16_t usFrequency = 200; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
- bsp_LedToggle(2);
- bsp_LedToggle(3);
-
- /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskMsgPro
- * 功能说明: 消息处理,等待任务AppTaskUserIF发来的消息邮箱数据
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 3
- *********************************************************************************************************
- */
- __task void AppTaskMsgPro(void)
- {
- uint8_t *pMsg;
- OS_RESULT xResult;
- const uint16_t usMaxBlockTime = 200; /* 延迟周期 */
-
- while(1)
- {
- 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:
- bsp_LedToggle(1);
- bsp_LedToggle(4);
- break;
-
- /* 其他值不处理 */
- default:
- break;
- }
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskStart
- * 功能说明: 启动任务,也就是最高优先级任务。这里实现按键扫描。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 4
- *********************************************************************************************************
- */
- __task void AppTaskStart(void)
- {
- /* 创建任务 */
- AppTaskCreate();
-
- /* 创建任务通信机制 */
- AppObjCreate();
-
- while(1)
- {
- /* 按键扫描 */
- bsp_KeyScan();
- os_dly_wait(10);
- }
- }
16.3.2 STM32F407开发板实验
配套例子:
V5-414_RTX实验_消息邮箱
实验目的:
1. 学习RTX的消息邮箱。
2. RTX的消息邮箱等同于uCOS的消息队列,uCOS的消息邮箱仅支持一个消息的收发。
实验内容:
1. K1按键按下,串口打印。
2. K2键按下,向消息邮箱发送数据。任务AppTaskMsgPro接收到消息后进行消息处理。
3. 各个任务实现的功能如下:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,等待任务AppTaskUserIF发来的消息邮箱数据。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
RTX配置:
RTX配置向导详情如下:
Task Configuration
Number of concurrent running tasks
允许创建4个任务,实际创建了如下四个任务
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :消息处理,等待任务AppTaskUserIF发来的消息邮箱数据。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现按键扫描。
Number of tasks with user-provided stack
创建的4个任务都是采用自定义堆栈方式。
RTX任务调试信息:
程序设计:
任务栈大小分配:
staticuint64_t AppTaskUserIFStk[512/8]; /* 任务栈 */
staticuint64_t AppTaskLEDStk[256/8]; /* 任务栈 */
staticuint64_t AppTaskMsgProStk[512/8]; /* 任务栈 */
staticuint64_t AppTaskStartStk[512/8]; /* 任务栈 */
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数,浮点运算和uint64_t类型数据运算会出问题。
系统栈大小分配:
RTX初始化:
- /*
- *********************************************************************************************************
- * 函 数 名: main
- * 功能说明: 标准c程序入口。
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- int main (void)
- {
- /* 初始化外设 */
- bsp_Init();
-
- /* 创建启动任务 */
- os_sys_init_user (AppTaskStart, /* 任务函数 */
- 4, /* 任务优先级 */
- &AppTaskStartStk, /* 任务栈 */
- sizeof(AppTaskStartStk)); /* 任务栈大小,单位字节数 */
- while(1);
- }
RTX任务创建:
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskCreate
- * 功能说明: 创建应用任务
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppTaskCreate (void)
- {
- HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF, /* 任务函数 */
- 1, /* 任务优先级 */
- &AppTaskUserIFStk, /* 任务栈 */
- sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */
-
- HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任务函数 */
- 2, /* 任务优先级 */
- &AppTaskLEDStk, /* 任务栈 */
- sizeof(AppTaskLEDStk)); /* 任务栈大小,单位字节数 */
-
- HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro, /* 任务函数 */
- 3, /* 任务优先级 */
- &AppTaskMsgProStk, /* 任务栈 */
- sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */
- }
创建消息邮箱:
- /* 声明一个支持10个消息的消息邮箱 */
- os_mbx_declare (mailbox, 10);
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppObjCreate
- * 功能说明: 创建任务通信机制
- * 形 参: 无
- * 返 回 值: 无
- *********************************************************************************************************
- */
- static void AppObjCreate (void)
- {
- /* 创建消息邮箱 */
- os_mbx_init (&mailbox, sizeof(mailbox));
-
- }
四个RTX任务的实现:
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskUserIF
- * 功能说明: 按键消息处理
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反)
- *********************************************************************************************************
- */
- __task void AppTaskUserIF(void)
- {
- uint8_t ucMsg = 0;
- uint8_t ucKeyCode;
-
- while(1)
- {
- ucKeyCode = bsp_GetKey();
-
- if (ucKeyCode != KEY_NONE)
- {
- switch (ucKeyCode)
- {
- /* K1键按下,打印调试说明 */
- case KEY_DOWN_K1:
- printf("K1键按下,使用MDK中自带的RTX调试组件,请务必使用MDK4.74版本进行调试\r\n");
- break;
-
- /* K2键按下,向消息邮箱发送数据 */
- case KEY_DOWN_K2:
- ucMsg++;
-
- /* 向消息邮箱发数据,如果消息邮箱满了,等待100个时钟节拍 */
- if(os_mbx_send (&mailbox, &ucMsg, 100) != OS_R_OK)
- {
- /* 发送失败,即使等待了100个时钟节拍 */
- printf("K2键按下,向消息邮箱发送数据失败,即使等待了100个时钟节拍\r\n");
- }
- else
- {
- /* 发送成功 */
- printf("K2键按下,向消息邮箱发送数据成功\r\n");
- }
-
- break;
-
- /* 其他的键值不处理 */
- default:
- break;
- }
- }
-
- os_dly_wait(20);
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskLED
- * 功能说明: LED闪烁。
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 2
- *********************************************************************************************************
- */
- __task void AppTaskLED(void)
- {
- const uint16_t usFrequency = 200; /* 延迟周期 */
-
- /* 设置延迟周期 */
- os_itv_set(usFrequency);
-
- while(1)
- {
- bsp_LedToggle(2);
- bsp_LedToggle(3);
-
- /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/
- os_itv_wait();
- }
- }
-
- /*
- *********************************************************************************************************
- * 函 数 名: AppTaskMsgPro
- * 功能说明: 消息处理,等待任务AppTaskUserIF发来的消息邮箱数据
- * 形 参: 无
- * 返 回 值: 无
- * 优 先 级: 3
- *********************************************************************************************************
- */
- __task void AppTaskMsgPro(void)
- {
- uint8_t *pMsg;
- OS_RESULT xResult;
- const uint16_t usMaxBlockTime = 200; /* 延迟周期 */
-
- while(1)
- {
- xResult = os_mbx_wait(&mailbox, (void *)&pMsg, usMaxBlockTime);
-
- switch (xResult)
- {
- /* 无需等待接受到消息邮箱数据 */
- case OS_R_OK:
- printf("无需等待接受到消息邮箱数据,pMsg = %d\r\n", *pMsg);
- break;