cancel
Showing results for 
Search instead for 
Did you mean: 

SPI using DMA on STM32F103 transmits one word too much

Sven Pauli
Associate II

On an STM32F103 I configure SPI in 16bit wide mode and set up a DMA transfer to transmit one word:

SPI2->CR1 &= ~SPI_CR1_SPE;
SPI2->CR2 = 0;
SPI2->CR1 =
    SPI_CR1_SSM |
    SPI_CR1_SSI |
    SPI_CR1_DFF |
    SPI_CR1_SPE |
    /* Some baud rate flags */ |
    SPI_CR1_MSTR;
 
/* Set up DMA transfers */
DMA1_Channel4->CMAR = (uint32_t) /* some buffer */;
DMA1_Channel4->CPAR = (uint32_t) &SPI2->DR;
DMA1_Channel4->CNDTR = 1;
DMA1_Channel4->CCR = DMA_CCR4_MINC | DMA_CCR4_EN;
 
uint16_t dummy = 0xFFFF;
DMA1_Channel5->CMAR = (uint32_t) &dummy;
DMA1_Channel5->CPAR = (uint32_t) &SPI2->DR;
DMA1_Channel5->CNDTR = 1;
DMA1_Channel5->CCR = DMA_CCR5_DIR | DMA_CCR5_EN;
 
/* Transfer */
SPI2->CR1 |= SPI_CR1_SPE;
DMA1->IFCR = DMA_IFCR_CTCIF5 | DMA_IFCR_CTCIF4;
SPI2->CR2 |= SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN;
SPI2->CR1 |= SPI_CR1_SPE;

I connected a counter to the SPI's clock line and the above code produces 32 clocks. No interrupts are enabled or whatsoever; the behaviour can constantly be observed. If both the DMAs are set up to transmit zero words, no clocks are generated. When I set them up to transfer e.g., 200 words, 201*16 clocks are generated.

Why does it clock out 16 more bits?

1 ACCEPTED SOLUTION

Accepted Solutions
Sven Pauli
Associate II

Sh*t, sh*t, sh*t :face_screaming_in_fear:

I accidentally enabled the SPI's CRC somewhere. So the additional tranfer stems from the checksum...

Thanks a lot!

View solution in original post

8 REPLIES 8
S.Ma
Principal

try this:

  1. /* Transfer */
  2. //SPI2->CR1 |= SPI_CR1_SPE;
  3. DMA1->IFCR = DMA_IFCR_CTCIF5 | DMA_IFCR_CTCIF4;
  4. //SPI2->CR2 |= SPI_CR2_TXDMAEN | SPI_CR2_RXDMAEN;
  5. SPI2->CR1 |= SPI_CR1_SPE;
  6. SPI2->CR2 |= SPI_CR2_RXDMAEN;
  7. SPI2->CR2 |= SPI_CR2_TXDMAEN;

Sven Pauli
Associate II

Same result...

S.Ma
Principal

Here is a code that implements a SPI + DMA in an ISR state machine running on STM32F437 with SPL.

Maybe looking at the guts of the code you might compare and find clues to fix the behaviour.

#define FT1_SPI SPI4
 
// SPI4 RX is DMA2 Stream0 channel 4
#define DMA_RX_Channel DMA_Channel_4
#define DMA_RX_Stream DMA2_Stream0
#define DMA_RX_Flags DMA_FLAG_TCIF0 | DMA_FLAG_HTIF0 | DMA_FLAG_TEIF0 | DMA_FLAG_DMEIF0 | DMA_FLAG_FEIF0
#define DMA_RX_TC_Flag DMA_FLAG_TCIF0
  
// SPI1 TX is DMA2 Stream1 channel 4
#define DMA_TX_Channel DMA_Channel_4
#define DMA_TX_Stream DMA2_Stream1
#define DMA_TX_Flags DMA_FLAG_TCIF1 | DMA_FLAG_HTIF1 | DMA_FLAG_TEIF1 | DMA_FLAG_DMEIF1 | DMA_FLAG_FEIF1  
#define DMA_TX_TC_Flag DMA_FLAG_TCIF1
 
#define SPI_DR_BASE_ADR (uint32_t) (&(SPI4->DR)) //0x4001300C
 
 DMA_InitTypeDef FT1_DMA_TX_InitStruct,FT1_DMA_RX_InitStruct;
 
void FT1_InitHW(void) {
  EXTI_InitTypeDef EXTI_InitStructure;
  NVIC_InitTypeDef NVIC_InitStructure;  
  SPI_InitTypeDef  SPI_InitStructure;
 
    // CLOCK TREE ENABLE
//  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI4, ENABLE);
 
  FTM_CS1_SET_HIGH;
 
  SPI_I2S_DeInit(FT1_SPI);
  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//SPI_Direction_2Lines_FullDuplex;SPI_Direction_2Lines_RxOnly
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; // Fingertip CPOL = 1
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; // Fingertip CPHA = 0
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;//SPI_NSS_Hard SPI_NSS_Soft
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;//4;//16;
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
  SPI_InitStructure.SPI_CRCPolynomial = 7;
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
  SPI_Init(FT1_SPI, &SPI_InitStructure);
 
// now we have also to prepare the DMA2 stream   
  /* Initialize the DMA_Channel member */
  FT1_DMA_TX_InitStruct.DMA_Channel = DMA_TX_Channel;
  FT1_DMA_RX_InitStruct.DMA_Channel = DMA_RX_Channel;  
  FT1_DMA_TX_InitStruct.DMA_PeripheralBaseAddr = SPI_DR_BASE_ADR;
  FT1_DMA_RX_InitStruct.DMA_PeripheralBaseAddr = SPI_DR_BASE_ADR;
  FT1_DMA_TX_InitStruct.DMA_Memory0BaseAddr = 0;
  FT1_DMA_RX_InitStruct.DMA_Memory0BaseAddr = 0;
  FT1_DMA_TX_InitStruct.DMA_DIR = DMA_DIR_MemoryToPeripheral;
  FT1_DMA_RX_InitStruct.DMA_DIR = DMA_DIR_PeripheralToMemory;
  FT1_DMA_TX_InitStruct.DMA_BufferSize = 0;
  FT1_DMA_RX_InitStruct.DMA_BufferSize = 0;
  FT1_DMA_TX_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  FT1_DMA_RX_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  FT1_DMA_TX_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
  FT1_DMA_RX_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable;
  FT1_DMA_TX_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  FT1_DMA_RX_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
  FT1_DMA_TX_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
  FT1_DMA_RX_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
  FT1_DMA_TX_InitStruct.DMA_Mode = DMA_Mode_Normal;
  FT1_DMA_RX_InitStruct.DMA_Mode = DMA_Mode_Normal;
  FT1_DMA_TX_InitStruct.DMA_Priority = DMA_Priority_High;
  FT1_DMA_RX_InitStruct.DMA_Priority = DMA_Priority_High;
  FT1_DMA_TX_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
  FT1_DMA_RX_InitStruct.DMA_FIFOMode = DMA_FIFOMode_Disable;
  FT1_DMA_TX_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
  FT1_DMA_RX_InitStruct.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;
  FT1_DMA_TX_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  FT1_DMA_RX_InitStruct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
  FT1_DMA_TX_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  FT1_DMA_RX_InitStruct.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;
  DMA_Init(DMA_RX_Stream, &FT1_DMA_RX_InitStruct);
  DMA_Init(DMA_TX_Stream, &FT1_DMA_TX_InitStruct);
 
  // interrupt configuration, only for TX side.... RX is driven by TX DMA which controls the clock pulses generation
  
  // interrupt configuration, only for RX side.... RX is driven by TX DMA which controls the clock pulses generation
  NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream0_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = FTM_INT_PREEMPT;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = FTM_INT_SUBGROUP;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
  
  FT1_IntDisable();    
}
 
void FT1_INT_Event(void) {
  FT1_IntDisable();
  FT1_ExecutePRogramLine(); // continue the sequencer
}
 
FTM_instruction_typedef* FT1_pInstruction;
u16 FT1_ProgramLineCounter;
volatile u8 FT1_Running;
 
void FT1_DMA_TX_InterruptEnable(void) {
  DMA_ClearFlag(DMA_TX_Stream, DMA_TX_Flags); // clear all flags
  DMA_ITConfig(DMA_TX_Stream, DMA_IT_TC, ENABLE); // enable transfer complete interrupt  
}
 
void FT1_DMA_TX_InterruptDisable(void) {
  DMA_ClearFlag(DMA_TX_Stream, DMA_TX_Flags); // clear all flags
  DMA_ITConfig(DMA_TX_Stream, DMA_IT_TC, DISABLE); // enable transfer complete interrupt  
}
 
void FT1_DMA_RX_InterruptEnable(void) {
  DMA_ClearFlag(DMA_RX_Stream, DMA_RX_Flags); // clear all flags
  DMA_ITConfig(DMA_RX_Stream, DMA_IT_TC, ENABLE); // enable transfer complete interrupt  
}
 
void FT1_DMA_RX_InterruptDisable(void) {
  DMA_ClearFlag(DMA_RX_Stream, DMA_RX_Flags); // clear all flags
  DMA_ITConfig(DMA_RX_Stream, DMA_IT_TC, DISABLE); // enable transfer complete interrupt  
}
 
void FT1_RunProgram(FTM_instruction_typedef* program) {
   FT1_pInstruction = program;
  FT1_Running = TRUE;
  FT1_WaitingOthers = FALSE;
  FT1_ProgramLineCounter = 0;
  FT1_ProgramLineBreakpoint = 0;
  // DISABLE ALL INTERRUPTS
  FT1_ExecutePRogramLine();
  // ENABLE ALL INTERRUPTS
}
 
u8 FT1_dummy = 0xA5;
#define TX_MODE 1
#define RX_MODE 2
// this function is called by main "start program" or any interrupt event...
void FT1_ExecutePRogramLine(void) {
  u16 nbofbytes;
  while(FT1_Running) {
    switch(FT1_pInstruction[FT1_ProgramLineCounter].operation) {
    case FT_START:
      FTM_CS1_SET_LOW;
      SPI_Cmd(FT1_SPI, ENABLE); // this should trigger the interrupt for sending the first byte... enable the spi adter the interrupt enable
      FT1_ProgramLineCounter++;
      break;
      
    case FT_STOP:
      FTM_CS1_SET_HIGH;
      FT1_DMA_RX_InterruptDisable();
	  FT1_wait_us(2);
      //SPI_Cmd(SPI1, DISABLE); // this should trigger the interrupt for sending the first byte... enable the spi adter the interrupt enable
      FT1_ProgramLineCounter++;
      break;
 
    case FT_SEND:
      FT1_SPI_Enable_MOSI();
      // add start for RAM buffer based packet using TxDummy sized FTM_TX_nbofbytes 
      nbofbytes = FT1_pInstruction[FT1_ProgramLineCounter].memory_size;      
      if(nbofbytes==0) //if nbofbytes is zero, use a SRAM variable instead which has to be defined first
        nbofbytes = FTM_TX_nbofbytes;
      if(nbofbytes==0) while(1); // error detected      
      
      FT1_DMA_RX_Set(0,nbofbytes); // this counts effectively completed transaction - byte fully transmitted!
      FT1_DMA_RX_InterruptEnable();
      FT1_DMA_TX_Set(FT1_pInstruction[FT1_ProgramLineCounter].memory_adr,nbofbytes);
      FT1_ProgramLineCounter++;
      return; // will come back by interrupt
      break;
      
    case FT_READ:
      FT1_SPI_Disable_MOSI();// for slave which does not have a dummy byte
      //FT1_DMA_TX_InterruptDisable();
      //--- add start for RAM buffer based packet using FTx_RxBuf sized FTM_RX_nbofbytes 
      nbofbytes = FT1_pInstruction[FT1_ProgramLineCounter].memory_size;
      if(nbofbytes==0) //if nbofbytes is zero, use a SRAM variable instead which has to be defined first
        nbofbytes = FTM_RX_nbofbytes;
      if(nbofbytes==0) while(1); // error detected            
       FT1_DMA_RX_Set(FT1_pInstruction[FT1_ProgramLineCounter].memory_adr,nbofbytes);
      FT1_DMA_RX_InterruptEnable();
      FT1_DMA_TX_Set(0,nbofbytes); // this is to generate the SPI clocks to get the data in...
      FT1_ProgramLineCounter++;
      return; // will come back by interrupt
      break;
      
    case FT_END_PROGRAM:
...
    break;
      
     }
  }
}
 
// the base strategy is to send SPI data by interrupt, byte by byte
// then the reception will later use the DMA Stream (exclude the dummy byte)
 
// we have to disable then enable the stream to extend the DMA
void FT1_DMA_TX_Set(u32 blockstart, u16 nbofbytes) {
// first we have to clear all flags
  DMA_ClearFlag(DMA_TX_Stream, DMA_TX_Flags);
  DMA_Cmd(DMA_TX_Stream, DISABLE);
  while(DMA_GetCmdStatus(DMA_TX_Stream)==ENABLE) __NOP(); // wait for the DMA to be completely disabled
  FT1_DMA_TX_InitStruct.DMA_BufferSize = nbofbytes;
  FT1_DMA_TX_InitStruct.DMA_Memory0BaseAddr = blockstart;
  DMA_Init(DMA_TX_Stream, &FT1_DMA_TX_InitStruct);
  DMA_Cmd(DMA_TX_Stream, ENABLE);
  SPI_I2S_DMACmd(FT1_SPI, SPI_I2S_DMAReq_Tx, ENABLE);
}
 
void FT1_DMA_RX_Set(u32 blockstart, u16 nbofbytes) {
// first we have to clear all flags
//  if(DMA_GetFlagStatus(DMA_TX_Stream, DMA_RX_Flags)!=DMA_TX_TC_Flag)
//    while(1) __NOP();
  DMA_ClearFlag(DMA_RX_Stream, DMA_RX_Flags);
  DMA_Cmd(DMA_RX_Stream, DISABLE);
  while(DMA_GetCmdStatus(DMA_RX_Stream)==ENABLE) __NOP(); // wait for the DMA to be completely disabled
  SPI_I2S_ReceiveData(SPI1);  // in case clear any SPI RX flag before DMA RX kicks in!
  FT1_DMA_RX_InitStruct.DMA_BufferSize = nbofbytes;
  FT1_DMA_RX_InitStruct.DMA_Memory0BaseAddr = blockstart;
  DMA_Init(DMA_RX_Stream, &FT1_DMA_RX_InitStruct);
  DMA_Cmd(DMA_RX_Stream, ENABLE);
 
  SPI_I2S_DMACmd(FT1_SPI, SPI_I2S_DMAReq_Rx, ENABLE);
}
 
// interrupt event (DMA TX completed)
void DMA2_Stream1_Event(void) {
  // flags and interrupt management will be done inside...
    FT1_ExecutePRogramLine();
}
 
// interrupt event (DMA RX completed)
void DMA2_Stream0_Event(void) {
  // flags and interrupt management will be done inside...
    FT1_ExecutePRogramLine();
}

S.Ma
Principal

Due to post size limitation, the source code had to be cropped to keep only the related SPI / DMA code. This was working fine as interrupt based state machine. all the SPI transactions running in the background, ticked by DMA interrupts.

Bob S
Principal

Are you intentionally setting the SPI_CR1_SPE bit 2 times?

Don't know which CPU you are using. But looking at the L433 reference manual and the HAL SPI source, it looks like:

(1) You should only set the SPI_CR1_SPE bit one time, and that is AFTER you configure all the DMA stuff (in the L433 ref manual DM0394, page 1371 in section "Communication using DMA").

(2) The STM32L4xx HAL library does not follow the reference manual. HAL_SPI_TransmitReceive_DMA() does the following (skipping some lines of code):

  • set SPI_CR2_RXDMAEN
  • call HAL_DMA_Start_IT() using the transmit count
  • enable SPI (set SPI_CR1_SPE if not already enabled)
  • set SPI_CR2_TXDMAEN

So, as totally untested suggestions, from your original code try first removing the first line that sets SPI_CR1_SPE. If that doesn't fix it, then try ALSO only setting the RXDMAEN, then set SPI_CR1_SPE and then set TXDMAEN (so your code matches the HAL library code).

Bob,

the CPU is 'F103, it's in the title and in the first sentence of OP... 😉

> Are you intentionally setting the SPI_CR1_SPE bit 2 times?

3 times.

I'd recommend to try not to set it in the first line, together with other bits in SPI_CR1.

JW

Sven Pauli
Associate II

Sh*t, sh*t, sh*t :face_screaming_in_fear:

I accidentally enabled the SPI's CRC somewhere. So the additional tranfer stems from the checksum...

Thanks a lot!

Thanks for coming back with the solution.

Can you please set your post as "best" so that the thread is marked as answered?

JW