• 沒有找到結果。

Mailbox

在文檔中 µC/OS-II Goals Preface (頁 147-153)

Message

Figure 6-6, Relationship between tasks, ISRs and a message mailbox.

6.06.01 Creating a Mailbox, OSMboxCreate()

The code to create a mailbox is shown in listing 6.14 and is basically identical to OSSemCreate() except that the ECB type is set to OS_EVENT_TYPE_MBOX L6.14(1) and, instead of using the .OSEventCnt field, we use the .OSEventPtr field to hold the message pointer L6.14(2).

OSMboxCreate() returns a pointer to the ECB L6.14(3). This pointer MUST be used in subsequent calls to access the mailbox (OSMboxPend(), OSMboxPost(), OSMboxAccept() and OSMboxQuery() ). The pointer is basically used as the mailbox handle. Note that if there were no more ECBs, OSMboxCreate() would have returned a NULL pointer.

You should note that once a mailbox has been created, it cannot be deleted. It would be ‘dangerous’to delete a message mailbox object if tasks were waiting on the mailbox.

OS_EVENT *OSMboxCreate (void *msg) {

OS_EVENT *pevent;

OS_ENTER_CRITICAL();

pevent = OSEventFreeList;

if (OSEventFreeList != (OS_EVENT *)0) {

OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;

}

OS_EXIT_CRITICAL();

if (pevent != (OS_EVENT *)0) {

pevent->OSEventType = OS_EVENT_TYPE_MBOX; (1) pevent->OSEventPtr = msg; (2) OSEventWaitListInit(pevent);

}

return (pevent); (3) }

Listing 6.14, Creating a mailbox.

6.06.02 Waiting for a message at a Mailbox, OSMboxPend()

The code to wait for a message to arrive at a mailbox is shown in listing 6.15. Again, the code is very similar to OSSemPend() so I will only discuss the differences. OSMboxPend() verifies that the ECB being pointed to by pevent has been created by OSMboxCreate() L6.15(1). A message is available when .OSEventPtr contains a non-NULL pointer L6.15(2). In this case, OSMboxPend() stores the pointer to the message in msg and places a NULL-pointer in .OSEventPtr to empty the mailbox L6.15(3). Again, this is the outcome you are looking for. This also happens to be the fastest path through OSMboxPend().

If a message is not available (.OSEventPtr contains a NULL-pointer), we check to see if the function was called by an ISR L6.15(4). As with OSSemPend(), you should not call OSMboxPend() from an ISR because an ISR cannot be made to wait. Again, I decided to add this check just in case. However, if the message is in fact available, the call to OSMboxPend() would be successful even if called from an ISR!

If a message is not available then the calling task must be suspended until either a message is posted or the specified timeout period expires L6.15(5). When a message is posted to the mailbox (or the timeout period expires) and the task that called OSMboxPend() is again the highest priority task then OSSched() returns. OSMboxPend() checks to see if a message was placed in the t ask’s TCB by OSMboxPost() L6.15(6). If this is the case, the call is successful and the message is returned to the caller. Note that we again need to clear the mailbox’s content by placing a NULL-pointer in .OSEventPtr.

A timeout is detected by looking at the .OSTCBStat field in the task’s TCB to see if the OS_STAT_MBOX bit is still set. A timeout occurred when the bit is set L6.15(7). The task is removed from the mailbox’s wait list by calling OSEventTO() L6.15(8). Note that the returned pointer is set to NULL L6.15(9) because there was no message. If the status flag in the task’s TCB doesn’t have the OS_STAT_MBOX bit set then a message must have been sent. The task that called OSMboxPend() will thus receive the pointer to the message L6.15(10). Also, the link to the ECB is removed L6.15(11).

void *OSMboxPend (OS_EVENT *pevent, INT16U timeout, INT8U *err) {

void *msg;

OS_ENTER_CRITICAL();

if (pevent->OSEventType != OS_EVENT_TYPE_MBOX) { (1) OS_EXIT_CRITICAL();

*err = OS_ERR_EVENT_TYPE;

return ((void *)0);

}

msg = pevent->OSEventPtr;

if (msg != (void *)0) { (2) pevent->OSEventPtr = (void *)0; (3) OS_EXIT_CRITICAL();

*err = OS_NO_ERR;

} else if (OSIntNesting > 0) { (4) OS_EXIT_CRITICAL();

*err = OS_ERR_PEND_ISR;

} else {

OSTCBCur->OSTCBStat |= OS_STAT_MBOX; (5) OSTCBCur->OSTCBDly = timeout;

OSEventTaskWait(pevent);

OS_EXIT_CRITICAL();

OSSched();

OS_ENTER_CRITICAL();

if ((msg = OSTCBCur->OSTCBMsg) != (void *)0) { (6) OSTCBCur->OSTCBMsg = (void *)0;

OSTCBCur->OSTCBStat = OS_STAT_RDY;

OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;

OS_EXIT_CRITICAL();

*err = OS_NO_ERR;

} else if (OSTCBCur->OSTCBStat & OS_STAT_MBOX) { (7) OSEventTO(pevent); (8) OS_EXIT_CRITICAL();

msg = (void *)0; (9) *err = OS_TIMEOUT;

} else {

msg = pevent->OSEventPtr; (10) pevent->OSEventPtr = (void *)0;

OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0; (11) OS_EXIT_CRITICAL();

*err = OS_NO_ERR;

} }

return (msg);

}

Listing 6.15, Waiting for a message to arrive at a mailbox.

6.06.03 Sending a message to a mailbox, OSMboxPost()

The code to deposit a message in a mailbox is shown in listing 6.16. After making sure that the ECB is used as a mailbox L6.16(1), OSMboxPost() checks to see if any task is waiting for a message to arrive at the mailbox L6.16(2). There are tasks waiting when the OSEventGrp field in the ECB contains a non-zero value. The highest priority task waiting for the message will be removed from the wait list by OSEventTaskRdy() (see section 6.02, Making a task ready, OSEventTaskRdy() ) L6.16(3), and this task will be made ready-to-run. OSSched() is then called to see if the task made ready is now the highest priority task ready-to-run. If it is, a context switch will result (only if OSMboxPost() is called from a task) and the readied task will be executed. If the readied task is not the highest priority task then OSSched() will return and the task that called OSMboxPost() will continue execution.

If there were no tasks waiting for a message to arrive at the mailbox, then the pointer to the message is saved in the mailbox L6.16(6), assuming there isn’t already a non-NULL pointer L6.16(5). Storing the pointer in the mailbox allows the next task to call OSMboxPend() to immediately get the message.

You should note that a context switch does not occur if OSMboxPost() is called by an ISR because context switching from an ISR can only occurs when OSIntExit() is calle d at the completion of the ISR, and from the last nested ISR (see section 3.09, Interrupts under µC/OS-II).

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);

}

} }

Listing 6.16, Depositing a message in a mailbox.

6.06.04 Getting a message without waiting, OSMboxAccept()

It is possib le to obtain a message from a mailbox without putting a task to sleep if the mailbox is empty. This is accomplished by calling OSMboxAccept() and the code for this function is shown in listing 6.17.

OSMboxAccept() starts by checking that the ECB being po inted to by pevent has been created by OSMboxCreate() L6.17(1). OSMboxAccept() then gets the current contents of the mailbox L6.17(2) in order to determine whether a message is available (i.e. non-NULL pointer) L6.17(3). If a message is available, the ma ilbox is emptied L6.17(4). Finally, the original contents of the mailbox is returned to the caller L6.17(5). The code that called OSMboxAccept() will need to examine the returned value. If OSMboxAccept() returns a NULL pointer then a message was not ava ilable. A non-NULL pointer indicates that a message was deposited in the mailbox. An ISR should use OSMboxAccept() instead of OSMboxPend().

You can use OSMboxAccept() to ‘flush’the contents of a mailbox.

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) }

Listing 6.17, Getting a message without waiting.

6.06.05 Obtaining the status of a mailbox, OSMboxQuery()

OSMboxQuery() allows your application to take a ‘snapshot’of an ECB used for a message mailbox. The code for this function is shown in listing 6.18. OSMboxQuery() is passed two arguments: pevent contains a pointer to the message mailbox which is returned by OSMboxCreate() when the mailbox is created and, pdata which is a pointer to a data structure (OS_MBOX_DATA, see uCOS_II.H) that will hold information about the message mailbox.

Your application will thus need to allocate a variable of type OS_MBOX_DATA that will be used to receive the information about the desired mailbox. I decided to use a new data structure because the caller should only be concerned with mailbox specific data as opposed to the more generic OS_EVENT data structure which contain two additional fields (i.e. .OSEventCnt and .OSEventType). OS_MBOX_DATA contains the current contents of the message (i.e. .OSMsg) and the list of tasks waiting for a message to arrive ( .OSEventTbl[] and .OSEventGrp).

As always, our function checks that pevent points to an ECB containing a mailbox L6.18(1). OSMboxQuery() then copies the wait list L6.18(2) followed by the current message L6.18(3) from the OS_EVENT structure to the OS_MBOX_DATA structure.

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);

}

Listing 6.18, Obtaining the status of a mailbox.

6.06.06 Using a mailbox as a binary semaphore

A message mailbox can be used as a binary semaphore by initializing the mailbox with a non -NULL pointer ((void

*1) works well). A task requesting the ‘semaphore’would call OSMboxPend() and would release the ‘semaphore’

by calling OSMboxPost(). Listing 6.19 shows how this works. You would use this technique to conserve code space if your application only needed binary semaphores and mailboxes. In this case, you could set OS_SEM_EN to 0 and only use mailboxes instead of both mailboxes and semaphores.

OS_EVENT *MboxSem;

void Task1 (void *pdata) {

INT8U err;

for (;;) {

OSMboxPend(MboxSem, 0, &err); /* Obtain access to resource(s) */

.

. /* Task has semaphore, access resource(s) */

.

OSMboxPost(MboxSem, (void )1); /* Release access to resource(s) */

} }

Listing 6.19, Using a mailbox as a binary semaphore.

6.06.07 Using a mailbox instead of OSTimeDly()

The timeout feature of a mailbox can be used to simulate a call to OSTimeDly(). As shown in listing 6.20, Task1() resumes execution after the time period expired if no message is received within the specified TIMEOUT period. This is basically identical to OSTimeDly(TIMEOUT). However, the task can be resumed by Task2() when Task(2) post a ‘dummy’ message to the mailbox before the timeout expires. This is the same as calling OSTimeDlyResume() had Task1() called OSTimeDly() . You should note that the returned message is ignored because we are not actually looking to get a message from another task or an ISR.

OS_EVENT *MboxTimeDly;

void Task1 (void *pdata) {

INT8U err;

for (;;) {

OSMboxPend(MboxTimeDly, TIMEOUT, &err); /* Delay task */

.

. /* Code executed after time delay */

. } }

void Task2 (void *pdata) {

INT8U err;

for (;;) {

OSMboxPost(MboxTimeDly, (void *)1); /* Cancel delay for Task1 */

. . } }

Listing 6.20, Using a mailbox as a time delay.

6.07 Message Queues

A message queue (or simply a queue) is a µC/OS-II object that allows a task or an ISR to send pointer size variables to another task. Each pointer would typically be initialized to point to some application specific data structure containing a ‘message’. To enable µC/OS-II’s message queue services, you must set the configuration constant OS_Q_EN to 1 (see file OS_CFG.H ) and determine how many message queues µC/OS-II will need to support by setting the configuration constant OS_MAX_QS, also found in OS_CFG.H.

A queue needs to be created before it can be used. Creating a queue is accomplished by calling OSQCreate() (see next section) and specifying the number of entries (i.e. pointers) that a queue can hold.

µC/OS-II provides seven services to access message queues: OSQCreate(), OSQPend(), OSQPost(), OSQPostFront(), OSQAccept(), OSQFlush() and OSQQuery(). Figure 6-7 shows a flow diagram to illustrate the relationship between tasks, ISRs and a message queue. Note that the symbology used to represent a queue looks like a mailbox with multiple entries. In fact, you can think of a queue as an array of mailboxes except that there is only one wait list associated with the queue. Again, what the pointers point to is application specific. ‘N’

represents the number of entries that the queue holds. The queue is full when your application has called OSQPost() (or OSQPostFront()) ‘N’times before your application has called OSQPend() or OSQAccept(). As you can see from figure 6-7, a task or an ISR can call OSQPost(), OSQPostFront(), OSQFlush() or OSQAccept().

However, only tasks task are allowed to call OSQPend() and OSQQuery().

Task

ISR

Task

OSQPend() OSQAccept() OSQQuery() OSQPost()

OSQPostFront() OSQFlush()

OSQPost() OSQPostFront() OSQFlush() OSQAccept()

OSQCreate()

在文檔中 µC/OS-II Goals Preface (頁 147-153)