邮箱是UCOS通信机制, 它可以将一个任务或一个中断服务程序发送到另一个任务void *OSEventPtr指针变量指向特定的数据结构.即通过该指针传递消息.
本文使用的UCOS版本:V2.91.
事件控制块
说到消息邮箱, 首先需要解释UCOS事件控制块.UCOS信号量、消息邮箱、消息队列.都是使用OS_EVENT识别结构. 结构成员变量OSEventType不同类型的事件标识(信号量、消息队列等)。.
OS_EVENT *OSMboxCreate (void *pmsg) { OS_EVENT *pevent; #if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */ OS_CPU_SR cpu_sr = 0u; #endif #ifdef OS_SAFETY_CRITICAL_IEC61508 if (OSSafetyCriticalStartFlag == OS_TRUE) { OS_SAFETY_CRITICAL_EXCEPTION(); } #endif if (OSIntNesting > 0u) { /* See if called from ISR ... */ return ((OS_EVENT *)0); /* ... can't CREATE from an ISR */ } OS_ENTER_CRITICAL(); /* 在事件控制块链表上找到可用的事件控制块,(实质为 OSEventTbl[OS_MAX_EVENTS]数组中的一个可用项.) 设置对应的pevent结构体成员变量的属性值. 最后调用OS_EventWaitListInit清除 OSEventGrp OSEventTbl值. OSMboxCreate执行完成后,我们得到了一个OS_EVENT*类型的消息邮箱. */ pevent = OSEventFreeList; /* Get next free event control block */ if (OSEventFreeList != (OS_EVENT *)0) { /* See if pool of free ECB pool was empty */ OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr; } OS_EXIT_CRITICAL(); if (pevent != (OS_EVENT *)0) { pevent->OSEventType = OS_EVENT_TYPE_MBOX; pevent->OSEventCnt = 0u; pevent->OSEventPtr = pmsg; /* Deposit message in event control block */ #if OS_EVENT_NAME_EN > 0u pevent->OSEventName = (INT8U *)(void *)"?"; #endif OS_EventWaitListInit(pevent); } return (pevent); /* Return pointer to event control block */ }
创建新闻邮箱
新闻邮箱的创建主要是从事件控制块数组中取出空闲事件控制块. 设置事件控制块的相关属性.
OS_EVENT *OSMboxCreate (void *pmsg) { OS_EVENT *pevent; #if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */ OS_CPU_SR cpu_sr = 0u; #endif #ifdef OS_SAFETY_CRITICAL_IEC61508 if (OSSafetyCriticalStartFlag == OS_TRUE) { OS_SAFETY_CRITICAL_EXCEPTION(); } #endif if (OSIntNesting > 0u) { /* See if called from ISR ... */ return ((OS_EVENT *)0); /* ... can't CREATE from an ISR */ } OS_ENTER_CRITICAL(); /* 在事件控制块链表上找到可用的事件控制块,(实质为 OSEventTbl[OS_MAX_EVENTS]数组中的一个可用项.) 设置对应的pevent结构体成员变量的属性值. 最后调用OS_EventWaitListInit清除 OSEventGrp OSEventTbl值. OSMboxCreate执行完成后,我们得到了一个OS_EVENT*类型的消息邮箱. */ pevent = OSEventFreeList; /* Get next free event control block */ if (OSEventFreeList != (OS_EVENT *)0) { /* See if pool of free ECB pool was empty */ OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr; } OS_EXIT_CRITICAL(); if (pevent != (OS_EVENT *)0) { pevent->OSEventType = OS_EVENT_TYPE_MBOX; pevent->OSEventCnt = 0u; pevent->OSEventPtr = pmsg; /* Deposit message in event control block */ #if OS_EVENT_NAME_EN > 0u pevent->OSEventName = (INT8U *)(void *)"?"; #endif OS_EventWaitListInit(pevent); } return (pevent); /* Return pointer to event control block */ }
消息邮箱请求
OSMboxPend代码中以注释的形式反映了函数的详细解释.
void *OSMboxPend (OS_EVENT *pevent, INT32U timeout, INT8U *perr) { void *pmsg; #if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */ OS_CPU_SR cpu_sr = 0u; #endif #ifdef OS_SAFETY_CRITICAL if (perr == (INT8U *)0) { OS_SAFETY_CRITICAL_EXCEPTION(); } #endif #if OS_ARG_CHK_EN > 0u if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */ *perr = OS_ERR_PEVENT_NULL; return ((void *)0); } #endif if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* Validate event block type */ *perr = OS_ERR_EVENT_TYPE; return ((void *)0); } if (OSIntNesting > 0u) { /* See if called from ISR ... */ *perr = OS_ERR_PEND_ISR; /* ... can't PEND from an ISR */ return ((void *)0); } if (OSLockNesting > 0u) { /* See if called with scheduler locked ... */ *perr = OS_ERR_PEND_LOCKED; /* ... can't PEN when locked */
return ((void *)0);
}
OS_ENTER_CRITICAL();
/*
如果有可用的消息,就返回消息指针, ucos的消息邮箱采用的是直接传递指针的形式,没有使用memcpy.
如果在发送消息的时候 pevent->OSEventPtr指向的内存是使用malloc申请的.记得在接收处理完消息释放内存.
*/
pmsg = pevent->OSEventPtr;
if (pmsg != (void *)0) { /* See if there is already a message */
pevent->OSEventPtr = (void *)0; /* Clear the mailbox */
OS_EXIT_CRITICAL();
*perr = OS_ERR_NONE;
return (pmsg); /* Return the message received (or NULL) */
}
/*
如果还没有消息,会运行到这里. 设置当前任务控制块的相关状态属性.
OS_EventTaskWait函数将当前任务的优先级放入此消息邮箱的结构体成员变量,
并将此任务的优先级从就绪表中移除.然后触发一次任务调度.CPU转到其他任务运行.
此任务即处于了挂起等待消息邮箱的状态.
*/
OSTCBCur->OSTCBStat |= OS_STAT_MBOX; /* Message not available, task will pend */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK;
OSTCBCur->OSTCBDly = timeout; /* Load timeout in TCB */
OS_EventTaskWait(pevent); /* Suspend task until event or timeout occurs */
OS_EXIT_CRITICAL();
// 执行完OS_Sched();任务被切换.
OS_Sched(); /* Find next highest priority task ready to run */
/*
当收到消息或超时时间到导致任务被放入就绪表, 且被调度器调度时,会从这里开始接着运行.
先检查pend到消息的原因, 如果是等待超时,就将此任务的优先级从信号量的等待事件中移除.
最后设置任务的相关状态.
*/
OS_ENTER_CRITICAL();
switch (OSTCBCur->OSTCBStatPend) { /* See if we timed-out or aborted */
case OS_STAT_PEND_OK:
pmsg = OSTCBCur->OSTCBMsg;
*perr = OS_ERR_NONE;
break;
case OS_STAT_PEND_ABORT:
pmsg = (void *)0;
*perr = OS_ERR_PEND_ABORT; /* Indicate that we aborted */
break;
case OS_STAT_PEND_TO:
default:
OS_EventTaskRemove(OSTCBCur, pevent);
pmsg = (void *)0;
*perr = OS_ERR_TIMEOUT; /* Indicate that we didn't get event within TO */
break;
}
OSTCBCur->OSTCBStat = OS_STAT_RDY; /* Set task status to ready */
OSTCBCur->OSTCBStatPend = OS_STAT_PEND_OK; /* Clear pend status */
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; /* Clear event pointers */
#if (OS_EVENT_MULTI_EN > 0u)
OSTCBCur->OSTCBEventMultiPtr = (OS_EVENT **)0;
#endif
OSTCBCur->OSTCBMsg = (void *)0; /* Clear received message */
OS_EXIT_CRITICAL();
return (pmsg); /* Return received message */
}
消息邮箱发送
INT8U OSMboxPost (OS_EVENT *pevent,
void *pmsg)
{
#if OS_CRITICAL_METHOD == 3u /* Allocate storage for CPU status register */
OS_CPU_SR cpu_sr = 0u;
#endif
#if OS_ARG_CHK_EN > 0u
if (pevent == (OS_EVENT *)0) { /* Validate 'pevent' */
return (OS_ERR_PEVENT_NULL);
}
if (pmsg == (void *)0) { /* Make sure we are not posting a NULL pointer */
return (OS_ERR_POST_NULL_PTR);
}
#endif
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { /* Validate event block type */
return (OS_ERR_EVENT_TYPE);
}
OS_ENTER_CRITICAL();
/*
OSEventGrp != 0 时表示有其他任务在等待此消息. OS_EventTaskRdy函数的作用是通过pevent的结构体成员
找到等待此信号量的那个任务(假定为任务A),并将任务A的优先级放入就绪表,且从等待事件表移除.然后
触发一次任务调度.
*/
if (pevent->OSEventGrp != 0u) { /* See if any task pending on mailbox */
/* Ready HPT waiting on event */
(void)OS_EventTaskRdy(pevent, pmsg, OS_STAT_MBOX, OS_STAT_PEND_OK);
OS_EXIT_CRITICAL();
OS_Sched(); /* Find highest priority task ready to run */
return (OS_ERR_NONE);
}
/*
消息邮箱满,直接返回. 因为消息邮箱只能携带一条消息.pevent->OSEventPtr非空就表示有数据.消息邮箱满.
*/
if (pevent->OSEventPtr != (void *)0) { /* Make sure mailbox doesn't already have a msg */
OS_EXIT_CRITICAL();
return (OS_ERR_MBOX_FULL);
}
//保存要发送的消息到消息邮箱事件控制块指针.
pevent->OSEventPtr = pmsg; /* Place message in mailbox */
OS_EXIT_CRITICAL();
return (OS_ERR_NONE);
}
问答环节
1.问: UCOS中消息邮箱有哪些应用场景. 答: 消息邮箱一般用于任务与任务之间,或者是中断向任务传递消息.使用消息邮箱也间接性的做到了共享数据的互斥 比如任务A采集各个传感器数据,任务B上送各个传感器数据.直接在任务A,B使用全局变量的形式处理传感器数据.会出现 可能A采集了一半,跳转到了B运行,造成数据一致性缺失的问题. 如果是任务A发送消息给任务B,就不会有这种情况发生.
2.问: UCOS消息邮箱可以传递哪些类型的数据 答: UCOS消息邮箱可以传递任意类型的数据, 消息邮箱参数是一个void *类型的指针,它可以执行任意数据类型的数据. 如果只是传递一个简单的数据,可以使用基本数据类型,如果传递的数据较多,可以自定义一个结构体.接收方按照结构体约定 的数据格式解析即可.