Queue Management
A queue is a First In First Out (FIFO) mechanism provided by FreeRTOS for safely passing data between tasks or between tasks and interrupts. It supports blocking operations, allowing the sender to wait for space, the receiver to wait for data, and provides dedicated interrupt-safe functions. It is commonly used to implement inter-task communication or the producer-consumer model.
This document only covers some commonly used APIs in queue management, for more information, please refer to the FreeRTOS official documentation.
Note
The producer-consumer model is a common concurrent programming pattern, where the producer is responsible for generating data and putting it into the buffer, and the consumer takes data from the buffer for processing. The two are decoupled through a shared buffer, do not directly depend on each other, and are suitable for handling data collection and processing decoupling scenarios.
When calling queue-related APIs in ESP-IDF, you need to include the following header file in the file:
#include "freertos/queue.h"
Dynamically Create Queue
API Prototype
QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength,
UBaseType_t uxItemSize );
xQueueCreate()
is used to create a queue for inter-task communication, allocate memory and initialize the queue structure, enabling tasks to safely send and receive data.
Input Parameter |
Parameter Function |
Parameter Description |
---|---|---|
|
The maximum number of items that the queue can store at one time. |
Determines the queue capacity, limiting how many data can be stored at once. |
|
The size (in bytes) required to store each item in the queue. |
The queue stores each item by copying, so this parameter value is the number of bytes that each enqueued item will copy. Each item in the queue must have the same size. |
Return Value |
Description |
---|---|
Returns the handle of the created queue |
The queue is successfully created, returning a valid handle for subsequent operations. |
|
Memory allocation failed, queue creation failed. |
Example 1: Dynamically Create Queue
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
// Define a QueueHandle_t type variable to store the handle returned after the queue is successfully created
QueueHandle_t queue = NULL;
#define QUEUE_LENGTH 5 // Define queue length
#define ITEM_SIZE 4 // Define item size (4 bytes per item)
void app_main(void)
{
// Create queue
queue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);
if (queue == NULL) {
printf("Create FALSE\n");
}
else {
printf("Create SUCCESS\n");
}
}
The above code execution result is as follows:
Create SUCCESS
This example demonstrates the use of xQueueCreate()
to create a queue with a length of 5 and a size of 4 bytes per item, by judging whether the returned handle is NULL
to confirm whether the queue is successfully created.
When calling this API, please note:
Set the queue length and element size reasonably, and note that all element sizes must be consistent and stored by copying.
Must check the return value to prevent failure to create due to insufficient memory. It is recommended to create the queue during system initialization to ensure that the queue is ready when the task is running, and to ensure that the system has enough heap space.
The queue itself is thread-safe and can be safely used for inter-task communication.
Delete Queue
API Prototype
void vQueueDelete( QueueHandle_t xQueue );
vQueueDelete()
is used to delete a created queue and release the memory resources occupied by the queue. After the call, the queue handle will become invalid and cannot be used again.
Note
This API is only applicable to dynamically created queues.
Input Parameter |
Parameter Function |
Parameter Description |
---|---|---|
|
Handle of the queue to be deleted |
For example, a valid handle returned by |
After deleting the queue, the value of the queue handle itself will not automatically become NULL
or other special values, it still maintains the original numerical value.
But this handle has become invalid and cannot be used for any queue operations, otherwise it will cause undefined behavior.
Therefore, after calling vQueueDelete()
, it is recommended to manually set the handle to NULL
to prevent misuse.
Send Data
API Prototype
BaseType_t xQueueSend( QueueHandle_t xQueue,
const void * pvItemToQueue,
TickType_t xTicksToWait );
xQueueSend()
is used to send data to the end of the queue. If the queue is full, you can choose to block and wait until there is space or timeout to ensure the safe delivery of data to the receiver.
Input Parameter |
Parameter Function |
Parameter Description |
---|---|---|
|
Handle of the target queue for data transmission |
For example, a valid handle returned by |
|
Pointer to the data to be sent |
The data will be directly copied into the created queue, ensuring that the data size does not exceed the queue element size. |
|
Maximum waiting time |
When the queue is full, the maximum time that the caller blocks and waits, defined in tick cycles; returns immediately when it is 0. |
Note
When a task tries to operate on a queue (such as sending or receiving data), if the conditions are not met (the queue is full or empty), the task will pause execution and enter a waiting state until the conditions are met or timeout.
In the queue, this mechanism ensures that tasks do not busy wait or waste CPU resources, but effectively wait for the queue to become available, improving system efficiency and responsiveness.
Return Value |
Description |
---|---|
|
Send successful, data has been added to the queue. |
|
Send failed, the queue is full and did not wait or wait timeout. |
Example 2: Send data to the queue
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
// Define a QueueHandle_t type variable to store the handle returned after successful queue creation
QueueHandle_t queue = NULL;
#define QUEUE_LENGTH 5 // Define queue length
#define ITEM_SIZE 4 // Define item size (4 bytes per item)
void app_main(void)
{
// Create a queue
queue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);
int num = 1; // Data to be sent
BaseType_t xReturn; // Used to receive the return value
if (queue == NULL) {
printf("Create FALSE\n");
}
else {
printf("Create SUCCESS\n");
}
while (1) {
xReturn = xQueueSend(queue, (void *)&num, 10);
// Determine whether the data is sent successfully through the return value
if (xReturn == pdTRUE) {
printf("%d: Item Send SUCCESS\n", num);
}
else {
printf("Item Send FALSE\n");
}
num++;
}
}
The execution result of the above code is as follows:
Create SUCCESS
1: Item Send SUCCESS
2: Item Send SUCCESS
3: Item Send SUCCESS
4: Item Send SUCCESS
5: Item Send SUCCESS
Item Send FALSE
Item Send FALSE
This example mainly demonstrates the basic usage of xQueueSend()
.
By creating a fixed-length queue and continuously sending integers, it demonstrates how to use this function to write data into the queue and determine whether the sending is successful through the return value.
Since no task is set to receive data in the example, the queue space will not be released, and the queue will gradually fill up with continuous sending.
When the queue is full, calling xQueueSend()
again will block and wait for up to 10 ticks (specified by the parameter).
If there is still no space after timeout, the function returns errQUEUE_FULL
, indicating that the sending failed.
In addition, the second parameter passed in must be the data address, usually forced to be converted to void *
.
The queue implements transmission by copying data copies, not passing pointers, ensuring data safety and consistency.
Receiving data
API prototype
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );
xQueueReceive()
is used to receive data from the head of the queue. If the queue is empty, you can choose to block and wait until there is data or timeout, ensuring the safe acquisition of data in the queue.
Input parameter |
Parameter function |
Parameter description |
---|---|---|
|
The handle of the target queue for receiving data |
For example, a valid handle returned by |
|
Pointer to the buffer used to store the received data |
The data will be copied to this buffer, ensuring that the size of the buffer is not less than the size of the queue element. |
|
Maximum waiting time |
The maximum time the caller blocks and waits when the queue is empty, defined in tick cycles; returns immediately when it is 0. |
Return Value |
Description |
---|---|
|
Reception successful, data has been copied to the specified buffer. |
|
Reception failed, the queue is empty and no wait or wait timeout. |
Example 3: Sending and Receiving Data
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
// Define QueueHandle_t type variable to store the handle returned after successful queue creation
QueueHandle_t queue = NULL;
#define QUEUE_LENGTH 5 // Define queue length
#define ITEM_SIZE 4 // Define item size (4 bytes per item)
static void vSendTask( void *pvParameters )
{
int num = 1; // Data to be sent
BaseType_t xReturn; // Used to receive return value
while (1) {
xReturn = xQueueSend(queue, (void *)&num, 10);
// Determine whether the data is sent successfully through the return value
if (xReturn == pdTRUE) {
printf("Item Send: %d \n", num);
num++;
}
else {
printf("Item Send FALSE\n");
}
vTaskDelay(1);
}
}
static void vReceiveTask( void *pvParameters )
{
int receiver = 0; // Used to receive data
BaseType_t xReturn; // Used to receive return value
while (1) {
xReturn = xQueueReceive(queue, (void *)&receiver, 10);
// Determine whether the data is received successfully through the return value
if (xReturn == pdTRUE) {
printf("Item Receive: %d \n", receiver);
}
else {
printf("Item Receive FALSE\n");
}
vTaskDelay(1);
}
}
void app_main(void)
{
// Create queue
queue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);
if (queue != NULL) {
xTaskCreatePinnedToCore(vSendTask, "SendTask", 20480, NULL, 2, NULL, 0);
xTaskCreatePinnedToCore(vReceiveTask, "ReceiveTask", 4096, NULL, 2, NULL, 0);
}
}
The execution result of the above code is as follows:
Item Send: 1
Item Receive: 1
Item Send: 2
Item Receive: 2
Item Send: 3
Item Receive: 3
Item Send: 4
Item Receive: 4
Item Send: 5
Item Receive: 5
Item Send: 6
Item Receive: 6
This example demonstrates the basic process of data transfer between two tasks using xQueueSend()
and xQueueReceive()
.
The send task writes an integer to the queue each time, and the receive task reads and prints the data from the queue. Both tasks run at the same frequency and determine whether the operation is successful based on the return value.
From the execution results, it can be seen that the data has been successfully written and received, indicating that the queue creation and basic communication functions are normal. Since the two tasks run at the same frequency, have the same scheduling rhythm and priority, the queue always remains available, and there are no cases of full or empty queues, so there are no send or receive failures.
In the ESP-IDF environment, if two tasks involve the same queue for data read and write, it is recommended to bind them to the same CPU core to reduce the uncertainty and performance loss brought by cross-core scheduling and ensure more stable and reliable communication behavior.
Data Writing
API Prototype
BaseType_t xQueueOverwrite( QueueHandle_t xQueue,
const void * pvItemToQueue );
xQueueOverwrite()
is used to write data into the queue. If the queue is full, it will overwrite the old data. It is only suitable for queues with a length of 1 and is often used in scenarios where the latest data is always maintained.
Note
In cases where the queue length is greater than 1, xQueueOverwrite()
should not be used. Instead, the standard xQueueSend()
should be used.
Avoid directly overwriting the header data, skipping the normal first-in-first-out rule, and causing damage to the queue structure.
This practice may lead to data consistency issues, especially when multiple tasks access the same queue at the same time, it can easily cause logical errors or data confusion.
Input Parameter |
Parameter Function |
Parameter Description |
---|---|---|
|
Handle of the target queue for data writing |
For example, a valid handle returned by |
|
Pointer to the data to be written |
The data will be directly copied to the created queue, ensuring that the data size does not exceed the size of the queue element. |
Return Value |
Description |
---|---|
|
Write successful, data has been added to the queue. |
Example 4: Latest Data Overwrite
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
// Define QueueHandle_t type variable, used to store the handle returned after successful queue creation
QueueHandle_t queue = NULL;
#define QUEUE_LENGTH 1 // Define queue length
#define ITEM_SIZE 4 // Define item size (4 bytes per item)
static void vSendTask( void *pvParameters )
{
int num = 1; // Data to be sent
BaseType_t xReturn; // For receiving return value
while (1) {
xReturn = xQueueOverwrite(queue, (void *)&num);
// Determine if the data is sent successfully through the return value
if (xReturn == pdTRUE) {
printf("Item Send: %d \n", num);
num++;
}
vTaskDelay(1);
}
}
static void vReceiveTask( void *pvParameters )
{
int receiver = 0; // For receiving data
BaseType_t xReturn; // For receiving return value
while (1) {
xReturn = xQueueReceive(queue, (void *)&receiver, 10);
// Determine if the data is received successfully through the return value
if (xReturn == pdTRUE) {
printf("Item Receive: %d \n", receiver);
}
else {
printf("Item Receive FALSE\n");
}
vTaskDelay(5);
}
}
void app_main(void)
{
// Create a queue
queue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);
if (queue != NULL) {
xTaskCreatePinnedToCore(vSendTask, "SendTask", 20480, NULL, 2, NULL, 0);
xTaskCreatePinnedToCore(vReceiveTask, "ReceiveTask", 4096, NULL, 2, NULL, 0);
}
}
The execution result of the above code is as follows:
Item Send: 1
Item Receive: 1
Item Send: 2
Item Send: 3
Item Send: 4
Item Send: 5
Item Receive: 5
Item Send: 6
Item Send: 7
Item Send: 8
Item Receive: 8
This example demonstrates the behavior characteristics of data sending using xQueueOverwrite()
when the queue length is 1.
The sending task continuously writes integers into the queue, and the receiving task periodically reads data from the queue.
Since xQueueOverwrite()
will overwrite old data when the queue is full, the sending task can write continuously without waiting for reception, and the new data will always replace the old data retained in the queue.
The receiving task reads the queue once every 5 ticks, so it can only read the data that has not been overwritten again after the last overwrite in the queue.
From the execution results, it can be seen that the receiving task only successfully received part of the data, and most of the intermediate values could not be obtained because they had been overwritten before receiving.
This clearly reflects the overwrite feature of xQueueOverwrite()
, which is suitable for scenarios that only care about the latest data, such as sensor readings or status refresh.
View data
API prototype
BaseType_t xQueuePeek( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );
xQueuePeek()
is used to view the data at the head of the queue, but it does not remove the data from the queue. It is suitable for reading the current head element without changing the queue content.
Input Parameter |
Parameter Function |
Parameter Description |
---|---|---|
|
The handle of the queue to be viewed |
For example, a valid handle returned by |
|
Pointer to the buffer used to store the received data |
The data will be copied to this buffer, ensure that the size of this buffer is not less than the size of the queue element. |
|
Maximum waiting time |
The maximum time the caller blocks and waits when the queue is empty, defined in tick cycles; returns immediately when it is 0. |
Return Value |
Explanation |
---|---|
|
Received successfully, data has been copied to the specified buffer. |
|
Failed to receive, the queue is empty and did not wait or the wait timed out. |
Example 5: Viewing Data
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
// Define QueueHandle_t type variable, used to store the handle returned after the queue is created successfully
QueueHandle_t queue = NULL;
#define QUEUE_LENGTH 5 // Define queue length
#define ITEM_SIZE 4 // Define item size (4 bytes per item)
static void vSendTask( void *pvParameters )
{
int num = 1; // Data to be sent
BaseType_t xReturn; // Used to receive return value
while (1) {
xReturn = xQueueSend(queue, (void *)&num, 10);
// Determine whether the data is sent successfully through the return value
if (xReturn == pdTRUE) {
printf("Item Send: %d \n", num);
num++;
}
vTaskDelay(1);
}
}
static void vReceiveTask( void *pvParameters )
{
int receiver = 0; // Used to receive data
BaseType_t xReturn; // Used to receive return value
while (1) {
xReturn = xQueuePeek(queue, (void *)&receiver, 10);
// Determine whether the data is received successfully through the return value
if (xReturn == pdTRUE) {
printf("Item Peek: %d \n", receiver);
}
else {
printf("Item Peek FALSE\n");
}
vTaskDelay(1);
}
}
void app_main(void)
{
// Create queue
queue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);
if (queue != NULL) {
xTaskCreatePinnedToCore(vSendTask, "SendTask", 20480, NULL, 2, NULL, 0);
xTaskCreatePinnedToCore(vReceiveTask, "ReceiveTask", 4096, NULL, 2, NULL, 0);
}
}
The execution result of the above code is as follows:
Item Send: 1
Item Peek: 1
Item Send: 2
Item Peek: 1
Item Send: 3
Item Peek: 1
Item Send: 4
Item Peek: 1
Item Send: 5
This example demonstrates the function of using xQueuePeek()
to view the data at the head of the queue.
The send task continuously writes increasing integers to the queue, while the receive task uses xQueuePeek()
to read the first element of the queue, but does not remove it.
The execution result shows that although the send task keeps writing new data, the receive task always reads the first data item in the queue (i.e., the value written for the first time), indicating that xQueuePeek()
is only used to view data and will not change the queue content, which is suitable for obtaining the current head information of the queue without affecting the queue status.