-
2020-04-12 21:30:00
#include "FreeRTOS.h" #include "task.h" #include "queue.h" #include "os_start.h" #define START_TASK_PRIO 1 #define START_STK_SIZE 128 TaskHandle_t StartTask_Handler; void start_task(void *pvParameters); #define LED1_TASK_PRIO 2 #define LED1_STK_SIZE 50 TaskHandle_t LED1Task_Handler; void led1_task(void *pvParameters); #define KEYPROCESS_TASK_PRIO 3 #define KEYPROCESS_STK_SIZE 50 TaskHandle_t KeyprocessTask_Handler; void Keyprocess_task(void *pvParameters); #define KEYMSG_Q_LENGTH 1 //队列长度 #define RXMSG_Q_LENGTH 4 #define RX_BUF_NUM 30 uint8_t Rx_Buf[RX_BUF_NUM]; uint8_t Rx_HAL_Buff_Temp[2]; //HAL函数接收临时缓存 QueueHandle_t Key_Queue; //按键队列 QueueHandle_t UartRX_Queue; //消息队列 void os_start(void) { User_SysTick_ReConfig(configTICK_RATE_HZ); //创建开始任务 xTaskCreate((TaskFunction_t )start_task, //任务函数 (const char* )"start_task", //任务名称 (uint16_t )START_STK_SIZE, //任务堆栈大小 (void* )NULL, //传递给任务函数的参数 (UBaseType_t )START_TASK_PRIO, //任务优先级 (TaskHandle_t* )&StartTask_Handler); //任务句柄 vTaskStartScheduler(); //开启任务调度 } //开始任务任务函数 void start_task(void *pvParameters) { taskENTER_CRITICAL(); //进入临界区 //创建队列 Key_Queue = xQueueCreate(KEYMSG_Q_LENGTH,1); UartRX_Queue = xQueueCreate(RXMSG_Q_LENGTH,1); HAL_UART_Receive_IT(&huart6,Rx_HAL_Buff_Temp,1); //创建LED1任务 xTaskCreate((TaskFunction_t )led1_task, (const char* )"led1_task", (uint16_t )LED1_STK_SIZE, (void* )NULL, (UBaseType_t )LED1_TASK_PRIO, (TaskHandle_t* )&LED1Task_Handler); //创建key任务 xTaskCreate((TaskFunction_t )Keyprocess_task, (const char* )"keyprocess_task", (uint16_t )KEYPROCESS_STK_SIZE, (void* )NULL, (UBaseType_t )KEYPROCESS_TASK_PRIO, (TaskHandle_t* )&KeyprocessTask_Handler); vTaskDelete(StartTask_Handler); //删除开始任务 taskEXIT_CRITICAL(); //退出临界区 } //LED1任务函数 void led1_task(void *pvParameters) { BaseType_t err; while(1) { //按键检测 if(KeyScan((uint8_t *)&KeyValue) == 0) { if((KeyValue > 0) && (KeyValue < 17)) { err = xQueueSend(Key_Queue,(uint8_t *)&KeyValue,10); if(err != pdTRUE) { printf("Queue Send Fail!\r\n"); } KeyValue = 0; } } vTaskDelay(10); } } //key任务函数 void Keyprocess_task(void *pvParameters) { BaseType_t err; uint8_t keyVal; while(1) { if(Key_Queue != NULL) { err = xQueueReceive(Key_Queue,&keyVal,portMAX_DELAY); if(err == pdTRUE) { printf("KeyVal = %d\r\n",keyVal); } } else { vTaskDelay(1000); } } } //发生溢出中断或DMA中断时 void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { huart->ErrorCode = HAL_UART_ERROR_NONE; HAL_UART_Receive_IT(&huart6,Rx_HAL_Buff_Temp,1); } //接收到1字节数据,回调函数如下 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { BaseType_t err; BaseType_t pxHigherPriorityTaskWoken; if(huart->Instance==USART6) { //加上自己的代码 if(UartRX_Queue != NULL) { err = xQueueSendFromISR(UartRX_Queue,Rx_Buf,&pxHigherPriorityTaskWoken); if(err == pdTRUE) { HAL_UART_Receive_IT(&huart6,Rx_HAL_Buff_Temp,1); } else { printf("Queue Send ISR Fail!\r\n"); } portYIELD_FROM_ISR(pxHigherPriorityTaskWoken); } HAL_UART_Receive_IT(&huart6,Rx_HAL_Buff_Temp,1); } } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { /* USER CODE BEGIN Callback 0 */ BaseType_t err; BaseType_t pxHigherPriorityTaskWoken; /* USER CODE END Callback 0 */ if (htim->Instance == TIM1) { HAL_IncTick(); } /* USER CODE BEGIN Callback 1 */ else if (htim->Instance == TIM2) { if(UartRX_Queue != NULL) { err = xQueueReceiveFromISR(UartRX_Queue,ReceiveBuf,&pxHigherPriorityTaskWoken); if(err == pdTRUE) { printf("Receive:%s\r\n",ReceiveBuf); } else { printf("Receive Failed!\r\n"); } portYIELD_FROM_ISR(pxHigherPriorityTaskWoken); } } /* USER CODE END Callback 1 */ }
更多相关内容 -
STM32奋斗板-LED闪烁-ucos-消息邮箱.rar
2020-04-14 22:01:46串口通信,PC端发送指令定义LED闪烁时间,利用消息邮箱控制LED闪烁,避免了闪烁时间不严格的问题, 由STM32来编写注释清晰完整 -
V5-415_RTX实验_消息邮箱(中断方式).7z
2021-04-15 13:37:34V5-415_RTX实验_消息邮箱(中断方式).7z -
电子-信号量消息邮箱消息队列.rar
2019-09-05 14:34:29电子-信号量消息邮箱消息队列.rar,单片机/嵌入式STM32-F0/F1/F2 -
UCOSII-消息邮箱与消息队列
2019-11-21 21:02:22本文介绍了消息邮箱和消息队列的原理及使用方法,消息邮箱是消息队列的特殊情况,因此可以看做一起,消息对垒在任务之间进行消息(指针)传递应用方便,供大家参考,谢谢!文章目录
消息邮箱是特殊的消息队列,是大小为1的消息队列!!!一.事件控制块及事件处理函数
1.等待任务列表
对于事件来说,当其被占用时,会导致其他请求该事件的任务因暂时的得不到该事件的服务而处于等待状态。作为功能完善的事件,应该对等待任务具有两方面的管理功能:
一是要对等待事件的所有任务进行记录并排序;
二是应该允许等待任务有一个等待时限。
UCOSII定义了一个INT8U类型的数组OSEventTb1[]作为等待事件的任务的记录表,即等待任务表。位为1则表示为等待任务,0则不是。
为了加快对该表的访问速度,也定义了一个INT8U类型的变量OSEventGrp来表示等待任务表中的等待组。OSEventTb1[]与变量OSEventGrp示意图如下:
2.事件控制块的结构
为了把事件统一起来,UCOSII用事件控制块ECB的数据结构来描述信号量、消息邮箱、和消息队列等事件。结构如下:
#if (OS_EVENT_EN) && (OS_MAX_EVENTS > 0u) typedef struct os_event { INT8U OSEventType; /* 事件的类型 */ INT16U OSEventCnt; /* 信号量计数器 */ void *OSEventPtr; /* 消息或消息队列的指针 */ OS_PRIO OSEventGrp; /* 等待事件的任务组 */ OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; /* 任务等待表 */ } OS_EVENT;
3.操作事件控制块的函数
1)事件控制块初始化函数
void OS_EventWaitListInit( OS_EVENT *pevent //事件控制块的指针 )
2)使一个任务进入等待状态的函数void OS_EventTaskWait( OS_EVENT *pevent //事件控制块的指针 )
3)使一个正在等待的任务进入就绪状态的函数
INT8U OS_EventTaskRdy( OS_EVENT *pevent //事件控制块的指针 void *msg, //未使用 INT8U msk //清除TCB状态标识掩码 )
4)使一个等待超时的任务进入就绪状态的函数
void OS_EventTO( OS_EVENT *pevent //事件控制块的指针 )
4.空事件控制块列表
UCOSII初始化时,系统会在初始化函数OSInit()中按照程序使用事件的总数OS_MAX_EVENTS(在文件OS_CFG.H中定义)创建OS_MAX_EVENTS个空事件控制块并借用成员OSEventPtr作为链接指针。由于链表中所有控制块尚未与具体事件想关联,因此叫空事件控制块链表。以后,每当应用程序创建一个事件时,系统会从链表中取出一个空事件控制块,并对它进行初始化以描述该事件。当删除一个事件时,系统会将该事件的控制块归还给空事件控制块链表。
二.消息邮箱
1.消息邮箱介绍
如果要在任务与与任务之间传递一个数据,那么为了适应不同数据的需要最好在存储器中建立一个数据缓存区,然后以这个缓存区为中介来实现任务间的数据传递。
把数据缓存区指针赋给控制块的成员OSEventPtr,同时使事件控制块成员OSEventType为常数OS_EVENT_TYPE_MBOX,则事件控制块就叫做消息邮箱。消息邮箱通过在两个需要通信的任务之间传递数据缓冲区指针进行通信。消息邮箱数据结构如下图:
2.消息邮箱操作步骤
1.定义消息邮箱
OS_EVENT * msg_key; //定义按键消息邮箱事件块指针
2.创建消息邮箱
msg_key=OSMboxCreate((void*)0); //创建消息邮箱,定义msg初值为NULL
3请求消息邮箱
//在主任务中请求消息邮箱,切等待时间为10ms,根据消息邮箱中的键值做出不同的动作 void main_task(void *pdata) { u32 key=0; u8 err; u8 semmask=0; u8 tcnt=0; while(1) { key=(u32)OSMboxPend(msg_key,10,&err); //请求邮箱函数,请求消息邮箱指针为msg_key,等待时间为100ms switch(key) { case 1://控制DS1 LED1=!LED1; break; case 2://发送信号量 semmask=1; OSSemPost(sem_beep);//发送信号量函数,释放信号量,释放信号量后UART_TASK任务才能执行 OSMutexPost(sem_mutex);//发送互斥型信号量 break; case 3://清除 LCD_Fill(0,121,lcddev.width,lcddev.height,WHITE); break; case 4://校准 OSTaskSuspend(TOUCH_TASK_PRIO); //挂起触摸屏任务 if((tp_dev.touchtype&0X80)==0)TP_Adjust(); OSTaskResume(TOUCH_TASK_PRIO); //解挂 ucos_load_main_ui(); //重新加载主界面 break; } if(semmask||sem_beep->OSEventCnt)//需要显示sem,即此时的信号量计数器值不为0 { LCD_ShowxNum(192,50,sem_beep->OSEventCnt,3,16,0X80);//显示信号量的值 if(sem_beep->OSEventCnt==0)semmask=0; //停止更新 } if(tcnt==50)//0.5秒更新一次CPU使用率 { tcnt=0; POINT_COLOR=BLUE; LCD_ShowxNum(192,30,OSCPUUsage,3,16,0); //显示CPU使用率 } tcnt++; delay_ms(10); } }
4.发生消息邮箱
//在按键扫描任务中发生消息邮箱 void key_task(void *pdata) { u8 key; while(1) { key=KEY_Scan(0); if(key)OSMboxPost(msg_key,(void*)key);//向邮箱发送消息函数,把当前KEY值传送到邮箱当中,在main_task中查询该值,以实现KEY任务与MAINR任务之间的通信 delay_ms(10); } }
5.查询邮箱的状态
INT8U OSMboxQuery( OS_EVENT *pevent, //消息邮箱指针 SO_MBOX_DATA *pdata //存放邮箱消息的结构 )
SO_MBOX_DATA 结构如下:
typedef struct { void *OSMsg; INT8U OSEventTb1[OS_EVENT_TBL_SIZE]; INTU8 OSEventGrp; }SO_MBOX_DATA
6.删除邮箱
OS_EVENT *OSboxDel( OS_EVENT *pevent, //消息邮箱指针 INT8U opt, //删除选项 INT8U *err //错误信息 )
三.消息队列
使用消息队列可以在任务之间传递多条消息,消息队列由三部分组成:事件控制块、消息队列、消息。
当事件控制块成员OSEventType值为OS_EVENT_TYPE_Q时,该事件控制块代表一个消息队列。
消息队列相当于一个共用一个任务等待列表的消息邮箱数组,事件控制块成员OSEventPtr指向一个叫做队列控制块(OS_Q)的结构,该结构管理着一个数组MsgTb1[],该数组中的元素都是指向消息的指针。
1.消息指针数组
消息队列的核心部件为消息指针数组。
可以采用2种方式向指针数组插入消息:先进先出的FIFO方式和后进先出的LIFO方式。当采用FIFO方式时,消息队列将在指针OSQIn指向的位置插入消息指针,而OSQOut指向的消息指针为输出;当采用LIFO方式时,则只使用指针OSQOut。2.队列控制块
UCOSII把消息指针数组的基本参数都记录在一个叫做队列控制块的结构中。队列控制块的结构如下:
#if OS_Q_EN > 0u typedef struct os_q { /* QUEUE CONTROL BLOCK */ struct os_q *OSQPtr; /* Link to next queue control block in list of free blocks */ void **OSQStart; /* Pointer to start of queue data */ void **OSQEnd; /* Pointer to end of queue data */ void **OSQIn; /* Pointer to where next message will be inserted in the Q */ void **OSQOut; /* Pointer to where next message will be extracted from the Q */ INT16U OSQSize; /* Size of queue (maximum number of entries) */ INT16U OSQEntries; /* Current number of entries in the queue */ } OS_Q;
在UCOSII系统初始化时,系统将按文件OS_CFG.H中配置OS_MAX_QS个队列控制块,并用队列控制块中的指针OSQPtr将所有队列控制块链接为链表。由于这时还没有使用它们,因此这个链表叫做空队列控制块列表。
3.消息队列的操作流程
1)定义消息队列
OS_EVENT * q_msg; //定义消息队列控制块
2)创建消息队列
q_msg=OSQCreate(&MsgGrp[0],256); //创建消息队列,缓存区指针数组地址为MsgGrp[0],数组大小为256
3)请求消息队列
//队列消息显示任务:每500ms显示当前消息队列的内容 void qmsgshow_task(void *pdata) { u8 *p; u8 err; while(1) { p=OSQPend(q_msg,0,&err);//请求消息队列,且为无限等待 LCD_ShowString(5,170,240,16,16,p);//显示消息 myfree(SRAMIN,p); delay_ms(500); } }
4)向消息队列发生消息
//软件定时器3的回调函数,100ms溢出一次,自动发送消息到消息队列 void tmr3_callback(OS_TMR *ptmr,void *p_arg) { u8* p; u8 err; static u8 msg_cnt=0; //msg编号 p=mymalloc(SRAMIN,13); //申请13个字节的内存 if(p) { sprintf((char*)p,"ALIENTEK %03d",msg_cnt); msg_cnt++; err=OSQPost(q_msg,p); //发送队列,q_msg:消息队列指针。p:待发送消息指针 if(err!=OS_ERR_NONE) //发送失败 { myfree(SRAMIN,p); //释放内存 OSTmrStop(tmr3,OS_TMR_OPT_NONE,0,&err); //关闭软件定时器3 } } }
5)清空消息队列
INT8U OSQFlush( OS_EVENT *pevent //消息队列指针 )
6)删除消息队列
INT8U OSQDel( OS_EVENT *pevent //消息队列指针 )
7)查询消息队列
INT8U OSQQuery( OS_EVENT *pevent //消息队列指针 OS_Q_DATA*pdata //存放状态信息的结构 )
函数中的参数pdata是OS_Q_DATApdata类型的指针。OS_Q_DATApdata的机构如下:
typedef struct struct{ void *OSMsg; /* Pointer to next message to be extracted from queue */ INT16U OSNMsgs; /* Number of messages in message queue */ INT16U OSQSize; /* Size of message queue */ OS_PRIO OSEventTbl[OS_EVENT_TBL_SIZE]; /* List of tasks waiting for event to occur */ OS_PRIO OSEventGrp; /* Group corresponding to tasks waiting for event to occur */ } OS_Q_DATA;
四.总结
1.消息邮箱是特殊的消息队列,是大小为1的消息队列。
2.消息邮箱是能够在任务之间传递消息指针的数据结构。
3.消息队列是能够在任务之间传递一组消息指针的数据结构。
4.信号量、消息邮箱、消息队列都叫做“事件”,每个事件都有一个用来记录等待事件的任务的表----等待任务表,而任务的等待时限则记录在OSTCBDly中。
5.UCOSII统一用事件控制块来描述各种事件。
6.操作系统中各任务之间的同步与通信是通过各种各样的事件来完成的。
7.重要总结:消息队列的应用有点类似于信号量。
函数需要通过访问事件控制块的成员OSEventPtr指向的队列控制块OS_Q的成员OSQEntries来判断是否有消息可用(有点类似于信号量计数值cnt)。如果有消息可用,则返回OS_Q成员OSQOut指向的消息,同时调整指针OSQOut,使之指向下一条消息并把有效消息数的变量OSQEntries减1,如果无消息可用(也就是OSQEntries=0),则使用调用函数OSQPend()的任务挂起,使之处于等待状态并引发一次任务调度。同样,向队列发送消息后OSQEntries+1.如果希望任务无等待的请求一个消息队列,则可调用函数OSQAccept();函数原型为:void OSQAccept(OS_EVENT *pevent); -
ucos-ii学习笔记——消息邮箱的原理及使用
2013-04-22 18:13:01详细介绍了ucos-ii操作系统的消息邮箱原理及应用,程序完美可以运行,注释详细,希望对你有所帮助 -
ucosii 消息邮箱
2016-07-17 11:42:38ucosii 互斥信号量 -
discuz X3.0邮件消息邮箱推送插件
2021-04-03 07:22:45discuz X3.0邮件消息邮箱推送插件,一直以来,大家都在为网站用户粘性发愁,有的甚至通过QQ邮箱群发消息,让会员每天来登录访问,这些方法有点雷人,免费分享discuz X3.0邮件消息邮箱推送插件,详细请看说明。... -
UCOSII中消息邮箱的使用方法详解
2021-04-29 10:31:40UCOS中到底如何使用邮箱来进行任务间通信? 1、在中断服务程序中(ISR)可不可以发送消息? 在ISR中,是可以发送消息的。类似的,也可以发送信号量、消息队列等。这里需要注意的一点是不能在ISR中请求消息,即不能...UCOSII中到底如何使用邮箱来进行任务间通信?
前言:什么是行为同步,什么是资源同步?
行为同步:
资源同步:
1、在中断服务程序中(ISR)可不可以发送消息?
在ISR中,是可以发送消息的。类似的,也可以发送信号量、消息队列等。这里需要注意的一点是不能在ISR中请求消息,即不能调用OSXXXPend()函数。原因是在该函数中首先要判断中断的嵌套层数,如果当前的嵌套层数大于0,那么会直接返回空,如下图(UCOSII源码:OSXXXPend()函数)
这里另外说一下,判断某个函数能否在中断中使用,这里需要去看源代码,在该函数真正执行相应动作之前都会先进行一系列的判断操作,如上图这种,如果没有对嵌套层数做判断,那么就可以直接在ISR中使用。2、可以在什么地方使用消息邮箱发送消息?
这里分为两种情况:一是在任务中发送消息,二是在ISR中(例如串口中断、定时器中断等)发送消息。
在任务中发送消息:这种情况很简单,只要任务没有被删除,那么保存消息的变量(无论是全局还是局部)总是存在的,接收消息一方总能通过指针访问到这个变量。
在ISR中发送消息:这种情况需要特别注意,如果直接用ISR的局部变量来保存消息,则接收消息的一方就不能获得真正的消息。原因是接收消息的一方在获得消息指针时ISR已经结束,那么其局部变量也就一同消失。
3、在中断中(ISR)如何发送消息?
刚刚说了在中断中不能使用定义的局部变量来保存消息,那么要怎么做才能得到正确消息呢?这里有三种方法:将消息保存在全局变量中、将消息保存在ISR的静态局部变量中、将消息内容冒充指针进行发送。
将消息保存在全局变量中:这种方法无疑是绝对可靠的,缺点是变量定义和ISR代码分离,程序的可读性下降。
将消息保存在ISR的静态局部变量中:由于静态局部变量分配有固定的地址,存放在全局数据区,即使ISR结束了,该变量也仍然存在,其效果等同于全局变量。但是其可以定义在ISR的代码中,因此程序可读性较好。推荐使用。
将消息内容冒充指针进行发送:这种方式是直接从“消息指针”中提取消息内容,但是消息指针为(void*),占用4个字节,只能发送不超过4个字节的“短消息”。当消息的内容本身就为0时,变成空指针。因此在发送之前要确保数据本身不为0。这种方法需要一定技巧,小心使用。用通俗一点的话来说,就是将数据本身强制转换为一个地址来发送出去,接收的时候,同样将该地址再次强制转换为对应的数据即可。使用方式如下图:
发送消息:
接收消息:
4、使用消息邮箱进行通信需要满足的条件?
由于消息邮箱中只能存放一条消息,因此在用消息邮箱通信的时候,要求接收消息的任务总是在等待消息,一得到消息总能在下一个消息产生之前处理完毕,也就是说消息的生产速度总是慢于消息的消费速度。因此,当通信双方的执行均具有周期性(且周期相同)时,消息邮箱时合适的通信工具。
5、使用全局变量和邮箱来进行通信的区别?
全局变量作为一种共享资源,对其访问必须遵守“资源同步”的规则(如关中断)。
全局变量虽然可以实现数据的传输,但是不能实现行为同步:新的数据产生之后并不能自动通知使用者,使用者也不知道当前数据是何时产生(刷新)的,因此全局变量只能用于没有行为同步要求的任务之间,即每次产生的新数据不要求立即使用,甚至可以不被使用。
6、消息邮箱只能一对一的使用?
OSMboxPost()函数只能给一个任务发送消息,如果有多个任务在等待邮箱的消息,那么只有优先级最高的任务才能得到这个消息,并且在将该消息返回以后,就会清除该消息,即消息指针指向“NULL”,因此低优先级低的任务是得不到消息的。如下图:
那么如果一个任务(或者ISR)产生的消息,有多个使用者的时候怎么办呢?这时需要使用另一个函数OSMboxPostOpt()来进行广播发送,发送给所有请求该消息的任务,并且该函数的参数之一opt=OS_POST_OPT_BROADCAST7、请求消息邮箱时的等待时间如何理解?
书中的描述为:当任务等待的时间超过timeout时,可以结束等待状态而进入就绪状态。如果参数timeout设置为0,则表明任务的等待时间为无限长。当信号量/消息等数据无效的时候,则会将等待任务列表中对应任务的位置1,而让任务处于等待状态,并把等待时限timeout保存在任务控制块TCB的OSTCBDly中。
我的理解:通过查看源代码,可以知道,当信号/消息无效的时候(指针指向空,也就是说请求消息的时候,消息还没产生),这时不能返回数据缓冲区的指针,而是设置该事件的状态,同时将等待时间timeout放进当前任务的OSTCBDly中,同时引发任务调度。也就是说,当请求事件(消息)时,若事件无效,那么当前任务的程序段不会继续向下执行,而是保存当前断点,转向其他任务继续执行。如下图:
说明:本文内容大多参考《基于嵌入式实时操作系统程序设计》一书,这本书和《嵌入式实时操作系统UCOS-II原理及应用》不同。前者主要是讲如何使用UCOS,如何在UCOS的基础上写代码(何如划分任务,如何选择通信方式等),后者则类似于对源码的分析(任务调度时怎么进行的)。笔者认为要写好代码,这两本书缺一不可。 -
UcOS-II消息邮箱的使用
2020-10-19 19:07:39文章标题:UcOS-II消息邮箱的使用。中国IT实验室嵌入式开发频道提供最全面的嵌入式开发培训及行业的信息、技术以及相关资料的下载. -
基于消息邮箱的_C_OS_II应用程序设计方法
2015-11-07 13:42:38基于消息邮箱的_C_OS_II应用程序设计方法 -
玩转RT-Thread系列教程(3)--消息邮箱的使用
2021-06-01 10:34:44玩转RT-Thread系列教程(3)–消息邮箱的使用 一、什么是消息邮箱 邮箱服务是实时操作系统中一种典型的线程间通信方法。 RT-Thread 操作系统的邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件...玩转RT-Thread系列教程(3)–消息邮箱的使用
一、什么是消息邮箱
邮箱服务是实时操作系统中一种典型的线程间通信方法。
RT-Thread 操作系统的邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的 4 字节内容
(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针)。
通常来说,邮件收取过程可能是阻塞的,这取决于邮箱中是否有邮件,以及收取邮件时设置的超时时间。
1.发送邮件
当一个线程向邮箱发送邮件时,如果邮箱没满,将把邮件复制到邮箱中。如果邮箱已经满了,发送线程可以设置超时时间,
选择等待挂起或直接返回 - RT_EFULL。如果发送线程选择挂起等待,那么当邮箱中的邮件被收取而空出空间来时,等待挂起的发送线程将被唤醒继续发送。
2.接收邮件
当一个线程从邮箱中接收邮件时,如果邮箱是空的,接收线程可以选择是否等待挂起直到收到新的邮件而唤醒,或可以设置超时时间。
当达到设置的超时时间,邮箱依然未收到邮件时,这个选择超时等待的线程将被唤醒并返回 - RT_ETIMEOUT。如果邮箱中存在邮件,
那么接收线程将复制邮箱中的 4 个字节邮件到接收缓存中。
二、消息邮箱的使用
介绍完了消息邮箱,那么消息邮箱的使用场合是什么呢?邮箱是一种简单的线程间消息传递方式,特点是开销比较低,效率较高。邮箱具备一定的存储功能,能够缓存一定数量的邮件数。
接下来让我们用一段示例代码演示以下消息邮箱的使用:
1.功能设计
线程1会向线程2发送消息,线程2接收到消息存放到邮箱中并串口输出该消息,同时发送消息给线程1,线程1接收到消息存放到邮箱中并串口输出该消息。当运行完2次发送任务时,删除二者邮箱,并串口输出“Mailboxes demo finsh”。
2.代码设计
#include <rtthread.h> #include <rtdevice.h> #include <board.h> /* defined the LED0 pin: PB5 */ #define LED0_PIN GET_PIN(B, 5) /* defined the LED1 pin: PE5 */ #define LED1_PIN GET_PIN(E, 5) /* 邮箱控制块 */ static struct rt_mailbox mb1; /* 用于放邮件的内存池 */ static char mb_pool1[128]; /* 邮箱控制块 */ static struct rt_mailbox mb2; /* 用于放邮件的内存池 */ static char mb_pool2[128]; static rt_uint8_t thread2_stack[512]; //线程栈 static struct rt_thread static_thread; //线程控制块 static char mb_str1[] = "thread1 send information"; static char mb_str2[] = "thread2 send information"; int main(void) { /* set LED0 pin mode to output */ rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT); /* set LED1 pin mode to output */ rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT); rt_pin_mode(KEY0_PIN, PIN_MODE_INPUT_PULLUP); rt_pin_mode(KEY1_PIN, PIN_MODE_INPUT_PULLUP); } //thread1 static void dynamic_entry(void *param) { char *str; static rt_uint8_t cnt; while (cnt < 2) { cnt++; /* 发送 mb_str1 地址到邮箱中 */ rt_mb_send(&mb2, (rt_uint32_t)&mb_str1); rt_kprintf("@thread1 send mb to thread2\r\n"); /* 从邮箱中收取邮件 */ if (rt_mb_recv(&mb1, (rt_ubase_t *)&str, RT_WAITING_FOREVER) == RT_EOK) { rt_kprintf("thread1: get a mail from mailbox1, the content:%s\r\n", str); rt_thread_mdelay(50); } rt_thread_mdelay(50); } /* 执行邮箱对象脱离 */ if(rt_mb_detach(&mb1) != RT_EOK) rt_kprintf("mb1 detach fail\r\n"); } //thread2 static void static_entry(void *param) { char *str; static rt_uint8_t cnt; while (cnt < 2) { cnt++; /* 从邮箱中收取邮件 */ if (rt_mb_recv(&mb2, (rt_ubase_t *)&str, RT_WAITING_FOREVER) == RT_EOK) { rt_kprintf("thread2: get a mail from mailbox2, the content:%s\r\n", str); rt_thread_mdelay(50); rt_kprintf("@thread2 send mb to thread1\r\n"); /* 发送 mb_str2 地址到邮箱中 */ rt_mb_send(&mb1, (rt_uint32_t)&mb_str2); } rt_thread_mdelay(50); } if(rt_mb_detach(&mb2) != RT_EOK) rt_kprintf("mb2 detach fail\r\n"); rt_kprintf("Mailboxes demo finsh\r\n"); } int MailBox_demo(void) { rt_err_t result1, result2; /* 初始化一个 mailbox */ result1 = rt_mb_init(&mb1, "mbt1", /* 名称是 mbt */ &mb_pool1[0], /* 邮箱用到的内存池是 mb_pool */ sizeof(mb_pool1) / 4, /* 邮箱中的邮件数目,因为一封邮件占 4 字节 */ RT_IPC_FLAG_FIFO); /* 采用 FIFO 方式进行线程等待 */ if (result1 != RT_EOK) { rt_kprintf("init mailbox1 failed.\r\n"); return -1; } /* 初始化一个 mailbox */ result2 = rt_mb_init(&mb2, "mbt2", /* 名称是 mbt */ &mb_pool2[0], /* 邮箱用到的内存池是 mb_pool */ sizeof(mb_pool2) / 4, /* 邮箱中的邮件数目,因为一封邮件占 4 字节 */ RT_IPC_FLAG_FIFO); /* 采用 FIFO 方式进行线程等待 */ if (result2 != RT_EOK) { rt_kprintf("init mailbox2 failed.\r\n"); return -1; } // static rt_thread_t thread_id = RT_NULL; thread_id = rt_thread_create("thread1", //名称 dynamic_entry, //线程代码 RT_NULL, //参数 512, //栈大小 14, //优先级 20); //时间片 if (thread_id != RT_NULL) rt_thread_startup(thread_id); //线程进入就绪态 else rt_kprintf("dynamic_thread create failure\r\n"); // rt_thread_init(&static_thread, //线程handle "thread2", //线程名称 static_entry, //线程入口函数 RT_NULL, //线程入口参数 &thread2_stack[0], //线程栈地址 sizeof(thread2_stack), //线程栈大小 15, //线程优先级 10); //线程时间片 rt_thread_startup(&static_thread); //线程进入就绪态 return RT_EOK; } MSH_CMD_EXPORT(MailBox_demo, MailBox_demo);
3.编译、下载、查看结果
通过串口数据我们不难发现,消息邮箱的运行机制,一方发送一方接收。
-
一步一步移植ucos到stm32f103之串口和消息邮箱
2014-10-11 17:48:26与本人写的一步步移植ucos到stm32配套,详细步骤可参考博客http://blog.csdn.net/zhqh100/article/category/2622075 -
电子-ucosii消息邮箱.zip
2019-09-05 17:19:44电子-ucosii消息邮箱.zip,单片机/嵌入式STM32-F0/F1/F2 -
RT-Thread消息邮箱和队列在STM32串口中的应用
2020-06-24 13:18:44使用消息邮箱处理串口消息 rt_thread_app.c: #include "rtthread.h" #include "rt_thread_app.h" #include "bsp_usart.h" static rt_thread_t usart1_thread = RT_NULL; rt_mailbox_t usart1_mail = RT_NULL; ... -
V5-414_RTX实验_消息邮箱.7z
2021-04-15 13:37:23V5-414_RTX实验_消息邮箱.7z -
uCOS消息邮箱实验(基于STC90C516)
2013-10-24 22:46:57uCOS消息邮箱实验(基于STC90C516) -
基于μC/OS—Ⅱ消息邮箱监控的任务设计及应用 (2007年)
2021-04-25 22:28:46通过引入实时操作系统μC/OS-Ⅱ,给每个核心任务分配不同优先级,利用操作系统提供的消息邮箱服务,由监控任务通过收发消息管理各个任务的正常运行.该方法为仪器仪表的软件系统设计提供了一种任务管理新途径,有利... -
消息邮箱和消息队列
2017-06-01 15:59:31每个邮箱包含一个用于发送消息的消息队列和一个用来接受消息的消息队列。由于是在共享存储区域,因此它对每个任务都是可见的。 而一般的消息队列,还可用来处理任务与外部事件之间的通信。比如一个按键消息。然后... -
Ucosii消息邮箱使用
2017-07-18 09:29:49//设置任务优先级 #define LED_TASK_Prio 6 #define LED1_TASK_Prio 5 ...消息邮箱创建函数: OS_EVENT *OSMboxCreate (void *msg) 请求消息邮箱函数: void *OSMboxPend (OS_EVENT *pevent, INT16U tim -
UCOS学习日(6)互斥信号量,消息邮箱,消息队列
2019-03-31 21:40:33消息队列可以在任务之间传递多条消息,消息队列又多了一个消息队列控制块,可以说是消息邮箱的升级版也不为过。 消息队列控制块 OSQStart 是消息队列的起始地址 OSQSize 是消息队列的大小 OSQOut ... -
从零开始学习UCOSII操作系统10--消息邮箱
2018-04-03 19:08:54从零开始学习UCOSII操作系统10--消息邮箱 1、什么是消息邮箱? UCOSII中的另一种的通信机制,可以使得一个任务或者中断服务子程序向另一个任务发送一个指针型的变量,通常指针指向一个包含了消息的特定数据结构... -
嵌入式实时操作系统uC/os-II(十四)-消息邮箱及其操作
2020-09-08 16:00:52消息邮箱的定义 把数据缓冲区的指针赋给事件控制块成员OSEventPtr,同时使事件控制块的成员OSEventType为常数OS_EVENT_TYPE_MBOX,则该事件控制块就叫做消息邮箱。 消息邮箱的数据结构如图6-1所示。 图6-1 ... -
ucosii中消息队列、消息邮箱、信号量的区别
2018-06-26 10:09:022、但由于消息邮箱里只能存放一条消息,所以使用消息邮箱进行任务的同步时,需要满足一个条件:消息的产生速度总要慢于消息的消费速度,即被控制任务总是在等待消息,否则会导致消息丢失。3、若遇到出现消息的产生... -
消息邮箱及其操作_读书笔记_7
2016-10-08 23:18:064.4消息邮箱及其操作 4.4.1消息邮箱 (1)任务与任务之间要传递数据,在存储器中建立一个数据缓冲区,将传递的的数据放到数据缓冲区,以此实现任务间的通信。 (2)消息邮箱是在两个需要通信的任务之间通过传递数据... -
ucos 消息邮箱解析--
2012-07-12 13:59:24本文结合源代码,详细说明了ucos-ii 消息邮箱使用的注意事项,重点分析了消息堆积发生在何处,以及广播方式发送时的注意事项.本文是原创. 如您积分不够,也可发送邮件至my_friend_ship@163.com索取,额会及时发送到您邮箱... -
ucosii 消息邮箱使用!!
2016-01-25 16:34:24ucosii消息邮箱使用大致分三步: 1,OS_EVENT *Str_Box; Str_Box=OSMboxCreate(0); 2,char s;或者 char *s; OSMboxPost (Str_Box,(void*)s); 3,对应的 char s; ss=(char)OSMboxPend (Str_Box, 0, &err); 或者 ... -
uCOS中的消息邮箱(:task与task之间传递一个数据(数据缓冲区))
2017-06-07 11:01:53程序需求,需要完成task与task之间 字符数组的数据传输 ...图1:消息邮箱测试中的 向消息邮箱发送消息(OSMboxPost(Str_Box,(void*)rec);//发送消息--(向 消息邮箱_msg_key发送消息) ),实际项目需求的