Understanding Communication Between M4 and M7 Cores Using FreeRTOS Message Buffers

Follow+Star Public Account, don’t miss the wonderful content

Understanding Communication Between M4 and M7 Cores Using FreeRTOS Message Buffers

Author | strongerHuang

WeChat Official Account | Embedded Column

Since FreeRTOS version 10.4.0, the version number is no longer managed in the Vx_x_x format as before. The latest version as of now (2020.11) is FreeRTOS V202011.00. The source code can be found at:
https://github.com/freertos/freertos
(The public account does not support external links, please copy the link to your browser to open)
Today, I will share with you: using FreeRTOS message buffers to achieve simple asymmetric multiprocessing (AMP) core-to-core communication, taking the STM32H7 (M4 and M7) dual-core processor as an example.

Embedded Column

1

Overview
Implementing communication between STM32H7 dual cores is a solution provided by FreeRTOS, based on FreeRTOS message buffers. This message buffer is a lock-free circular buffer that can transmit data packets of different sizes from a single sender to a single receiver.
Note that this message buffer only provides data transmission, and does not handle communication-related protocol processing.

Embedded Column

2

Basic Principle
The basic principle of implementing communication between dual cores: sending and receiving tasks are located on different cores of a multi-core microcontroller (MCU) in an asymmetric multiprocessing (AMP) configuration, meaning each core runs its own FreeRTOS program.
At the same time, one core has the ability to generate interrupts in the other core, and both cores have access to a memory area (shared memory). The message buffer is placed in shared memory at an address known to the application running on each core, as shown in the figure below:

Understanding Communication Between M4 and M7 Cores Using FreeRTOS Message Buffers

Ideally, there will also be a memory protection unit (MPU) to ensure that the message buffer can only be accessed through the message buffer API of the core, and it is best to mark the shared memory as not being used by other programs.

Embedded Column

3

Single Message Code Description

Here, the official basic code for implementing this solution is provided (for reference only).

Code to send data to the stream buffer:
xMessageBufferSend(){    /* If a time out is specified and there isn't enough    space in the message buffer to send the data, then    enter the blocked state to wait for more space. */    if( time out != 0 )    {        while( there is insufficient space in the buffer &&               not timed out waiting )        {            Enter the blocked state to wait for space in the buffer        }    }    if( there is enough space in the buffer )    {        write data to buffer        sbSEND_COMPLETED()    }}
Code to read data from the stream buffer:
xMessageBufferReceive(){    /* If a time out is specified and the buffer doesn't    contain any data that can be read, then enter the    blocked state to wait for the buffer to contain data. */    if( time out != 0 )    {        while( there is no data in the buffer &&               not timed out waiting )        {            Enter the blocked state to wait for data        }    }    if( there is data in the buffer )    {        read data from buffer        sbRECEIVE_COMPLETED()    }}

If a task enters a blocked state in xMessageBufferReceive() waiting for the buffer to contain data, sending data to the buffer must unblock that task so it can complete its operation.

When xMessageBufferSend() calls sbSEND_COMPLETED(), the task will not be blocked.

Understanding Communication Between M4 and M7 Cores Using FreeRTOS Message Buffers

ISR unblocks the task by passing the message buffer handle as a parameter to the xMessageBufferSendCompletedFromISR() function.

As shown by the arrows in the figure, where the sending and receiving tasks are located on different MCU cores:

1.The receiving task tries to read data from an empty message buffer and enters a blocked state waiting for data to arrive.
2.The sending task writes data to the message buffer.
3.sbSEND_COMPLETED() triggers an interrupt in the core where the receiving task is executing.
4.The interrupt service routine calls xMessageBufferSendCompletedFromISR() to unblock the receiving task, which can now read from the buffer as it is no longer empty.

Embedded Column

4

Multi-Message Code Description

When there is only one message buffer, it is easy to pass the message buffer handle to xMessageBufferSendCompletedFromISR().

However, consider the case where there are two or more message buffers; the ISR must first determine which message buffer contains data. If the number of message buffers is small, there are several ways to implement this:
  • If hardware allows, each message buffer can use a different interrupt line, thus keeping a one-to-one mapping between interrupt service routines and message buffers.

  • The interrupt service routine can simply query each message buffer to see if it contains data.

  • A single message buffer can replace multiple message buffers by passing metadata (what the message is, who the intended recipient of the message is, etc.) along with the actual data.

However, if there are a large number of message buffers or unknown message buffers, these techniques are not efficient.
In this case, a scalable solution is to introduce a separate control message buffer. As shown in the code below, sbSEND_COMPLETED() passes the handle of the message buffer containing data to the interrupt service routine using the control message buffer.
Implementation using sbSEND_COMPLETED():
/* Added to FreeRTOSConfig.h to override the default implementation. */#define sbSEND_COMPLETED( pxStreamBuffer ) vGenerateCoreToCoreInterrupt( pxStreamBuffer )/* Implemented in a C file. */void vGenerateCoreToCoreInterrupt( MessageBufferHandle_t xUpdatedBuffer ){size_t BytesWritten;/* Called by the implementation of sbSEND_COMPLETED() in FreeRTOSConfig.h.    If this function was called because data was written to any message buffer    other than the control message buffer then write the handle of the message    buffer that contains data to the control message buffer, then raise an    interrupt in the other core.  If this function was called because data was    written to the control message buffer then do nothing. */if( xUpdatedBuffer != xControlMessageBuffer )    {        BytesWritten = xMessageBufferSend(  xControlMessageBuffer,                                            &xUpdatedBuffer,                                            sizeof( xUpdatedBuffer ),                                            0 );/* If the bytes could not be written then the control message buffer        is too small! */configASSERT( BytesWritten == sizeof( xUpdatedBuffer );/* Generate interrupt in the other core (pseudocode). */GenerateInterrupt();    }}

Then, the ISR reads the control message buffer to obtain the handle and passes it as a parameter to xMessageBufferSendCompletedFromISR():

void InterruptServiceRoutine( void ){MessageBufferHandle_t xUpdatedMessageBuffer;BaseType_t xHigherPriorityTaskWoken = pdFALSE;/* Receive the handle of the message buffer that contains data from the    control message buffer.  Ensure to drain the buffer before returning. */while( xMessageBufferReceiveFromISR( xControlMessageBuffer,                                         &xUpdatedMessageBuffer,                                         sizeof( xUpdatedMessageBuffer ),                                         &xHigherPriorityTaskWoken )                                           == sizeof( xUpdatedMessageBuffer ) )    {        /* Call the API function that sends a notification to any task that is        blocked on the xUpdatedMessageBuffer message buffer waiting for data to        arrive. */        xMessageBufferSendCompletedFromISR( xUpdatedMessageBuffer,                                            &xHigherPriorityTaskWoken );    }/* Normal FreeRTOS "yield from interrupt" semantics, where    xHigherPriorityTaskWoken is initialised to pdFALSE and will then get set to    pdTRUE if the interrupt unblocks a task that has a priority above that of    the currently executing task. */portYIELD_FROM_ISR( xHigherPriorityTaskWoken );}

Understanding Communication Between M4 and M7 Cores Using FreeRTOS Message Buffers

As shown in the figure, the sequence when using the control message buffer:
1.The receiving task tries to read data from an empty message buffer and enters a blocked state waiting for data to arrive.
2.The sending task writes data to the message buffer.
3.sbSEND_COMPLETED() sends the handle of the message buffer that now contains data to the control message buffer.
4.sbSEND_COMPLETED() triggers an interrupt in the core where the receiving task is executing.
5.The interrupt service routine reads the handle of the message buffer containing data from the control message buffer, then passes that handle to the xMessageBufferSendCompletedFromISR() API function to unblock the receiving task, which can now read from the buffer as it is no longer empty.

Of course, the above only provides the basic principles and methods; specific implementations need to be combined with the actual project situation. For more related content, please refer to the official relevant materials.

———— END ————

Reply with『RTOS』『FreeRTOS』to read more related articles.
Follow the WeChat official account『Embedded Column』, check the bottom menu for more content, reply “Join Group” to join the technical exchange group according to the rules.

Understanding Communication Between M4 and M7 Cores Using FreeRTOS Message Buffers

Click “Read Original” to see more shares, welcome to share, collect, like, and view.

Leave a Comment