• 沒有找到結果。

OS_MAX_QS

在文檔中 µC/OS-II Goals Preface (頁 155-165)

Figure 6-9, List of free queue control blocks.

A queue control block is a data structure that is used to maintain information about the queue and it contains the fields described below. Note that the fields are preceded with a dot to show that they are members of a structure as opposed to simple variables.

.OSQPtr is used to link queue control blocks in the list of free queue control blocks. Once the queue is allocated, this field is not used.

.OSQStart contains a pointer to the start of the message queue storage area. Your application must declare this storage area before creating the queue.

.OSQEnd is a pointer to one location past the end of the queue. This pointer is used to make the queue a circular buffer.

.OSQIn is a pointer to the location in the queue where the next message will be inserted. .OSQIn is adjusted back to the beginning of the message storage area when .OSQIn equals .OSQEnd.

.OSQOut is a pointer to the next message to be extracted from the queue. .OSQOut is adjusted back to the beginning of the message storage area when .OSQOut equals .OSQEnd . .OSQOut is also used to i nsert a message (see OSQPostFront()).

.OSQSize contains the size of the message storage area. The size of the queue is determined by your application when the queue is created. Note that µC/OS-II allows the queue to contain up to 65535 entries.

.OSQEntries contains the current number of entries in the message queue. The queue is empty when .OSQEntries is zero and full when it equals .OSQSize. The message queue is empty when the queue is created.

A message queue is basically a circular buffer as shown in figure 6-10. Each entry contains a pointer. The pointer to the next message is deposited at the entry pointed to by .OSQIn F6-10(1) unless the queue is full (i.e. .OSQEntries == .OSQSize) F6-10(3). Depositing the pointer at .OSQIn implements a FIFO (First-In-First-Out) queue. We can implement a LIFO (Last-In-First-Out) queue by pointing to the entry preceeding .OSQOut F6-10(2) and depositing the pointer at that location. The pointer is also considered full when .OSQEntries == .OSQSize. Message poin ters are always extracted from the entry pointed to by .OSQOut. The pointers .OSQStart and .OSQEnd are simply markers used to establish the beginning and end of the array so that .OSQIn and .OSQOut can wrap around to implement this circular motion.

.OSQOut

.OSQIn .OSQEntries

.OSQSize

.OSQStart .OSQEnd

Pointer to message .OSQOut

(1) (2)

(3)

(4) (3)

(5) (5)

Figure 6-10, Message queue is a circular buffer of pointers.

6.07.01 Creating a Queue, OSQCreate()

The code to create a message queue is shown in listing 6.21. OSQCreate() requires that you allocate an array of pointers that will hold the message. The array MUST be declared as an array of pointers to void.

OSQCreate() starts by obtaining an ECB from the free list of ECBs (see figure 6-3) L6.21(1). The linked list of free ECBs is adjusted to point to the next free ECB L6.21(2). Other OSQ???() function calls will check this field to make sure that the ECB is of the proper type. This prevents you from calling OSQPost() on an ECB that was created for use as a semaphore (see section 6.05). Next, OSQCreate() obtains a queue control block from the free list L6-21(3). OSQCreate() initializes the queue control block L6 -21(4) (if one was available), sets the ECB type to OS_EVENT_TYPE_Q L6.21(5) and makes .OSEventPtr point to the queue control block L6.21(6). The wait list is initialized by calling OSEventWaitListInit() (see section 6.01, Initializing an ECB, OSEventWaitListInit()) L6.9(7). Because the queue is being initialized, there are no tasks waiting. Finally, OSQCreate() returns a pointer to the allocated ECB L6.21(9). This pointer MUST be used in subsequent calls that operate on message queues (OSQPend(), OSQPost(), OSQPostFront(), OSQFlush(), OSQAccept() and OSQQuery() ). The pointer is basically used as the queue’s handle. Note that if there were no more ECBs, OSQCreate() would have returned a NULL pointer. If a queue control block was not available, OSQCreate() returns the ECB back to the list of free ECBs L6.21(8) (there is no point in wasting the ECB).

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

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

Listing 6.21, Creating a queue.

6.07.02 Waiting for a message at a Queue, OSQPend()

The code to wait for a message to arrive at a queue is shown in listing 6.22. OSQPend() verifies that the ECB being pointed to by pevent has been created by OSQCreate() L6.22(1). A message is available when .OSQEntries is greater than 0 L6.22(2). In this case, OSQPend() stores the pointer to the message in msg, moves the .OSQOut pointer so that it points to the next entry in the queue L6.22(3) a nd, OSQPend() decrements the number of entries left in the queue L6.22(4). Because we are implementing a circular buffer, we need to check that .OSQOut has not moved past the last valid entry in the array L6.22(5). When this happens, however, .OSQOut is adjusted to point back at the beginning of the array L6.22(6). This is the path you are looking for when calling OSQPend() and it also happens to be the fastest.

If a message is not available (.OSEventEntries is 0) then we check to see if the function was called by an ISR L6.22(7). As with OSSemPend() and OSMboxPend(), you should not call OSQPend() from an ISR because an ISR cannot be made to wait. However, if the message is in fact available, the call to OSQPend() 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.22(8). When a message is posted to the queue (or the timeout period expired) and the task that called OSQPend() is again the highest priority task then OSSched() returns L6.22(9). OSQPend() then checks to see if a message was placed in the task’s TCB by OSQPost() L6.22(10). If this is the case, the call is successful, some cleanup work is done to un link the message queue from the TCB L6.22(11) and the message is returned to the caller L6.22(17).

A timeout is detected by looking at the .OSTCBStat field in the task’s TCB to see if the OS_STAT_Q bit is still set.

A timeout occurred when the bit is set L6.22(12). The task is removed from the queue’s wait list by calling OSEventTO()L6.22(13). Note that the returned pointer is set to NULL L6.22(14) (no message was available).

If the status flag in the task’s TCB doesn’t have the OS_STAT_Q bit set the n a message must have been sent and thus, the message is extracted from the queue L6.22(15). Also, the link to the ECB is removed because the task will no longer wait on that message queue L6.22(16).

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

Listing 6.22, Waiting for a message to arrive at a queue.

6.07.03 Sending a message to a queue (FIFO), OSQPost()

The code to deposit a message in a queue is shown in listing 6.23. After making sure that the ECB is used as a queue L6.23(1), OSQPost() checks to see if any task is waiting for a message to arrive at the queue L6.23(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.23(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 OSQPost() 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 OSQPost() will continue execution. If there were no tasks waiting for a message to arrive at the queue, then the pointer to the message is saved in the queue L6.23(5) unless the queue is already full L6.23(4). You should note that if the queue is full, the message will not be inserted in the queue and thus, the message will basically be lost. Storing the pointer to the message in the queue allows the next tas k that calls OSQPend() (on this queue) to immediately get the pointer.

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

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

return (OS_NO_ERR);

} else {

pq = pevent->OSEventPtr;

if (pq->OSQEntries >= pq->OSQSize) { (4) OS_EXIT_CRITICAL();

return (OS_Q_FULL);

} else {

*pq->OSQIn++ = msg; (5) pq->OSQEntries++;

if (pq->OSQIn == pq->OSQEnd) { pq->OSQIn = pq->OSQStart;

}

OS_EXIT_CRITICAL();

}

return (OS_NO_ERR);

} }

Listing 6.23, Depositing a message in a queue (FIFO).

6.07.04 Sending a message to a queue (LIFO), OSQPostFront()

OSQPostFront() is basically identical to OSQPost() except that OSQPostFront() uses .OSQOut instead of .OSQIn as the pointer to the next entry to insert. The code is shown in listing 6.24. You should note, however, that .OSQOut points to an already inserted entry and thus, .OSQOut must be made to point to the previous entry.

If .OSQOut points at the beginning of the array L6.24(1), then a decrement really means positioning .OSQOut at the end of the array L6.24(2). However, .OSQEnd points to one entry past the array and thus .OSQOut needs to be adjusted to be within range L6.24(3). OSQPostFront() implements a LIFO queue because the next message extracted by OSQPend() will be the last message inserted by OSQPostFront().

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

} }

Listing 6.24, Depositing a message in a queue (LIFO).

6.07.05 Getting a message without waiting, OSQAccept()

It is possible to obtain a message from a queue without putting a task to sleep if the queue is empty. This is accomplished by calling OSQAccept() and the code for this function is shown in listing 6.25. OSQAccept() starts by checking that the ECB being pointed to by pevent has been created by OSQCreate() L6.25(1).

OSQAccept() then checks to see if there are any entries in the queue L6.25(2). If the queue contains at least one message, the next pointer is extracted from the queue L6.25(3). The code that calls OSQAccept() will need to examine the returned value. If OSQAccept() returns a NULL pointer then a message was not available L6.25(4). A

non-NULL pointer indicates that a message pointer was available. An ISR should use OSQAccept() instead of OSQPend(). If an entry was available, OSQAccept() extracts the entry from the queue.

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

}

Listing 6.25, Getting a message without waiting.

6.07.06 Flushing a queue, OSQFlush()

OSQFlush() allows your application to remove all the messages posted to a queue and basically, start with a fresh queue. The code for this function is shown in listing 6.26. As usual, µC/OS-II chec ks to ensure that pevent is pointing to a message queue L6.26(1). The IN and OUT pointers are then reset to the beginning of the array and the number of entries is cleared L6.26(2). I decided to not check to see if there were any tasks pending on the queue because this would be irrelevant anyway. In other words, if tasks were waiting on the queue then .OSQEntries would already have been set to 0. The only difference is that .OSQIn and .OSQOut may have been pointing elsewhere in the array.

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

}

Listing 6.26, Flushing the contents of a queue.

6.07.07 Obtaining the status of a queue, OSQQuery()

OSQQuery() allows your application to take a ‘snapshot’of the contents of a message queue. The code for this function is shown in listing 6.27. OSQQuery() is passed two arguments: pevent contains a pointer to the message queue which is returned by OSQCreate() when the queue is created and, pdata which is a pointer to a data structure (OS_Q_DATA, see uCOS_II.H) that will hold information about the message queue. Your application will thus need to allocate a variable of type OS_Q_DATA that will be used to receive the information about the desired queue. OS_Q_DATA contains the following fields:

.OSMsg contains the contents pointed to by .OSQOut if there are entries in the queue. If the queue is empty, .OSMsg contains a NULL-pointer.

.OSNMsgs contains the number of messages in the queue (i.e. a copy of .OSQEntries).

.OSQSize contains the size of the queue (in number of entries).

.OSEventTbl[] and .OSEventGrp contains a snapshot of the message queue wait list. The caller to OSQQuery() can thus determine how many tasks are waiting for the queue.

As always, our function checks that pevent points to an ECB containing a queue L6.27(1). OSQQuery() then copies the wait list L6.27(2). If the queue contains any entries L6.27(3), the next message pointer to be extracted from the queue is copied into the OS_Q_DATA structure L6.27(4). If the queue is empty, then a NULL-pointer is placed in OS_Q_DATA L6.27(5). Finally, we copy the number of entries and the size of the message queue L6.27(6).

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

}

Listing 6.27, Obtaining the status of a queue.

6.07.08 Using a message queue when reading analog inputs

It is often useful in control applications to read analog inputs at a regular interval. To accomplish this, you can create a task and call OSTimeDly() (see section 5.00, OSTimeDly()) and specify the desired sampling period. As shown in figure 6-11, you could use a message queue instead and have your task pend on the queue with a timeout. The timeout corresponds to the desired sampling period. If no other task sends a message to the queue, the task will be resumed after the specified timeout which basically emulates the OSTimeDly() function.

You are probably wondering why I decided to use a queue when OSTimeDly() does the trick just fine. By adding a queue, you can have other tasks abort the wait by sending a message thus forcing an immediate conversion. If you add some intelligence to your messages, you can tell the ADC task to convert a specific channel, tell the task to increase the sampling rate, and more. In other words, you can say to the task: “Can you convert analog input #3 for me now?”.

After servicing the message, the task would initiate the pend on the queue which would restart the scanning process.

Task

ADC MUX

Timeout

OSQPend() OSQPost()

Queue Analog Inputs

(1) (2)

(3) (4)

(5)

Figure 6-11, Reading analog inputs.

6.07.09 Using a queue as a counting semaphore

A message queue can be used as a counting semaphore by initializing and loading a queue with as many non-NULL pointer ((void *1) works well) as there are resources available. A task requesting the ‘semaphore’would call OSQPend() and would release the ‘semaphore’by calling OSQPost(). Listing 6.28 shows how this works. You would use this technique to conserve code space if your application only needed counting semaphores and message queues (you would then have no need for the semaphore services). In this case, you could set OS_SEM_EN to 0 and only use queues instead of both queues and semaphores. You should note that this technique consumes a pointer size variable for each resource that the semaphore is guarding as well as requiring a queue control block. In other words, you will be sacrificing RAM space in order to save code space. Also, message queues services are slower than semaphore services. This technique would be very inefficient if your counting semaphore (in this case a queue) is guarding a large amount of resources (you would require a large array of pointers).

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); /* Obtain access to resource(s) */

.

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

.

OSMQPost(QSem, (void )1); /* Release access to resource(s) */

} }

Listing 6.28, Using a queue as a counting semaphore.

Chapter 7

在文檔中 µC/OS-II Goals Preface (頁 155-165)