cancel
Showing results for 
Search instead for 
Did you mean: 

STM32F407: SPI as slave using DMA

konstantin2
Associate
Posted on May 27, 2016 at 17:31

Hi there,

I'm desperatly trying to get the STM32F407 work as a SPI slave with DMA TX/RX functionality. I literally read any thread about this topic Google found (~5 pages of results) but it's just not working and I just don't know why. The STM32F4 should work as a SPI slave using the GPIOB Pins 13, 14, This means I have to use SPI2 and DMA1 Stream 4 (TX) and DMA1 Stream 3 (RX). See my code: First, I'm powering up all relevant parts.

void
CTRLSPI_RCC_init() 
{
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_DMA1, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
}

Then I configure the pins named above as Alternate Function and couple them:

void
CTRLSPI_GPIO_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_PinAFConfig(GPIOB, GPIO_PinSource1, GPIO_AF_SPI2);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource2, GPIO_AF_SPI2);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI2);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}

As a next step I set up the SPI as a slave and enable a interupt:

void
CTRLSPI_SPI_init()
{
SPI_I2S_DeInit(SPI2);
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 0;
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
SPI_Init(SPI2, &SPI_InitStructure);
SPI_I2S_ITConfig(SPI2,SPI_I2S_IT_RXNE,ENABLE);
}

Then I configure and initialise the DMA Streams for receiving and transmitting over the SPI2 Data Register:

void
CTRLSPI_DMA_init()
{
DMA_InitTypeDef DMA_InitStructure;
DMA_DeInit(DMA1_Stream4);
DMA_DeInit(DMA1_Stream3);
// Configure the Rx part
DMA_InitStructure.DMA_Channel = DMA_Channel_0; 
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(SPI2->DR); 
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&CTRLSPI_RX_buffer[0]; 
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; 
DMA_InitStructure.DMA_BufferSize = CTRLSPI_RX_BUFFER_SIZE; 
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; 
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; 
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_Init(DMA1_Stream3, &DMA_InitStructure);
DMA_ITConfig(DMA1_Stream3, DMA_IT_TC, ENABLE);
DMA_Cmd(DMA1_Stream4, DISABLE);
//DMA TX.. just modify the relevant parts...
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(SPI2->DR);
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&CTRLSPI_TX_buffer[0];
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_InitStructure.DMA_BufferSize = CTRLSPI_TX_BUFFER_SIZE;
DMA_Init(DMA1_Stream4, &DMA_InitStructure);
DMA_ITConfig(DMA1_Stream4, DMA_IT_TC, ENABLE);
DMA_Cmd(DMA1_Stream4, DISABLE);
}

Here I the DMA interrupts I use:

extern
''C''
void
DMA1_Stream4_IRQHandler(
void
)
{
if
(DMA_GetITStatus(DMA1_Stream4, DMA_IT_TCIF4))
{
CTRLSPI_TX_statusled=!CTRLSPI_TX_statusled;
if
(CTRLSPI_TX_statusled){GPIOD->BSRRL=GPIO_Pin_14;}
else
{GPIOD->BSRRH=GPIO_Pin_14;}
DMA_ClearITPendingBit(DMA1_Stream4, DMA_IT_TCIF4);
}
}
extern
''C''
void
DMA1_Stream3_IRQHandler(
void
)
{
if
(DMA_GetITStatus(DMA1_Stream3, DMA_IT_TCIF3))
{
CTRLSPI_RX_statusled=!CTRLSPI_RX_statusled;
if
(CTRLSPI_RX_statusled){GPIOD->BSRRL=GPIO_Pin_15;}
else
{GPIOD->BSRRH=GPIO_Pin_15;}
DMA_ClearITPendingBit(DMA1_Stream3, DMA_IT_TCIF3);
}
}

As a last the I set up the Interrupts using NVIC:

void
CTRLSPI_NVIC_init()
{
NVIC_InitTypeDef NVIC_InitStructure;
//DMA TX
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//DMA RX
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Stream3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority= 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

Finally I put it all together and enable the stream:

SystemInit();
...
CTRLSPI_RCC_init();
CTRLSPI_GPIO_init()
CTRLSPI_SPI_init();
CTRLSPI_DMA_init();
CTRLSPI_NVIC_init();
DMA_Cmd(DMA1_Stream4, ENABLE);
DMA_Cmd(DMA1_Stream3, ENABLE);
SPI_I2S_DMACmd(SPI2,SPI_I2S_DMAReq_Tx,ENABLE);
SPI_I2S_DMACmd(SPI2,SPI_I2S_DMAReq_Rx,ENABLE);
DMA_ClearITPendingBit(DMA1_Stream4, DMA_IT_TCIF4);
DMA_ClearITPendingBit(DMA1_Stream3, DMA_IT_TCIF3);
DMA_ClearFlag(DMA1_Stream3, DMA_FLAG_FEIF3);
SPI_Cmd(SPI2, ENABLE);
...

Then I connect some other SPI-port of the STM32F4 without DMA to the one I'm trying to configure with DMA. I already checked that the simple DMA is outputting data (used a logic analyzer), but they just won't be read in. Using the debugger I found out, that neither the DMA1_Stream4 nor the DMA1_Stream3 interrupt handler are ever called. What's wrong with my configuration? Thank you very much for any advice! I appreciate your help 🙂 #!stm32-!spi-!dma
4 REPLIES 4
mark239955_stm1
Associate II
Posted on May 28, 2016 at 03:33

Maybe I'm missing it, but I don't see you enabling SPI2_NSS anywhere, or using any other edge detection interrupt to start the SPI slave transaction.

If you're permanently holding SPI2 in ''selected'' mode, i.e. always ready for reception (highly unwise in practice imho, you should use the NSS pin), then the SSM bit should be set and the SSI bit should be clear.

I suggest that you use a debugger to see what state the SPI registers are in after the Cube initialisation, just to make sure that the register values actually match the recommended configuration for SPI slave in the reference manual. 
konstantin2
Associate
Posted on May 28, 2016 at 12:18

Dear markt,

I changed my yesterday GPIO init (the pin sources had been wrong, added a NSS pin).

void

CTRLSPI_GPIO_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
/* changed pin sources to 13, 14, 15 instead of 1,2,3 */
GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_SPI2);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource14, GPIO_AF_SPI2);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_SPI2);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
GPIO_InitStructure.GPIO_Speed = GPIO_High_Speed;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* new: configure GPIOB Pin 12 as Input */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_Speed = GPIO_Fast_Speed;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}

Nonetheless it's still not working :( About the NSS: I don't understand how the SPI in all those example configurations you find on the internet can ''know'' about the NSS pin. In the SPI_InitStructure I just configure the SPI so that SPI knows there is a software configured pin somewhere used as a Slave Select. If I understand you right, in a productive environment there should be an External Interrupt enabling SPI and DMA, right? Regardless of this interrupt: If I just start the SPI and DMA without any interrupt (another possible error source)like in the code above they should nonetheless react to the toggling CLK and MISO pin and and some point call the DMA interrupt routine, which does not happen. As you suggested, I used a debugger to read the SPI2 control and status registers and compared them to the reference manual. SPI2->CR1: 0x0A40 SPI2->CR2: 0x0043 SPI2->SR: 0x0000 SPI2->DR: 0x0000 This means: In Control Register1 the bits DFF, SSM, SPIE and CHPA are set. Seems right, doesn't it? In Control Register2 the bits RXNEIE (Rx buffer not empty enable) and TX- and RXDMAEN are set. This seems right to me as well. That the Status and Data registers are completly unset. Any other suggestions? Thank you for your help!
megahercas6
Senior
Posted on May 28, 2016 at 16:07

I didn't have time to read your thread, but i will write major things what you need to know.

1) Configure DMA to copy data in and out from memory to SPI. You will have two arrays, one for Rx, one For Tx. Each time you get CLOCK on your SPI , DMA will transfer data to memory, and sends data to SPI. NOTE, DMA care only about clock pulses after it was enabled, if you enable DMA , and you miss first clock, all data will be shifted by one bit.

2) DMA does not CARE about chip select, all it is caring about CLOCK of SPI

3) If you configure SPI as master, you will send CLOCK ourself with data, and if you set SLAVE SPI, you must get CLOCK from master, in order for DMA to work

4) You must configure DMA and SPI for correct size of the word, half word, or byte. that means you can work with 8b ( array will be char rx[] ), or 16b, (array will be uint16_t Rx[])

I have video how can you make SPI communication with computer, it will apply for F0 too

https://www.youtube.com/watch?v=JArPHfvhkCQ

Again, chip select is only for synchronizing DMA pointer, because DMA must be enabled before any clock pulse from SPI Slave ( it does not matter for master )

megahercas6
Senior
Posted on May 28, 2016 at 16:10

#define SPIx SPI2
#define SPIx_CLK RCC_APB1Periph_SPI2
#define SPIx_CLK_INIT RCC_APB1PeriphClockCmd
#define SPIx_IRQn SPI2_IRQn
#define SPIx_IRQHANDLER SPI2_IRQHandler
#define SPIx_SCK_PIN GPIO_Pin_1
#define SPIx_SCK_GPIO_PORT GPIOI
#define SPIx_SCK_GPIO_CLK RCC_AHB1Periph_GPIOI
#define SPIx_SCK_SOURCE GPIO_PinSource1
#define SPIx_SCK_AF GPIO_AF_SPI2
#define SPIx_MISO_PIN GPIO_Pin_2
#define SPIx_MISO_GPIO_PORT GPIOI
#define SPIx_MISO_GPIO_CLK RCC_AHB1Periph_GPIOI
#define SPIx_MISO_SOURCE GPIO_PinSource2
#define SPIx_MISO_AF GPIO_AF_SPI2
#define SPIx_MOSI_PIN GPIO_Pin_3
#define SPIx_MOSI_GPIO_PORT GPIOI
#define SPIx_MOSI_GPIO_CLK RCC_AHB1Periph_GPIOI
#define SPIx_MOSI_SOURCE GPIO_PinSource3
#define SPIx_MOSI_AF GPIO_AF_SPI2
#define SPIx_DMA DMA1
#define SPIx_DMA_CLK RCC_AHB1Periph_DMA1
#define SPIx_TX_DMA_CHANNEL DMA_Channel_0
#define SPIx_TX_DMA_STREAM DMA1_Stream4
#define SPIx_TX_DMA_FLAG_TCIF DMA_FLAG_TCIF4
#define SPIx_RX_DMA_CHANNEL DMA_Channel_0
#define SPIx_RX_DMA_STREAM DMA1_Stream3
#define SPIx_RX_DMA_FLAG_TCIF DMA_FLAG_TCIF3
#define BUFFERSIZE 100
static void SPI_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
/* Peripheral Clock Enable -------------------------------------------------*/
/* Enable the SPI clock */
SPIx_CLK_INIT(SPIx_CLK, ENABLE);
/* Enable GPIO clocks */
RCC_AHB1PeriphClockCmd(SPIx_SCK_GPIO_CLK | SPIx_MISO_GPIO_CLK | SPIx_MOSI_GPIO_CLK, ENABLE);
/* Enable DMA clock */
RCC_AHB1PeriphClockCmd(SPIx_DMA_CLK, ENABLE);
/* SPI GPIO Configuration --------------------------------------------------*/
/* GPIO Deinitialisation */
GPIO_DeInit(SPIx_SCK_GPIO_PORT);
GPIO_DeInit(SPIx_MISO_GPIO_PORT);
GPIO_DeInit(SPIx_MOSI_GPIO_PORT);
/* Connect SPI pins to AF5 */ 
GPIO_PinAFConfig(SPIx_SCK_GPIO_PORT, SPIx_SCK_SOURCE, SPIx_SCK_AF);
GPIO_PinAFConfig(SPIx_MISO_GPIO_PORT, SPIx_MISO_SOURCE, SPIx_MISO_AF); 
GPIO_PinAFConfig(SPIx_MOSI_GPIO_PORT, SPIx_MOSI_SOURCE, SPIx_MOSI_AF);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
/* SPI SCK pin configuration */
GPIO_InitStructure.GPIO_Pin = SPIx_SCK_PIN;
GPIO_Init(SPIx_SCK_GPIO_PORT, &GPIO_InitStructure);
/* SPI MISO pin configuration */
GPIO_InitStructure.GPIO_Pin = SPIx_MISO_PIN;
GPIO_Init(SPIx_MISO_GPIO_PORT, &GPIO_InitStructure); 
/* SPI MOSI pin configuration */
GPIO_InitStructure.GPIO_Pin = SPIx_MOSI_PIN;
GPIO_Init(SPIx_MOSI_GPIO_PORT, &GPIO_InitStructure);
/* SPI configuration -------------------------------------------------------*/
SPI_I2S_DeInit(SPIx);
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
/* DMA configuration -------------------------------------------------------*/
/* Deinitialize DMA Streams */
DMA_DeInit(SPIx_TX_DMA_STREAM);
DMA_DeInit(SPIx_RX_DMA_STREAM);
/* Configure DMA Initialization Structure */
DMA_InitStructure.DMA_BufferSize = BUFFERSIZE ;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable ;
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull ;
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single ;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_PeripheralBaseAddr =(uint32_t) (&(SPIx->DR)) ;
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
/* Configure TX DMA */
DMA_InitStructure.DMA_Channel = SPIx_TX_DMA_CHANNEL ;
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral ;
DMA_InitStructure.DMA_Memory0BaseAddr =(uint32_t)aTxBuffer ;
DMA_Init(SPIx_TX_DMA_STREAM, &DMA_InitStructure);
/* Configure RX DMA */
DMA_InitStructure.DMA_Channel = SPIx_RX_DMA_CHANNEL ;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory ;
DMA_InitStructure.DMA_Memory0BaseAddr =(uint32_t)aRxBuffer ; 
DMA_Init(SPIx_RX_DMA_STREAM, &DMA_InitStructure);
SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
SPI_Init(SPIx, &SPI_InitStructure);
/* Enable DMA SPI TX Stream */
DMA_Cmd(SPIx_TX_DMA_STREAM,ENABLE);
/* Enable DMA SPI RX Stream */
DMA_Cmd(SPIx_RX_DMA_STREAM,ENABLE); 
/* Enable SPI DMA TX Requsts */
SPI_I2S_DMACmd(SPIx, SPI_I2S_DMAReq_Tx, ENABLE);
/* Enable SPI DMA RX Requsts */
SPI_I2S_DMACmd(SPIx, SPI_I2S_DMAReq_Rx, ENABLE);
/* Enable the SPI peripheral */
SPI_Cmd(SPIx, ENABLE);
}