FreeRTOS QueueManagement
FreeRTOS QueueManagement
3
Queue: Task-to-task communication
• Scope
– How to create a queue
– How a queue manages the data it contains
– How to send data to a queue
– How to receive data from a queue
– What it means to block on a queue
– How to block on multiple queues.
– How to overwrite data in a queue.
– How to clear a queue.
– The effect of task priorities when writing to and
reading from a queue 4
Queue Characteristics – Data storage
• A queue can hold a finite number of fixed size
data items.
– Normally, used as FIFO buffers where data is
written to the end of the queue and removed from
the front of the queue.
– Also possible to write to the front of a queue and
to overwrite data that is already at the front of a
queue..
– Writing data to a queue causes a byte-for-byte
copy of the data to be stored in the queue itself.
– Reading data from a queue causes the copy of
the data to be removed from the queue.
5
6
• Queues are objects in their own right
– Not owned by or assigned to any particular task
– Any number of tasks can write to the same queue and any
number of tasks can read from the same queue.
– Very common to have multiple writers, but very rare to
have multiple readers.
7
Blocking on Queue Reads
• When a task attempts to read from a queue it
can optionally specify a ‘block’ time
– The maximum time that the task should be kept in the
Blocked state to wait for data to be available from the
queue, should the queue already be empty.
– It is automatically moved to the Ready state when
another task or interrupt places data into the queue.
– It will also be moved automatically from the Blocked
state to the Ready state if the specified block time
expires before data becomes available.
– Queues can have multiple readers, so it is possible
for a single queue to have more than one task
blocked on it waiting for data. When this is the case:
8
Blocking on Queue Reads
• Only one task will be unblocked when data
becomes available.
– Queue can have multiple readers.
• So, it is possible for a single queue to have more than
one task blocked on it waiting for data.
– The task that is unblocked will always be the
highest priority task that is waiting for data.
– If the blocked tasks have equal priority, the task
that has been waiting for data the longest will be
unblocked.
9
Blocking on Queue Writes
• Just as it can when reading from a
queue, A task can optionally specify a
‘block’ time when writing to a queue.
– The maximum time that task should be held in the
Blocked state to wait for space to be available on
the queue, should the queue already be full.
10
Blocking on Queue Writes
12
• Function Prototype
QueueHandle_t xQueueCreate(
UBaseType_t uxQueueLength,
UBaseType_t uxItemSize );
Returns:
If the queue is created successfully then a handle to the created queue is
returned. If the memory required to create the queue could not be
allocated then NULL is returned. 13
xQueueSendToBack() and
xQueueSendToFront() API Functions
• xQueueSendToBack()
– Be equivalent to xQueueSend()
– Be used to send data to the back (tail) of a queue
• xQueueSendToFront()
– Be used to send data to the front (head) of a queue
• Please note, never call these two API functions
from an interrupt service routine (ISR).
– Interrupt-safe versions will be used in their place and
described in next chapter.
14
• Function prototypes
BaseType_t xQueueSendToBack (
QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
BaseType_t xQueueSendToFront(
QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait);
17
xQueueReceive() and xQueuePeek() API
Function
• xQueueReceive()
– Be used to receive (consume) an item from a queue.
The item received is removed from the queue.
• xQueuePeek()
– Be used receive an item from the queue without the
item being removed from the queue.
– Receives the item from the head of the queue.
• Please note, never call these two API functions
from an interrupt service routine (ISR).
18
•Function prototypes
BaseType_t xQueueReceive (
QueueHandle_t xQueue,
const void *pvBuffer,
TickType_t xTicksToWait);
BaseType_t xQueuePeek(
QueueHandle_t xQueue,
const void * pvBuffer,
TickType_t xTicksToWait);
22
Example 10. Blocking when receiving
from a queue
• To demonstrate
– a queue being created,
• Hold data items of type long
– data being sent to the queue from multiple tasks,
• Sending tasks do not specify a block time, lower priority than
receiving task.
– And data being received from the queue
• Receiving task specifies a block time 100ms
• So, queue never contains more than one item
– Once data is sent to the queue, the receiving task will
unblock, pre-empt the sending tasks, and remove the
data – leaving the queue empty once again.
23
static void vSenderTask( void *pvParameters )
{
long lValueToSend;
BaseType_t xStatus;
27
{
/* Create the task that will read from the queue. The task is created with
priority 2, so above the priority of the sender tasks. */
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );
for( ;; );
// return 0;
}
29
Execution sequence
30
Receiving Data From Multiple Sources
/* Declare two variables of type xData that will be passed on the queue. */
static const xData xStructsToSend[ 2 ] =
{
{ 100, mainSENDER_1 }, /* Used by Sender1. */
{ 200, mainSENDER_2 } /* Used by Sender2. */
};
34
static void vSenderTask( void *pvParameters )
{
BaseType_t xStatus;
const TickType_t xTicksToWait = 100 / portTICK_PERIOD_MS;
36
xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );
for( ;; );
} 38
39
• In vSenderTask(), the sending task specifies a
block time of 100ms.
– So, it enters the Blocked state to wait for space to
become available each time the queue becomes full.
– It leaves the Blocked state when either the space is
available on the queue or 100ms expires without
space be available (should never expire as receiving
task is continuously removing items from the queue).
xStatus = xQueueSendToBack(xQueue,
pvParameters,
100/portTICK_PERIOD_MS);
If (xStatus != pdPASS) {
Serial.print(“Could not send to the queue.\n”);
}
taskYIELD(); 40
• vReceiverTask() will run only when both
sending tasks are in the Blocked state.
– Sending tasks will enter the Blocked state only
when the queue is full as they have higher
priorities.
– The receiving task will execute only when the
queue is already full. -> it always expects to
receive data even without a ‘block’ time.
xStatus = xQueueReceive(xQueue,
&xReceivedStructure, 0);
if (xStatus == pdPASS) {
// print the data received
}
41
Execution sequence – Sender 1 and 2
have higher priorities than Receiver
42
Working with large data
43
The owner of the RAM being pointed to is clearly defined.
44
The RAM being pointed to remains valid.
45
Create a queue that can hold up to 5 pointers.
for( ;; )
{
pcStringToSend = ( char * ) prvGetBuffer(xMaxStringLength);
snprintf( pcStringToSend, xMaxStringLength, "String number %d\r\n",
xStringNumber );
xStringNumber++;
xQueueSend( xPointerQueue, &pcStringToSend, portMAX_DELAY );
}
}
Receive a pointer to a buffer from the queue, then
prints the string contained in the buffer.
void vStringReceivingTask( void *pvParameters )
{
char *pcReceivedString;
for( ;; )
{
xQueueReceive( xPointerQueue,
&pcReceivedString, portMAX_DELAY );
vPrintString( pcReceivedString );
prvReleaseBuffer( pcReceivedString );
}
}
Using a Queue to Send Different Types
and Lengths of Data
• Previous sections of this book demonstrated two
powerful design patterns; sending structures to a
queue, and sending pointers to a queue. Combining
those techniques allows a task to use a single queue to
receive any data type from any data source. The
implementation of the FreeRTOS+TCP TCP/IP stack
provides a practical example of how this is achieved.
• The TCP/IP stack, which runs in its own task, must
process events from many different sources. Different
event types are associated with different types and
lengths of data. IPStackEvent_t structures describe all
events that occur outside of the TCP/IP task, and are
sent to the TCP/IP task on a queue.
Receiving From Multiple Queues –
Queue Sets
• Often application designs require a single task to receive data of
different sizes, data with different meanings, and data from
different sources.
• The previous section demonstrated how to do this in a neat and
efficient way using a single queue that receives structures.
• However, sometimes an application's designer is working with
constraints that limit their design choices, necessitating the use of
a separate queue for some data sources. In such cases a 'queue
set' can be used.
• Queue sets allow a task to receive data from more than one queue
without the task polling each queue in turn to determine which, if
any, contains data.
• A design that uses a queue set to receive data from multiple
sources is less neat, and less efficient, than a design that achieves
the same functionality using a single queue that receives
structures. For that reason, it is recommended to only use queue
sets if design constraints make their use absolutely necessary.
Creating a queue set
void setup() {
Serial.begin(9600);
/* Create the two queues, both of which send character
pointers. The priority of the receiving task is above the
priority of the sending tasks, so the queues will never have
more than one item in them at any one time*/
xQueue1 = xQueueCreate( 1, sizeof( char * ) );
xQueue2 = xQueueCreate( 1, sizeof( char * ) );
/* Create the queue set. Two queues will be added to
the set, each of which can contain 1 item, so the
maximum number of queue handles the queue set
will ever have to hold at one time is 2 (2 queues
multiplied by 1 item per queue). */
xQueueSet = xQueueCreateSet( 1 * 2 );
for( ;; )
{