Skip to main content
Explorer
July 7, 2025
Question

STM32H7 SPI slave mode with DMA - transfer issues above 18MHz clock

  • July 7, 2025
  • 4 replies
  • 1086 views

Hello,

I am using STM32H7 as SPI slave with DMA transfer, the communication works fine till 18MHz of clock frequency from the master side however for higher clock speeds of 20 MHz - 31MHz it is noted that the bit shifts by 1 bit for the whole stream of data.

e.g if STM is sending 00100111 00010010 01101101 00000000 then in the master side the received bits are 00010011 10001001 00110110 10000000.

The LSB of first byte shifts to MSB of 2nd byte thus corrupting the whole data. The target is to achieve maximum speed of 31 Mhz. Below I have provided my STM setup code for reference, moreover software NSS is used:

 

 
void SPI_Slave_Init(void)

{

/* SPI5 disable */

LL_SPI_Disable(SPI5);



/* USER CODE BEGIN SPI5_Re_Init 0 */



/* USER CODE END SPI5_Re_Init 0 */



LL_SPI_InitTypeDef SPI_InitStruct = {0};



LL_GPIO_InitTypeDef GPIO_InitStruct = {0};



/* Peripheral clock enable */

LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_SPI5);



LL_AHB4_GRP1_EnableClock(LL_AHB4_GRP1_PERIPH_GPIOD);

LL_AHB4_GRP1_EnableClock(LL_AHB4_GRP1_PERIPH_GPIOF);

LL_AHB4_GRP1_EnableClock(LL_AHB4_GRP1_PERIPH_GPIOG);



LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA2);



GPIO_InitStruct.Pin = SPI_SLAVE_TC_PIN;

GPIO_InitStruct.Mode = LL_GPIO_MODE_INPUT;

GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;

LL_GPIO_Init(SPI_SLAVE_TC_GPIO_PORT, &GPIO_InitStruct);



GPIO_InitStruct.Pin = SPI_SLAVE_CLK_PIN | SPI_SLAVE_MISO_PIN | SPI_SLAVE_MOSI_PIN;

GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;

GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;

GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;

GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;

GPIO_InitStruct.Alternate = LL_GPIO_AF_5;

LL_GPIO_Init(SPI_SLAVE_GPIO_PORT, &GPIO_InitStruct);



GPIO_InitStruct.Pin = SPI_SLAVE_DRDY_PIN;

GPIO_InitStruct.Mode = LL_GPIO_MODE_OUTPUT;

GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;

GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;

GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;

LL_GPIO_Init(SPI_SLAVE_DRDY_GPIO_PORT, &GPIO_InitStruct);



LL_GPIO_SetOutputPin(SPI_SLAVE_DRDY_GPIO_PORT, SPI_SLAVE_DRDY_PIN); // Initially high, goes low when data is ready

/* SPI5 DMA Init */





/* SPI5_RX Init */



LL_DMA_SetPeriphRequest(DMA2, LL_DMA_STREAM_4, LL_DMAMUX1_REQ_SPI5_RX);



LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_STREAM_4, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);



LL_DMA_SetStreamPriorityLevel(DMA2, LL_DMA_STREAM_4, LL_DMA_PRIORITY_VERYHIGH);



LL_DMA_SetMode(DMA2, LL_DMA_STREAM_4, LL_DMA_MODE_NORMAL);



LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_STREAM_4, LL_DMA_PERIPH_NOINCREMENT);



LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_STREAM_4, LL_DMA_MEMORY_INCREMENT);



LL_DMA_SetPeriphSize(DMA2, LL_DMA_STREAM_4, LL_DMA_PDATAALIGN_BYTE);



LL_DMA_SetMemorySize(DMA2, LL_DMA_STREAM_4, LL_DMA_MDATAALIGN_BYTE);



LL_DMA_DisableFifoMode(DMA2, LL_DMA_STREAM_4);



/* DMA2_Stream3_IRQn interrupt configuration */

NVIC_SetPriority(DMA2_Stream4_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),1, 0));

NVIC_EnableIRQ(DMA2_Stream4_IRQn);





/* SPI5_TX Init */



LL_DMA_SetPeriphRequest(DMA2, LL_DMA_STREAM_5, LL_DMAMUX1_REQ_SPI5_TX);



LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_STREAM_5, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);



LL_DMA_SetStreamPriorityLevel(DMA2, LL_DMA_STREAM_5, LL_DMA_PRIORITY_VERYHIGH);



LL_DMA_SetMode(DMA2, LL_DMA_STREAM_5, LL_DMA_MODE_NORMAL);



LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_STREAM_5, LL_DMA_PERIPH_NOINCREMENT);



LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_STREAM_5, LL_DMA_MEMORY_INCREMENT);



LL_DMA_SetPeriphSize(DMA2, LL_DMA_STREAM_5, LL_DMA_PDATAALIGN_BYTE);



LL_DMA_SetMemorySize(DMA2, LL_DMA_STREAM_5, LL_DMA_MDATAALIGN_BYTE);



LL_DMA_DisableFifoMode(DMA2, LL_DMA_STREAM_5);



/* DMA2_Stream3_IRQn interrupt configuration */

NVIC_SetPriority(DMA2_Stream5_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),1, 0));

NVIC_EnableIRQ(DMA2_Stream5_IRQn);



/* SPI5 interrupt Init */

NVIC_SetPriority(SPI5_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),1, 0));

NVIC_EnableIRQ(SPI5_IRQn);



/* USER CODE END SPI5_Re_Init 1 */



/* SPI5 parameter configuration*/

SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;

SPI_InitStruct.Mode = LL_SPI_MODE_SLAVE;

SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_8BIT;

SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_LOW; //CPOL=0

SPI_InitStruct.ClockPhase = LL_SPI_PHASE_2EDGE; // CPHA = 1

SPI_InitStruct.NSS = LL_SPI_NSS_SOFT; 

SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST;

SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;

SPI_InitStruct.CRCPoly = 0x1021;

LL_SPI_SetFIFOThreshold(SPI5, LL_SPI_FIFO_TH_04DATA);

LL_SPI_Init(SPI5, &SPI_InitStruct);



LL_SPI_SetStandard(SPI_SLAVE_INSTANCE, LL_SPI_PROTOCOL_MOTOROLA);

}


void SPI5_slave_core_Init(void){

Tx_prepared = false;



// Reinitialize SPI5 (including GPIO setup)

SPI_Slave_Init();



// Disable TX DMA stream and wait for it to stop

xdma_disable_stream(SPI_5_slave.xdma_tx);

while (xdma_check_stream_is_enabled(SPI_5_slave.xdma_tx));



// Disable RX DMA stream and wait for it to stop

xdma_disable_stream(SPI_5_slave.xdma_rx);

while (xdma_check_stream_is_enabled(SPI_5_slave.xdma_rx));



// Setup DMA peripheral and memory addresses

// TX: peripheral address = SPI5 TXDR, memory address = next dataset to send

xdma_set_peripheral_addr(SPI_5_slave.xdma_tx, (uint32_t) &(SPI_5_slave.spi->TXDR));



xdma_set_memory_addr(SPI_5_slave.xdma_tx, (uint32_t)&Raw_Buffer.raw_buffer[Raw_Buffer.raw_buffer_idx_tail]);



// RX: peripheral address = SPI5 RXDR, memory address = RX buffer

xdma_set_peripheral_addr(SPI_5_slave.xdma_rx, (uint32_t) &(SPI_5_slave.spi->RXDR));

xdma_set_memory_addr(SPI_5_slave.xdma_rx, (uint32_t)spi5_rx_dummy_buffer);





const uint32_t data_length_bytes = DATASTORAGE_RAW_BUFFER_LENGTH *sizeof(Raw_Dataset);

xdma_set_data_length(SPI_5_slave.xdma_tx, data_length_bytes);

xdma_set_data_length(SPI_5_slave.xdma_rx, data_length_bytes);



// Clear Flags

SPI_SLAVE_DMA_TX_CLEAR_TC_FLAG;

SPI_SLAVE_DMA_RX_CLEAR_TC_FLAG;

LL_SPI_ClearFlag_EOT(SPI5);





// Enable interrupts for TX DMA (transfer complete and error)

xdma_enable_IT_TC(SPI_5_slave.xdma_tx);

xdma_enable_IT_TE(SPI_5_slave.xdma_tx);



// Enable interrupts for RX DMA (transfer complete and error)

xdma_enable_IT_TC(SPI_5_slave.xdma_rx);

xdma_enable_IT_TE(SPI_5_slave.xdma_rx);



// Set SPI transfer size in number of 32-bit words

LL_SPI_SetTransferSize(SPI_5_slave.spi, data_length_bytes);



// Enable relevant SPI error/status interrupts for monitoring and debugging

LL_SPI_EnableIT_OVR(SPI_5_slave.spi);

LL_SPI_EnableIT_MODF(SPI_5_slave.spi);

LL_SPI_EnableIT_FRE(SPI_5_slave.spi);

LL_SPI_EnableIT_EOT(SPI_5_slave.spi);



//Set NSS low

// CLEAR_BIT(SPI5->CR1, SPI_CR1_SSI);



// Enable DMA streams after configuring everything

xdma_enable_stream(SPI_5_slave.xdma_rx);

xdma_enable_stream(SPI_5_slave.xdma_tx);





// Enable DMA requests on SPI peripheral

SPI_ENABLE_DMA_RX(SPI_5_slave.spi);

SPI_ENABLE_DMA_TX(SPI_5_slave.spi);



//Set NSS low

CLEAR_BIT(SPI5->CR1, SPI_CR1_SSI);



// Enable the SPI peripheral (starts operation)

ENABLE_SPI(SPI_5_slave.spi);



// Activate DRDY only if data available

if(Raw_Buffer.raw_buffer_idx_head !=

Raw_Buffer.raw_buffer_idx_tail) {

LL_GPIO_ResetOutputPin(SPI_SLAVE_DRDY_GPIO_PORT, SPI_SLAVE_DRDY_PIN);

}



Tx_prepared = true;

}




Edited to apply source code formatting - please see How to insert source code for future reference.

    This topic has been closed for replies.

    4 replies

    Explorer II
    July 7, 2025

    How are the two MCUs connected? If you're using wires, try shorter ones.

    I've seen bit shifting/smearing as low as 8MHz over 6" wires.

    Amit_RSAuthor
    Explorer
    July 7, 2025

    Thanks for your feedback. But at the moment the wires are short. They are 10 cm or 3.9''

    Super User
    July 8, 2025

    >  20 MHz - 31MHz

    > They are 10 cm or 3.9''

    These are not short wires. I would expect data to start dropping out around this frequency. Shorter wires, better shielding, less EF noise, will all help. Termination resistors can help. Possibly, a lower pin frequency can help but check datasheet to see if 31 MHz can still be sent. Differential signals will solve noise issues but impose design requirements.

     

    Since only one bit is shifting, consistently, it's probably cross-talk, possibly from the CS line, getting coupled onto the SCK line.

     

    This is a hardware issue, not a software setup issue.

    Amit_RSAuthor
    Explorer
    July 8, 2025

    Thanks for your reply. As I am using software NSS (CS) for STM and there is no physical CS line- I would still like to know if that could also cause a cross-talk?

    Super User
    July 8, 2025

    If CS signal isn't present, it can't cause cross-talk.

    Super User
    July 9, 2025

    Can you show a scope trace of the first few bits of the transaction on SCK/MOSI, where the bit shift occurs?

    Amit_RSAuthor
    Explorer
    July 10, 2025

    At 25 MHz: Green is MISO and blue is SCKAt 25 MHz: Green is MISO and blue is SCK

     

    At 18 MHz: Green is MISO and blue is SCKAt 18 MHz: Green is MISO and blue is SCK

    At 25 MHz I see bit shift while for 18 MHz the data is received correct. In the image: green signal is MISO and blue is SCK

    Super User
    July 11, 2025

    Lots of overshoot/ringing on blue but none on green. Signals are also not synchronized. The rising edge of blue should line up with edges in green, but it's not. Green is lagging, and lags more with higher clocks. Don't really know why, but that's likely part of the issue. These are faults of the master SPI.

    Amit_RSAuthor
    Explorer
    July 11, 2025

    The rising edge of blue signal(SCK) is not lining up with green rising edge (MISO) as I am operating in SPI mode 01 where the data is sampled at the falling edge of the clock or blue signal. For 18 MHz it samples 15.2 ns prior to falling edge of clock while for 25MHz the timing reduces to 7.8ns. Hope this helps to understand the scope readings better. 

    Super User
    July 11, 2025

    I am aware it’s in SPI mode 1. Things should still be aligned as stated.