cancel
Showing results for 
Search instead for 
Did you mean: 

How to implement inter-processor communication in an STM32H7 using two SPIs

B.Montanari
ST Employee

Summary

This article shows how to implement inter-processor communication between CM7 and CM4 cores on the STM32H7 series, which also targets the basics on how to set up the debugger, relies on two SPIs to externally transfer data as if they were completely isolated internally. 

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

Introduction

In this article, we implement a basic inter-processor communication channel technique using STM32H7 Series hardware resources, such as using a communication/synchronization protocol. In case your application needs RTOS, there is another article that details the usage of HSEM with FreeRTOS™:
How to use FreeRTOS™ message buffers on STM32H7

1. Prerequisites

2. Communication between CM4 and CM7

2.1. Inter-Processor Communication via SPI with DMA

The simplest way to share data between cores is using the board’s hardware itself, via peripherals. By this, one core can communicate with another through a synchronization protocol, such as SPI. This article showcases how to implement communication to share data using SPI with DMA.

First, we need to create a new project on STM32CubeIDE. For this example we start by selecting 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. Within this project, we need to configure some additional peripherals (initially the SPIs) and remove a few that will not be used (Ethernet and USB). The initial pinout configuration uses the debug mode as serial wire, the high and low speed clock as crystal/ceramic resonator. Moreover, the user push button as GPIO input and the board’s LEDs as GPIO output, which is kept as is.

We use the SPI1 on CM7 as target and SPI3 on CM4 as controller. Firstly, we can configure the SPI1 by going to [Connectivity] and checking the M7 box on [SPI1]:

BMontanari_0-1725369162283.png

The mode is [Full-Duplex Slave] with the hardware NSS signal disabled. The parameter settings are described in the image below:

BMontanari_1-1725369162301.png

Also, this SPI uses DMA. Configure the DMA settings and the NVIC settings so they align with the figures below:

BMontanari_2-1725369162315.png

 

BMontanari_3-1725369162322.png

We also define the PD14 GPIO as output to be the CS pin for the SPI1. Select the PD14 as GPIO_Output and go to System Core → GPIO to configure the following settings, making sure that the PD14 is assigned to the M7 core:

BMontanari_4-1725369162329.png

 

BMontanari_5-1725369162343.png

That is all for the SPI1. The SPI3 configuration is almost the same, but the M4 box is checked:

BMontanari_6-1725369162344.png

The mode is [Full-Duplex Master] with the hardware NSS output signal enabled. The parameter settings are listed in the image below:

BMontanari_7-1725369162365.png

This SPI uses DMA only for transmitting. So, configure the DMA settings and the NVIC settings like the figure below:

BMontanari_8-1725369162377.png

 

BMontanari_9-1725369162384.png

With this, the SPI3 is all done and now we need to allocate the GPIO pins for each core. As this application example is quite simple, we use the PC13 (Blue button) and the PB14 (LD3) for the M4. The M7 is responsible for the PB0 (LD1) and PE1 (LD2).

BMontanari_10-1725369162406.png

We can disable the ETH and USB_FS, since they are not used for this application example.

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

BMontanari_11-1725369162409.png

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

3. Code execution

The code is implemented only within the “main.c” files for both cores. Starting with the CM4, which is the controller of this communication, you can define the buffer variables for transmission and reception. Lastly, create the condition to transmit only when the button is pressed.

Add the following code to each section of the “main.c” file on CM4:

/* USER CODE BEGIN PV */
/* Define the Master’s buffers */
uint8_t uTxBufferMaster[] = {0,1,2,3,4,5,6,7,8,9};
uint8_t uRxBufferMaster[10];
/* USER CODE END PV */
/* USER CODE BEGIN WHILE */
while (1)
{
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
  /* Wait for the button to be pressed */
      if(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13))
      {
         /* Blink once the LD3 */
         HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
         HAL_Delay(200);
         HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin);
         /* Transmit the buffer */
         HAL_SPI_TransmitReceive(&hspi3, uTxBufferMaster,uRxBufferMaster,10,1000);
         /* Wait for the button to be release */
         while(HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_13));
      }
}

/* USER CODE END 3 */

 

The CM7 is the target of the communication and you can add the following code to each section of the “main.c” file on CM7:

/* USER CODE BEGIN PV */
/* Define the Slave’s buffers */
uint8_t uTxBufferSlave[]={0,1,2,3,4,5,6,7,8,9};
uint8_t uRxBufferSlave[10];
/* Define the flag for Tx/Rx validation */
uint8_t uFlagSPISlave = 0;
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
/* Blink the LD2 */
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
HAL_Delay(100);
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
/* Transmit and Receive the buffer */
HAL_SPI_TransmitReceive_DMA(&hspi1, uTxBufferSlave, uRxBufferSlave,10);
/* USER CODE END 2 */

/* USER CODE BEGIN WHILE */
while (1)
{
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
      /* Check if the flag was set */
      if(uFlagSPISlave)
      {
             /* Clear the flag and toggle the LD1 pin */
             uFlagSPISlave = 0;
             HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin);
      }
}
/* USER CODE END 3 */
/* USER CODE BEGIN 4 */
/* SPI callback for Tx/Rx completed */
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi)
{
      /* Trigger again the Transmit/Receive function and set the flag */
      HAL_SPI_TransmitReceive_DMA(&hspi1, uTxBufferSlave, uRxBufferSlave,10);
      uFlagSPISlave = 1;
}
/* USER CODE END 4 */

After adding these lines, we position the CM4 NVIC to start at FLASH BANK 2, while the CM7’s NVIC will be positioned at the start of FLASH BANK 1. This can be edited by uncommenting the #define USER_VECT_TAB_ADDRESS in the system_stm32h7xx_dualcore_boot_cm4_cm7.c.

You can build your code and start debugging your application. If you have any questions on how to configure the debugger on dual core or want to know more details, watch this tutorial:

You can also use this application note as a reference:
STM32H7x5/x7 dual-core microcontroller debugging - Application note
.

In a summarized version, you need to follow these steps to configure the debugger:

  1. Right-click the CM7 project and go to [Debug as][STM32 C/C++ Application] and open the Debug tab.
  2. In the [Debugger] tab, make sure the Shared STLINK checkbox is checked. You might need to scroll down to locate this.
  3. In the [Startup] tab, at the Load Image and Symbols section, click on [Add…] and link the CM4 Project just as below:
    BMontanari_12-1725369162414.png

     

  4. It should look like this:
    BMontanari_13-1725369162427.png

     

  5. If you are ready, you can click [Apply] and [Debug] to enter in debug mode. Remember to physically connect the SPI controller pins with their corresponding match on the target side, including the CS and NSS pins.

4. Code validation

At the end, you can check the buffers at the controller and target side while debugging to understand the hole process of transmitting and receiving. Add the buffer in the live expressions tab so you can see it dynamically changing.

This is the initial value for the uTxBufferMaster and uRxBufferSlave:

BMontanari_14-1725369162442.png

By adding a breakpoint to the CM7 “main.c” in HAL_SPI_TransmitReceive_DMA function, located inside the HAL_SPI_TxRxCpltCallback, we can see that it is triggered by pressing the user button. The uRxBufferSlave receives the uTxBufferMaster data:

BMontanari_15-1725369162459.png

At the same time, the uTxBufferSlave transmits its data and it is received by the uRxBufferMaster:

BMontanari_16-1725369162483.png

Happy coding!

Related links

 

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