Showing results for 
Search instead for 
Did you mean: 

HAL_SPI_TransmitReceive_DMA() transmit interrupt always triggered after receive interrupt

Ross Yeager
Associate III

I have a HAL_SPI_TransmitReceive_DMA() transaction that I am performing. The data can be seen on the line to properly get clocked out over SPI.

The problem is that in the receive interrupt, we kick off the next transaction (a DMA SPI write). This write fails because the HAL erroneously still has a lock on the DMA tx handler. The transmit eventually comes through, but we fail to write a transaction due to the lock.

I cannot figure out why the transmit interrupt does not fire before the rx interrupt. I did see that the SW priority of the DMA streams was the same, and the natural priority in that case is to prefer the lower stream number (which in my case was the rx stream); however, when I bumped up the priority of the sw dma stream priority to be higher than the rx stream, that did nothing to change the outcome.


If you transmit and receive the same number of bytes (frames, whatever), from the point of view of DMA, transmit is finished at the moment when the last byte (frame) is moved into the holding buffer, i.e. two frames before the end (there is the holding buffer and then the transmit buffer, i.e. the shift register itself). Reception of course is finished when the last byte (frame) is safely in.

I'm not going to comment on Cube's "architecture"; I don't use Cube.


by your definition, wouldn't that guarantee that the dma stream for the tx would occur before the corresponding rx? what conditions would not make that an invariant?

i guess if there was a higher priority stream interrupt that was higher than the tx but lower than the rx, it could happen? although if that were the case, i'd expect it to still properly service the interrupts


Actually I never use DMA TX interrupt, only use RX DMA interrupt to manage data flow. When using HAL you have to use SPI callback, not the DMA. And remember that HAL will activate all possible interrupts to be able to plumb any user callbacks.

Now let me make things absolutely clear. I am talking about the order in which the DMAx_StreamY_IRQHandler are invoked as triggered by their respective DMA Stream Transfer Complete event. And this for two DMA Streams, handling respectively the same master-mode SPIx Rx and Tx, being started correctly at the same time with the same number of transfers (NDTR), (and without any pathologic side effects like residui from previous failed/aborted transfers etc. )

As the last SPI frame cannot be received before it has been transmitted, the Rx DMA Stream can not signal Transfer Complete before the Tx DMA Stream signals it. This is regardless of their natural or set priority in DMA controller.

Now, as with any two interrupts, if interrupt A has a lower priority (natural or set) than interrupt B, even if A is triggered sooner than B, if there is anything which delays their execution (e.g. execution of an interrupt C with even higher priority than B, or interrupts being globally disabled from whatever process for long enough), then the interrupt priority prevails and B is executed sooner than A. It would be foolish to set the Tx DMA Stream interrupt handler's priority to lower than the Rx's one.

Now what does and does not Cube/HAL do or set or handle, or where do you observe that order reversal - that is something I don't know and, honestly, don't want to know.


Got it, so you set up the DMA Tx, but disable it's interrupt, and let the DMA Rx interrupt manage the data flow. That sounds like a good solution, although it may be tricky to do if using the HAL. Thanks for the input.


It's much easier to use HAL SPI+DMA than USART+DMA (where LL seems a better fit)

Let me try to grap extracts of my working code for SPI Master 4 wires with SW NSS:

Maybe this will give you some hints.

// STM32L4R5 SPI Master 16 bit words 4 wire transfer
HAL_StatusTypeDef SPIP_MasterConfigureSpi(SPI_HandleTypeDef *hspi){
  hspi->Instance               = SPI2;
  hspi->Init.Direction         = SPI_DIRECTION_2LINES;
  hspi->Init.CLKPhase          = SPI_PHASE_1EDGE;
  hspi->Init.CLKPolarity       = SPI_POLARITY_LOW;
  hspi->Init.DataSize          = SPI_DATASIZE_16BIT;
  hspi->Init.FirstBit          = SPI_FIRSTBIT_MSB;
  hspi->Init.TIMode            = SPI_TIMODE_DISABLE;
  hspi->Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE;
  hspi->Init.CRCPolynomial     = 7;
  hspi->Init.CRCLength         = SPI_CRC_LENGTH_8BIT;
  hspi->Init.NSS               = SPI_NSS_SOFT;
  hspi->Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;
  hspi->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;//16;
  hspi->Init.Mode = SPI_MODE_MASTER;
  return (HAL_SPI_Init(hspi));
void SPIP_MasterConfigureDma(SPI_HandleTypeDef *hspi){
  /* Configure the DMA handler for Transmission process */
  pSPIP->hdma_tx->Instance                 = DMA1_Channel2;//DMA1_Channel5;
  pSPIP->hdma_tx->Init.Request             = DMA_REQUEST_SPI2_TX;//DMA_REQUEST_1;
  pSPIP->hdma_tx->Init.Direction           = DMA_MEMORY_TO_PERIPH;
  pSPIP->hdma_tx->Init.PeriphInc           = DMA_PINC_DISABLE;
  pSPIP->hdma_tx->Init.MemInc              = DMA_MINC_ENABLE;
  pSPIP->hdma_tx->Init.PeriphDataAlignment = /*DMA_PDATAALIGN_BYTE;*/ DMA_PDATAALIGN_HALFWORD;
  pSPIP->hdma_tx->Init.MemDataAlignment    = /*DMA_MDATAALIGN_BYTE;*/ DMA_MDATAALIGN_HALFWORD;
  pSPIP->hdma_tx->Init.Mode                = DMA_NORMAL;
  pSPIP->hdma_tx->Init.Priority            = DMA_PRIORITY_HIGH;
  /* Associate the initialized DMA handle to the the SPI handle */
  __HAL_LINKDMA(hspi, hdmatx, *(pSPIP->hdma_tx));
  /* Configure the DMA handler for Reception process */
  pSPIP->hdma_rx->Instance                 = DMA1_Channel1;//DMA1_Channel4;
  pSPIP->hdma_rx->Init.Request             = DMA_REQUEST_SPI2_RX;//DMA_REQUEST_1;
  pSPIP->hdma_rx->Init.Direction           = DMA_PERIPH_TO_MEMORY;
  pSPIP->hdma_rx->Init.PeriphInc           = DMA_PINC_DISABLE;
  pSPIP->hdma_rx->Init.MemInc              = DMA_MINC_ENABLE;
  pSPIP->hdma_rx->Init.PeriphDataAlignment = /*DMA_PDATAALIGN_BYTE;*/ DMA_PDATAALIGN_HALFWORD;
  pSPIP->hdma_rx->Init.MemDataAlignment    = /*DMA_MDATAALIGN_BYTE;*/ DMA_MDATAALIGN_HALFWORD;
  pSPIP->hdma_rx->Init.Mode                = DMA_NORMAL;
  pSPIP->hdma_rx->Init.Priority            = DMA_PRIORITY_HIGH;
  /* Associate the initialized DMA handle to the the SPI handle */
  __HAL_LINKDMA(hspi, hdmarx, *(pSPIP->hdma_rx));
// this code from ISR state machine...
HAL_StatusTypeDef SPIP_MasterSerialTransferStart_ISR(SPIP_t* pSPIP){
  if(HAL_SPI_TransmitReceive_DMA(pSPIP->hspi, pSPIP->pMasterSerialTxBuffer, pSPIP->pMasterSerialRxBuffer, SERIAL_BLOCK_SIZE_WORD) != HAL_OK)
  return HAL_OK;
//==== ISR Callbacks =====================================================
/* @brief  TxRx Transfer completed callback.
  * @param  hspi: SPI handle
  * @retval None  */
void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi){
  // we only need this in master mode!
  if(pSPIP->Device == IS_SLAVE) return;
  SPIP_InterruptBasedStateMachine(0); // what's next to do? next block or rise NSS

@KIC8462852 EPIC204278916, thanks for the snippet, although since you are using the HAL_SPI_TransmitReceive_DMA(), I don't see how you are avoiding using the DMA tx interrupt. That function calls HAL_DMA_Start_IT() for both TX and RX, and that function sets the TC interrupt bit for both. The HAL then uses its broken "locking" mechanism to lock the DMA handlers until the respective interrupt comes through.

From your code above, I don't see how you're avoiding that.

The ideal way to do this would be to do everything the same, except for the transmit, do not enable the TC interrupt and to unlock both handlers when the rx completes...then we're only relying on rx for flow control.

Which Cube, and which version of it are you using?