Detailed Explanation of Message Queues in FreeRTOS

Scan to FollowLearn Embedded Together, learn and grow together

Detailed Explanation of Message Queues in FreeRTOS

This is a series of introductory articles on FreeRTOS. While organizing my knowledge, I hope to help beginners quickly get started and master the basic principles and usage of FreeRTOS.

Quick Start with FreeRTOS – Initial Exploration of the System

FreeRTOS Official Chinese Website is Now Live

FreeRTOS Coding Standards and Data Types

Quick Start with FreeRTOS – Task Management

This article introduces the message queue related content in FreeRTOS.

The message queue is one of the most important inter-task communication mechanisms in FreeRTOS, allowing tasks to send and receive variable-length messages in a First In First Out (FIFO) manner.

Characteristics of Message Queues

  • Thread Safe: Built-in mutex mechanism, no additional protection needed for multi-task access
  • Blocking Operations: Tasks can choose to block wait or return immediately when sending/receiving
  • Variable Length: Supports transmission of messages of different lengths
  • Multi-task Wakeup: Multiple tasks can wait for the same queue simultaneously
  • Multiple Access Methods: Supports both FIFO and LIFO access methods

Application Scenarios of Message Queues

  • Data transmission between tasks

  • Communication between interrupt service routines and tasks

  • Implementation of producer-consumer model

  • Event notification mechanism

Message Queue Operation Functions

Queue Creation and Deletion

/* Create dynamic queue */
QueueHandle_t xQueueCreate(
    UBaseType_t uxQueueLength,   // Queue length (maximum number of items)
    UBaseType_t uxItemSize       // Size of each item (bytes)
);

/* Create static queue */
QueueHandle_t xQueueCreateStatic(
    UBaseType_t uxQueueLength,
    UBaseType_t uxItemSize,
    uint8_t *pucQueueStorageBuffer,  // User-provided storage area
    StaticQueue_t *pxQueueBuffer     // User-provided queue structure
);

/* Delete queue */
void vQueueDelete(QueueHandle_t xQueue);

Send Messages to Queue

/* Standard send (add to the end) */
BaseType_t xQueueSend(
    QueueHandle_t xQueue,        // Queue handle
    const void *pvItemToQueue,    // Pointer to data to send
    TickType_t xTicksToWait       // Maximum blocking time
);

/* Send to front (insert at the head) */
BaseType_t xQueueSendToFront(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    TickType_t xTicksToWait
);

/* Overwrite send (overwrite oldest data when queue is full) */
BaseType_t xQueueOverwrite(
    QueueHandle_t xQueue,
    const void *pvItemToQueue
);

/* Send from interrupt service routine */
BaseType_t xQueueSendFromISR(
    QueueHandle_t xQueue,
    const void *pvItemToQueue,
    BaseType_t *pxHigherPriorityTaskWoken
);

Receive Messages from Queue

/* Standard receive */
BaseType_t xQueueReceive(
    QueueHandle_t xQueue,        // Queue handle
    void *pvBuffer,             // Pointer to receive buffer
    TickType_t xTicksToWait      // Maximum blocking time
);

/* Peek at the head item of the queue (without removing) */
BaseType_t xQueuePeek(
    QueueHandle_t xQueue,
    void *pvBuffer,
    TickType_t xTicksToWait
);

/* Receive from interrupt service routine */
BaseType_t xQueueReceiveFromISR(
    QueueHandle_t xQueue,
    void *pvBuffer,
    BaseType_t *pxHigherPriorityTaskWoken
);

Query Queue Status

/* Get the current number of items in the queue */
UBaseType_t uxQueueMessagesWaiting(QueueHandle_t xQueue);

/* Get remaining space in the queue */
UBaseType_t uxQueueSpacesAvailable(QueueHandle_t xQueue);

/* Reset the queue */
BaseType_t xQueueReset(QueueHandle_t xQueue);

Example of Using Message Queues

Basic Data Transmission Example

#include "FreeRTOS.h"
#include "queue.h"

/* Define queue handle */
QueueHandle_t xDataQueue;

/* Define message structure */
typedef struct 
{
    uint8_t ucValue;
    uint32_t ulTimestamp;
} DataMessage_t;

void vSenderTask(void *pvParameters) 
{
    DataMessage_t xMessage;
    const TickType_t xDelay = pdMS_TO_TICKS(500);
    
    for(;;) 
    {
        /* Prepare message */
        xMessage.ucValue = (uint8_t)rand() % 256;
        xMessage.ulTimestamp = xTaskGetTickCount();
        
        /* Send message to queue */
        if(xQueueSend(xDataQueue, &xMessage, 0) != pdPASS) 
        {
            printf("Queue is full, send failed\n");
        }
        
        vTaskDelay(xDelay);
    }
}

void vReceiverTask(void *pvParameters) 
{
    DataMessage_t xReceivedMessage;
    
    for(;;) 
    {
        /* Receive message from queue */
        if(xQueueReceive(xDataQueue, &xReceivedMessage, portMAX_DELAY) == pdPASS) 
        {
            printf("Received value: %d, Timestamp: %lu\n", 
                   xReceivedMessage.ucValue, 
                   xReceivedMessage.ulTimestamp);
        }
    }
}

int main(void) 
{
    /* Create queue, can hold up to 5 DataMessage_t items */
    xDataQueue = xQueueCreate(5, sizeof(DataMessage_t));
    
    if(xDataQueue != NULL) 
    {
        /* Create sender and receiver tasks */
        xTaskCreate(vSenderTask, "Sender", 128, NULL, 2, NULL);
        xTaskCreate(vReceiverTask, "Receiver", 128, NULL, 1, NULL);
        
        /* Start scheduler */
        vTaskStartScheduler();
    }
    
    /* If scheduler fails to start, execution will reach here */
    for(;;);
    return 0;
}

Using Queues in Interrupt Service Routines

#include "FreeRTOS.h"
#include "queue.h"

QueueHandle_t xInterruptQueue;

/* Simulate external interrupt handling */
void EXTI_IRQHandler(void) 
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    uint32_t ulInterruptValue = 0x12345678;
    
    /* Send message in interrupt */
    xQueueSendFromISR(xInterruptQueue, &ulInterruptValue, &xHigherPriorityTaskWoken);
    
    /* If a higher priority task is woken, request context switch */
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

void vInterruptHandlerTask(void *pvParameters) 
{
    uint32_t ulReceivedValue;
    
    for(;;) {
        /* Wait for interrupt message */
        if(xQueueReceive(xInterruptQueue, &ulReceivedValue, portMAX_DELAY) == pdPASS) 
        {
            printf("Received interrupt message: 0x%08lX\n", ulReceivedValue);
            /* Process interrupt event */
        }
    }
}

int main(void) 
{
    /* Create queue, depth of 10, each item is uint32_t */
    xInterruptQueue = xQueueCreate(10, sizeof(uint32_t));
    
    if(xInterruptQueue != NULL) 
    {
        xTaskCreate(vInterruptHandlerTask, "IntHandler", 128, NULL, 3, NULL);
        vTaskStartScheduler();
    }
    
    for(;;);
    return 0;
}

Multi-Producer Multi-Consumer Model

#include "FreeRTOS.h"
#include "queue.h"

#define NUM_PRODUCERS    3
#define NUM_CONSUMERS    2

QueueHandle_t xWorkQueue;

typedef struct {
    TaskHandle_t xProducer;
    uint32_t ulData;
} WorkItem_t;

void vProducerTask(void *pvParameters) 
{
    WorkItem_t xItem;
    xItem.xProducer = xTaskGetCurrentTaskHandle();
    
    for(;;) 
    {
        /* Generate work item */
        xItem.ulData = (uint32_t)rand() % 1000;
        
        /* Send to work queue */
        xQueueSend(xWorkQueue, &xItem, portMAX_DELAY);
        
        vTaskDelay(pdMS_TO_TICKS(200 + (rand() % 300)));
    }
}

void vConsumerTask(void *pvParameters) 
{
    WorkItem_t xReceivedItem;
    
    for(;;) 
    {
        /* Get work item from queue */
        if(xQueueReceive(xWorkQueue, &xReceivedItem, portMAX_DELAY) == pdPASS) 
        {
            printf("Consumer processing data from task%p: %lu\n", 
                   xReceivedItem.xProducer,
                   xReceivedItem.ulData);
            
            /* Simulate processing time */
            vTaskDelay(pdMS_TO_TICKS(50 + (rand() % 100)));
        }
    }
}

int main(void) 
{
    /* Create work queue, depth of 5 */
    xWorkQueue = xQueueCreate(5, sizeof(WorkItem_t));
    
    if(xWorkQueue != NULL) 
    {
        /* Create multiple producers and consumers */
        for(int i = 0; i < NUM_PRODUCERS; i++) 
        {
            xTaskCreate(vProducerTask, "Producer", 128, NULL, 2, NULL);
        }
        
        for(int i = 0; i < NUM_CONSUMERS; i++) 
        {
            xTaskCreate(vConsumerTask, "Consumer", 128, NULL, 1, NULL);
        }
        
        vTaskStartScheduler();
    }
    
    for(;;);
    return 0;
}

Advanced

Queue Sets

Queue sets allow tasks to wait for multiple queues or semaphores simultaneously:

#include "FreeRTOS.h"
#include "queue.h"

void vQueueSetExample(void) 
{
    /* Create two queues and one queue set */
    QueueHandle_t xQueue1 = xQueueCreate(5, sizeof(int));
    QueueHandle_t xQueue2 = xQueueCreate(5, sizeof(float));
    QueueSetHandle_t xQueueSet = xQueueCreateSet(5 + 5);  // Sum of depths of two queues
    
    /* Add queues to the queue set */
    xQueueAddToSet(xQueue1, xQueueSet);
    xQueueAddToSet(xQueue2, xQueueSet);
    
    /* Task main loop */
    for(;;) 
    {
        /* Wait for any queue to have data */
        QueueSetMemberHandle_t xActivatedMember = xQueueSelectFromSet(xQueueSet, portMAX_DELAY);
        
        /* Determine which queue was activated */
        if(xActivatedMember == xQueue1) 
        {
            int iReceivedValue;
            xQueueReceive(xQueue1, &iReceivedValue, 0);
            printf("Received integer from queue 1: %d\n", iReceivedValue);
        }
        elseif(xActivatedMember == xQueue2) 
        {
            float fReceivedValue;
            xQueueReceive(xQueue2, &fReceivedValue, 0);
            printf("Received float from queue 2: %f\n", fReceivedValue);
        }
    }
}

4.2 Large Message Transmission

When large blocks of data need to be transmitted, it is recommended to transmit pointers rather than the data itself:

#include "FreeRTOS.h"
#include "queue.h"
#include "stdlib.h"

#define LARGE_DATA_SIZE 1024

QueueHandle_t xLargeDataQueue;

void vLargeDataProducer(void *pvParameters) 
{
    for(;;) 
    {
        /* Dynamically allocate a large memory block */
        uint8_t *pucData = pvPortMalloc(LARGE_DATA_SIZE);
        
        if(pucData != NULL) 
        {
            /* Fill data */
            memset(pucData, 0xAA, LARGE_DATA_SIZE);
            
            /* Send pointer to queue */
            xQueueSend(xLargeDataQueue, &pucData, portMAX_DELAY);
        }
        
        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

void vLargeDataConsumer(void *pvParameters) 
{
    uint8_t *pucReceivedData;
    
    for(;;) 
    {
        if(xQueueReceive(xLargeDataQueue, &pucReceivedData, portMAX_DELAY) == pdPASS) 
        {
            printf("Received large data block, first byte: 0x%02X\n", pucReceivedData[0]);
            
            /* Free memory after processing */
            vPortFree(pucReceivedData);
        }
    }
}

int main(void) 
{
    /* Create queue for transmitting pointers */
    xLargeDataQueue = xQueueCreate(3, sizeof(uint8_t *));
    
    if(xLargeDataQueue != NULL) 
    {
        xTaskCreate(vLargeDataProducer, "BigDataProd", 128, NULL, 2, NULL);
        xTaskCreate(vLargeDataConsumer, "BigDataCons", 128, NULL, 1, NULL);
        vTaskStartScheduler();
    }
    
    for(;;);
    return 0;
}

Using Message Queues

  1. Set Queue Length Reasonably:
  • Balance selection based on producer and consumer speeds
  • Consider worst-case accumulation
  • Optimize Message Size:
    • Use small and fixed message structures whenever possible
    • Transmit pointers for large data instead of the data itself
  • Error Handling:
    • Check return values of all queue operations
    • Handle queue full/empty situations
  • Performance Considerations:
    • High-priority tasks waiting on a queue will block low-priority tasks
    • Avoid long queue operations in interrupts
  • Debugging Techniques:
    • Use uxQueueMessagesWaiting() to monitor queue usage
    • Name queues for easier debugging (vQueueAddToRegistry())
  • Resource Management:
    • Ensure dynamically created queues are properly deleted
    • Static allocated queues must have a sufficiently long lifecycle

    Common Issues

    Queue Blocking Issues

    Problem: Task permanently blocked on queue operations

    Solution:

    • Set reasonable timeout (not portMAX_DELAY)
    • Use xQueueSendToBack() and xQueueReceive() pairs instead of mixing front and back operations
    • Implement a watchdog mechanism to detect long blocks

    Queue Overflow Issues

    Problem: Data loss or send failure

    Solution:

    • Increase queue length
    • Use xQueueOverwrite() to overwrite oldest data (if applicable)
    • Implement flow control mechanisms

    Performance Bottlenecks

    Problem: System performance degradation, queue operations taking time

    Solution:

    • Optimize message structure, reduce message size
    • Consider using task notifications instead of simple event notifications
    • Evaluate if direct task notifications can be used

    In Conclusion

    The message queues in FreeRTOS provide a powerful and flexible inter-task communication mechanism. By using the queue API properly, developers can build efficient and reliable communication architectures for embedded systems.

    Mastering various usage patterns and technical details of queues can help solve complex task synchronization and data transmission issues.

    Detailed Explanation of Message Queues in FreeRTOS

    Follow 【Learn Embedded Together】 to become better together.

    If you find this article useful, click “Share“, “Like“, “Recommend“!

    Leave a Comment