FreeRTOS Development: Message Buffer – An Advanced Choice for Task Communication

FreeRTOS Development: Message Buffer - An Advanced Choice for Task Communication

1. Basic Concepts

In FreeRTOS, the “message buffer” refers to <span>Message Buffer</span>, which is a communication mechanism provided by FreeRTOS for transmitting variable-length data (byte streams) between tasks or between interrupts and tasks. It is part of FreeRTOS and was officially introduced in version <span>v10.0.0</span>.

Simply put, a message buffer (Message Buffer) is equivalent to a <span>buffer for variable-length message communication</span>. Compared to queues, message buffers are more suitable for sending byte streams or variable-length data rather than fixed-size data items.

Main Features

Feature Description
Supports variable-length messages Each message can be of different lengths
Unidirectional communication Typically used for one sender and one receiver
Does not support multi-task reception Multiple tasks receiving from the message buffer is unsafe
Supports sending from ISR Messages can be sent in interrupts (using special APIs)
Underlying structure is a circular buffer Efficient and low-overhead implementation

Message Buffer vs Queue

Feature Queue Message Buffer
Data Type Fixed size Variable size
Multi-task Access Both sending and receiving can be done by multiple tasks Generally used for one-to-one
Real-time Performance Supported Better real-time performance supported
Interrupt Support Supported More flexible support
Complexity of Use Simple Somewhat complex (due to variable length)

2. Working Principle

Core Idea

Circular Buffer + Variable Length Header

The essence of the message buffer is a circular buffer (ring buffer), which stores data in the following format:

| Message Header (2 bytes) | Message Content (n bytes) | Message Header (2 bytes) | Message Content (m bytes) | ...

✳️ Each message has a header (2 bytes) that indicates the length of the message body

  • Thus, the receiving end knows how many bytes to read from the buffer.

Buffer Structure

typedef struct StreamBufferDef_t
{
    volatile size_t xTail;                       ///<  Read pointer (next byte index to read)
    volatile size_t xHead;                       ///<  Write pointer (next byte index to write)
    size_t xLength;                              ///<  Total length of the buffer (in bytes)
    size_t xTriggerLevelBytes;                   ///<  Trigger level (only for task blocking wake-up) If a task is waiting for data to be received, it will only be woken up when xTriggerLevelBytes bytes are readable.
    volatile TaskHandle_t xTaskWaitingToReceive; ///<  Current blocking task handle waiting to receive (if any)
    volatile TaskHandle_t xTaskWaitingToSend;    ///<  Current blocking task handle waiting to send (if any)
    uint8_t * pucBuffer;                         ///<  Pointer to the actual memory space of the circular buffer (i.e., where the data is located)
    uint8_t ucFlags;                             ///<  Control flags to identify whether the current buffer is a Stream Buffer or Message Buffer, and possibly other options.

    #if ( configUSE_TRACE_FACILITY == 1 )
        UBaseType_t uxStreamBufferNumber;        ///<  Debugging/tracing number (optional), used for tools like Tracealyzer.
    #endif

    #if ( configUSE_SB_COMPLETED_CALLBACK == 1 )
        // Can be used for event-driven programming (no polling or blocking)
        StreamBufferCallbackFunction_t pxSendCompletedCallback;    ///<  Optional callback function: for notification mechanism when sending is complete
        StreamBufferCallbackFunction_t pxReceiveCompletedCallback; ///<  Optional callback function: for notification mechanism when receiving is complete
    #endif
    UBaseType_t uxNotificationIndex;                               ///<  Used in conjunction with task notification mechanism (xTaskNotify()), supports FreeRTOS's Task Notification interface
} StreamBuffer_t;

Stream Buffer vs Message Buffer

Feature Stream Buffer Message Buffer
Data Structure <span>StreamBuffer_t</span> Also <span>StreamBuffer_t</span>
Data Type Byte stream (no message boundaries) Messages with boundaries (length of each message is indicated by the first 2 bytes)
Applicable Scenarios Continuous data streams such as audio/video/serial Log messages, command packets, variable-length protocols, etc.
Reading Method on Reception Specify read length Automatically read the entire complete message based on the message header

3. Interface Definitions

<1>. Create Message Buffer

MessageBufferHandle_t xMessageBufferCreate( size_t xBufferSizeBytes );

<2>. Send Message

size_t xMessageBufferSend( MessageBufferHandle_t xMessageBuffer,
                           const void * pvTxData,
                           size_t xDataLengthBytes,
                           TickType_t xTicksToWait );

<3>. Receive Message

size_t xMessageBufferReceive( MessageBufferHandle_t xMessageBuffer,
                              void * pvRxData,
                              size_t xBufferLengthBytes,
                              TickType_t xTicksToWait );

<4>. Send from ISR

size_t xMessageBufferSendFromISR( MessageBufferHandle_t xMessageBuffer,
                                  const void * pvTxData,
                                  size_t xDataLengthBytes,
                                  BaseType_t *pxHigherPriorityTaskWoken );

4. Application Examples

Application Example

There are two tasks:

  • SenderTask: Periodically sends variable-length messages (strings)
  • ReceiverTask: Receives messages and prints content

Communication is implemented using <span>xMessageBufferSend()</span> and <span>xMessageBufferReceive()</span>.

Example Code
#include "FreeRTOS.h"
#include "task.h"
#include "message_buffer.h"
#include <stdio.h>
#include <string.h>

/* Define message buffer size (in bytes) */
#define MESSAGE_BUFFER_SIZE    100

/* Task stack size and priority */
#define TASK_STACK_SIZE        128
#define TASK_PRIORITY          2

/* Declare message buffer handle */
MessageBufferHandle_t xMessageBuffer;

/* Sender Task */
void SenderTask(void *pvParameters)
{
    const char *pcMessageArray[] = {
        "Hello",
        "FreeRTOS",
        "Message Buffer Example",
        "End"
    };

    for (int i = 0; i < sizeof(pcMessageArray) / sizeof(pcMessageArray[0]); i++)
    {
        const char *msg = pcMessageArray[i];
        size_t msgLen = strlen(msg) + 1; // Including '\0'
        
        /* Send message */
        size_t bytesSent = xMessageBufferSend(xMessageBuffer,
                                              (void *)msg,
                                              msgLen,
                                              pdMS_TO_TICKS(1000));  // Wait up to 1 second

        if (bytesSent != msgLen)
        {
            printf("Failed to send message: %s\n", msg);
        }
        else
        {
            printf("Sent message: %s\n", msg);
        }

        vTaskDelay(pdMS_TO_TICKS(1000));  // Delay 1 second
    }

    vTaskDelete(NULL);
}

/* Receiver Task */
void ReceiverTask(void *pvParameters)
{
    char recvBuffer[50];

    while (1)
    {
        /* Try to receive message */
        size_t bytesReceived = xMessageBufferReceive(xMessageBuffer,
                                                     recvBuffer,
                                                     sizeof(recvBuffer),
                                                     portMAX_DELAY); // Wait indefinitely

        if (bytesReceived > 0)
        {
            printf("Received message: %s\n", recvBuffer);
        }
    }
}

int main(void)
{
    /* Create message buffer */
    xMessageBuffer = xMessageBufferCreate(MESSAGE_BUFFER_SIZE);

    if (xMessageBuffer == NULL)
    {
        printf("Failed to create message buffer!\n");
        while (1);
    }

    /* Create sender and receiver tasks */
    xTaskCreate(SenderTask, "Sender", TASK_STACK_SIZE, NULL, TASK_PRIORITY, NULL);
    xTaskCreate(ReceiverTask, "Receiver", TASK_STACK_SIZE, NULL, TASK_PRIORITY, NULL);

    /* Start scheduler */
    vTaskStartScheduler();

    /* If scheduler fails to start, it will not execute here */
    for (;;);
}
Code Explanation
  • <span>xMessageBufferCreate()</span>: Creates a message buffer of size 100 bytes.
  • <span>xMessageBufferSend()</span>: Sends string messages.
  • <span>xMessageBufferReceive()</span>: Receives a complete message (including variable-length content).
  • Using <span>portMAX_DELAY</span> allows the receiving task to block until a message is available to read.

Application Scenarios

  • A task receives data from a serial port and sends it to another task for processing.
  • An interrupt service routine from a sensor packages data and sends it to a task.
  • Used for asynchronous transmission of logs, debugging information, etc.

Application Notes

  • Sending and receiving tasks cannot access concurrently (unless using mutex or ISR-safe interfaces)
  • Generally, <span>Message Buffer / Stream Buffer</span> is only used for one-to-one communication
  • If truly needing many-to-one, consider adding synchronization mechanisms or using queues.

FreeRTOS Development: Message Buffer - An Advanced Choice for Task Communication

Previous Recommendations

RECOMMEND

FreeRTOS Development: Message Buffer - An Advanced Choice for Task Communication

[1]. FreeRTOS Development: In-depth Analysis of Stream Buffers – A Tool for Real-time Byte Stream Communication!

[2]. Linux Basics: How to Analyze CPU Performance in Embedded Systems

[3]. Can FreeRTOS be Low Power? Yes, just change these few configurations!

[4]. FreeRTOS Development: Fully Understand Event Flag Groups – A Tool for Multi-task Synchronization

I am Aike, an embedded software engineer.

Follow me for more embedded insights.

Remember to like, share, and click to view,

Your encouragement is my greatest motivation to continue sharing!

See you next time.

Looking forward to your

sharing

likes

views

NEWS

WeChat IDaike_eureka

Baijiahao|Master Embedded Systems

Leave a Comment