Scan to FollowLearn Embedded Together, learn and grow together

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
- Set Queue Length Reasonably:
- Balance selection based on producer and consumer speeds
- Consider worst-case accumulation
- Use small and fixed message structures whenever possible
- Transmit pointers for large data instead of the data itself
- Check return values of all queue operations
- Handle queue full/empty situations
- High-priority tasks waiting on a queue will block low-priority tasks
- Avoid long queue operations in interrupts
- Use uxQueueMessagesWaiting() to monitor queue usage
- Name queues for easier debugging (vQueueAddToRegistry())
- 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.

Follow 【Learn Embedded Together】 to become better together.
If you find this article useful, click “Share“, “Like“, “Recommend“!