How to Use SPI with DMA in Embedded C: Simple Guide
To use
SPI with DMA in embedded C, configure the SPI peripheral and DMA controller to handle data transfers automatically. Initialize DMA channels for SPI TX and RX, then start the SPI communication; DMA moves data in the background, freeing the CPU.Syntax
Using SPI with DMA involves setting up the SPI peripheral and DMA channels. You typically configure:
- SPI initialization: Set SPI mode, clock, and data size.
- DMA setup: Configure source and destination addresses, data length, and transfer direction.
- Start transfer: Enable DMA streams and SPI to begin communication.
c
/* SPI and DMA initialization pseudocode */ void SPI_DMA_Init(void) { // Initialize SPI peripheral SPI_InitTypeDef SPI_InitStruct = {0}; SPI_InitStruct.Mode = SPI_MODE_MASTER; SPI_InitStruct.Direction = SPI_DIRECTION_2LINES; SPI_InitStruct.DataSize = SPI_DATASIZE_8BIT; SPI_InitStruct.CLKPolarity = SPI_POLARITY_LOW; SPI_InitStruct.CLKPhase = SPI_PHASE_1EDGE; SPI_InitStruct.NSS = SPI_NSS_SOFT; SPI_InitStruct.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; SPI_InitStruct.FirstBit = SPI_FIRSTBIT_MSB; SPI_InitStruct.TIMode = SPI_TIMODE_DISABLE; SPI_InitStruct.CRCCalculation = SPI_CRCCALCULATION_DISABLE; SPI_InitStruct.CRCPolynomial = 7; SPI_Init(&SPI_InitStruct); // Configure DMA for SPI TX DMA_InitTypeDef DMA_TX_InitStruct = {0}; DMA_TX_InitStruct.Channel = DMA_CHANNEL_3; DMA_TX_InitStruct.Direction = DMA_MEMORY_TO_PERIPH; DMA_TX_InitStruct.PeriphInc = DMA_PINC_DISABLE; DMA_TX_InitStruct.MemInc = DMA_MINC_ENABLE; DMA_TX_InitStruct.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; DMA_TX_InitStruct.MemDataAlignment = DMA_MDATAALIGN_BYTE; DMA_TX_InitStruct.Mode = DMA_NORMAL; DMA_TX_InitStruct.Priority = DMA_PRIORITY_LOW; DMA_Init(DMA1_Stream3, &DMA_TX_InitStruct); // Configure DMA for SPI RX DMA_InitTypeDef DMA_RX_InitStruct = {0}; DMA_RX_InitStruct.Channel = DMA_CHANNEL_3; DMA_RX_InitStruct.Direction = DMA_PERIPH_TO_MEMORY; DMA_RX_InitStruct.PeriphInc = DMA_PINC_DISABLE; DMA_RX_InitStruct.MemInc = DMA_MINC_ENABLE; DMA_RX_InitStruct.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; DMA_RX_InitStruct.MemDataAlignment = DMA_MDATAALIGN_BYTE; DMA_RX_InitStruct.Mode = DMA_NORMAL; DMA_RX_InitStruct.Priority = DMA_PRIORITY_HIGH; DMA_Init(DMA1_Stream0, &DMA_RX_InitStruct); }
Example
This example shows how to send and receive data over SPI using DMA on an STM32 microcontroller. It initializes SPI and DMA, then starts a transfer of 10 bytes.
c
#include "stm32f4xx.h" #include <string.h> uint8_t txBuffer[10] = {0,1,2,3,4,5,6,7,8,9}; uint8_t rxBuffer[10] = {0}; void SPI_DMA_Init(void); void SPI_TransmitReceive_DMA(uint8_t *txData, uint8_t *rxData, uint16_t size); int main(void) { SPI_DMA_Init(); SPI_TransmitReceive_DMA(txBuffer, rxBuffer, 10); // Wait for transfer complete (polling DMA flags for simplicity) while(!(DMA1->HISR & DMA_HISR_TCIF0)) {} DMA1->HIFCR |= DMA_HIFCR_CTCIF0; // Clear flag // Now rxBuffer contains received data while(1) {} } void SPI_DMA_Init(void) { // Enable clocks RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; // Configure SPI1 as master, 8-bit, mode 0 SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_SPE; // Configure DMA streams // DMA1 Stream3 Channel3 for SPI1_TX DMA1_Stream3->CR = 0; DMA1_Stream3->PAR = (uint32_t)&SPI1->DR; DMA1_Stream3->M0AR = (uint32_t)txBuffer; DMA1_Stream3->NDTR = 10; DMA1_Stream3->CR = DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_DIR_0 | DMA_SxCR_TCIE; // DMA1 Stream0 Channel3 for SPI1_RX DMA1_Stream0->CR = 0; DMA1_Stream0->PAR = (uint32_t)&SPI1->DR; DMA1_Stream0->M0AR = (uint32_t)rxBuffer; DMA1_Stream0->NDTR = 10; DMA1_Stream0->CR = DMA_SxCR_CHSEL_0 | DMA_SxCR_MINC | DMA_SxCR_TCIE; } void SPI_TransmitReceive_DMA(uint8_t *txData, uint8_t *rxData, uint16_t size) { // Update DMA memory addresses and size DMA1_Stream3->M0AR = (uint32_t)txData; DMA1_Stream3->NDTR = size; DMA1_Stream0->M0AR = (uint32_t)rxData; DMA1_Stream0->NDTR = size; // Enable DMA streams DMA1_Stream0->CR |= DMA_SxCR_EN; DMA1_Stream3->CR |= DMA_SxCR_EN; // Enable SPI DMA requests SPI1->CR2 |= SPI_CR2_RXDMAEN | SPI_CR2_TXDMAEN; }
Common Pitfalls
Common mistakes when using SPI with DMA include:
- Not enabling the DMA streams before starting SPI communication.
- Forgetting to enable SPI DMA requests with
SPI_CR2_RXDMAENandSPI_CR2_TXDMAEN. - Incorrectly configuring DMA memory increment mode, causing data overwrite.
- Not handling DMA transfer complete interrupts or flags, leading to premature data use.
- Misconfiguring SPI clock polarity and phase, causing communication errors.
c
/* Wrong: Not enabling DMA streams */ // DMA1_Stream3->CR |= DMA_SxCR_EN; // Missing // DMA1_Stream0->CR |= DMA_SxCR_EN; // Missing /* Right: Enable DMA streams before SPI transfer */ DMA1_Stream0->CR |= DMA_SxCR_EN; DMA1_Stream3->CR |= DMA_SxCR_EN;
Quick Reference
Remember these key steps for SPI with DMA:
- Initialize SPI peripheral with correct mode and settings.
- Configure DMA channels for TX and RX with proper addresses and data size.
- Enable DMA streams and SPI DMA requests before starting transfer.
- Wait for DMA transfer complete flags or interrupts before using data.
Key Takeaways
Configure SPI and DMA peripherals carefully before starting transfers.
Enable DMA streams and SPI DMA requests to allow automatic data movement.
Use DMA transfer complete flags or interrupts to know when data is ready.
Set DMA memory increment mode to avoid data overwrite.
Match SPI clock polarity and phase with the connected device.