2026-04-06 3:31 AM
Greetings!
I am trying to establish a connection between STM32F439IIH6 (Slave) and a customize board (Master) through SPI DMA application. The customize board has its own built-in driver with SPI application. The communication between STM32F4 and customize board eventually broke down after few thousands of cycles. Through some investigation, I found out that the SPI_WaitOnFlagUntilTimeout() hangs inside SPI_DMATransmitReceiveCplt(). It sutcks inside the else statement of SPI_WaitOnFlagUntilTimeout(). The clock used in customize board (Master) is 80Mhz with clock division 4
/**
* @brief This function handles SPI Communication Timeout.
* @PAram hspi: pointer to a SPI_HandleTypeDef structure that contains
* the configuration information for SPI module.
* @retval HAL status
*/
static HAL_StatusTypeDef SPI_WaitOnFlagUntilTimeout(SPI_HandleTypeDef *hspi, uint32_t Flag, FlagStatus Status, uint32_t Timeout)
{
uint32_t tickstart = 0;
/* Get tick */
tickstart = HAL_GetTick();
/* Wait until flag is set */
if(Status == RESET)
{
while(__HAL_SPI_GET_FLAG(hspi, Flag) == RESET)
{
if(Timeout != HAL_MAX_DELAY)
{
if((Timeout == 0)||((HAL_GetTick() - tickstart ) > Timeout))
{
/* Disable the SPI and reset the CRC: the CRC value should be cleared
on both master and slave sides in order to resynchronize the master
and slave for their respective CRC calculation */
/* Disable TXE, RXNE and ERR interrupts for the interrupt process */
__HAL_SPI_DISABLE_IT(hspi, (SPI_IT_TXE | SPI_IT_RXNE | SPI_IT_ERR));
/* Disable SPI peripheral */
__HAL_SPI_DISABLE(hspi);
/* Reset CRC Calculation */
if(hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLED)
{
__HAL_SPI_RESET_CRC(hspi);
}
hspi->State= HAL_SPI_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(hspi);
return HAL_TIMEOUT;
}
}
}
}
else
{
while(__HAL_SPI_GET_FLAG(hspi, Flag) != RESET)
{
if(Timeout != HAL_MAX_DELAY)
{
if((Timeout == 0)||((HAL_GetTick() - tickstart ) > Timeout))
{
/* Disable the SPI and reset the CRC: the CRC value should be cleared
on both master and slave sides in order to resynchronize the master
and slave for their respective CRC calculation */
/* Disable TXE, RXNE and ERR interrupts for the interrupt process */
__HAL_SPI_DISABLE_IT(hspi, (SPI_IT_TXE | SPI_IT_RXNE | SPI_IT_ERR));
/* Disable SPI peripheral */
__HAL_SPI_DISABLE(hspi);
/* Reset CRC Calculation */
if(hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLED)
{
__HAL_SPI_RESET_CRC(hspi);
}
hspi->State= HAL_SPI_STATE_READY;
/* Process Unlocked */
__HAL_UNLOCK(hspi);
return HAL_TIMEOUT;
}
}
}
}
return HAL_OK;
}I tried send a dummy message to the slave and it 'reset' the flag and breaks the loop. The message received from previous cycle still remain intact. Is it possible that the timing mismatch between master and slave the main cause?
Below is simplified code segment from STM32F4 (Slave)
STM32F439IIH6(Slave)
#define BUFFER_SIZE 37 //37 bytes messages
void Main()
{
SPI_DMA_Init(); //Initialize SPI_DMA application
//Start receiving and transmiting message
While(1)
{
Toggle_GPIO_Pin(); //an extra gpio pin to notify master that slave ready for next cycle
While(!HAL_READ_GPIO(SPI2_NSS_PORT, SPI2_NSS_PIN, GPIO_PIN_RESET) //waiting NSS pin drop)
if(HAL_SPI_TransmitReceive_DMA(&SpiHandle, recv_packet, trns_buffer, BUFFER_SIZE) != HAL_OK)
{
}
Toggle_GPIO_Pin(); //block the next cycle
while(HAL_SPI_GetState(SpiHandle) != HAL_SPI_STATE_READY)
{
}
restart_SPI_DMA(); //Deinitalize and reinitalize SPI_DMA for next cycle, excluding the extra GPIO.
}
}
Here's is the summarize of the configuration of SPI_DMA that I use.
/*##-1- Configure the SPI peripheral #######################################*/
/* Set the SPI parameters */
SpiHandle.Instance = SPI2;
SpiHandle.Init.Direction = SPI_DIRECTION_2LINES;
SpiHandle.Init.CLKPhase = SPI_PHASE_1EDGE;
SpiHandle.Init.CLKPolarity = SPI_POLARITY_LOW;
SpiHandle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED;
SpiHandle.Init.CRCPolynomial = 7;
SpiHandle.Init.DataSize = SPI_DATASIZE_8BIT;
SpiHandle.Init.FirstBit = SPI_FIRSTBIT_MSB;
SpiHandle.Init.NSS = SPI_NSS_HARD_INPUT;
SpiHandle.Init.TIMode = SPI_TIMODE_DISABLED;
SpiHandle.Init.Mode = SPI_MODE_SLAVE;
/*##-3- Configure the DMA streams ##########################################*/
/* Configure the DMA handler for Transmission process */
hdma_spi_tx.Instance = DMA1_Stream4;
hdma_spi_tx.Init.Channel = DMA_CHANNEL_0;
hdma_spi_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
hdma_spi_tx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdma_spi_tx.Init.MemBurst = DMA_MBURST_INC4;
hdma_spi_tx.Init.PeriphBurst = DMA_PBURST_INC4;
hdma_spi_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_spi_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi_tx.Init.Mode = DMA_NORMAL;
hdma_spi_tx.Init.Priority = DMA_PRIORITY_LOW;
HAL_DMA_Init(&hdma_spi_tx);
/* Associate the initialized DMA handle to the the SPI handle */
__HAL_LINKDMA(&SpiCanHandle, hdmatx, hdma_spi_tx); //hdma_tx handle has to be global as HAL_LINKDMA will be called to link SpiHandle to DMA Handle
/* Configure the DMA handler for Receive process */
hdma_spi_rx.Instance = DMA1_Stream3;
hdma_spi_rx.Init.Channel = DMA_CHANNEL_0;
hdma_spi_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
hdma_spi_rx.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
hdma_spi_rx.Init.MemBurst = DMA_MBURST_INC4;
hdma_spi_rx.Init.PeriphBurst = DMA_PBURST_INC4;
hdma_spi_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_spi_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi_rx.Init.Mode = DMA_NORMAL;
hdma_spi_rx.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_spi_rx);
/* Associate the initialized DMA handle to the the SPI handle */
__HAL_LINKDMA(&SpiHandle, hdmarx, hdma_spi_rx); //hdma_tx handle has to be global as HAL_LINKDMA will be called to link SpiHandle to DMA Handle
/*##-4- Configure the NVIC for DMA #########################################*/
/* NVIC configuration for DMA transfer complete interrupt (SPI2_TX) */
HAL_NVIC_SetPriority(spi_DMA_TX_IRQn, 0, 1);
HAL_NVIC_EnableIRQ(spi_DMA_TX_IRQn);
/* NVIC configuration for DMA transfer complete interrupt (SPI2_RX) */
HAL_NVIC_SetPriority(SPI_spi_DMA_RX_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(SPI_spi_DMA_RX_IRQn);
Thanks,
Regards
Cworth
Solved! Go to Solution.
2026-04-09 10:58 PM
Workaround
So, I have made some workaround based on this suggestion. The master side will send dummy bytes to slave if GPIO interrupt is not detected within pre-determine seconds.
2026-04-06 4:18 AM
Your code looks correct overall. However, I suggest using the latest STM32Cube firmware. Also, carefully manage DMA requests and SPI flags after each transfer to avoid inconsistent states. Additionally, adjust interrupt priorities so that SPI/DMA and SysTick are not blocked, which can prevent hangs in SPI_WaitOnFlagUntilTimeout(). I invite you to check the STM32F4 errata sheet for any SPI-related issues.
2026-04-07 3:18 AM
Following from my previous findings:
I have found out that HAL_GetTicks() will stop getting updated ticks when it entered else statement of the SPI_WaitOnFlagUntilTimeout(). Furthermore, SPI_WaitOnFlagUntilTimeout() is called from this code segment inside SPI_DMATransmitReceiveCplt()
/* Wait until TXE flag is set to send data */
if(SPI_WaitOnFlagUntilTimeout(hspi, SPI_FLAG_TXE, RESET, SPI_TIMEOUT_VALUE) != HAL_OK)
{
hspi->ErrorCode |= HAL_SPI_ERROR_FLAG;
}
@Bowman32 wrote:Additionally, adjust interrupt priorities so that SPI/DMA and SysTick are not blocked, which can prevent hangs in SPI_WaitOnFlagUntilTimeout()
I have changed the priorities of SysTick to be higher than SPI/DMA but it still led to same failure. What cause HAL_GetTicks() failed to get updated ticks when SPI/DMA is under busy state?
@Bowman32 wrote
Also, carefully manage DMA requests and SPI flags after each transfer to avoid inconsistent states.
I will continue investigate the quoted matters as it may be the main underlying cause of the issue
Thanks.
2026-04-09 10:58 PM
Workaround
So, I have made some workaround based on this suggestion. The master side will send dummy bytes to slave if GPIO interrupt is not detected within pre-determine seconds.