In this tutorial, we will learn how to create a mailbox using Queues and FreeRTOS API with Arduino. As you know that whenever a task reads data from the queue, data will be deleted from the queue after the read operation. But in real-time operating systems, sometimes we need a queue where data should not be deleted from the queue after data being read by any task from the queue. It should be deleted only when a writing task overwrites that data of the queue. In such a case, we can use the mailbox to create a queue that can hold data permanently until overwrites by other tasks.

If you are new to FreeRTOS and do not know how to use queue management using Arduino, you can read these getting started guides:

Mailbox Introduction

Queues are the linear data structure used for inter-process data communication such as sharing data between tasks or interrupt services routines. Sender task writes data to the queue and the receiver task reads data from the queue and after read operation data item removes from the queue. Unlike traditional queues, a mailbox holds the data that can be read by any task, and data remains in the mailbox until overwrites by another task.

In other words, In the case of a mailbox, a writes task sends data to the queue and a receiver task reads data from the queue but does not remove it until it is overwritten by a sender task.

How to use FreeRTOS queue API to Create Mailbox

FreeRTOS queue management API also provides function which can be used to create a mailbox. These are the two functions:

FreeRTOS xQueueOverwrite() API

Like xQueueWrite() API function, xQueueOverwrite() also used to write data to a queue. But if queue is full xQueueWrite() enters the blocking state. On the contrary, xQueueOverwrite() overwrites the data without going to blocking state.

Note: xQueueOverwrite() must used with a queue of length one only.

Furthermore, the input arguments to this function is same as xQueueWrite() function. More information about this link is available in this link:

Note: Never call xQueueOverwrite() from an interrupt service routine. The interrupt-safe version xQueueOverwriteFromISR() should be used in its place.

FreeRTOS xQueuePeek() API

Like xQueueRead() API function, xQueuePeek() is also used to read data to a queue. But unlike xQueueRead(), xQueuePeek() function does not delete data from the queue after reading it. This function does not remove the item from the queue or not modify it. More information about this link is available in this link:

Note: Never call xQueuePeek() from an interrupt service routine. The interrupt-safe version xQueuePeekFromISR() should be used in its place.

Arduino FreeRTOS MailBox Code

#include <Arduino_FreeRTOS.h>
#include <queue.h>
typedef struct xExampleStructure
{
TickType_t xTimeStamp;
uint32_t ulValue;
} Example_t;
/* A mailbox is a queue, so its handle is stored in a variable of type
QueueHandle_t. */
QueueHandle_t xMailbox;
TaskHandle_t TaskHandle_1; // handler for Task1
TaskHandle_t TaskHandle_2; // handler for Task2
void setup()
{
// put your setup code here, to run once:
xMailbox = xQueueCreate( 1, sizeof( Example_t ) );
Serial.begin(9600); // Enable serial communication library.
xTaskCreate(vUpdateMailbox, "Sender", 100, NULL, 1, &TaskHandle_1);
xTaskCreate(vReadMailbox, "Receiver", 100, NULL, 1, &TaskHandle_2);
}
void loop()
{
// put your main code here, to run repeatedly:
}
void vUpdateMailbox( uint32_t ulNewValue )
{
/* Example_t was defined in Listing 67. */
Example_t xData;
/* Write the new data into the Example_t structure.*/
xData.ulValue = ulNewValue;
/* Use the RTOS tick count as the time stamp stored in the Example_t structure. */
xData.xTimeStamp = xTaskGetTickCount();
/* Send the structure to the mailbox - overwriting any data that is already in the
mailbox. */
xQueueOverwrite( xMailbox, &xData );
}
BaseType_t vReadMailbox( Example_t *pxData )
{
TickType_t xPreviousTimeStamp;
BaseType_t xDataUpdated;
/* This function updates an Example_t structure with the latest value received
from the mailbox. Record the time stamp already contained in *pxData before it
gets overwritten by the new data. */
xPreviousTimeStamp = pxData->xTimeStamp;
/* Update the Example_t structure pointed to by pxData with the data contained in
the mailbox. If xQueueReceive() was used here then the mailbox would be left
empty, and the data could not then be read by any other tasks. Using
xQueuePeek() instead of xQueueReceive() ensures the data remains in the mailbox.
A block time is specified, so the calling task will be placed in the Blocked
state to wait for the mailbox to contain data should the mailbox be empty. An
infinite block time is used, so it is not necessary to check the value returned
from xQueuePeek(), as xQueuePeek() will only return when data is available. */
xQueuePeek( xMailbox, pxData, portMAX_DELAY );
/* Return pdTRUE if the value read from the mailbox has been updated since this
function was last called. Otherwise return pdFALSE. */
if( pxData->xTimeStamp > xPreviousTimeStamp )
{
xDataUpdated = pdTRUE;
}
else
{
xDataUpdated = pdFALSE;
}
return xDataUpdated;
}

FreeRTOS Mailbox Example using Arduino

Now let’s see an example of a mailbox with Arduino. In this example, we create two tasks, one task writes integer value to the mailbox after every delay of vTaskDelay(500) and vReadMailbox() task reads that integer value from the mailbox after every delay of vTaskDelay(100). This means vUpdateMailbox() overwrite mailbox data one time as compare to vReadMailbox() that will receive it five times. Because, task delay time of vUpdateMailbox() is five times of vReadMailbox() task.

Program Output

As you can see from the output of the serial monitor, the reader task keeps reading the same data from the mailbox until the data update task does not overwrite the mailbox value. It also shows that unlike the FreeRTOS queue, data from the mailbox does not remove on a read operation until it is overwritten by a sender task.