We continue to improve our STM32CubeMx course and today we’ll speak about the combined usage of SPI and DMA peripherals. Of course, we’ll also create an example for STM32 microcontrollers. To make our task more difficult I’ve decided to use four SPI modules and respectively four different DMA channels. So, let’s begin immediately! 🙂
Firstly, we should create the new project in the STM32CubeMx application. Don’t forget to choose the proper MCU when creating the project (as for me, I’ll use STM32F429). Secondly, we should enable all necessary SPI peripherals:
As a result STM32CubeMx marked 12 GPIOs which are used for SPI communication:
- SPI1_SCK
- SPI1_MOSI
- SPI1_MISO
- SPI2_SCK
- SPI2_MOSI
- SPI2_MISO
- SPI3_SCK
- SPI3_MOSI
- SPI3_MISO
- SPI4_SCK
- SPI4_MOSI
- SPI4_MISO
Let’s open the configuration tab. To activate the STM32 DMA channels for SPI modules we should add some extra configuration. So, click on the SPI1 button.
In this window we are interested in the “DMA Settings” tab. To add the DMA channel to SPI1 we should click on the “Add” button and choose the necessary DMA channel. In this example we’ll use only SPI1_TX DMA request:
Moreover, let’s enable the DMA interrupt. It will occure, when the transmission is completed. Thus, we’ll be able to start the new data exchange:
Now, we should repeat all these steps for other SPI peripherals (SPI2, SPI3, SPI4). There are no differences between the configurations of different modules, so, we won’t stop on this point. After that, the configuration is fully completed, so, we can begin the programming process 🙂
Let’s look at some generated functions using the SPI1 module as an example. First of all, the initialization function is called:
void MX_SPI1_Init(void) { hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLED; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED; HAL_SPI_Init(&hspi1); }
The DMA initialization is held in the HAL_SPI_MspInit() function:
hdma_spi1_tx.Instance = DMA2_Stream3; hdma_spi1_tx.Init.Channel = DMA_CHANNEL_3; hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE; hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE; hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hdma_spi1_tx.Init.Mode = DMA_NORMAL; hdma_spi1_tx.Init.Priority = DMA_PRIORITY_VERY_HIGH; hdma_spi1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; HAL_DMA_Init(&hdma_spi1_tx); __HAL_LINKDMA(hspi,hdmatx,hdma_spi1_tx);
All interrupts are located in the file stm32f4xx_it.c (if you use another microcontroller this file could have another name, for example, stm32f1xx_it.c. As we activated four interrupt requests, let’s define four flags which will indicate the transmission status for all SPIs:
uint8_t completed1 = 1; uint8_t completed2 = 1; uint8_t completed3 = 1; uint8_t completed4 = 1;
You should also declare these variables in all of the files where they are used:
extern uint8_t completed1; extern uint8_t completed2; extern uint8_t completed3; extern uint8_t completed4;
If the completed1 flag is set to 1, the data exchange via SPI1 is finished. If it is set to 0, the SPI1 peripheral is busy. Thus, we should reset the flag when starting the transmission and set it when the transmission is completed. Let’s edit the interrupt handlers:
/** * @brief This function handles DMA2 Stream3 global interrupt. */ void DMA2_Stream3_IRQHandler(void) { /* USER CODE BEGIN DMA2_Stream3_IRQn 0 */ completed1 = 1; /* USER CODE END DMA2_Stream3_IRQn 0 */ HAL_DMA_IRQHandler(&hdma_spi1_tx); /* USER CODE BEGIN DMA2_Stream3_IRQn 1 */ /* USER CODE END DMA2_Stream3_IRQn 1 */ } /** * @brief This function handles DMA2 Stream1 global interrupt. */ void DMA2_Stream1_IRQHandler(void) { /* USER CODE BEGIN DMA2_Stream1_IRQn 0 */ completed4 = 1; /* USER CODE END DMA2_Stream1_IRQn 0 */ HAL_DMA_IRQHandler(&hdma_spi4_tx); /* USER CODE BEGIN DMA2_Stream1_IRQn 1 */ /* USER CODE END DMA2_Stream1_IRQn 1 */ } /** * @brief This function handles DMA1 Stream4 global interrupt. */ void DMA1_Stream4_IRQHandler(void) { /* USER CODE BEGIN DMA1_Stream4_IRQn 0 */ completed2 = 1; /* USER CODE END DMA1_Stream4_IRQn 0 */ HAL_DMA_IRQHandler(&hdma_spi2_tx); /* USER CODE BEGIN DMA1_Stream4_IRQn 1 */ /* USER CODE END DMA1_Stream4_IRQn 1 */ } /** * @brief This function handles DMA1 Stream5 global interrupt. */ void DMA1_Stream5_IRQHandler(void) { /* USER CODE BEGIN DMA1_Stream5_IRQn 0 */ completed3 = 1; /* USER CODE END DMA1_Stream5_IRQn 0 */ HAL_DMA_IRQHandler(&hdma_spi3_tx); /* USER CODE BEGIN DMA1_Stream5_IRQn 1 */ /* USER CODE END DMA1_Stream5_IRQn 1 */ }
Moreover, we should declare the data array and fill it with the test data:
uint8_t testDataToSend[128]; for (uint8_t i = 0; i < 128; i++) { testDataToSend[i] = i + 1; }
And the final step is calling the HAL_SPI_Transmit_DMA() function in order to send the data. Don’t forget to reset the status flag:
completed1 = 0; HAL_SPI_Transmit_DMA(&hspi1, testDataToSend, 128);
Similarly, add the following code for SPI2 data exchange:
completed2 = 0; HAL_SPI_Transmit_DMA(&hspi2, testDataToSend, 128);
After that we should wait while the status flag is reset, before we can start the new transmission.
So that's all for today, don’t miss the new posts on MicroTechnics! 🙂