Embedded Development Board Learning


Queue in FreeRTOS #7 Task Communication

Date: marzo 13, 2025

Author: Guillermo Garcia

Categories: RTOS Tags:

Queue in FreeRTOS provide a robust mechanism to exchange data between tasks or between tasks. In embedded systems, efficient inter-task communication is crucial for system performance and reliability. This article explores how to use queues in FreeRTOS, provides an in-depth example, and analyzes performance using SystemView.

What is a Queue in FreeRTOS?

A queue is a first-in, first-out (FIFO) data structure used to send messages between tasks. It ensures data integrity and helps in synchronizing tasks without requiring complex shared memory handling. Queues can store various data types, including simple integers and complex structures.

Characteristics of a Queue

Data Storage: A queue can hold a finite number of fixed size data items. The maximum number of items a
queue can hold is called its ‘length’. Both the length and the size of each data item are set
when the queue is created.

Access by Multiple Tasks: Queues are objects in their own right that are 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.

Blocking on Queue Reads: When a task attempts to read from a queue it can optionally specify a ‘block’ time. This is the time 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. A task that is in the Blocked state, waiting for data to become available from a queue, is automatically moved to the Ready state when another task or interrupt places data into the queue.

Blocking on Queue Writes: Just as when reading from a queue, a task can optionally specify a block time when writing to a queue. In this case, the block time is the maximum time the task should be held in the
Blocked state to wait for space to become available on the queue, should the queue already be full.

Using a Queue

Now that we know that a Queue is simply a register that behaves like a FIFO before using this mechanism, it is important to explore the APIs available to manage Queues. We can use the main APIs that FreeRTOS provides us.

Create a Queue

Creating a queue is very similar to creating a task as we did in this article, we simply call the API to create a queue. It is important to consider that we must create a queue before trying to use it.

QueHandle_t xQueueCreate(
    UBaseType_t uxQueueLength   /*number of max elements to hold*/
    UBaseType_t uxItemSize      /*size in bytes of elements to hold*/
);

Here we are creating a queue and its handler to send or receive data in the queue is the variable xQueue this record will have the capacity to store QUEUE_LENGTH data of type ITEM_SIZE.

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

#define QUEUE_LENGTH 5
#define ITEM_SIZE sizeof(int)

QueueHandle_t xQueue;

void createQueue() {
    xQueue = xQueueCreate(QUEUE_LENGTH, ITEM_SIZE);
    if (xQueue == NULL) {
        // Queue creation failed
    }
}

Sending Data to a Queue

When we have new data from a task we need to send it into the queue, we call the following API, this will place the value in the record that we previously created, for this we use the xQueue handle.

void senderTask(void *pvParameters) {
    int value = 100;
    while (1) {
        if (xQueueSend(xQueue, &value, pdMS_TO_TICKS(100)) == pdPASS) {
            // Data successfully sent
        }
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

Receiving Data from a Queue

We need to read data from the queue, if there is data in the queue we can extract it from a task different from the task that wrote that data to the queue, again we need to use the xQueue handle.

void receiverTask(void *pvParameters) {
    int receivedValue;
    while (1) {
        if (xQueueReceive(xQueue, &receivedValue, pdMS_TO_TICKS(100)) == pdPASS) {
            // Process received data
        }
    }
}

Using Queues to Transfer Structures

Queues can be used to store any type of data, which makes them a very useful resource when you need to control and send data between tasks. Storing data in a queue becomes very convenient when you use typedefs, that is, when you type data.

Let’s see how to handle transferring data structures between tasks using queues.

#include "Task_Queue.h"

#define QUEUE_LENGTH  3
#define SOURCE_TASK_TX_1 1
#define SOURCE_TASK_TX_2 2

QueueHandle_t xQueue;

typedef struct
{
 unsigned char ucValue;
 unsigned char ucSource;
} xData; 

static const xData xStructsToSend[ 2 ] =
{
 { 100, SOURCE_TASK_TX_1 }, /* Used by tASK 1. */
 { 200, SOURCE_TASK_TX_2 }  /* Used by tASK 2. */
}; 

void TaskTx(void* pvParameters)
{
  portBASE_TYPE xStatus;
  const portTickType xTicksToWait = 100 / portTICK_RATE_MS;

  while(1)
  {   
    xStatus = xQueueSendToBack( xQueue, pvParameters, xTicksToWait );

    if( xStatus != pdPASS )
    {
      /* The send operation could not complete, even after waiting for 100ms.
      This must be an error as the receiving task should make space in the
      queue as soon as both sending tasks are in the Blocked state. */
      SEGGER_SYSVIEW_Print( "Could not send to the queue.\n" );
    } 

    /* Allow the other sender task to execute. */
    taskYIELD();
  }

}

void TaskRx(void* NotUsed)
{
  xData xReceivedStructure; 
  portBASE_TYPE xStatus; 

  while(1)
  {   
    xStatus = xQueueReceive( xQueue, &xReceivedStructure, portMAX_DELAY );
    if( xStatus == pdPASS )
    {
      /* Data was successfully received from the queue, print out the received
      value and the source of the value. */
      if( xReceivedStructure.ucSource == SOURCE_TASK_TX_1 )
      {
        SEGGER_SYSVIEW_PrintfHost( "From Task 1 = %u, Stack = %u ", xReceivedStructure.ucValue, uxTaskGetStackHighWaterMark( NULL ) );
      }
      else
      {
        SEGGER_SYSVIEW_PrintfHost( "From Task 2 = %u, Stack = %u ", xReceivedStructure.ucValue, uxTaskGetStackHighWaterMark( NULL ) );
      }
    }
    else
    {
      /* Nothing was received from the queue. This must be an error
      as this task should only run when the queue is full. */
      SEGGER_SYSVIEW_Print( "Could not receive from the queue.\n" );
    } 
  }

}

void Task_Communication(void)
{
  /* Create the Queue */
  xQueue = xQueueCreate(QUEUE_LENGTH, sizeof(xData));

  /* Create tasks */
  xTaskCreate(TaskTx,"Tarea1",configMINIMAL_STACK_SIZE, &( xStructsToSend[ 0 ] ),tskIDLE_PRIORITY+2,NULL);
  xTaskCreate(TaskTx,"Tarea2",configMINIMAL_STACK_SIZE, &( xStructsToSend[ 1 ] ),tskIDLE_PRIORITY+2,NULL);
  xTaskCreate(TaskRx,"Tarea3", configMINIMAL_STACK_SIZE,NULL,tskIDLE_PRIORITY+1,NULL);
}

SystemView Output:

Task 1 executes and sends data to the queue.

Task 3 is blocked waiting to receive a data in the queue. When Task 1 sends a data in the queue, Task 3 reads the data from the queue.

Task 2 executes and sends data to the queue.

Task 2 sends a data to the queue, this unblocks Task 3 which reads a data from the queue.

Using Queues to Transfer Pointer

We can optimize memory usage by handling data structures through queues using pointers, that is, we can not send a copy of the structure within the queue but only a pointer that is referenced to the structure.

Let’s make some changes to the above code.

#include "Task_Queue.h"

#define QUEUE_LENGTH  3
#define SOURCE_TASK_TX_1 1
#define SOURCE_TASK_TX_2 2

QueueHandle_t xQueue;

typedef struct
{
 unsigned char ucValue;
 unsigned char ucSource;
} xData; 

static const xData xStructsToSend[ 2 ] =
{
 { 100, SOURCE_TASK_TX_1 }, /* Used by tASK 1. */
 { 200, SOURCE_TASK_TX_2 }  /* Used by tASK 2. */
}; 

void TaskTx(void* pvParameters)
{
  portBASE_TYPE xStatus;
  const portTickType xTicksToWait = 100 / portTICK_RATE_MS;
  xData *xSendStruct = (xData*)pvParameters;

  while(1)
  {   
    xStatus = xQueueSendToBack( xQueue, &xSendStruct, xTicksToWait );

    if( xStatus != pdPASS )
    {
      /* The send operation could not complete, even after waiting for 100ms.
      This must be an error as the receiving task should make space in the
      queue as soon as both sending tasks are in the Blocked state. */
      SEGGER_SYSVIEW_Print( "Could not send to the queue.\n" );
    } 

    /* Allow the other sender task to execute. */
    taskYIELD();
  }

}

void TaskRx(void* NotUsed)
{
  xData *xReceivedStructure; 
  portBASE_TYPE xStatus; 

  while(1)
  {   
    xStatus = xQueueReceive( xQueue, &xReceivedStructure, portMAX_DELAY );
    
    if( xStatus == pdPASS )
    {
      /* Data was successfully received from the queue, print out the received
      value and the source of the value. */
      if( xReceivedStructure->ucSource == SOURCE_TASK_TX_1 )
      {
        SEGGER_SYSVIEW_PrintfHost( "From Task 1 = %u, Stack = %u ", xReceivedStructure->ucValue, uxTaskGetStackHighWaterMark( NULL ) );
      }
      else
      {
        SEGGER_SYSVIEW_PrintfHost( "From Task 2 = %u, Stack = %u ", xReceivedStructure->ucValue, uxTaskGetStackHighWaterMark( NULL ) );
      }
    }
    else
    {
      /* Nothing was received from the queue. This must be an error
      as this task should only run when the queue is full. */
      SEGGER_SYSVIEW_Print( "Could not receive from the queue.\n" );
    } 
  }

}

void Task_Communication(void)
{
  /* Create the Queue */
  xQueue = xQueueCreate(QUEUE_LENGTH, sizeof(xData*));

  /* Create tasks */
  xTaskCreate(TaskTx,"Tarea1",configMINIMAL_STACK_SIZE, &( xStructsToSend[ 0 ] ),tskIDLE_PRIORITY+2,NULL);
  xTaskCreate(TaskTx,"Tarea2",configMINIMAL_STACK_SIZE, &( xStructsToSend[ 1 ] ),tskIDLE_PRIORITY+2,NULL);
  xTaskCreate(TaskRx,"Tarea3", configMINIMAL_STACK_SIZE,NULL,tskIDLE_PRIORITY+1,NULL);
}

SystemView Output:

Closing

FreeRTOS queues are an essential tool for inter-task communication. If the size of the data being stored in the queue is large, then it is preferable to use the queue to transfer pointers to the data, rather than copy the data itself into and out of the queue byte by byte. Transferring pointers is more efficient in both processing time and the amount of RAM required to create the queue.



Card image cap
Guillermo Garcia Thanks for reading! I am one of the editors of this site and I am committed to providing you with the best information possible by sharing my experience in embedded systems.


Comments... There are no comments.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Publicidad