In this guide, we shall send data over SPI bus using DMA.
In the previous guide (here), we discussed how to send a single character using SPI in polling mode. This method will decrease the CPU performance since the CPU has to wait for each time before sending the character. In this guide, we shall introduce DMA to send number of byte without blocking the operation of the CPU.
In this guide, we shall cover the following:
- SPI Pins initialization.
- SPI configuration.
- DMA configuration.
- SPI Interrupt handler.
- DMA Interrupt handler
- SPI DMA send function.
- Header file.
- Main code.
- Results.
1. SPI initialization:
Create new source and header file with name of SPI1_DMA.c and SPI1_DMA.h respectively.
Within the source file:
Include the following header file:
#include "stm32f7xx.h" #include "SPI1_DMA.h"
Declare the following two macros:
#define AF05 0x05 #define CH3 0x03
First one for alternate function number for the GPIO.
The second one is for the DMA channel selection.
Declare the SPI pins initialization function:
void SPI1_Pins_Init(void)
Within the function:
- Enable clock access to GPIOA.
- Set PA5, PA6 and PA7 to alternate function.
- Select which alternate function to use.
//enable clock for GPIOA RCC->AHB1ENR|=RCC_AHB1ENR_GPIOAEN; //set PA5, PA6 and PA7 to alternate function mode GPIOA->MODER|=GPIO_MODER_MODER5_1|GPIO_MODER_MODER6_1|GPIO_MODER_MODER7_1; GPIOA->MODER&=~(GPIO_MODER_MODER5_0|GPIO_MODER_MODER6_0|GPIO_MODER_MODER7_0); GPIOA->AFR[0]|=(AF05<<GPIO_AFRL_AFRL5_Pos)|(AF05<<GPIO_AFRL_AFRL6_Pos)|(AF05<<GPIO_AFRL_AFRL7_Pos);
Now declare the following function which will initialize the SPI in DMA mode:
void SPI1_DMA_Init(void)
The SPI configuration as following:
In Control Register 1 (CR1):
- Enable Clock access to SPI
- Baud to be Fclk/8 (according to your application).
- CPOL and CPHA to 0 (according to your application also).
- Full duplex mode.
- Master Mode.
- Software slave management.
//enable clock access to SPI1 RCC->APB2ENR|=RCC_APB2ENR_SPI1EN; //software slave management SPI1->CR1|=SPI_CR1_SSM|SPI_CR1_SSI; /*Set clock to fPCLK/8*/ SPI1->CR1 |=(SPI_CR1_BR_1); // set SPI as master mode SPI1->CR1|=SPI_CR1_MSTR; /*Set CPOL to 0 and CPHA to 0*/ SPI1->CR1 &=~SPI_CR1_CPOL; SPI1->CR1 &=~SPI_CR1_CPHA;
In Control Register 2 (CR2):
- Enable TXDMA.
- Set data size to 8-bit.
/*Set data size to 8-bit*/ SPI1->CR2&=~(SPI_CR2_DS_Msk); SPI1->CR2|=(0x07<<SPI_CR2_DS_Pos); /*Enable TXDMA*/ SPI1->CR2|=SPI_CR2_TXDMAEN;
Enable SPI global interrupt:
/*Enable SPI global interrupt*/ NVIC_EnableIRQ(SPI1_IRQn);
Enable SPI:
/*Enable SPI peripheral*/ SPI1->CR1|=SPI_CR1_SPE;
2. DMA Configuration:
Before the Initializing, we need to know which Stream and Channel is Connected to SPI_TX
According the figure below, we have two options, either Stream 5 or Stream 3 and both on Channel 3.
For the DMA2_Stream3 configuration:
- Channel number is 3.
- Direction is Memory to peripheral.
- Memory increment mode.
- Enable Transfer Complete Interrupt.
- Enable DMA2_Stream3 Interrupt in NVIC.
/*Enable Clock Access to DMA2*/ RCC->AHB1ENR|=RCC_AHB1ENR_DMA2EN; /*Disable the Stream*/ /*Disable the Stream and make sure it is disabled*/ DMA2_Stream3->CR&=~DMA_SxCR_EN; while((DMA2_Stream3->CR)&DMA_SxCR_EN){;} /*Configure the DMA with the following parameters * * CH -> channel 3. * Memory increment mode. * Enable Transfer Complete interrupt * */ DMA2_Stream3->CR=(CH3<<DMA_SxCR_CHSEL_Pos)|DMA_SxCR_MINC|DMA_SxCR_DIR_0|DMA_SxCR_TCIE; /*Enable DMA2_Stream3 interrupt in NVIC*/ NVIC_EnableIRQ(DMA2_Stream3_IRQn);
3. SPI Interrupt Handler:
For the SPI interrupt handler:
Check if the source of the interrupt is Tx buffer is empty and the bus is not busy, it those two condition met:
- Call SPI_TX_Finished.
- Disable Tx buffer is empty interrupt.
__WEAK void SPI_TX_Finished(void) { /*Override with user code*/ } void SPI1_IRQHandler(void) { if ((SPI1->SR & SPI_SR_TXE) && ((SPI1->SR & SPI_SR_BSY)==0)) { /*Set finished to 1*/ SPI_TX_Finished(); /*Disable SPI Tx Buffer empty interrupt*/ SPI1->CR2&=~SPI_CR2_TXEIE; } }
4. DMA Interrupt Handler:
Within the interrupt handler:
- If the interrupt source is transfer completed, Enable SPI TX buffer is empty and clear the flag.
void DMA2_Stream3_IRQHandler(void) { if(DMA2->LISR&(DMA_LISR_TCIF3)) { /*Enable SPI Tx buffer empty interrupt */ SPI1->CR2|=SPI_CR2_TXEIE; /*Clear pending flag*/ DMA2->LIFCR |=DMA_LIFCR_CTCIF3; } }
5. SPI DMA Send function:
Declare the following function:
void SPI1_TX_DMA(uint8_t *data,uint16_t len)
This function will send data using DMA over SPI and it takes two parameters:
- Pointer to the data to be sent.
- Length of the data.
Within the function:
- Clear transfer completed interrupt flag.
- Set the peripheral address to be SPI1->DR.
- Set the memory address to be the address of the data buffer.
- Number of transfer to be the length of the data.
- Enable the stream.
void SPI1_TX_DMA(uint8_t *data,uint16_t len) { DMA2->LIFCR |=DMA_LIFCR_CTCIF3; DMA2_Stream3->PAR=(uint32_t)&SPI1->DR; DMA2_Stream3->M0AR=(uint32_t)data; DMA2_Stream3->NDTR=len; DMA2_Stream3->CR|=DMA_SxCR_EN; }
Hence, the entire source code as following:
#include "stm32f7xx.h" #include "SPI1_DMA.h" #define AF05 0x05 #define CH3 0x03 void SPI1_Pins_Init(void) { //enable clock for GPIOA RCC->AHB1ENR|=RCC_AHB1ENR_GPIOAEN; //set PA5, PA6 and PA7 to alternate function mode GPIOA->MODER|=GPIO_MODER_MODER5_1|GPIO_MODER_MODER6_1|GPIO_MODER_MODER7_1; GPIOA->MODER&=~(GPIO_MODER_MODER5_0|GPIO_MODER_MODER6_0|GPIO_MODER_MODER7_0); GPIOA->AFR[0]|=(AF05<<GPIO_AFRL_AFRL5_Pos)|(AF05<<GPIO_AFRL_AFRL6_Pos)|(AF05<<GPIO_AFRL_AFRL7_Pos); } void SPI1_DMA_Init(void) { //enable clock access to SPI1 RCC->APB2ENR|=RCC_APB2ENR_SPI1EN; //software slave management SPI1->CR1|=SPI_CR1_SSM|SPI_CR1_SSI; /*Set clock to fPCLK/8*/ SPI1->CR1 |=(SPI_CR1_BR_1); // set SPI as master mode SPI1->CR1|=SPI_CR1_MSTR; /*Set CPOL to 0 and CPHA to 0*/ SPI1->CR1 &=~SPI_CR1_CPOL; SPI1->CR1 &=~SPI_CR1_CPHA; /*Set data size to 8-bit*/ SPI1->CR2&=~(SPI_CR2_DS_Msk); SPI1->CR2|=(0x07<<SPI_CR2_DS_Pos); /*Enable TXDMA*/ SPI1->CR2|=SPI_CR2_TXDMAEN; /*Enable SPI global interrupt*/ NVIC_EnableIRQ(SPI1_IRQn); /*Enable SPI peripheral*/ SPI1->CR1|=SPI_CR1_SPE; /*Enable Clock Access to DMA2*/ RCC->AHB1ENR|=RCC_AHB1ENR_DMA2EN; /*Disable the Stream*/ /*Disable the Stream and make sure it is disabled*/ DMA2_Stream3->CR&=~DMA_SxCR_EN; while((DMA2_Stream3->CR)&DMA_SxCR_EN){;} /*Configure the DMA with the following parameters * * CH -> channel 3. * Memory increment mode. * Enable Transfer Complete interrupt * */ DMA2_Stream3->CR=(CH3<<DMA_SxCR_CHSEL_Pos)|DMA_SxCR_MINC|DMA_SxCR_DIR_0|DMA_SxCR_TCIE; /*Enable DMA2_Stream3 interrupt in NVIC*/ NVIC_EnableIRQ(DMA2_Stream3_IRQn); } void SPI1_TX_DMA(uint8_t *data,uint16_t len) { DMA2->LIFCR |=DMA_LIFCR_CTCIF3; DMA2_Stream3->PAR=(uint32_t)&SPI1->DR; DMA2_Stream3->M0AR=(uint32_t)data; DMA2_Stream3->NDTR=len; DMA2_Stream3->CR|=DMA_SxCR_EN; } __WEAK void SPI_TX_Finished(void) { /*Override with user code*/ } void DMA2_Stream3_IRQHandler(void) { if(DMA2->LISR&(DMA_LISR_TCIF3)) { /*Enable SPI Tx buffer empty interrupt */ SPI1->CR2|=SPI_CR2_TXEIE; /*Clear pending flag*/ DMA2->LIFCR |=DMA_LIFCR_CTCIF3; } } void SPI1_IRQHandler(void) { if ((SPI1->SR & SPI_SR_TXE) && ((SPI1->SR & SPI_SR_BSY)==0)) { /*Set finished to 1*/ SPI_TX_Finished(); /*Disable SPI Tx Buffer empty interrupt*/ SPI1->CR2&=~SPI_CR2_TXEIE; } }
6. Header File:
The entire header file:
#ifndef SPI1_DMA_H_ #define SPI1_DMA_H_ #include "stdint.h" void SPI1_Pins_Init(void); void SPI1_DMA_Init(void); void SPI1_TX_DMA(uint8_t *data,uint16_t len); #endif /* SPI1_DMA_H_ */
7. Main Code:
main.c file:
#include "stm32f7xx.h" #include "SPI1_DMA.h" #define size_of_data 10 uint8_t data[size_of_data]; volatile uint8_t finished_transfer=0; int main(void) { SPI1_Pins_Init(); SPI1_DMA_Init(); /*Configure PA3 to act as CS pin*/ RCC->AHB1ENR|=RCC_AHB1ENR_GPIOAEN; GPIOA->MODER|=GPIO_MODER_MODER3_0; GPIOA->MODER&=~GPIO_MODER_MODER3_1; GPIOA->BSRR=GPIO_BSRR_BS3; for (int i=0;i<size_of_data;i++) { data[i]=i; } while(1) { /*Set PA3 to low */ GPIOA->BSRR=GPIO_BSRR_BR3; /*Send the data*/ SPI1_TX_DMA(&data,size_of_data); /*Wait until the data have been transfer*/ while(finished_transfer==0); /*Set PA0 to high*/ GPIOA->BSRR=GPIO_BSRR_BS3; /*Reset the variable*/ finished_transfer=0; } } void SPI_TX_Finished(void) { finished_transfer=1; }
8. Results:
After flashing the code to your STM32F4xx, connect logic analyzer to pins PA5, PA7 and PA0 and decode the SPI data, you should get the following:
Happy coding 😉
Add Comment