AnsweredAssumed Answered

SPI slave with DMA

Question asked by antuna.juan on May 6, 2013
Latest reply on May 24, 2013 by belei.horatiu

I am trying to use a STM32F103C8T6 MCU as a slave in a SPI bus, where the master is another ARM MCU. The bidirectional full-duplex transmission frame will consist out of 74 16-bit words at 15Mbps, so for that the master will keep NSS (slave select) line at low level during the whole frame transmission. Other than that, the CLK, MISO and MOSI are connected back to back between both MCU's.
I need to remap the SPI bus on the slave side, so it is placed on P.A15, P.B3,P.B4 and P.B5.

My idea is to set interrupts on the transition high-to-low of NSS (when the slave is selected).In the interrupt routine, I enable the DMA transfers for both reception and transmission, and also enable an interrupt for the full trasnfer complete in transmission. In that second routine I make sure both transmissions are over, and then disable the DMA transfers until the slave is selected again.

My initialization code is the following:

void InitializeSPI()
 //declaration of variables
 SPI_InitTypeDef SPI_InitStructure;
 DMA_InitTypeDef DMA_SPITx;
 DMA_InitTypeDef DMA_SPIRx;
 GPIO_InitTypeDef PortA;
 GPIO_InitTypeDef PortB;
 NVIC_InitTypeDef InterruptSPI_NSS;
 NVIC_InitTypeDef InterruptSPI_TCIF;
 EXTI_InitTypeDef EXTI_InitStructure;
 //enable the clocks
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO | RCC_APB2Periph_SPI1, ENABLE);
 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

 //Configure the pins of the SPI
 PortA.GPIO_Pin = GPIO_Pin_15;      //NSS
 PortA.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &PortA);
 PortB.GPIO_Pin = GPIO_Pin_3;      //CLK
 PortB.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOB, &PortB);
 PortA.GPIO_Pin = GPIO_Pin_4;      //MISO
  PortA.GPIO_Mode = GPIO_Mode_AF_PP;
 PortA.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOB, &PortB);
 PortA.GPIO_Pin = GPIO_Pin_5;      //MOSI
 PortA.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOB, &PortB); 
 //remap the SPI1 pins
 GPIO_PinRemapConfig(GPIO_Remap_SPI1, ENABLE);
 //configure the interrupts
 //Connect EXTI Line to NSS pin
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource15);

  // Configure NSS EXTI line
  EXTI_InitStructure.EXTI_Line = EXTI_Line15;
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; 
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;

  // Enable and set NSS EXTI Interrupt to the highest priority
  InterruptSPI_NSS.NVIC_IRQChannel = EXTI15_10_IRQn;
  InterruptSPI_NSS.NVIC_IRQChannelPreemptionPriority = 0;
  InterruptSPI_NSS.NVIC_IRQChannelSubPriority = 0;
  InterruptSPI_NSS.NVIC_IRQChannelCmd = ENABLE;
 //Enable and set Transfer Complete for DMA in tx to the highest priority
 InterruptSPI_TCIF.NVIC_IRQChannel = DMA1_Channel3_IRQn;
  InterruptSPI_TCIF.NVIC_IRQChannelPreemptionPriority = 0;
  InterruptSPI_TCIF.NVIC_IRQChannelSubPriority = 0;
  InterruptSPI_TCIF.NVIC_IRQChannelCmd = ENABLE;

 //configure the DMA channels
 //Reinitialize DMA to default values
 //Set the fields of the DMA initialization struct for rx
 DMA_SPIRx.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
 DMA_SPIRx.DMA_MemoryBaseAddr = (uint32_t)&(SPIRxFrame[0]);
 DMA_SPIRx.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
 DMA_SPIRx.DMA_MemoryInc = DMA_MemoryInc_Enable;
 DMA_SPIRx.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
 DMA_SPIRx.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
 DMA_SPIRx.DMA_Mode = DMA_Mode_Circular;
 DMA_SPIRx.DMA_Priority = DMA_Priority_VeryHigh;
 DMA_SPIRx.DMA_M2M = DMA_M2M_Disable;
 //Initialize the DMA for rx
 DMA_Init(DMA1_Channel2, &DMA_SPIRx); 
 DMA_Cmd(DMA1_Channel2, ENABLE);
 //Set the fields of the DMA initialization struct for tx
 DMA_SPITx.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
 DMA_SPITx.DMA_MemoryBaseAddr = (uint32_t)&(SPITxFrame[0]);
 DMA_SPITx.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
 DMA_SPITx.DMA_MemoryInc = DMA_MemoryInc_Enable;
 DMA_SPITx.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
 DMA_SPITx.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
 DMA_SPITx.DMA_Mode = DMA_Mode_Circular;
 DMA_SPITx.DMA_Priority = DMA_Priority_VeryHigh;
 DMA_SPITx.DMA_M2M = DMA_M2M_Disable; 
 //Initialize the DMA for tx
 DMA_Init(DMA1_Channel3, &DMA_SPITx);
 DMA_Cmd(DMA1_Channel3, ENABLE); 
 //Initialize the SPI
 //According to errata, also disable the I2C1 clock (all the module is disabled)
 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
 SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
 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_Hard; 
 SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
 SPI_InitStructure.SPI_CRCPolynomial = 0x1; 
 SPI_Init(SPI1, &SPI_InitStructure);
 // SPI1 enable

And the interrupt routines:

//Interrupt Service Routine for SPI1_NSS: when the master selects the slave through NSS
void EXTI15_10_IRQHandler(void)
  if(EXTI_GetITStatus(EXTI_Line15) != RESET)
  //The DMA transfers both for rx and tx are enabled here
  //Configure the tx DMA to interrupt when the transfer is complete
  DMA_ITConfig(DMA1_Channel3, DMA_IT_TC, ENABLE);
  //Clear the interrupt flag

//Interrupt Service Routine for DMA1_channel3: when the slave finishes transmitting the data to the master
void DMA1_Channel3_IRQHandler(void)
  if(DMA_GetFlagStatus(DMA1_FLAG_TC3) != RESET)
  //Clear the interrupt flag
  while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);  //wait until TXE=1
  while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) != RESET);  //wait until BSY=0
  while(DMA_GetFlagStatus(DMA1_FLAG_TC2) == RESET); //also wait until the DMA transfer for reception is finished
  //Disable the interrupt when the transfer is complete
  DMA_ITConfig(DMA1_Channel3, DMA_IT_TC, DISABLE);
  //Disable the DMA transfers

In the main loop, I call the initialization code and then wait in a loop doing some other tasks (like ADC conversions). I am also using DMA (with low priority) for the ADC, and that seems to work fine. I am also getting slightly confused with the difference between "enabling the SPI DMA transfer" and "enabling the DMA channel". They sound similar, but the standard library functions (SPI_I2S_DMACmd() and DMA_Cmd()) write on different registers.

The interrupts based on the NSS pin going low seems to work fine, but after that, in the debugger, the TCIF flag is set almost immediately (even before sending data from the master) and the program remains in the loop of the second interrupt source waiting for TXE to be set (data buffer free), so it seems the buffer remains occupied forever.

Any help will be very appreciated. Perhaps there is also some problem with the initialization order for remapping pins, alternate functions,... Thank you very much,