UC/OS-II-6.1
0赞
程序清单 L6.16 向邮箱中发送一条消息
INT8U OSMboxPost (OS_EVENT *pevent, void *msg)
{
OS_ENTER_CRITICAL();
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { (1)
OS_EXIT_CRITICAL();
return (OS_ERR_EVENT_TYPE);
}
if (pevent->OSEventGrp) { (2)
OSEventTaskRdy(pevent, msg, OS_STAT_MBOX); (3)
OS_EXIT_CRITICAL();
OSSched(); (4)
return (OS_NO_ERR);
} else {
if (pevent->OSEventPtr != (void *)0) { (5)
OS_EXIT_CRITICAL();
return (OS_MBOX_FULL);
} else {
pevent->OSEventPtr = msg; (6)
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
}
}
应用程序也可以以无等待的方式从邮箱中得到消息。这可以通过程序清单 L6.17中的OSMboxAccept()函数来实现。OSMboxAccept()函数开始也是检查事件控制块是否是由OSMboxCreate()函数建立的 [L6.17(1)]。接着,它得到邮箱中的当前内容[L6.17(2)],并判断是否有消息是可用的[L6.17(3)]。如果邮箱中有消息,就把邮箱清空[L6.17(4)],而邮箱中原来指向消息的指针被返回给OSMboxAccept()的调用函数[L6.17(5)]。OSMboxAccept()函数的调用函数必须检查该返回值是否为NULL。如果该值是NULL,说明邮箱是空的,没有可用的消息。如果该值是非NULL值,说明邮箱中有消息可用,而且该调用函数已经得到了该消息。中断服务子程序在试图得到一个消息时,应该使用OSMboxAccept()函数,而不能使用OSMboxPend()函数。
void *OSMboxAccept (OS_EVENT *pevent)
{
void *msg;
OS_ENTER_CRITICAL();
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { (1)
OS_EXIT_CRITICAL();
return ((void *)0);
}
msg = pevent->OSEventPtr; (2)
if (msg != (void *)0) { (3)
pevent->OSEventPtr = (void *)0; (4)
}
OS_EXIT_CRITICAL();
return (msg); (5)
}
OSMboxQuery()函数使应用程序可以随时查询一个邮箱的当前状态。程序清单 L6.18是该函数的源代码。它需要两个参数:一个是指向邮箱的指针pevent。该指针是在建立该邮箱时,由OSMboxCreate()函数返回的;另一个是指向用来保存有关邮箱的信息的OS_MBOX_DATA(见uCOS_II.H)数据结构的指针pdata。在调用OSMboxCreate()函数之前,必须先定义该结构变量,用来保存有关邮箱的信息。之所以定义一个新的数据结构,是因为这里关心的只是和特定邮箱有关的内容,而非整个OS_EVENT数据结构的内容。后者还包含了另外两个域(.OSEventCnt和.OSEventType),而OS_MBOX_DATA只包含邮箱中的消息指针(.OSMsg)和该邮箱现有的等待任务列表(.OSEventTbl[]和.OSEventGrp)。
INT8U OSMboxQuery (OS_EVENT *pevent, OS_MBOX_DATA *pdata)
{
INT8U i;
INT8U *psrc;
INT8U *pdest;
OS_ENTER_CRITICAL();
if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { (1)
OS_EXIT_CRITICAL();
return (OS_ERR_EVENT_TYPE);
}
pdata->OSEventGrp = pevent->OSEventGrp; (2)
psrc = &pevent->OSEventTbl[0];
pdest = &pdata->OSEventTbl[0];
for (i = 0; i < OS_EVENT_TBL_SIZE; i++) {
*pdest++ = *psrc++;
}
pdata->OSMsg = pevent->OSEventPtr; (3)
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
一个邮箱可以被用作二值的信号量。首先,在初始化时,将邮箱设置为一个非零的指针(如 void *1)。这样,一个任务可以调用OSMboxPend()函数来请求一个信号量,然后通过调用OSMboxPost()函数来释放一个信号量。程序清单 L6.19说明了这个过程是如何工作的。如果用户只需要二值信号量和邮箱,这样做可以节省代码空间。这时可以将OS_SEM_EN设置为0,只使用邮箱就可以了。
OS_EVENT *MboxSem;
void Task1 (void *pdata)
{
INT8U err;
for (;;) {
OSMboxPend(MboxSem, 0, &err); /* 获得对资源的访问权 */
.
. /* 任务获得信号量,对资源进行访问 */
.
OSMboxPost(MboxSem, (void*)1); /* 释放对资源的访问权 */
}
}
邮箱的等待超时功能可以被用来模仿OSTimeDly()函数的延时,如程序清单 L6.20所示。如果在指定的时间段TIMEOUT内,没有消息到来,Task1()函数将继续执行。这和OSTimeDly(TIMEOUT)功能很相似。但是,如果Task2()在指定的时间结束之前,向该邮箱发送了一个“哑”消息,Task1()就会提前开始继续执行。这和调用OSTimeDlyResume()函数的功能是一样的。注意,这里忽略了对返回的消息的检查,因为此时关心的不是得到了什么样的消息。
OS_EVENT *MboxTimeDly;
void Task1 (void *pdata)
{
INT8U err;
for (;;) {
OSMboxPend(MboxTimeDly, TIMEOUT, &err); /* 延时该任务 */
.
. /* 延时结束后执行的代码 */
.
}
}
void Task2 (void *pdata)
{
INT8U err;
for (;;) {
OSMboxPost(MboxTimeDly, (void *)1); /* 取消任务1的延时 */
.
.
}
}
消息队列是µC/OS-II中另一种通讯机制,它可以使一个任务或者中断服务子程序向另一个任务发送以指针方式定义的变量。因具体的应用有所不同,每个指针指向的数据结构变量也有所不同。为了使用µC/OS-II的消息队列功能,需要在OS_CFG.H 文件中,将OS_Q_EN常数设置为1,并且通过常数OS_MAX_QS来决定µC/OS-II支持的最多消息队列数。
图 F6.7 任务、中断服务子程序和消息队列之间的关系——Figure 6.7
图F6.8 用于消息队列的数据结构——Figure 6.8
图F6.9 空闲队列控制块链表——Figure 6.9
图F6.10 消息队列是一个由指针组成的循环缓冲区——Figure 6.10
程序清单 L6.21是OSQCreate()函数的源代码。该函数需要一个指针数组来容纳指向各个消息的指针。该指针数组必须声名为void类型。
OS_EVENT *OSQCreate (void **start, INT16U size)
{
OS_EVENT *pevent;
OS_Q *pq;
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; (1)
if (OSEventFreeList != (OS_EVENT *)0) {
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr; (2)
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) {
OS_ENTER_CRITICAL();
pq = OSQFreeList; (3)
if (OSQFreeList != (OS_Q *)0) {
OSQFreeList = OSQFreeList->OSQPtr;
}
OS_EXIT_CRITICAL();
if (pq != (OS_Q *)0) {
pq->OSQStart = start; (4)
pq->OSQEnd = &start[size];
pq->OSQIn = start;
pq->OSQOut = start;
pq->OSQSize = size;
pq->OSQEntries = 0;
pevent->OSEventType = OS_EVENT_TYPE_Q; (5)
pevent->OSEventPtr = pq; (6)
OSEventWaitListInit(pevent); (7)
} else {
OS_ENTER_CRITICAL();
pevent->OSEventPtr = (void *)OSEventFreeList; (8)
OSEventFreeList = pevent;
OS_EXIT_CRITICAL();
pevent = (OS_EVENT *)0;
}
}
return (pevent); (9)
}
程序清单 L6.22是OSQPend()函数的源代码。OSQPend()函数首先检查事件控制块是否是由OSQCreate()函数建立的[L6.22(1)],接着,该函数检查消息队列中是否有消息可用(即.OSQEntries是否大于0)[L6.22(2)]。如果有,OSQPend()函数将指向消息的指针复制到msg变量中,并让.OSQOut指针指向队列中的下一个单元[L6.22(3)],然后将队列中的有效消息数减1 [L6.22(4)]。因为消息队列是一个循环的缓冲区,OSQPend()函数需要检查.OSQOut是否超过了队列中的最后一个单元 [L6.22(5)]。当发生这种越界时,就要将.OSQOut重新调整到指向队列的起始单元 [L6.22(6)]。这是我们调用OSQPend()函数时所期望的,也是执行OSQPend()函数最快的路径。
void *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
{
void *msg;
OS_Q *pq;
OS_ENTER_CRITICAL();
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (1)
OS_EXIT_CRITICAL();
*err = OS_ERR_EVENT_TYPE;
return ((void *)0);
}
pq = pevent->OSEventPtr;
if (pq->OSQEntries != 0) { (2)
msg = *pq->OSQOut++; (3)
pq->OSQEntries--; (4)
if (pq->OSQOut == pq->OSQEnd) { (5)
pq->OSQOut = pq->OSQStart; (6)
}
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
} else if (OSIntNesting > 0) { (7)
OS_EXIT_CRITICAL();
*err = OS_ERR_PEND_ISR;
} else {
OSTCBCur->OSTCBStat |= OS_STAT_Q; (8)
OSTCBCur->OSTCBDly = timeout;
OSEventTaskWait(pevent);
OS_EXIT_CRITICAL();
OSSched(); (9)
OS_ENTER_CRITICAL();
if ((msg = OSTCBCur->OSTCBMsg) != (void *)0) { (10)
OSTCBCur->OSTCBMsg = (void *)0;
OSTCBCur->OSTCBStat = OS_STAT_RDY;
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; (11)
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
} else if (OSTCBCur->OSTCBStat & OS_STAT_Q) { (12)
OSEventTO(pevent); (13)
OS_EXIT_CRITICAL();
msg = (void *)0; (14)
*err = OS_TIMEOUT;
} else {
msg = *pq->OSQOut++; (15)
pq->OSQEntries--;
if (pq->OSQOut == pq->OSQEnd) {
pq->OSQOut = pq->OSQStart;
}
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; (16)
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
}
}
return (msg); (17)
}
程序清单 L6.23是OSQPost()函数的源代码。在确认事件控制块是消息队列后 [L6.23(1)],OSQPost()函数检查是否有任务在等待该消息队列中的消息[L6.23(2)]。当事件控制块的.OSEventGrp域为非0值时,说明该消息队列的等待任务列表中有任务。这时,调用OSEventTaskRdy()函数 [见6.02节,使一个任务进入就绪状态,OSEventTaskRdy()]从列表中取出最高优先级的任务[L6.23(3)],并将它置于就绪状态。然后调用函数OSSched() [L6.23(4)]进行任务的调度。如果上面取出的任务的优先级在整个系统就绪的任务里也是最高的,而且OSQPost()函数不是中断服务子程序调用的,就执行任务切换,该最高优先级任务被执行。否则的话,OSSched()函数直接返回,调用OSQPost()函数的任务继续执行。
INT8U OSQPost (OS_EVENT *pevent, void *msg)
{
OS_Q *pq;
OS_ENTER_CRITICAL();
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (1)
OS_EXIT_CRITICAL();
return (OS_ERR_EVENT_TYPE);
}
if (pevent->OSEventGrp) { (2)
OSEventTaskRdy(pevent, msg, OS_STAT_Q); (3)
OS_EXIT_CRITICAL();
OSSched(); (4)
return (OS_NO_ERR);
} else {
pq = pevent->OSEventPtr;
if (pq->OSQEntries >= pq->OSQSize) { (5)
OS_EXIT_CRITICAL();
return (OS_Q_FULL);
} else {
*pq->OSQIn++ = msg; (6)
pq->OSQEntries++;
if (pq->OSQIn == pq->OSQEnd) {
pq->OSQIn = pq->OSQStart;
}
OS_EXIT_CRITICAL();
}
return (OS_NO_ERR);
}
}
OSQPostFront()函数和OSQPost()基本上是一样的,只是在插入新的消息到消息队列中时,使用.OSQOut作为指向下一个插入消息的单元的指针,而不是.OSQIn。程序清单 L6.24是它的源代码。值得注意的是,.OSQOut指针指向的是已经插入了消息指针的单元,所以再插入新的消息指针前,必须先将.OSQOut指针在消息队列中前移一个单元。如果.OSQOut指针指向的当前单元是队列中的第一个单元[L6.24(1)],这时再前移就会发生越界,需要特别地将该指针指向队列的末尾[L6.24(2)]。由于.OSQEnd指向的是消息队列中最后一个单元的下一个单元,因此.OSQOut必须被调整到指向队列的有效范围内[L6.24(3)]。因为QSQPend()函数取出的消息是由OSQPend()函数刚刚插入的,因此OSQPostFront()函数实现了一个LIFO队列。
INT8U OSQPostFront (OS_EVENT *pevent, void *msg)
{
OS_Q *pq;
OS_ENTER_CRITICAL();
if (pevent->OSEventType != OS_EVENT_TYPE_Q) {
OS_EXIT_CRITICAL();
return (OS_ERR_EVENT_TYPE);
}
if (pevent->OSEventGrp) {
OSEventTaskRdy(pevent, msg, OS_STAT_Q);
OS_EXIT_CRITICAL();
OSSched();
return (OS_NO_ERR);
} else {
pq = pevent->OSEventPtr;
if (pq->OSQEntries >= pq->OSQSize) {
OS_EXIT_CRITICAL();
return (OS_Q_FULL);
} else {
if (pq->OSQOut == pq->OSQStart) { (1)
pq->OSQOut = pq->OSQEnd; (2)
}
pq->OSQOut--; (3)
*pq->OSQOut = msg;
pq->OSQEntries++;
OS_EXIT_CRITICAL();
}
return (OS_NO_ERR);
}
}
如果试图从消息队列中取出一条消息,而此时消息队列又为空时,也可以不让调用任务等待而直接返回调用函数。这个操作可以调用OSQAccept()函数来完成。程序清单 L6.25是该函数的源代码。OSQAccept()函数首先查看pevent指向的事件控制块是否是由OSQCreate()函数建立的[L6.25(1)],然后它检查当前消息队列中是否有消息[L6.25(2)]。如果消息队列中有至少一条消息,那么就从.OSQOut指向的单元中取出消息[L6.25(3)]。OSQAccept()函数的调用函数需要对OSQAccept()返回的指针进行检查。如果该指针是NULL值,说明消息队列是空的,其中没有消息可以 [L6.25(4)]。否则的话,说明已经从消息队列中成功地取得了一条消息。当中断服务子程序要从消息队列中取消息时,必须使用OSQAccept()函数,而不能使用OSQPend()函数。
void *OSQAccept (OS_EVENT *pevent)
{
void *msg;
OS_Q *pq;
OS_ENTER_CRITICAL();
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (1)
OS_EXIT_CRITICAL();
return ((void *)0);
}
pq = pevent->OSEventPtr;
if (pq->OSQEntries != 0) { (2)
msg = *pq->OSQOut++; (3)
pq->OSQEntries--;
if (pq->OSQOut == pq->OSQEnd) {
pq->OSQOut = pq->OSQStart;
}
} else {
msg = (void *)0; (4)
}
OS_EXIT_CRITICAL();
return (msg);
}
OSQFlush()函数允许用户删除一个消息队列中的所有消息,重新开始使用。程序清单 L6.26是该函数的源代码。和前面的其它函数一样,该函数首先检查pevent指针是否是执行一个消息队列[L6.26(1)],然后将队列的插入指针和取出指针复位,使它们都指向队列起始单元,同时,将队列中的消息数设为0 [L6.26(2)]。这里,没有检查该消息队列的等待任务列表是否为空,因为只要该等待任务列表不空,.OSQEntries就一定是0。唯一不同的是,指针.OSQIn和.OSQOut此时可以指向消息队列中的任何单元,不一定是起始单元。
INT8U OSQFlush (OS_EVENT *pevent)
{
OS_Q *pq;
OS_ENTER_CRITICAL();
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (1)
OS_EXIT_CRITICAL();
return (OS_ERR_EVENT_TYPE);
}
pq = pevent->OSEventPtr;
pq->OSQIn = pq->OSQStart; (2)
pq->OSQOut = pq->OSQStart;
pq->OSQEntries = 0;
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
OSQQuery()函数使用户可以查询一个消息队列的当前状态。程序清单 L6.27是该函数的源代码。OSQQuery()需要两个参数:一个是指向消息队列的指针pevent。它是在建立一个消息队列时,由OSQCreate()函数返回的;另一个是指向OS_Q_DATA(见uCOS_II.H)数据结构的指针pdata。该结构包含了有关消息队列的信息。在调用OSQQuery()函数之前,必须先定义该数据结构变量。OS_Q_DATA结构包含下面的几个域:
INT8U OSQQuery (OS_EVENT *pevent, OS_Q_DATA *pdata)
{
OS_Q *pq;
INT8U i;
INT8U *psrc;
INT8U *pdest;
OS_ENTER_CRITICAL();
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (1)
OS_EXIT_CRITICAL();
return (OS_ERR_EVENT_TYPE);
}
pdata->OSEventGrp = pevent->OSEventGrp; (2)
psrc = &pevent->OSEventTbl[0];
pdest = &pdata->OSEventTbl[0];
for (i = 0; i < OS_EVENT_TBL_SIZE; i++) {
*pdest++ = *psrc++;
}
pq = (OS_Q *)pevent->OSEventPtr;
if (pq->OSQEntries > 0) { (3)
pdata->OSMsg = pq->OSQOut; (4)
} else {
pdata->OSMsg = (void *)0; (5)
}
pdata->OSNMsgs = pq->OSQEntries; (6)
pdata->OSQSize = pq->OSQSize;
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
}
在控制系统中,经常要频繁地读取模拟量的值。这时,可以先建立一个定时任务OSTimeDly() [见5.00节,延时一个任务,OSTimeDly()],并且给出希望的抽样周期。然后,如图 F6.11所示,让A/D采样的任务从一个消息队列中等待消息。该程序最长的等待时间就是抽样周期。当没有其它任务向该消息队列中发送消息时,A/D采样任务因为等待超时而退出等待状态并进行执行。这就模仿了OSTimeDly()函数的功能。
图 F6.11 读模拟量输入——Figure 6.11
在消息队列初始化时,可以将消息队列中的多个指针设为非NULL值(如void* 1),来实现计数信号量的功能。这里,初始化为非NULL值的指针数就是可用的资源数。系统中的任务可以通过OSQPend()来请求“信号量”,然后通过调用OSQPost()来释放“信号量”,如程序清单 L6.28。如果系统中只使用了计数信号量和消息队列,使用这种方法可以有效地节省代码空间。这时将OS_SEM_EN设为0,就可以不使用信号量,而只使用消息队列。值得注意的是,这种方法为共享资源引入了大量的指针变量。也就是说,为了节省代码空间,牺牲了RAM空间。另外,对消息队列的操作要比对信号量的操作慢,因此,当用计数信号量同步的信号量很多时,这种方法的效率是非常低的。
OS_EVENT *QSem;
void *QMsgTbl[N_RESOURCES]
void main (void)
{
OSInit();
.
.
QSem = OSQCreate(&QMsgTbl[0], N_RESOURCES);
for (i = 0; i < N_RESOURCES; i++) {
OSQPost(Qsem, (void *)1);
}
.
.
OSTaskCreate(Task1, .., .., ..);
.
.
OSStart();
}
void Task1 (void *pdata)
{
INT8U err;
for (;;) {
OSQPend(&QSem, 0, &err); /* 得到对资源的访问权 */
.
. /* 任务获得信号量,对资源进行访问 */
.
OSMQPost(QSem, (void*)1); /* 释放对资源的访问权 */
}
}
