September 11, 2025

Understanding Queues in FreeRTOS: A Comprehensive Guide

One of the most important features of FreeRTOS is its ability to manage inter-task communication using queues. Queues allow tasks to send and receive messages or data efficiently and safely, without the need for complex synchronization mechanisms like semaphores or mutexes.

In this article, we will delve into the concept of queues in FreeRTOS, how to use them with the ESP32 IDF (Espressif IoT Development Framework), and practical examples of integrating queues into your ESP32 applications.

What is a Queue in FreeRTOS?

A queue in FreeRTOS is a data structure that holds messages or items that can be passed between tasks or between an interrupt and a task. Queues provide a safe way to share data, ensuring that tasks can operate independently without the need for direct, blocking calls.

Queues are typically used in scenarios where:

  • Tasks need to send or receive data to/from other tasks.
  • Data needs to be passed from an interrupt handler to a task.
  • A producer-consumer model needs to be implemented.

Queues in FreeRTOS are thread-safe, meaning that multiple tasks or interrupt service routines (ISRs) can interact with the queue concurrently without corrupting the data, thanks to internal synchronization mechanisms.

Key Characteristics of FreeRTOS Queues

  • First-In-First-Out (FIFO): Queues follow a FIFO order, meaning that the first item inserted is the first item to be removed.
  • Fixed Size: Each queue has a predefined size determined at creation. This size limits the number of items that can be stored in the queue at any given time.
  • Blocking Behavior: When a queue is full or empty, the sending or receiving task can be made to wait (block) until there is space to send data or data is available to receive.

How to Create and Use a Queue in FreeRTOS

Queue Creation

To create a queue in FreeRTOS, you use the function xQueueCreate(). It creates a queue and specifies the number of items it can hold and the size of each item.

QueueHandle_t queue = xQueueCreate(queueLength, itemSize);
  • queueLength: The maximum number of items the queue can hold.
  • itemSize: The size of each item in bytes.

Sending Data to a Queue

To send data to a queue, you use the xQueueSend() , xQueueSendToBack() ,xQueueSendToFront(),

BaseType_t xQueueSend(QueueHandle_t xQueue, const void * pvItemToQueue, TickType_t xTicksToWait);
  • xQueue: The handle of the queue to which data is being sent.
  • pvItemToQueue: Pointer to the item being sent to the queue.
  • xTicksToWait: The maximum amount of time the sending task should block if the queue is full.

Receiving Data from a Queue

To receive data from a queue, you use the xQueueReceive() function. This function removes an item from the front of the queue and copies it to the specified variable.

BaseType_t xQueueReceive(QueueHandle_t xQueue, void * pvBuffer, TickType_t xTicksToWait);

Queue Delete

When you’re done using a queue, you can delete it to free the resources associated with it. This is done using vQueueDelete().

vQueueDelete(myQueue);

Implementing Queues in FreeRTOS

Example-1 xQueueSend and xQueueReceive

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"

QueueHandle_t myQueue;

void producerTask(void *pvParameters) {
    int counter = 0;
    while (1) {
        // Sending data to the queue
        ESP_LOGI("Producer", "Sending data: %d", counter);
        xQueueSend(myQueue, &counter, portMAX_DELAY);
        counter++;
        vTaskDelay(pdMS_TO_TICKS(1000));  // Delay 1 second
    }
}

void consumerTask(void *pvParameters) {
    int receivedData;
    while (1) {
        // Receiving data from the queue
        if (xQueueReceive(myQueue, &receivedData, portMAX_DELAY) == pdTRUE) {
            ESP_LOGI("Consumer", "Received data: %d", receivedData);
        }
    }
}

void app_main(void) {
    // Create the queue with a length of 10 and item size of int
    myQueue = xQueueCreate(10, sizeof(int));

    if (myQueue == NULL) {
        ESP_LOGE("app_main", "Failed to create queue");
        return;
    }

    // Create the producer and consumer tasks
    xTaskCreate(producerTask, "Producer", 2048, NULL, 2, NULL);
    xTaskCreate(consumerTask, "Consumer", 2048, NULL, 2, NULL);
}

Example-2 xQueueSendFromISR()

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"

// Define the queue and task handles
QueueHandle_t myQueue;
TaskHandle_t consumerTaskHandle = NULL;

// ISR Handler (simulated interrupt)
void IRAM_ATTR gpio_isr_handler(void *arg) {
    int value = 42;  // Simulating data to send to the queue

    // Send data from the ISR to the queue
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xQueueSendFromISR(myQueue, &value, &xHigherPriorityTaskWoken);

    // If a higher-priority task was woken, yield to it
    if (xHigherPriorityTaskWoken) {
        portYIELD_FROM_ISR();
    }
}

// Consumer Task (to process data from the queue)
void consumerTask(void *pvParameters) {
    int receivedValue;
    while (1) {
        // Receive data from the queue (this is done in the task)
        if (xQueueReceive(myQueue, &receivedValue, portMAX_DELAY) == pdPASS) {
            ESP_LOGI("Consumer", "Received value: %d", receivedValue);
        }
    }
}

// Main application function
void app_main(void) {
    // Create a queue to hold one integer
    myQueue = xQueueCreate(5, sizeof(int));

    if (myQueue == NULL) {
        ESP_LOGE("Main", "Queue creation failed!");
        return;
    }

    // Create the consumer task
    xTaskCreate(consumerTask, "Consumer", 2048, NULL, 2, &consumerTaskHandle);

    // Simulate the interrupt by calling the ISR handler every 3 seconds
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(3000));
        gpio_isr_handler(NULL);  // Simulate an interrupt
    }
}

Example-3 xQueueReceiveFromISR()

#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"
#include "esp_log.h"

// Define the queue handle
QueueHandle_t myQueue;

// Interrupt service routine (ISR) handler
void IRAM_ATTR gpio_isr_handler(void *arg) {
    int receivedValue;

    // Define a flag to indicate if a higher-priority task was woken by the ISR
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    // Try to receive data from the queue inside the ISR
    if (xQueueReceiveFromISR(myQueue, &receivedValue, &xHigherPriorityTaskWoken)) {
        ESP_LOGI("ISR", "Received value from queue: %d", receivedValue);
    } else {
        ESP_LOGI("ISR", "Queue is empty, no data to receive.");
    }

    // If a higher-priority task was woken, yield to it
    if (xHigherPriorityTaskWoken) {
        portYIELD_FROM_ISR();
    }
}

// Producer task that sends data to the queue
void producerTask(void *pvParameters) {
    int valueToSend = 0;

    while (1) {
        // Send data to the queue
        if (xQueueSend(myQueue, &valueToSend, portMAX_DELAY)) {
            ESP_LOGI("Producer", "Sent value to queue: %d", valueToSend);
            valueToSend++;
        }
        vTaskDelay(pdMS_TO_TICKS(1000));  // Wait for 1 second
    }
}

// Main application function
void app_main(void) {
    // Create a queue with a length of 10 and item size of int
    myQueue = xQueueCreate(10, sizeof(int));

    if (myQueue == NULL) {
        ESP_LOGE("Main", "Queue creation failed!");
        return;
    }

    // Create producer task
    xTaskCreate(producerTask, "Producer", 2048, NULL, 2, NULL);

    // Simulate setting up an interrupt (e.g., GPIO interrupt)
    // For simplicity, we will trigger the ISR manually here
    while (1) {
        vTaskDelay(pdMS_TO_TICKS(3000));  // Wait for 3 seconds
        gpio_isr_handler(NULL);  // Simulate the ISR being triggered
    }
}

Example-4xQueuePeek() and xQueueSendToFront()

Here’s an example demonstrating the use of xQueuePeek() and xQueueSendToFront() in FreeRTOS. These two functions are commonly used for queue operations where:

  • xQueuePeek(): Reads the front item of the queue without removing it (i.e., it doesn’t dequeue the item).
  • xQueueSendToFront(): Sends an item to the front of the queue, pushing all other items back.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"

// Define the queue handle
QueueHandle_t myQueue;

// Task handles
TaskHandle_t producerTaskHandle = NULL;
TaskHandle_t consumerTaskHandle = NULL;

// Producer task that sends data to the queue
void producerTask(void *pvParameters) {
    int producerCounter = 0;

    while (1) {
        // Simulate sending data to the front of the queue
        xQueueSendToFront(myQueue, &producerCounter, portMAX_DELAY);
        ESP_LOGI("Producer", "Sent value to front: %d", producerCounter);

        producerCounter++;
        vTaskDelay(pdMS_TO_TICKS(1000));  // Wait for 1 second
    }
}

// Consumer task that peeks at the front value of the queue
void consumerTask(void *pvParameters) {
    int peekedValue;

    while (1) {
        // Peek the front value from the queue without removing it
        if (xQueuePeek(myQueue, &peekedValue, portMAX_DELAY)) {
            ESP_LOGI("Consumer", "Peeked value: %d", peekedValue);
        } else {
            ESP_LOGI("Consumer", "No value in the queue.");
        }

        vTaskDelay(pdMS_TO_TICKS(2000));  // Wait for 2 seconds
    }
}

// Main application function
void app_main(void) {
    // Create a queue with a length of 5 and item size of int
    myQueue = xQueueCreate(5, sizeof(int));

    if (myQueue == NULL) {
        ESP_LOGE("Main", "Queue creation failed!");
        return;
    }

    // Create producer and consumer tasks
    xTaskCreate(producerTask, "Producer", 2048, NULL, 2, &producerTaskHandle);
    xTaskCreate(consumerTask, "Consumer", 2048, NULL, 2, &consumerTaskHandle);
}

Conclusion

Queues are a powerful and flexible mechanism for inter-task communication in FreeRTOS. They are essential for managing data exchange, task synchronization, and ensuring safe communication between tasks, even in multi-threaded or interrupt-driven environments. Understanding how to effectively use queues—considering blocking behavior, timeouts, and memory management—can help you build efficient and reliable real-time systems.

Leave a Reply

Your email address will not be published. Required fields are marked *