Library 56- Extend SPI with DMA for STM32F4xx
As said in one post before, here is SPI DMA library for STM32F4 devices. Instead of onl TX functionality (as in USART DMA library) SPI DMA extension library enables DMA for TX and RX modes at the same time, to receive and transmit data over DMA.
Library supports up to 6 SPIs (max number in STM32F4 devices). It can work in 3 main modes:
- Send data to slave device, receive data from slave device
- Send data to slave device, don’t care for received data from slave device
- Send dummy byte(s) to slave, receive data from slave device
DMA does not make any interrupts after it ends, user will have to check this by itself. There are also functions for that to allow easy check if DMA and SPI are done.
Library
Features
- Enables DMA feature for TM SPI library
- Can transmit, send only or receive only data via SPI and DMA
- Works for all possible SPIs in STM32F4 devices
- Supports changeable stream and channel settings
Dependencies
- CMSIS
- STM32F4xx
- STM32F4xx DMA
- TM
- TM SPI
- TM DMA
- defines.h
- TM SPI
Stream and channel settings
STM32F4xx devices have 2 DMA controllers. Each DMA controller has 8 DMA streams where each stream has 8 DMA channels for different peripherals available.
To get all available DMA peripherals, you should take a STM32F4xx Reference manual (1700+ pages) and take a look at DMA section. There are all available streams and channels for different peripheral.
This library uses only USART TX DMA. Default DMA streams and channels are in table below:
SPIx | DMA | DMA TX Stream | DMA TX Channel | DMA RX Stream | DMA RX Channel |
---|---|---|---|---|---|
SPI1 | DMA2 | DMA Stream 3 | DMA Channel 3 | DMA Stream 2 | DMA Channel 3 |
SPI2 | DMA1 | DMA Stream 4 | DMA Channel 0 | DMA Stream 3 | DMA Channel 0 |
SPI3 | DMA1 | DMA Stream 5 | DMA Channel 0 | DMA Stream 0 | DMA Channel 0 |
SPI4 | DMA2 | DMA Stream 1 | DMA Channel 4 | DMA Stream 0 | DMA Channel 4 |
SPI5 | DMA2 | DMA Stream 6 | DMA Channel 7 | DMA Stream 5 | DMA Channel 7 |
SPI6 | DMA2 | DMA Stream 5 | DMA Channel 1 | DMA Stream 6 | DMA Channel 0 |
Some SPIs uses also different streams and channels. This can be handy if you have 2 peripherals on the same stream and DMA and you want to enable DMA for both. You can’t do that because only one channel on specific stream can be used at a time. For that purpose, someking of “remapping” was enabled which allows you to select custom Stream and Channel for specific SPI if it is available. Always look for STM32F4xx Reference manual for that settings.
Functions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
/** * @defgroup TM_SPI_DMA_Functions * @brief Library Functions * @{ */ /** * @brief Initializes SPI peripheral DMA * @note This function initializes TX and RX DMA streams for SPI * * @note SPI HAVE TO be previously initialized using @ref TM_SPI library * @param *SPIx: Pointer to SPI peripheral where you want to enable DMA * @retlva None */ void TM_SPI_DMA_Init(SPI_TypeDef* SPIx); /** * @brief Initializes SPI DMA functionality with custom DMA stream and channel options * @note SPI HAVE TO be previously initialized using @ref TM_USART library * * @note Use this function only in case default Stream and Channel settings are not good for you * @param *SPIx: Pointer to SPIx where you want to set custom DMA streams and channels * @param *TX_Stream: Pointer to DMAy_Streamx, where y is DMA (1 or 2) and x is Stream (0 to 7) * @param TX_Channel: Select DMA TX channel for your SPI in specific DMA Stream * @param *RX_Stream: Pointer to DMAy_Streamx, where y is DMA (1 or 2) and x is Stream (0 to 7) * @param RX_Channel: Select DMA RX channel for your SPI in specific DMA Stream * @retval None */ void TM_SPI_DMA_InitWithStreamAndChannel(SPI_TypeDef* SPIx, DMA_Stream_TypeDef* TX_Stream, uint32_t TX_Channel, DMA_Stream_TypeDef* RX_Stream, uint32_t RX_Channel); /** * @brief Deinitializes SPI DMA functionality * @param *SPIx: Pointer to SPIx where you want to disable DMA mode * @retval None */ void TM_SPI_DMA_Deinit(SPI_TypeDef* SPIx); /** * @brief Transmits (exchanges) data over SPI with DMA * @note Try not to use local variables pointers for DMA memory as TX and RX Buffers * @param *SPIx: Pointer to SPIx where DMA transmission will happen * @param *TX_Buffer: Pointer to TX_Buffer where DMA will take data to sent over SPI. * Set this parameter to NULL, if you want to sent "0x00" and only receive data into *RX_Buffer pointer * @param *RX_Buffer: Pointer to RX_Buffer where DMA will save data from SPI. * Set this parameter to NULL, if you don't want to receive any data, only sent from TX_Buffer * @param count: Number of bytes to be send/received over SPI with DMA * @retval Transmission started status: * - 0: DMA has not started with sending data * - > 0: DMA has started with sending data */ uint8_t TM_SPI_DMA_Transmit(SPI_TypeDef* SPIx, uint8_t* TX_Buffer, uint8_t* RX_Buffer, uint16_t count); /** * @brief Sends data over SPI without receiving data back using DMA * @note Try not to use local variables pointers for DMA memory as TX and RX Buffers * @param *SPIx: Pointer to SPIx where DMA transmission will happen * @param *TX_Buffer: Pointer to TX_Buffer where DMA will take data to sent over SPI * @param count: Number of bytes to be send/received over SPI with DMA * @retval Sending started status: * - 0: DMA has not started with sending data * - > 0: DMA has started with sending data * @note Defined as macro for faster execution */ #define TM_SPI_DMA_Send(SPIx, TX_Buffer, count) (TM_SPI_DMA_Transmit(SPIx, TX_Buffer, NULL, count)) /** * @brief Sends dummy byte (0x00) over SPI to receive data back from slave over DMA * @note Try not to use local variables pointers for DMA memory as TX and RX Buffers * @param SPIx: Pointer to SPIx where DMA transmission will happen * @param RX_Buffer: Pointer to RX_Buffer where DMA will save data from SPI * @param count: Number of bytes to be received over SPI with DMA * @retval Receiving started status: * - 0: DMA has not started with sending data * - > 0: DMA has started with sending data * @note Defined as macro for faster execution */ #define TM_SPI_DMA_Receive(SPIx, RX_Buffer, count) (TM_SPI_DMA_Transmit(SPIx, NULL, RX_Buffer, count)) /** * @brief Checks if SPI DMA is still sending/receiving data * @param *SPIx: Pointer to SPIx where you want to enable DMA TX mode * @retval Sending status: * - 0: SPI DMA does not sending any more * - > 0: SPI DMA is still sending data */ uint8_t TM_SPI_DMA_Working(SPI_TypeDef* SPIx); /** * @} */ |
Example
Example was tested using Nucleo-F411 board. Using SPI1, I connected MOSI and MISO pins together to simulate data from slave.
I split example into three parts:
- In first part, I fill TX_Buffer and sent data over SPI. Because MOSI and MISO pins are connected together, I expected the same result in RX_Buffer after DMA finishes transmission. Image 1 proves successfull result.
- In second part I sent data over SPI with DMA, but I set RX_Buffer to NULL, which means that I don’t to receive any data from DMA slave, only sent data. RX_Buffer is the same as it was at the end of part one.
- Last part was to test receive method via SPI. I set TX_Buffer to NULL. In this case, DMA will send all zeros over SPI to slave, just to enable SPI clock for it. Because I had MOSI and MISO pins together, all zeros are received in RX_Buffer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
/** * Keil project for SPI DMA * * Before you start, select your target, on the right of the "Load" button * * @author Tilen Majerle * @email tilen@majerle.eu * @website http://stm32f4-discovery.net * @ide Keil uVision 5 * @conf PLL parameters are set in "Options for Target" -> "C/C++" -> "Defines" * @packs STM32F4xx Keil packs version 2.4.0 or greater required * @stdperiph STM32F4xx Standard peripheral drivers version 1.5.0 or greater required * * I have tested this example on F411-Nucleo board. * To fake slave device, I just connected MOSI and MISO pins together, so what I sent I also expect to receive. */ /* Include core modules */ #include "stm32f4xx.h" /* Include my libraries here */ #include "defines.h" #include "tm_stm32f4_delay.h" #include "tm_stm32f4_disco.h" #include "tm_stm32f4_spi.h" #include "tm_stm32f4_spi_dma.h" uint8_t TX_Buffer[5], RX_Buffer[5]; int main(void) { /* Initialize system */ SystemInit(); /* Initialize delay */ TM_DELAY_Init(); /* Init SPI */ TM_SPI_Init(SPI1, TM_SPI_PinsPack_1); /* Init SPI DMA */ TM_SPI_DMA_Init(SPI1); /* Set fake SPI TX buffer */ TX_Buffer[0] = 0xAA; TX_Buffer[1] = 0xDD; TX_Buffer[2] = 0xCC; TX_Buffer[3] = 0xAA; TX_Buffer[4] = 0x55; /* Set CS low first here before you send data over SPI */ /* Send data over SPI1 with DMA */ /* Exchange data with SPI slave using SPI DMA */ /* Exchange 5 bytes of data */ TM_SPI_DMA_Transmit(SPI1, TX_Buffer, RX_Buffer, 5); /* Wait till SPI DMA do it's job */ /* You can do other stuff instead */ while (TM_SPI_DMA_Working(SPI1)); /* Little delay, for debug */ Delayms(10); /* Send 5 bytes of data and don't care what you receive back from slave via DMA */ TM_SPI_DMA_Transmit(SPI1, TX_Buffer, NULL, 5); /* Wait till SPI DMA do it's job */ /* You can do other stuff instead */ while (TM_SPI_DMA_Working(SPI1)); /* Little delay, for debug */ Delayms(10); /* Receive 5 bytes of data, sent dummy 0x00 bytes to slave via DMA */ TM_SPI_DMA_Transmit(SPI1, NULL, RX_Buffer, 5); /* Wait till SPI DMA do it's job */ /* You can do other stuff instead */ while (TM_SPI_DMA_Working(SPI1)); /* Set CS high here after last byte is done by SPI */ while (1) { } } |
Project is available on my Github, download library below.
Recent comments