cancel
Showing results for 
Search instead for 
Did you mean: 

How to implement inter-processor communication in an STM32H7 using OpenAMP

B.Montanari
ST Employee

Summary

This article shows how to implement inter-processor communication between CM7 and CM4 cores on the STM32H7 series through hardware resources. This article uses the bare metal OpenAMP method.

If you're looking for inter-processor communication using HSEM or two SPI's, we have articles on these use cases as well. 

Introduction

In this article, we implement an inter-processor communication via STM32H7 Series library available framework open asymmetric multi-processing (OpenAMP) with RPMsg for a bare metal application.

1. Prerequisites

2. Communication between CM4 and CM7

2.1. Inter-Processor Communication via OpenAMP

OpenAMP is a framework that provides the required software components to enable the development of applications for asymmetric multi-processing (AMP) systems. It standardizes the interactions between operating environments in a heterogeneous embedded system through open-source components, such as remoteproc and RPMsg.

RPMsg is a component of the OpenAMP framework. It allows inter-processor communication between applications running on different CPUs. It is a virtual I/O-based messaging library that enables RTOS and bare metal applications on a master processor, to interact with remote CPU firmware and communicate with them using standard APIs. Master and slave terminology is defined with the OpenAMP framework.

First, we need to create a new project on STM32CubeIDE for the NUCLEO-H745ZI-Q board, this ensures the base hardware settings are already set. Click on [File] [New]→ [STM32 Project], go to the [Board Selector] and create the project for the Nucleo board. The first step is enabling the HSEM NVIC interrupt for each core, since the OpenAMP framework uses it. Go to [System Core] [NVICx]:

BMontanari_0-1725389707833.png

 

BMontanari_1-1725389707840.png

For this application, we need to enable the OpenAMP framework. Go to [Middleware and Software Package] → [OPENAMP_M7] and set the communication mode as Master. Once one of the OpenAMP is enabled, the other one is also automatically enabled. Keep the Parameter Settings as default:

BMontanari_2-1725389707851.png 

This project also uses the MPU resource on Cortex®-M7. You can enable it by going to [System Core] > [CORTEX_M7] and completing the following configurations for Parameter Settings:

 

BMontanari_3-1725389707863.png

 

BMontanari_4-1725389707890.png

It is important to the MPU Cacheable Permission in [DISABLE] and enable the MPU Shareability Permission. The MPU Region Base Address is according with the SRAM address of your choice. In this case, the address refers to SRAM4.

BMontanari_5-1725389707900.png

In this case, the SRAM4 of 64Kbyte from D3 domain was chosen, because it remains available even if D1 or D2 domain are in Low Power mode. You can check the start address at the reference manual:

BMontanari_6-1725389707906.png

To validate the code, the slave from the OpenAMP framework (Cortex®-M4) sends the data received by the master to the virtual COM port (VCP) USART, which is the USART3 in this board:

BMontanari_7-1725389707923.png

So, we need to enable the USART3 in this project as well. Go to [Connectivity] [USART3] and enable it for the Cortex®-M4 (slave). Keep the configurations at Parameter Settings as default:

BMontanari_8-1725389707939.png

The clock configuration can be kept as default and as a recommendation. An additional configuration that could be a powerful tool to organize the code workspace is: Check the option "Generate peripheral initialization as a pair of '.c/.h' files per peripheral" in Project Manager Code Generator:

BMontanari_9-1725389707942.png

Now that the basic peripherals and settings are configured, we can generate the code and jump to programming.

2. Code execution

The code is implemented within the “main.c” files and the “STM32H745ZITX_FLASH” linker script file for both cores. Starting with the CM7, go to the “main.c” file and add the following code in each section described:

/* USER CODE BEGIN PTD */
#define RPMSG_CHAN_NAME              "openamp_demo"
/* USER CODE END PTD */
/* USER CODE BEGIN PV */
char str2cm4[] = "Bare-Metal programming - sending str from CM7 to CM4 core using OpenAMP framework";
static volatile int message_received;
static volatile int service_created;
volatile unsigned int received_data_str;
static struct rpmsg_endpoint rp_endpoint;
/* USER CODE END PV */
/* USER CODE BEGIN PFP */
static int rpmsg_recv_callback(struct rpmsg_endpoint *ept, void *data, size_t len, uint32_t src, void *priv);
void service_destroy_cb(struct rpmsg_endpoint *ept);
void new_service_cb(struct rpmsg_device *rdev, const char *name, uint32_t dest);
/* USER CODE END PFP */

 

/* USER CODE BEGIN 2 */
  int32_t status = 0;

 	/* Initialize the mailbox use notify the other core on new message */
 	MAILBOX_Init();

 	/* Initialize the rpmsg endpoint to set default addresses to RPMSG_ADDR_ANY */
 	rpmsg_init_ept(&rp_endpoint, RPMSG_CHAN_NAME, RPMSG_ADDR_ANY, RPMSG_ADDR_ANY, NULL, NULL);

 	/* Initialize OpenAmp and libmetal libraries */
 	if (MX_OPENAMP_Init(RPMSG_MASTER, new_service_cb)!= HAL_OK)
 	{
 		Error_Handler();
 	}

 	/*
 	* The rpmsg service is initiate by the remote processor, on A7 new_service_cb
 	* callback is received on service creation. Wait for the callback
 	*/
 	OPENAMP_Wait_EndPointready(&rp_endpoint);

 	status = OPENAMP_send(&rp_endpoint, str2cm4, strlen(str2cm4) + 1);
 	if (status < 0)
 	{
 		Error_Handler();
 	}

 	/* Wait that service is destroyed on remote side */
 	while(service_created)
 	{
 		OPENAMP_check_for_message();
 	}

 	/* De-initialize OpenAMP */
 	OPENAMP_DeInit();

  /* USER CODE END 2 */

 

/* USER CODE BEGIN 4 */
static int rpmsg_recv_callback(struct rpmsg_endpoint *ept, void *data,
                size_t len, uint32_t src, void *priv)
{
  received_data_str = *((unsigned int *) data);
  message_received=1;

  return 0;
}


void service_destroy_cb(struct rpmsg_endpoint *ept)
{
  /* this function is called while remote endpoint as been destroyed, the
   * service is no more available
   */
  service_created = 0;
}

void new_service_cb(struct rpmsg_device *rdev, const char *name, uint32_t dest)
{
  /* create a endpoint for rmpsg communication */
  OPENAMP_create_endpoint(&rp_endpoint, name, dest, rpmsg_recv_callback,
                          service_destroy_cb);
  service_created = 1;
}

/* USER CODE END 4 */

Now, go to the CM7 “STM32H745ZITX_FLASH” file like and replace the MEMORY definition with:

/* Memories definition */
MEMORY
{
  RAM_D1 (xrw)   : ORIGIN = 0x24000000, LENGTH =  512K
  FLASH  (rx)    : ORIGIN = 0x08000000, LENGTH = 1024K    /* Memory is divided. Actual start is 0x08000000 and actual length is 2048K */
  DTCMRAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 128K
  RAM_D2 (xrw)   : ORIGIN = 0x30000000, LENGTH = 288K
  RAM_D3 (xrw)   : ORIGIN = 0x38000000, LENGTH = 64K
  ITCMRAM (xrw)  : ORIGIN = 0x00000000, LENGTH = 64K
  OPENAMP_RSC_TAB (xrw)   : ORIGIN = 0x38000000, LENGTH = 1K
  OPEN_AMP_SHMEM (xrw) : ORIGIN = 0x38000400, LENGTH = 63K
}
__OPENAMP_region_start__  = ORIGIN(OPEN_AMP_SHMEM);
__OPENAMP_region_end__ = ORIGIN(OPEN_AMP_SHMEM) + LENGTH(OPEN_AMP_SHMEM);

Then, add the following code right before the  /* Remove information from the compiler libraries */ comment, at the end of this file:

     .openamp_section (NOLOAD) : {
     . = ABSOLUTE(0x38000000);
     *(.resource_table) 
  } >OPENAMP_RSC_TAB  AT > FLASH

 

With this, the CM7 code is ready. Now, we need to do the same for the CM4 part. So, at the “main.c” file on CM4, add the following code in each section described:

/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
/* USER CODE BEGIN PV */
static volatile int message_received;
volatile char *received_data_str;
static struct rpmsg_endpoint rp_endpoint;
/* USER CODE END PV */
/* USER CODE BEGIN PFP */
static int rpmsg_recv_callback(struct rpmsg_endpoint *ept, void *data, size_t len, uint32_t src, void *priv);
unsigned int receive_message(void);
/* USER CODE END PFP */
  /* USER CODE BEGIN Init */
  /*Clear Flags generated during the wakeup notification */
  HSEM_COMMON->ICR |= ((uint32_t)__HAL_HSEM_SEMID_TO_MASK(HSEM_ID_0));
  HAL_NVIC_ClearPendingIRQ(HSEM2_IRQn);
  /* USER CODE END Init */

 

/* USER CODE BEGIN 2 */
  int32_t status = 0;

   /* Initialize the mailbox use notify the other core on new message */
   MAILBOX_Init();

   /* Initialize OpenAmp and libmetal libraries */
   if (MX_OPENAMP_Init(RPMSG_REMOTE, NULL)!= HAL_OK)
     Error_Handler();

   /* Create an endpoint for rmpsg communication */
   status = OPENAMP_create_endpoint(&rp_endpoint, RPMSG_SERVICE_NAME, RPMSG_ADDR_ANY, rpmsg_recv_callback, NULL);
   if (status < 0)
   {
     Error_Handler();
   }

   /* Receive an string from the master */
   receive_message();

   printf("String: %s\n\r", received_data_str);

   /* De-initialize OpenAMP */
   OPENAMP_DeInit();

  /* USER CODE END 2 */
/* USER CODE BEGIN 4 */
static int rpmsg_recv_callback(struct rpmsg_endpoint *ept, void *data,
               size_t len, uint32_t src, void *priv)
{
  received_data_str = (char *) data;
  message_received=1;

  return 0;
}

unsigned int receive_message(void)
{
  while (message_received == 0)
  {
    OPENAMP_check_for_message();
  }
  message_received = 0;

  return 0;
}

PUTCHAR_PROTOTYPE
{
	HAL_UART_Transmit(&huart3, (uint8_t*)&ch, 1, 0xFFFF);
	return ch;
}
/* USER CODE END 4 */

 

Now, at the CM4 “STM32H745ZITX_FLASH” file, replace the MEMORY definition with:

/* Specify the memory areas */
MEMORY
{
FLASH (rx)     : ORIGIN = 0x08100000, LENGTH = 1024K
RAM (xrw)      : ORIGIN = 0x10000000, LENGTH = 288K
OPENAMP_RSC_TAB (xrw)     : ORIGIN = 0x38000000, LENGTH = 1K
OPEN_AMP_SHMEM (xrw) : ORIGIN = 0x38000400, LENGTH = 63K
}
__OPENAMP_region_start__  = ORIGIN(OPEN_AMP_SHMEM);
__OPENAMP_region_end__ = ORIGIN(OPEN_AMP_SHMEM) + LENGTH(OPEN_AMP_SHMEM);

Then, add the following code right before the  /* Remove information from the standard libraries */ comment, at the end of this file:

     .openamp_section (NOLOAD) : {
     . = ABSOLUTE(0x38000000);
     *(.resource_table) 
  } >OPENAMP_RSC_TAB  AT > FLASH

When it is all done, you can build your project. The next step is to configure the debug configurations to each core. Right-click the CM4 project and go to [Debug As] [Debug Configurations] and double-click the STM32 C/C++ application to create the CM7 debug configuration.

At the [Startup] tab, add the CM4 project in the “Load Image and Symbols” section and configure it as follows by using the [Add…] button, locating the CM4 project. Then, click on the [Apply] button and close the configuration window.

BMontanari_10-1725389707952.png

 

Finally, you can launch the CM7 debugger first and the CM4 debugger after. Then, click on the Resume button (F8) for both projects and check the Virtual Terminal for code validation.

3. Code validation

The code is validated through serial communication, via USART3. So, we need a terminal to show the string message. In this case, Tera Term is used as a terminal, but you can use the STM32CubeIDE’s command shell console.

Open the Tera Term window and select the STLink Serial Port (may vary):

BMontanari_11-1725389707956.png

 

Then, go to [Setup] > [Serial port...] and configure as below:

BMontanari_12-1725389707960.png

 

Then, while debugging, you can check the string that was transmitted:

BMontanari_14-1725390067606.png

 


Conclusion

The example is fairly simple and just showcases how to use the OpenAMP sending and receiving data between cores. ST offers a ping pong demo running in bare metal and another one with FreeRTOS™, both can be accessed in the repository:
C:\Users\%username%\STM32Cube\Repository\STM32Cube_FW_H7_V1.11.2\Projects\STM32H745I-DISCO\Applications\OpenAMP.
Please note that the path needs to be adjusted based on the current H7 HAL driver version used.

Happy coding!

Related links

Version history
Last update:
‎2024-09-09 07:42 AM
Updated by: