
Queues were mentioned in a previous article (# 5). They provide a more flexible way to transfer simple messages between tasks compared to mailboxes.
Previous articles in the series:
Article # 22. Mailboxes: ancillary services and data structuresArticle # 21. Mailboxes: Introduction and Basic ServicesArticle # 20. Semaphores: helper services and data structuresArticle # 19. Semaphores: introduction and basic servicesArticle # 18. Event flag groups: helper services and data structuresArticle # 17. Event flag groups: introduction and basic servicesArticle # 16. SignalsArticle # 15. Memory sections: services and data structuresArticle # 14. Memory sections: introduction and basic servicesArticle # 13. Task data structures and unsupported API callsArticle # 12. Task ServicesArticle # 11. Tasks: configuration and introduction to the APIArticle # 10. Scheduler: additional features and context preservationArticle # 9. Scheduler: implementationArticle # 8. Nucleus SE: Inside and DeploymentArticle # 7. Nucleus SE: introductionArticle # 6. Other RTOS servicesArticle # 5. Interaction between tasks and synchronizationArticle # 4. Tasks, context switching and interruptsArticle # 3. Tasks and planningArticle # 2. RTOS: Structure and Real Time
Article # 1. RTOS: introduction.Using Queues
In the Nucleus SE, the queues are determined at the assembly stage. The application can have up to 16 queues. If there are no queues in the application, neither the data structures, nor the service code related to the queues are included in the application.
')
A queue is a set of areas in memory, the size of which is sufficient for one element of the type
ADDR and the safe access to which is controlled so that it can be used by several tasks. Tasks can write data to the queue until all areas are filled. Tasks can read data from a queue, and data is usually received on a FIFO (First-in-First-Out) basis. Attempting to write data to an overflowing queue or read data from an empty queue may result in an error or suspension of the task, depending on the selected API call parameters and the configuration of the Nucleus SE.
Queues and data channels
Nucleus SE supports data links, which were also mentioned in one of the previous articles (# 5) and will be discussed in detail in one of the following. The main difference between queues and channels is the size of the message. Queues contain messages consisting of one variable of type
ADDR (usually these are pointers). The channel contains messages of arbitrary size, individual for each channel in the application and assigned during parameter setting.
Setting up queues
Number of queues
As with most Nucleus SE objects, the configuration of queues is mainly controlled by the
#define directives in the
nuse_config.h file. The main parameter is
NUSE_QUEUE_NUMBER , which determines the number of configured queues in the application. The default value is zero (that is, there are no queues in the application) and can take values up to 16. An incorrect value will cause a compile-time error, which will be generated when checking in the
nuse_config_check.h file (it is included in the
nuse_config.c file and compiled with it), which will trigger the
#error directive.
The choice of a non-zero value serves as the main activator for the queues. This parameter is used when defining data structures and their size depends on its value (for more details, in the next article). In addition, a non-zero value activates the API settings.
Activate API calls
Each API function (service call) in Nucleus SE has an activating
#define directive in
nuse_config.h . For queues, these directives are:
NUSE_QUEUE_SEND NUSE_QUEUE_RECEIVE NUSE_QUEUE_JAM NUSE_QUEUE_RESET NUSE_QUEUE_INFORMATION NUSE_QUEUE_COUNT
By default, they are assigned the value
FALSE , thus disabling all service calls and blocking the inclusion of the code implementing them. To configure the queues in the application, you need to select the necessary API calls and set them to
TRUE .
Below is a snippet of code from the
nuse_config.h file:
#define NUSE_QUEUE_NUMBER 0 /* Number of queues in the system - 0-16 */ /* Service call enablers */ #define NUSE_QUEUE_SEND FALSE #define NUSE_QUEUE_RECEIVE FALSE #define NUSE_QUEUE_JAM FALSE #define NUSE_QUEUE_RESET FALSE #define NUSE_QUEUE_INFORMATION FALSE #define NUSE_QUEUE_COUNT FALSE
If the queue API functions are activated, but there are no queues in the application (except
NUSE_Queue_Count () , which is always enabled), a compilation error will appear. If your code uses an API call that has not been activated, it will cause a build error, since the implementation code was not included in the application.
Queue service calls
Nucleus RTOS supports ten queue-related service calls, which provide the following functionality:
- Queuing a message. The Nucleus SE is implemented as a NUSE_Queue_Send () function.
- Receive a message from the queue. Nucleus SE is implemented as a NUSE_Queue_Receive () function.
- Putting a message in the head queue. Nucleus SE is implemented in NUSE_Queue_Jam () .
- Restore the queue to an unused state with the release of all suspended tasks (reset). In Nucleus SE, implemented in NUSE_Queue_Reset () .
- Providing information about a specific queue. Nucleus SE is implemented in NUSE_Queue_Information () .
- Return the number of currently configured queues in the application. Nucleus SE is implemented in NUSE_Queue_Count () .
- Adding a new queue to the application (creating a queue). Nucleus SE is not implemented.
- Deleting a queue from an application. Nucleus SE is not implemented.
- Return pointers to all queues in the application. Nucleus SE is not implemented.
- Sending a message to all tasks suspended in a queue (broadcast). Nucleus SE is not implemented.
The implementation of each of these service calls is described in detail below.
Service calls for writing and reading from queues
The basic operations that are performed on queues are writing (which is sometimes called queuing messages) and reading (also known as receiving messages). It is also possible to record at the beginning of the queue (jamming). Nucleus RTOS and Nucleus SE provide three basic API calls for these operations, which will be discussed below.
Write to the queue
The Nucleus RTOS API service call for writing to the queue is very flexible and allows you to pause the task implicitly or with a specific timeout if the operation cannot be completed immediately (for example, when you try to write to a filled queue). Nucleus SE provides the same functions, but task suspension is optional, and timeout is not implemented.
Call to queue a message in the Nucleus RTOSService Call Prototype:
STATUS NU_Send_To_Queue (NU_QUEUE * queue, VOID * message, UNSIGNED size, UNSIGNED suspend);Options:
queue - pointer to the queue management block provided by the user;
message - a pointer to the message to be sent;
size - the number of data elements of type
UNSIGNED in the message. If the queue supports variable length messages, this parameter must be equal to the size of the message or be smaller than the size of the message supported by the queue. If the queue supports fixed-size messages, this parameter must exactly match the size of the message supported by the queue;
suspend - task suspension specification, can be
NU_NO_SUSPEND or
NU_SUSPEND, or timeout value.
Return value:
NU_SUCCESS - the call was successfully completed;
NU_INVALID_QUEUE - invalid pointer to the queue;
NU_INVALID_POINTER - null pointer to the message (
NULL );
NU_INVALID_SIZE - the message size is incompatible with the message size supported by the queue;
NU_INVALID_SUSPEND - the suspension was made from a thread not associated with the task;
NU_QUEUE_FULL - the queue is full and the suspension has not been specified;
NU_TIMEOUT - the queue is full even after pausing the task for the specified timeout;
NU_QUEUE_DELETED - the queue was deleted while the task was suspended;
NU_QUEUE_RESET — The queue was reset while the task was suspended.
Call to queue a message in the Nucleus SEThis API service call supports the core functionality of the Nucleus RTOS API.
Service Call Prototype:
STATUS NUSE_Queue_Send (NUSE_QUEUE queue, ADDR * message, U8 suspend);Options:
queue - the
queue index;
message - pointer to the message to be sent, is one variable of type
ADDR ;
suspend - specification for pausing tasks, can be
NUSE_NO_SUSPEND or NUSE_SUSPEND .
Return value:
NUSE_SUCCESS - the call was successfully completed;
NUSE_INVALID_QUEUE - invalid queue index;
NUSE_INVALID_POINTER - null pointer to the message (
NULL );
NUSE_INVALID_SUSPEND - attempt to pause a task from a
thread that is not associated with the task or when the API for blocking tasks is disabled;
NUSE_QUEUE_FULL - the queue is full and the suspension has not been specified;
NUSE_QUEUE_WAS_RESET — The queue was reset while the task was suspended.
Implement Message Queuing in the Nucleus SEThe code variant of the
NUSE_Queue_Send () API function (after checking the parameters) is selected using conditional compilation, depending on whether support for blocking tasks is activated or not. We will consider both options.
If task lock is not activated, the code for this service call is fairly simple:
if (NUSE_Queue_Items[queue] == NUSE_Queue_Size[queue]) /* queue full */ { return_value = NUSE_QUEUE_FULL; } else /* queue element available */ { NUSE_Queue_Data[queue][NUSE_Queue_Head[queue]++] = *message; if (NUSE_Queue_Head[queue] == NUSE_Queue_Size[queue]) { NUSE_Queue_Head[queue] = 0; } NUSE_Queue_Items[queue]++; return_value = NUSE_SUCCESS; }
The function simply checks if there is free space in the queue, and uses the
NUSE_Queue_Head [] index to store the message in the queue data area.
If task lock is activated, the code becomes more complex:
do { if (NUSE_Queue_Items[queue] == NUSE_Queue_Size[queue]) /* queue full */ { if (suspend == NUSE_NO_SUSPEND) { return_value = NUSE_QUEUE_FULL; } else { /* block task */ NUSE_Queue_Blocking_Count[queue]++; NUSE_Suspend_Task(NUSE_Task_Active, (queue << 4) | NUSE_QUEUE_SUSPEND); return_value = NUSE_Task_Blocking_Return[NUSE_Task_Active]; if (return_value != NUSE_SUCCESS) { suspend = NUSE_NO_SUSPEND; } } } else { /* queue element available */ NUSE_Queue_Data[queue][NUSE_Queue_Head[queue]++] = *message; if (NUSE_Queue_Head[queue] == NUSE_Queue_Size[queue]) { NUSE_Queue_Head[queue] = 0; } NUSE_Queue_Items[queue]++; if (NUSE_Queue_Blocking_Count[queue] != 0) { U8 index; /* check whether a task is blocked on this queue */ NUSE_Queue_Blocking_Count[queue]--; for (index=0; index<NUSE_TASK_NUMBER; index++) { if ((LONIB(NUSE_Task_Status[index]) == NUSE_QUEUE_SUSPEND) && (HINIB(NUSE_Task_Status[index]) == queue)) { NUSE_Task_Blocking_Return[index] = NUSE_SUCCESS; NUSE_Wake_Task(index); break; } } } return_value = NUSE_SUCCESS; suspend = NUSE_NO_SUSPEND; } } while (suspend == NUSE_SUSPEND);
Some explanations may be helpful.
The code is enclosed in a
do ... while loop , which runs until the task suspend parameter is
NUSE_SUSPEND .
If the queue is full and the
suspend parameter is
NUSE_NO_SUSPEND , the API call ends with
NUSE_QUEUE_FULL . If the suspend parameter is set to
NUSE_SUSPEND , the task is suspended. On completion (that is, when the task is resumed), if the return value is
NUSE_SUCCESS , that is, the task was resumed because the message was read (and not because the queue was reset), the code returns to the beginning of the cycle.
If the queue is not full, the provided message is stored using the
NUSE_Queue_Head [] index in the queue data area. Checks whether there are any pending tasks (waiting for messages) in the queue. If there are such tasks, the first one resumes. The variable suspend is assigned the value
NUSE_NO_SUSPEND , and the API call ends with the value
NUSE_SUCCESS .
Reading from the queue
The Nucleus RTOS API call to read from the queue is very flexible and allows you to suspend tasks implicitly or with a specific timeout if the operation cannot be completed immediately (for example, when trying to read from an empty queue). Nucleus SE provides the same functionality, but task suspension is optional, and timeout is not implemented.
Call to receive messages from the queue at the Nucleus RTOSService Call Prototype:
STATUS NU_Receive_From_Queue (NU_QUEUE * queue, VOID * message, UNSIGNED size, UNSIGNED * actual_size, UNSIGNED suspend);Options:
queue - a pointer to the queue management block provided by the user;
message - pointer to the repository for received messages;
size - the number of data elements of type
UNSIGNED in the message. This number must match the size of the message as determined when the queue was created;
suspend - task suspension specification, can be
NU_NO_SUSPEND or
NU_SUSPEND, or timeout value.
Return value:
NU_SUCCESS - the call was successfully completed;
NU_INVALID_QUEUE - invalid pointer to the queue;
NU_INVALID_POINTER - null pointer to the message (
NULL );
NU_INVALID_SUSPEND - an attempt to pause a task from a thread unrelated to a task;
NU_QUEUE_EMPTY - the queue is empty, and the suspension was not specified;
NU_TIMEOUT - says that the queue is still empty, even after pausing the task for a specified period of time;
NU_QUEUE_DELETED - the queue was deleted while the task was suspended;
NU_QUEUE_RESET — The queue was reset while the task was suspended.
Call to receive messages from the Nucleus SE queueThis API call supports the core Nucleus RTOS API.
Service Call Prototype:
STATUS NUSE_Queue_Receive (NUSE_QUEUE queue, ADDR * message, U8 suspend);Options:
queue - the
queue index;
message - pointer to the repository for received messages, is a single variable of type
ADDR ;
suspend - task suspension specification, can be
NUSE_NO_SUSPEND or
NUSE_SUSPEND .
Return value:
NUSE_SUCCESS - the call was successfully completed;
NUSE_INVALID_QUEUE - invalid queue index;
NUSE_INVALID_POINTER - null pointer to the message (
NULL );
NUSE_INVALID_SUSPEND - attempt to pause a task from a
thread that is not associated with the task or when the task lock support is disabled;
NUSE_QUEUE_EMPTY - the queue is empty, and the suspension was not specified;
NUSE_QUEUE_WAS_RESET — The queue was reset while the task was suspended.
Implementing queuing messages in the Nucleus SEThe code variant of the
NUSE_Queue_Receive () API function (after checking the parameters) is selected using conditional compilation, depending on whether support for blocking tasks is activated or not. Consider both options.
If lock support is activated, the code for this API call is quite simple:
if (NUSE_Queue_Items[queue] == 0) /* queue empty */ { return_value = NUSE_QUEUE_EMPTY; } else { /* message available */ *message = NUSE_Queue_Data[queue][NUSE_Queue_Tail[queue]++]; if (NUSE_Queue_Tail[queue] == NUSE_Queue_Size[queue]) { NUSE_Queue_Tail[queue] = 0; } NUSE_Queue_Items[queue]--; return_value = NUSE_SUCCESS; }
The function simply checks if there is a message in the queue, and uses the
NUSE_Queue_Tail [] index to retrieve the message from the queue and return the data with a pointer to the message.
If task lock is activated, the code becomes more complex:
do { if (NUSE_Queue_Items[queue] == 0) /* queue empty */ { if (suspend == NUSE_NO_SUSPEND) { return_value = NUSE_QUEUE_EMPTY; } else { /* block task */ NUSE_Queue_Blocking_Count[queue]++; NUSE_Suspend_Task(NUSE_Task_Active, (queue << 4) | NUSE_QUEUE_SUSPEND); return_value = NUSE_Task_Blocking_Return[NUSE_Task_Active]; if (return_value != NUSE_SUCCESS) { suspend = NUSE_NO_SUSPEND; } } } else { /* message available */ *message = NUSE_Queue_Data[queue][NUSE_Queue_Tail[queue]++]; if (NUSE_Queue_Tail[queue] == NUSE_Queue_Size[queue]) { NUSE_Queue_Tail[queue] = 0; } NUSE_Queue_Items[queue]--; if (NUSE_Queue_Blocking_Count[queue] != 0) { U8 index; /* check whether a task is blocked */ /* on this queue */ NUSE_Queue_Blocking_Count[queue]--; for (index=0; index<NUSE_TASK_NUMBER; index++) { if ((LONIB(NUSE_Task_Status[index]) == NUSE_QUEUE_SUSPEND) && (HINIB(NUSE_Task_Status[index]) == queue)) { NUSE_Task_Blocking_Return[index] = NUSE_SUCCESS; NUSE_Wake_Task(index); break; } } } return_value = NUSE_SUCCESS; suspend = NUSE_NO_SUSPEND; } } while (suspend == NUSE_SUSPEND);
Some explanations will be helpful.
The code is enclosed in a
do ... while loop , which runs until the task suspend parameter is
NUSE_SUSPEND .
If the queue is empty and the suspend parameter is
NUSE_NO_SUSPEND , the API call ends with the value
NUSE_QUEUE_EMPTY . If the
suspend parameter is set to
NUSE_SUSPEND , the task is suspended. Upon completion (that is, when the task resumes), if the return value is
NUSE_SUCCESS , that is, the task was resumed because the message was sent (and not because the queue was reset), the code returns to the beginning of the cycle.
If the queue contains messages, the stored message is returned using the
NUSE_Queue_Tail [] index. Checks whether there are any suspended (pending) tasks in this queue. If there are such tasks, the first one resumes. The variable suspend is assigned the value
NUSE_NO_SUSPEND , and the API call ends with the code
NUSE_SUCCESS .
Writing to head queue
The Nucleus RTOS API service call for writing a message to the queue's head is very flexible and allows you to suspend the task implicitly or with a specific timeout if the operation cannot be completed immediately (for example, when trying to write to a crowded queue). Nucleus SE provides the same functionality, but task suspension is optional, and timeout is not implemented.
Call to write message to head Nucleus RTOS queueService Call Prototype:
STATUS NU_Send_To_Front_Of_Queue (NU_QUEUE * queue, VOID * message, UNSIGNED size, UNSIGNED suspend);Options:
queue - pointer to the queue management block provided by the user;
message - a pointer to the message to be sent;
size - the number of data elements of type
UNSIGNED in the message. If the queue supports variable length messages, this parameter must be equal to the size of the message or less than the size of the message supported by the queue. If the queue supports fixed-length messages, this parameter must exactly match the size of the message supported by the queue;
suspend - task suspension specification, can be
NU_NO_SUSPEND or
NU_SUSPEND, or timeout value.
Return value:
NU_SUCCESS - the call was successfully completed;
NU_INVALID_QUEUE - invalid pointer to the queue;
NU_INVALID_POINTER - null pointer to the message (
NULL );
NU_INVALID_SIZE - the message size is incompatible with the size of the message supported by the queue;
NU_INVALID_SUSPEND - an attempt to pause from a stream that is not associated with a task
NU_QUEUE_FULL - the queue is full and the suspension has not been specified;
NU_TIMEOUT - the queue is full, even after pausing the task for a certain timeout;
NU_QUEUE_DELETED - the queue was deleted while the task was suspended;
NU_QUEUE_RESET — The queue was reset while the task was suspended.
Call to write message to head queue in Nucleus SEThis API call supports the core Nucleus RTOS API.
Service Call Prototype:
STATUS NUSE_Queue_Jam (NUSE_QUEUE queue, ADDR * message, U8 suspend);Options:
queue - the
queue index;
message - a pointer to the message, is a single variable of type
ADDR ;
suspend - task suspension specification, can be
NUSE_NO_SUSPEND or
NUSE_SUSPEND .
Return value:
NUSE_SUCCESS - the call was successfully completed;
NUSE_INVALID_QUEUE - invalid queue index;
NUSE_INVALID_POINTER - null pointer to the message (
NULL );
NUSE_INVALID_SUSPEND - attempt to pause a task from a
thread that is not associated with the task or when the task lock support is disabled;
NUSE_QUEUE_FULL - the queue is full and the suspension has not been specified;
NUSE_QUEUE_WAS_RESET — The queue was reset while the task was suspended.
Writing a message to the top of the queue in Nucleus SEThe
NUSE_Queue_Jam () API function code variant is very similar to
NUSE_Queue_Send () , only data is stored using the
NUSE_Queue_Tail [] index, thus:
if (NUSE_Queue_Items[queue] == NUSE_Queue_Size[queue]) /* queue full */ { return_value = NUSE_QUEUE_FULL; } else /* queue element available */ { if (NUSE_Queue_Tail[queue] == 0) { NUSE_Queue_Tail[queue] = NUSE_Queue_Size[queue] - 1; } else { NUSE_Queue_Tail[queue]--; } NUSE_Queue_Data[queue][NUSE_Queue_Tail[queue]] = *message; NUSE_Queue_Items[queue]++; return_value = NUSE_SUCCESS; }
The next article will look at additional API calls related to queues, as well as data structures.
About the author: Colin Walls has been working in the electronics industry for more than thirty years, spending a significant amount of time on embedded software. He is now an embedded software engineer in Mentor Embedded (a division of Mentor Graphics). Colin Walls often speaks at conferences and seminars, author of numerous technical articles and two books on embedded software. Lives in the UK.
Colin's professional
blog , e-mail: colin_walls@mentor.com.