This buffers works as communication method as the Queues, unlike this are designed and optimized to work with one transmitter and one receiver, as pass data from an interrupt routine to a task, or transmit data from a task to another task. Also can be used with a dual core CPUs, transmitting an receiving data between them.

Data is managed as a copy, the sender copies the data into the buffer, and the receiver out the data of the buffer.

đź’ˇ
Note: This buffers are designed to be used with a single sender task and single receiver task. However you can program multiples reader but is extremely not recommended to avoid a malfunction.

Stream Buffer

Stream buffers allow a stream of bytes to be passed from an interrupt service routine to a task, or from one task to another task. A byte stream can be of arbitrary length and does not necessarily have a beginning or end. Any number of bytes can be written in one go, and any number of bytes can be read in one go.

Blocking Reads

Blocking reads on a stream buffer refers to the ability of a task to wait until there is enough data available in the buffer before continuing. This is achieved by specifying a timeout (blocking time) when calling the xStreamBufferReceive() function. If there is not enough data available in the buffer, the task blocks (enters a blocked state) until:

  • Sufficient data arrives: The task unblocks and reads data from the buffer.
  • Timeout: The task is unblocked, but may not have read any data if not enough bytes arrived during the timeout.

Blocking Writes

API function xStreamBufferSend() has a parameter to allow block time, if block time is different to zero and the buffer is full, the task will be placed in a blocked state (this not consume CPU time) while the task waits to space available in buffer or the block time expires.

Trigger levels

The level of activation (Trigger level) of a stream buffer must be the number of elements that the buffer contains before the task is blocked waiting for the data to be unlocked.

Stream buffer functionality

Creation of stream buffer and the respective task to send data and task to receive. Suppose that a stream buffer is created with a trigger value of 2.

The “Task sender” sends data to the buffer, if the trigger value is set as 1, the “task receiver” can take the data from the buffer without any problem. But this isn’t the case.

The “task receiver” can not read the data from the buffer, the trigger value sets the number of elements that the buffer must have to allow to “task receiver” to take the element from the buffer.

The “task sender” send another data to buffer

Now the buffer contains the number of elements to unlock the “task receiver” to read the element from the buffer.

đź’ˇ
Note: Zero cannot be set in a trigger level value, if attempts to set this value, the trigger value will be set as 1. Also can not be greater than the stream buffer size.

Let's start with the API functions to create, send, and receive data, the first function is in charge of creating the stream buffer, setting parameters as the length of the buffer the trigger value to enable the receiver to read the elements.

Creation of Stream buffer using the API functions:

StreamBufferHandle_t xStreamBufferCreate( 
    size_t xBufferSizeBytes,                /* total number of bytes the stream buffer will be able to hold */
    size_t xTriggerLevelBytes               /* number of bytes that must be in the stream buffer before a task
                                               that is waiting for data is unblocked. */
);

The next API function allows to specify the data and the data size to send to the buffer, and also specify the time that the task will be in the blocked state if the buffer is full.

Send data to buffer:

size_t xStreamBufferSend( 
    StreamBufferHandle_t xStreamBuffer,   /* handle of the stream buffer to which a stream is being sent */
    const void *pvTxData,                 /* pointer to the buffer that holds the bytes to be copied into the stream buffer. */
    size_t xDataLengthBytes,              /* maximum number of bytes to copy from pvTxData */
    TickType_t xTicksToWait               /* the amount of time the task should remain in the Blocked state to wait for enough
                                             space to become available in the stream buffer */
);

The next API function is in charge of receiving the data from the buffer, and storing the data in a specified variable passed as a parameter, if the buffer is empty the task will pass to a blocked state.

Receive data from buffer:

size_t xStreamBufferReceive( 
    StreamBufferHandle_t xStreamBuffer,   /* handle of the stream buffer to which a stream are to be received */
    void *pvRxData,                       /* pointer to the buffer into which the received bytes will be copied. */
    size_t xBufferLengthBytes,            /* This sets the maximum number of bytes to receive in one call. */
    TickType_t xTicksToWait               /* the amount of time the task should remain in the Blocked state to wait
                                             for data to become available if the stream buffer is empty. */
);

To enable and use the stream buffer just search and download the file stream_buffer.c and add to the source freertos folder.

Now you need to modify the config file. The stream buffer implementation uses direct to task notifications.

#define configUSE_TASK_NOTIFICATIONS            1

If you don’t find configUSE_STREAM_BUFFERS in your FreeRTOS configuration file (FreeRTOSConfig.h), it might be because stream buffers are not enabled by default in your FreeRTOS version or configuration. Here's how you can add and configure it:

Add the next line to enable stream buffers

#define configUSE_STREAM_BUFFERS                1

Make sure you have enabled the dynamic allocation

#define configSUPPORT_DYNAMIC_ALLOCATION            1

Example Program:

#include "bsp.h"

#define BUFFER_SIZE 100
StreamBufferHandle_t xStreamBuffer; /* define stream buffer handle */

void Task1( void *pvParameters );   /* Task1 To turn on LEDs */
void Task2( void *pvParameters );   /* Task2 to turn off LEDs */

int main( void )
{
    HAL_Init();

    SEGGER_SYSVIEW_Conf( );
    SEGGER_SYSVIEW_Start( );

    xStreamBuffer = xStreamBufferCreate( BUFFER_SIZE, 1 );          /* Create the stream buffer with a size of 100 bytes */
    
    xTaskCreate( Task1, "SendData", 240, NULL, 2, NULL );           /* Register Task that simulate a read sensor */
    xTaskCreate( Task2, "ReceiveData", 240, NULL, 1, NULL );        /* Register Task to read the data from sensor */
    
    vTaskStartScheduler();      /* Init the Kernel */
}

void Task1( void *pvParameters )
{
    uint8_t data_to_send = 0;                       /* Variable that store the data to send */
    size_t data_size = sizeof( data_to_send );      /* size of the variable to send */

    for(;;)
    {
        xStreamBufferSend( xStreamBuffer, &data_to_send, data_size, portMAX_DELAY );    /* Send the data to the buffer */
        data_to_send++;                                                                 /* Increment the value to send */

        vTaskDelay(200);                            /* Period of the task */
    }
}

void Task2( void *pvParameters )
{
    uint8_t receiver_data;                          /* Variable to receive the data */
    size_t bytes_read;                              /* Variable to know if there are bytes to read on the buffer */
    for(;;)
    {
        bytes_read = xStreamBufferReceive(xStreamBuffer, &receiver_data, BUFFER_SIZE, portMAX_DELAY);   /* Read the buffer */
        if(bytes_read > 0)                                      /* if there are bytes to read */
        {
            SEGGER_SYSVIEW_PrintfHost( "%d", receiver_data );   /* Process the data */
        }

        vTaskDelay(500);                                        /* Period of the task */
    }   
}

Message Buffer

The message buffers allow to send messages of variable length, from a service routine interrupt to a task or from task to task. Unlike the stream buffer that manages continuous data flows, the message buffer is designed to manage discrete messages. This means that a message of 10 bytes will be read as a message of 10 bytes, not as individual bytes.

You can send messages with variable length using the same buffer, for example, messages of 10, 20, and 123 bytes can coexist in the same buffer.

Like the stream buffers, the message buffers are designed and optimized to be used with a single reader and a single receiver, messages buffers are based on stream buffers.

Sizing a message buffer

A message buffer stores discrete messages in buffer, now the size of the buffer needs to be modified taking into consideration the number of messages that application expects to receive. Unlike the stream buffer where the data is received individually in the message buffer, the data is encapsulated, when the data needs to be read, the receiver knows how many bytes reads.

Blocking reads and writes

just like in stream buffers, the task sender or receiver can be passed to a blocked state to wait for a change in the buffer, for example in the API function to send the message buffer xMessageBufferSend() you can set the maximum time in ticks to wait until the buffer have enough of space available. Another case is the APĂŹ function to receive the message, xMessageBufferReceive() also here you can set the maximum time to wait until the message has data to read in the buffer. Functions are designed to send the task to the blocked state until they can access to the buffer to write or read an element.

In this Functions a maximum time needs to be set this time establish how long the task must be in the blocked state. If the time elapsed the task moves to the ready state.

Stream Message functionality

Creation of message buffer to be used in a sender task and a receiver task.

When the “Task Sender“sends a message to the buffer, the message will take the necessary bytes. For example, the message sent is “HI“, this message takes 2 bytes in the buffer.

Again “Task Sender“ sends another message this time the message length changes, now the message takes 4 bytes in the buffer. Notice that each color represents a message.

Now “Task Receiver“reads a message from the buffer, the “Task_Receiver“ knows how many elements have the message and takes the corresponding bytes.

Observe that now the first message sent is deleted, now the “Task Receiver“ will read from the buffer the next message, taking the 4 bits and leaving empty the buffer.

Let’s with the API function to create, send, and receive the message buffer. Interrupt Api functions are explained in the examples. First, we have the function to create the message buffer that just needs the buffer size as a parameter, it does not need a trigger parameter.

Creation of message buffer:

MessageBufferHandle_t xMessageBufferCreate( 
    size_t xBufferSizeBytes                   /* The total number of bytes (not messages) the message buffer will be able
                                                 to hold at any one time. */
);

To send a discrete message into the buffer, just need to pass the array of the data to be sent and the size of the array. The data must be sent as a discrete message into the buffer.

Send a message to buffer:

size_t xMessageBufferSend( 
    MessageBufferHandle_t xMessageBuffer,   /* handle of the message buffer to which a message is being sent */
    const void *pvTxData,                   /* pointer to the message that is to be copied into the message buffer */
    size_t xDataLengthBytes,                /* The length of the message. The number of bytes to copy from pvTxData */
    TickType_t xTicksToWait                 /* the amount of time the calling task should remain in the Blocked state 
                                               to wait for enough space to become available in the message buffer */
);

The receiver function now doesn’t need a minimum of elements in the buffer to start the reception, now the function knows how many elements receive.

Receive the message from the buffer

size_t xMessageBufferReceive( 
    MessageBufferHandle_t xMessageBuffer,   /* handle of the message buffer from which a message is being received */
    void *pvRxData,                         /* pointer to the buffer into which the received message is to be copied */
    size_t xBufferLengthBytes,              /* the maximum length of the message that can be received */
    TickType_t xTicksToWait                 /* the  amount of time the task should remain in the Blocked state to wait for a message */
);

Example Program:

#include "bsp.h"
#include <string.h>

#define BUFFER_SIZE 100
MessageBufferHandle_t xMessageBuffer;

void Task1( void *pvParameters );   /* Task1 To send messages buffer */
void Task2( void *pvParameters );   /* Task2 to receive message buffer */

int main( void )
{
    HAL_Init();

    SEGGER_SYSVIEW_Conf( );
    SEGGER_SYSVIEW_Start( );

    xMessageBuffer = xMessageBufferCreate( BUFFER_SIZE );       /* Create a message buffer */
    
    xTaskCreate( Task1, "SendData", 240, NULL, 2, NULL );       /* Register Task1 to Send message to buffer */
    xTaskCreate( Task2, "ReceiveData", 240, NULL, 1, NULL );    /* Register Task2 to receive message from buffer */
    
    vTaskStartScheduler();      /* Init the Kernel */
}

void Task1( void *pvParameters )
{
    const char *data_to_send = "hello world";           /* data to send to buffer */
    size_t data_length = strlen( data_to_send );        /* Variable to indicate the message size */    

    for(;;)
    {
        xMessageBufferSend( xMessageBuffer, data_to_send, data_length, portMAX_DELAY ); /* Send the message to the buffer */

        vTaskDelay(500);                    /* Period of the task */
    }
}

void Task2( void *pvParameters )
{
    char receiver_data[BUFFER_SIZE];                    /* Variable to receive the message buffer */
    size_t bytes_read;                                  /* Variable that indicate the bytes receved from the buffer */
    for(;;)
    {
        bytes_read = xMessageBufferReceive( xMessageBuffer, &receiver_data, BUFFER_SIZE, portMAX_DELAY );   /* Receive the message from buffer */

        if(bytes_read > 0)                                  /* Verify that message is received */
        {
            SEGGER_SYSVIEW_PrintfHost( receiver_data );     /* Process the message buffer received */
        }

        vTaskDelay(1000);                    /* Period of the task */
    }   
}

Code Snippets

Exercises(printf)

  1. Create a bidirectional transmission of streams buffer, create 2 streams buffer to communicate 2 tasks, the sender task will send data, and if the receiver task receives the message send a stream buffer to indicate to the sender task that the message is successfully received, if the data from the sender is not receiver then the receiver task sends a stream buffer to indicate to the receiver task to send a new stream buffer.
  2. Create a program to configure a stream buffer, use a task to send a different letter every 500ms, create another task to put together all the letters received, and print the full message to print in the terminal, Message to be displayed "Hello Stream Buffer".
  3. Modify the last exercise and use a message buffer instead of the stream buffer. Modify the created tasks to work now with the message buffer.
  4. Using again the Message buffers, now create a program to communicate 2 tasks, the Sender Task going to send messages with different sizes, and the receiver task must print the message received. Make sure your buffer is greater than the message to send.

Exercises

  1. Modify the first Exercise(printf) to send an LED pin, the receiver task now Will process the data concurrently to change the LED state each time is received. Send at least 3 different LEDs.
  2. Configure the serial port to send a message buffer, and use an ISR to control the data received from the serial port, the receiver task going to switch the LED state according to the message received. If the serial message is equal to ON then the task going to turn on the LED, but if the message is OFF the LED switches OFF.
  3. Modify the previous exercise and add a new task (ErrorMsgTask). When the receiver task receives another parameter, then sends the message “Invalid Command try again“, using a message buffer to the ErrorMsgTask to be printed. Avoid blocking the receiver task even if no message is received, the ErrorMsgTask will be blocked if no message is received.