...
To enable µC/OS-II message queue services, you must set configuration constants in OS_CFG.H
. Specifically, table 11.1 shows which services are compiled based on the value of configuration constants found in OS_CFG.H
. You should note that NONE of the mailbox services are enabled when OS_Q_EN
is set to 0 or OS_MAX_QS
is set to 0. To enable a specific feature (i.e. service), simply set the corresponding configuration constant to 1. You will notice that OSQCreate()
and OSQPend()
cannot be individually disabled like the other services. That’s because they are always needed when you enable µC/OS-II message mailbox management. You must enable at least one of the post services: OSQPost()
, OSQPostFront()
and OSQPostOpt()
.
Figure
Anchor | ||
---|---|---|
|
OSQPend()
call. Again, what the pointers point to is application specific. N represents the number of entries the queue holds. The queue is full when your application calls OSQPost()
[or OSQPostFront()
or OSQPostOpt()
] N times before your application has called OSQPend()
or OSQAccept()
.As you can see from Figure 11.1, a task or an ISR can call OSQPost()
, OSQPostFront()
, OSQPostOpt()
, OSQFlush()
, or OSQAccept()
. However, only tasks are allowed to call OSQDel()
, OSQPend()
and OSQQuery()
.
Figure 11.2 shows the different data structures needed to implement a message queue.
The configuration constant OS_MAX_QS
in OS_CFG.H
specifies how many queues you are allowed to have in your application and must be greater than 0. When µC/OS-II is initialized, a list of free queue control blocks is created as shown in Figure11.3.
A queue control block is a data structure used to maintain information about the queue. 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
links queue control blocks in the list of free queue control blocks. Once the queue is created, 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 insert a message [see OSQPostFront()
and OSQPostOpt()
].
.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 65,535 entries.
.OSQEntries
contains the current number of entries in the message queue. The queue is empty when .OSQEntries
is 0 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 11.4.
Creating a message queue, OSQCreate()
A message queue (or simply a queue) needs to be created before it can be used. Creating a queue is accomplished by calling OSQCreate()
and passing it two arguments: a pointer to an array that will hold the messages and the size of this array. The array must be declared as an array of pointers to void as follows:
void *MyArrayOfMsg[SIZE];
You would pass the address of MyArrayOfMsg[]
to OSQCreate()
as well as the size of this array. The message queue is assumed to be initially empty – it doesn’t contain any messages.
The code to create a mailbox is shown in Listing 11.1.
Deleting a message queue, OSQDel()
The code to delete a message queue is shown in listing 11.2 and this code will only be generated by the compiler if OS_Q_DEL_EN
is set to 1 in OS_CFG.H
. This is a function that you must use with caution because multiple tasks could attempt to access a deleted message queue. You should always use this function with great care. Generally speaking, before you would delete a message queue, you would first delete all the tasks that access the message queue.
Waiting for a message at a queue (blocking), OSQPend()
The code to wait for a message to arrive at a queue is shown in Listing 11.3.
Sending a message to a queue (FIFO), OSQPost()
The code to deposit a message in a queue is shown in Listing 11.4.
Note that a context switch does not occur if OSQPost()
is called by an ISR because context switching from an ISR only occurs when OSIntExit()
is called at the completion of the ISR and from the last nested ISR (see section 3.10, Interrupts under µC/OS-II).
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 11.5.
Sending a message to a queue (FIFO or LIFO), OSQPostOpt()
You can also post a message to a queue using an alternate an more flexible function called OSQPostOpt()
. The reason there are three post calls is for backwards compatibility with previous versions of µC/OS-II. OSQPostOpt()
is the newer function and can replace both OSQPost()
and OSQPostFront()
with a single call. In addition, OSQPostOpt()
allows posting a message to all tasks (i.e. broadcast) waiting on the queue. The code to deposit a message in a queue is shown in Listing 11.6.
Note that a context switch does not occur if OSQPostOpt()
is called by an ISR because context switching from an ISR only occurs when OSIntExit()
is called at the completion of the ISR and from the last nested ISR (see section 3.10, Interrupts under µC/OS-II).
Getting a Message without Waiting, OSQAccept()
You can obtain a message from a queue without putting a task to sleep by calling OSQAccept()
if the queue is empty. The code for this function is shown in Listing 11.7.
The code that calls OSQAccept()
needs to examine the returned value. If OSQAccept()
returns a NULL pointer, then a message was not available. You don’t want your application to dereference a NULL pointer because, by convention, a NULL pointer is invalid. A non-NULL pointer indicates that a message pointer is available. An ISR should use OSQAccept()
instead of OSQPend()
.
Flushing a Queue, OSQFlush()
OSQFlush()
allows you 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 11.8.
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 11.9. 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 is a pointer to a data structure (OS_Q_DATA, see uCOS_II.H
) that holds information about the message queue. Your application thus needs to allocate a variable of type OS_Q_DATA
that will 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
will contain 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[]
.OSEventGrp
contain a snapshot of the message queue wait list. The caller toOSQQuery()
can thus determine how many tasks are waiting for the queue.
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, create a task, call OSTimeDly()
[see section 5.00, Delaying a Task, 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 is 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.
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 pointers [(void *)1 works well] as there are resources available. A task requesting the “semaphore” calls OSQPend()
and releases the “semaphore” by calling OSQPost()
. Listing 11.10 shows how this works. You can use this technique to conserve code space if your application only needs counting semaphores and message queues (you would then have no need for the semaphore services). In this case, set OS_SEM_EN
to 0 and only use queues instead of both queues and semaphores. Note that this technique consumes a pointer-sized variable for each resource that the semaphore is guarding and requires a queue control block. In other words, you are sacrificing RAM space in order to save code space. Also, message queue 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).
|
Panel | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ||||||||||||||||||||
|
Figure 11.1 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. The hourglass represents a timeout that can be specified with the OSQPend()
call. Again, what the pointers point to is application specific. N represents the number of entries the queue holds. The queue is full when your application calls OSQPost()
[or OSQPostFront()
or OSQPostOpt()
] N times before your application has called OSQPend()
or OSQAccept()
.
As you can see from Figure 11.1, a task or an ISR can call OSQPost()
, OSQPostFront()
, OSQPostOpt()
, OSQFlush()
, or OSQAccept()
. However, only tasks are allowed to call OSQDel()
, OSQPend()
and OSQQuery()
.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
Figure 11.2 shows the different data structures needed to implement a message queue.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
Panel | ||
---|---|---|
| ||
(1) An ECB is required because you need a wait list, and using an ECB allows queue services to use some of the same code used by semaphores, mutexes and mailboxes. (2) When a message queue is created, a queue control block (i.e., an (3) Before you create a queue, however, you need to allocate an array of pointers that contains the desired number of queue entries. In other words, the number of elements in the array corresponds to the number of entries in the queue. The starting address of the array is passed to |
The configuration constant OS_MAX_QS
in OS_CFG.H
specifies how many queues you are allowed to have in your application and must be greater than 0. When µC/OS-II is initialized, a list of free queue control blocks is created as shown in Figure11.3.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
A queue control block is a data structure used to maintain information about the queue. 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
links queue control blocks in the list of free queue control blocks. Once the queue is created, 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 insert a message [see OSQPostFront()
and OSQPostOpt()
].
.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 65,535 entries.
.OSQEntries
contains the current number of entries in the message queue. The queue is empty when .OSQEntries
is 0 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 11.4.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
Panel | ||
---|---|---|
| ||
(1) (3) Each entry contains a pointer. The pointer to the next message is deposited at the entry pointed to by (2) µC/OS-II implements a LIFO (Last-In-First-Out) queue by pointing to the entry preceeding (4) The pointer is also considered full when (5) The pointers |
Creating a message queue, OSQCreate()
A message queue (or simply a queue) needs to be created before it can be used. Creating a queue is accomplished by calling OSQCreate()
and passing it two arguments: a pointer to an array that will hold the messages and the size of this array. The array must be declared as an array of pointers to void as follows:
void *MyArrayOfMsg[SIZE];
You would pass the address of MyArrayOfMsg[]
to OSQCreate()
as well as the size of this array. The message queue is assumed to be initially empty – it doesn’t contain any messages.
The code to create a mailbox is shown in Listing 11.1.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
OS_EVENT *OSQCreate (void **start, INT16U size)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr; (1)
#endif
OS_EVENT *pevent;
OS_Q *pq;
if (OSIntNesting > 0) { (2)
return ((OS_EVENT *)0);
}
OS_ENTER_CRITICAL();
pevent = OSEventFreeList; (3)
if (OSEventFreeList != (OS_EVENT *)0) {
OSEventFreeList = (OS_EVENT *)OSEventFreeList->OSEventPtr;
}
OS_EXIT_CRITICAL();
if (pevent != (OS_EVENT *)0) { (4)
OS_ENTER_CRITICAL();
pq = OSQFreeList;
if (pq != (OS_Q *)0) {
OSQFreeList = OSQFreeList->OSQPtr;
OS_EXIT_CRITICAL();
pq->OSQStart = start; (5)
pq->OSQEnd = &start[size];
pq->OSQIn = start;
pq->OSQOut = start;
pq->OSQSize = size;
pq->OSQEntries = 0;
pevent->OSEventType = OS_EVENT_TYPE_Q; (6)
pevent->OSEventCnt = 0;
pevent->OSEventPtr = pq;
OS_EventWaitListInit(pevent); (7)
} else {
pevent->OSEventPtr = (void *)OSEventFreeList; (8)
OSEventFreeList = pevent;
OS_EXIT_CRITICAL();
pevent = (OS_EVENT *)0;
}
}
return (pevent); (9)
} |
Panel | ||
---|---|---|
| ||
(1) A local variable called cpu_sr to support (2) (3) (4) If there is an ECB available, (5) (6) If a queue control block was available from the free list, the fields of the queue control block are initialized followed by the ones of the ECB. You should note that the (7) The wait list is cleared indicating that no task is currently waiting on the message queue. (8) If an ECB was available but a queue control block was not then, the ECB is returned to the free list since we cannot satisfy the request to create a queue unless we also have a queue control block. (9) |
Deleting a message queue, OSQDel()
The code to delete a message queue is shown in listing 11.2 and this code will only be generated by the compiler if OS_Q_DEL_EN
is set to 1 in OS_CFG.H
. This is a function that you must use with caution because multiple tasks could attempt to access a deleted message queue. You should always use this function with great care. Generally speaking, before you would delete a message queue, you would first delete all the tasks that access the message queue.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
OS_EVENT *OSQDel (OS_EVENT *pevent, INT8U opt, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
BOOLEAN tasks_waiting;
OS_Q *pq;
if (OSIntNesting > 0) { (1)
*err = OS_ERR_DEL_ISR;
return ((OS_EVENT *)0);
}
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { (2)
*err = OS_ERR_PEVENT_NULL;
return (pevent);
}
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (3)
*err = OS_ERR_EVENT_TYPE;
return (pevent);
}
#endif
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0x00) { (4)
tasks_waiting = TRUE;
} else {
tasks_waiting = FALSE;
}
switch (opt) {
case OS_DEL_NO_PEND:
if (tasks_waiting == FALSE) {
pq = pevent->OSEventPtr; (5)
pq->OSQPtr = OSQFreeList;
OSQFreeList = pq;
pevent->OSEventType = OS_EVENT_TYPE_UNUSED; (6)
pevent->OSEventPtr = OSEventFreeList; (7)
OSEventFreeList = pevent;
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
return ((OS_EVENT *)0); (8)
} else {
OS_EXIT_CRITICAL();
*err = OS_ERR_TASK_WAITING;
return (pevent);
}
case OS_DEL_ALWAYS:
while (pevent->OSEventGrp != 0x00) { (9)
OS_EventTaskRdy(pevent, (void *)0, OS_STAT_Q); (10)
}
pq = pevent->OSEventPtr; (11)
pq->OSQPtr = OSQFreeList;
OSQFreeList = pq;
pevent->OSEventType = OS_EVENT_TYPE_UNUSED; (12)
pevent->OSEventPtr = OSEventFreeList; (13)
OSEventFreeList = pevent;
OS_EXIT_CRITICAL();
if (tasks_waiting == TRUE) {
OS_Sched(); (14)
}
*err = OS_NO_ERR;
return ((OS_EVENT *)0); (15)
default:
OS_EXIT_CRITICAL();
*err = OS_ERR_INVALID_OPT;
return (pevent);
}
} |
Panel | ||
---|---|---|
| ||
(1) (2) (3) If (4) Based on the option (i.e. opt) specified in the call, (5) When opt is set to (6) (7) (8) You will note that
(9) (10) When opt is set to (11) (12) (13) Once all pending tasks are readied, (14) The scheduler is called only if there were tasks waiting on the queue. (15) Again, you will note that |
Waiting for a message at a queue (blocking), OSQPend()
The code to wait for a message to arrive at a queue is shown in Listing 11.3.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void *OSQPend (OS_EVENT *pevent, INT16U timeout, INT8U *err)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
void *msg;
OS_Q *pq;
if (OSIntNesting > 0) { (1)
*err = OS_ERR_PEND_ISR;
return ((void *)0);
}
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { (2)
*err = OS_ERR_PEVENT_NULL;
return ((void *)0);
}
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (3)
*err = OS_ERR_EVENT_TYPE;
return ((void *)0);
}
#endif
OS_ENTER_CRITICAL();
pq = (OS_Q *)pevent->OSEventPtr;
if (pq->OSQEntries > 0) { (4)
msg = *pq->OSQOut++; (5)
pq->OSQEntries--; (6)
if (pq->OSQOut == pq->OSQEnd) { (7)
pq->OSQOut = pq->OSQStart; (8)
}
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
return (msg); (9)
}
OSTCBCur->OSTCBStat |= OS_STAT_Q; (10)
OSTCBCur->OSTCBDly = timeout; (11)
OS_EventTaskWait(pevent); (12)
OS_EXIT_CRITICAL();
OS_Sched(); (13)
OS_ENTER_CRITICAL();
msg = OSTCBCur->OSTCBMsg; (14)
if (msg != (void *)0) {
OSTCBCur->OSTCBMsg = (void *)0; (15)
OSTCBCur->OSTCBStat = OS_STAT_RDY;
OSTCBCur->OSTCBEventPtr = (OS_EVENT *)0;
OS_EXIT_CRITICAL();
*err = OS_NO_ERR;
return (msg);
}
OS_EventTO(pevent); (16)
OS_EXIT_CRITICAL();
*err = OS_TIMEOUT;
return ((void *)0); (17)
} |
Panel | ||
---|---|---|
| ||
(1) It doesn’t make sense to call (2) (3) If (4) (5) A message is available when (6) (7) (8) Because a message queue is a circular buffer, (9) The message that was extracted from the queue is then returned to the caller of If the message queue was empty, the calling task needs to be put to sleep until another task (or an ISR) sends a message through the queue (see section 11.04). (10) To put the calling task to sleep, (11) The timeout is also stored in the TCB so that it can be decremented by (12) The actual work of putting the task to sleep is done by (13) Because the calling task is no longer ready to run, the scheduler is called to run the next highest priority task that is ready to run. As far as your task is concerned, it made a call to (14) When (15) If so, the call is successful and the message is returned to the caller. (16) If a message is not received then (17) Note that the returned pointer is set to NULL because there is no message to return. The calling task should either examine the contents of the return pointer or the return code to determine whether a valid message was received. |
Sending a message to a queue (FIFO), OSQPost()
The code to deposit a message in a queue is shown in Listing 11.4.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSQPost (OS_EVENT *pevent, void *msg)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
OS_Q *pq;
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { (1)
return (OS_ERR_PEVENT_NULL);
}
if (msg == (void *)0) { (2)
return (OS_ERR_POST_NULL_PTR);
}
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (3)
return (OS_ERR_EVENT_TYPE);
}
#endif
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0x00) { (4)
OS_EventTaskRdy(pevent, msg, OS_STAT_Q); (5)
OS_EXIT_CRITICAL();
OS_Sched(); (6)
return (OS_NO_ERR);
}
pq = (OS_Q *)pevent->OSEventPtr;
if (pq->OSQEntries >= pq->OSQSize) { (7)
OS_EXIT_CRITICAL();
return (OS_Q_FULL);
}
*pq->OSQIn++ = msg; (8)
pq->OSQEntries++; (9)
if (pq->OSQIn == pq->OSQEnd) { (10)
pq->OSQIn = pq->OSQStart;
}
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
} |
Panel | ||
---|---|---|
| ||
(1) (2) (3) If (4) (5) The highest priority task waiting for the message is removed from the wait list by (6) (7) If no task is waiting for a message, the message to post needs to be placed in the queue. In this case, (8) (9) If there are no tasks waiting for a message to arrive at the queue and the queue is not already full then the message to post is inserted in the next free location (FIFO order) and the number of entries in the queue is incremented. (10) Finally, |
Note that a context switch does not occur if OSQPost()
is called by an ISR because context switching from an ISR only occurs when OSIntExit()
is called at the completion of the ISR and from the last nested ISR (see section 3.10, Interrupts under µC/OS-II).
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 11.5.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSQPostFront (OS_EVENT *pevent, void *msg)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
OS_Q *pq;
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) {
return (OS_ERR_PEVENT_NULL);
}
if (msg == (void *)0) {
return (OS_ERR_POST_NULL_PTR);
}
if (pevent->OSEventType != OS_EVENT_TYPE_Q) {
return (OS_ERR_EVENT_TYPE);
}
#endif
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0x00) {
OS_EventTaskRdy(pevent, msg, OS_STAT_Q);
OS_EXIT_CRITICAL();
OS_Sched();
return (OS_NO_ERR);
}
pq = (OS_Q *)pevent->OSEventPtr;
if (pq->OSQEntries >= pq->OSQSize) {
OS_EXIT_CRITICAL();
return (OS_Q_FULL);
}
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);
} |
Panel | ||
---|---|---|
| ||
(1) (2) You should note, however, that (3) However, |
Sending a message to a queue (FIFO or LIFO), OSQPostOpt()
You can also post a message to a queue using an alternate an more flexible function called OSQPostOpt()
. The reason there are three post calls is for backwards compatibility with previous versions of µC/OS-II. OSQPostOpt()
is the newer function and can replace both OSQPost()
and OSQPostFront()
with a single call. In addition, OSQPostOpt()
allows posting a message to all tasks (i.e. broadcast) waiting on the queue. The code to deposit a message in a queue is shown in Listing 11.6.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSQPostOpt (OS_EVENT *pevent, void *msg, INT8U opt)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
OS_Q *pq;
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { (1)
return (OS_ERR_PEVENT_NULL);
}
if (msg == (void *)0) { (2)
return (OS_ERR_POST_NULL_PTR);
}
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (3)
return (OS_ERR_EVENT_TYPE);
}
#endif
OS_ENTER_CRITICAL();
if (pevent->OSEventGrp != 0x00) { (4)
if ((opt & OS_POST_OPT_BROADCAST) != 0x00) { (5)
while (pevent->OSEventGrp != 0x00) { (6)
OS_EventTaskRdy(pevent, msg, OS_STAT_Q);
}
} else {
OS_EventTaskRdy(pevent, msg, OS_STAT_Q); (7)
}
OS_EXIT_CRITICAL();
OS_Sched(); (8)
return (OS_NO_ERR);
}
pq = (OS_Q *)pevent->OSEventPtr;
if (pq->OSQEntries >= pq->OSQSize) { (9)
OS_EXIT_CRITICAL();
return (OS_Q_FULL);
}
if ((opt & OS_POST_OPT_FRONT) != 0x00) { (10)
if (pq->OSQOut == pq->OSQStart) { (11)
pq->OSQOut = pq->OSQEnd;
}
pq->OSQOut--;
*pq->OSQOut = msg;
} else {
*pq->OSQIn++ = msg; (12)
if (pq->OSQIn == pq->OSQEnd) {
pq->OSQIn = pq->OSQStart;
}
}
pq->OSQEntries++; (13)
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
} |
Panel | ||
---|---|---|
| ||
(1) (2) (3) If (4) (5) (6) If you set the (7) If a broadcast was not requested then, only the highest priority task waiting for a message will be made ready to run. The highest priority task waiting for the message is removed from the wait list by (8) (9) If nobody is waiting for a message, the message to post needs to be placed in the queue. In this case, (10) (11) If LIFO order is selected, (12) If FIFO order, (13) In either case, the number of entries in the queue is incremented. |
Note that a context switch does not occur if OSQPostOpt()
is called by an ISR because context switching from an ISR only occurs when OSIntExit()
is called at the completion of the ISR and from the last nested ISR (see section 3.10, Interrupts under µC/OS-II).
Getting a Message without Waiting, OSQAccept()
You can obtain a message from a queue without putting a task to sleep by calling OSQAccept()
if the queue is empty. The code for this function is shown in Listing 11.7.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void *OSQAccept (OS_EVENT *pevent)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
void *msg;
OS_Q *pq;
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { (1)
return ((void *)0);
}
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (2)
return ((void *)0);
}
#endif
OS_ENTER_CRITICAL();
pq = (OS_Q *)pevent->OSEventPtr;
if (pq->OSQEntries > 0) { (3)
msg = *pq->OSQOut++; (4)
pq->OSQEntries--; (5)
if (pq->OSQOut == pq->OSQEnd) { (6)
pq->OSQOut = pq->OSQStart;
}
} else {
msg = (void *)0; (7)
}
OS_EXIT_CRITICAL();
return (msg);
} |
Panel | ||
---|---|---|
| ||
(1) (2) If (3) (4) (5) If a message is available, the oldest message (FIFO order) is retrieved from the queue and copied to the local pointer msg and the number of entries in the queue is decreased by one to reflect the extraction. (6) (7) If there were no entries in the queue, the local pointer is set to NULL. |
The code that calls OSQAccept()
needs to examine the returned value. If OSQAccept()
returns a NULL pointer, then a message was not available. You don’t want your application to dereference a NULL pointer because, by convention, a NULL pointer is invalid. A non-NULL pointer indicates that a message pointer is available. An ISR should use OSQAccept()
instead of OSQPend()
.
Flushing a Queue, OSQFlush()
OSQFlush()
allows you 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 11.8.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSQFlush (OS_EVENT *pevent)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
OS_Q *pq;
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { (1)
return (OS_ERR_PEVENT_NULL);
}
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (2)
return (OS_ERR_EVENT_TYPE);
}
#endif
OS_ENTER_CRITICAL();
pq = (OS_Q *)pevent->OSEventPtr; (3)
pq->OSQIn = pq->OSQStart;
pq->OSQOut = pq->OSQStart;
pq->OSQEntries = 0;
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
} |
Panel | ||
---|---|---|
| ||
(1) (2) If (3) The IN and OUT pointers are reset to the beginning of the array and the number of entries is cleared. I decided to not check to see if any tasks were pending on the queue because it would be irrelevant anyway and would take more processing time. In other words, if tasks are waiting on the queue, then |
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 11.9. 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 is a pointer to a data structure (OS_Q_DATA, see uCOS_II.H
) that holds information about the message queue. Your application thus needs to allocate a variable of type OS_Q_DATA
that will 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
will contain 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[]
.OSEventGrp
contain a snapshot of the message queue wait list. The caller toOSQQuery()
can thus determine how many tasks are waiting for the queue.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
INT8U OSQQuery (OS_EVENT *pevent, OS_Q_DATA *pdata)
{
#if OS_CRITICAL_METHOD == 3
OS_CPU_SR cpu_sr;
#endif
OS_Q *pq;
INT8U *psrc;
INT8U *pdest;
#if OS_ARG_CHK_EN > 0
if (pevent == (OS_EVENT *)0) { (1)
return (OS_ERR_PEVENT_NULL);
}
if (pevent->OSEventType != OS_EVENT_TYPE_Q) { (2)
return (OS_ERR_EVENT_TYPE);
}
#endif
OS_ENTER_CRITICAL();
pdata->OSEventGrp = pevent->OSEventGrp; (3)
psrc = &pevent->OSEventTbl[0];
pdest = &pdata->OSEventTbl[0];
#if OS_EVENT_TBL_SIZE > 0
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 1
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 2
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 3
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 4
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 5
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 6
*pdest++ = *psrc++;
#endif
#if OS_EVENT_TBL_SIZE > 7
*pdest = *psrc;
#endif
pq = (OS_Q *)pevent->OSEventPtr;
if (pq->OSQEntries > 0) { (4)
pdata->OSMsg = *pq->OSQOut;
} else {
pdata->OSMsg = (void *)0;
}
pdata->OSNMsgs = pq->OSQEntries; (5)
pdata->OSQSize = pq->OSQSize; (6)
OS_EXIT_CRITICAL();
return (OS_NO_ERR);
} |
Panel | ||
---|---|---|
| ||
(1) (2) As always, if (3) (4) If the queue is not empty, the oldest message is extracted (but not removed) from the queue and copied to (5) (6) Finally, the current number of entries and the queue size are placed in the |
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, create a task, call OSTimeDly()
[see section 5.00, Delaying a Task, 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 is 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.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
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 pointers [(void *)1 works well] as there are resources available. A task requesting the “semaphore” calls OSQPend()
and releases the “semaphore” by calling OSQPost()
. Listing 11.10 shows how this works. You can use this technique to conserve code space if your application only needs counting semaphores and message queues (you would then have no need for the semaphore services). In this case, set OS_SEM_EN
to 0 and only use queues instead of both queues and semaphores. Note that this technique consumes a pointer-sized variable for each resource that the semaphore is guarding and requires a queue control block. In other words, you are sacrificing RAM space in order to save code space. Also, message queue 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).
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
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) */
}
} |