cancel
Showing results for 
Search instead for 
Did you mean: 

SPI Rx double buffered DMA triggered from a GPIO

NPato
Associate II

Using an STM32H743Zi I'm trying to receive 32bits of data from two SPI interfaces triggered at the same time from an external GPIO at a fairly high rate e.g. ~200kHz. Trying to use double buffered DMA receive.

I started by trying to get an external GPIO to trigger a peripheral related DMA transaction. I tried using EXTI0 as the DMA synchronisation signal but that only seemed to work for the first time the GPIO was toggled, it never triggered again after that. So next I used a method mentioned else where in the forum which is to use a timer which can take a GPIO into to generate a DMA event, and this works.

I can generate the required 32 clock cycles on each SPI interface at the required time (using a 4 byte circular DMA SPI tx).

The problem I have is reception. I'd assumed I could just start the SPI Rx double buffered DMA and it would just process data as the SPI device indicates that it has Rx data ready. But when I do this the DMA rx starts producing FIFO errors and completing very quickly even without any SPI clock active. It's as if the DMA is reading out of the SPI device without taking any notice of whether there is any data to read or not. The code to start the SPI DMA is based on the HAL_SPI_TransmitReceive_DMA() but modified to handle the double buffered Rx DMA.

Any insight into what I might be doing wrong?

HAL_StatusTypeDef SPI_TxRx_DMA(SPI_HandleTypeDef *hspi, uint8_t * txData, uint16_t txSize, uint8_t * rxData1, uint8_t * rxData2, uint16_t rxSize)
{
    HAL_StatusTypeDef errorcode = HAL_OK;
 
    /* Process locked */
    __HAL_LOCK(hspi);
 
    hspi->State = HAL_SPI_STATE_BUSY_TX_RX;
 
    /* Set the transaction information */
    hspi->ErrorCode = HAL_SPI_ERROR_NONE;
    hspi->pTxBuffPtr = (uint8_t *) txData;
    hspi->TxXferSize = txSize;
    hspi->TxXferCount = txSize;
    hspi->pRxBuffPtr = NULL;
    hspi->RxXferSize = rxSize;
    hspi->RxXferCount = rxSize;
 
    /* Init field not used in handle to zero */
    hspi->RxISR = NULL;
    hspi->TxISR = NULL;
 
    /* Reset the Tx/Rx DMA bits */
    CLEAR_BIT(hspi->Instance->CFG1, SPI_CFG1_TXDMAEN | SPI_CFG1_RXDMAEN);
 
    /* Adjust XferCount according to DMA alignment / Data size */
    if (hspi->Init.DataSize <= SPI_DATASIZE_8BIT)
    {
        if (hspi->hdmatx->Init.MemDataAlignment == DMA_MDATAALIGN_HALFWORD)
        {
            hspi->TxXferCount = (hspi->TxXferCount + (uint16_t) 1UL) >> 1UL;
        }
        if (hspi->hdmatx->Init.MemDataAlignment == DMA_MDATAALIGN_WORD)
        {
            hspi->TxXferCount = (hspi->TxXferCount + (uint16_t) 3UL) >> 2UL;
        }
        if (hspi->hdmarx->Init.MemDataAlignment == DMA_MDATAALIGN_HALFWORD)
        {
            hspi->RxXferCount = (hspi->RxXferCount + (uint16_t) 1UL) >> 1UL;
        }
        if (hspi->hdmarx->Init.MemDataAlignment == DMA_MDATAALIGN_WORD)
        {
            hspi->RxXferCount = (hspi->RxXferCount + (uint16_t) 3UL) >> 2UL;
        }
    }
    else if (hspi->Init.DataSize <= SPI_DATASIZE_16BIT)
    {
        if (hspi->hdmatx->Init.MemDataAlignment == DMA_MDATAALIGN_WORD)
        {
            hspi->TxXferCount = (hspi->TxXferCount + (uint16_t) 1UL) >> 1UL;
        }
        if (hspi->hdmarx->Init.MemDataAlignment == DMA_MDATAALIGN_WORD)
        {
            hspi->RxXferCount = (hspi->RxXferCount + (uint16_t) 1UL) >> 1UL;
        }
    }
    else
    {
        /* Adjustment done */
    }
 
#if 1
    hspi->hdmarx->XferHalfCpltCallback = NULL;
    hspi->hdmarx->XferCpltCallback = NULL; // TBD SPI_DMAReceiveCplt;
    hspi->hdmarx->XferErrorCallback = SPI_DMAError;
    hspi->hdmarx->XferAbortCallback = NULL;
 
    /* Enable the Rx DMA Stream/Channel  */
    if (HAL_OK != HAL_DMAEx_MultiBufferStart_IT(hspi->hdmarx,
                    (uint32_t) &hspi->Instance->RXDR, (uint32_t) rxData1,
                    (uint32_t) rxData2, hspi->RxXferCount))
    {
        /* Update SPI error code */
        SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_DMA);
        errorcode = HAL_ERROR;
        hspi->State = HAL_SPI_STATE_READY;
        return errorcode;
    }
 
    /* Enable Rx DMA Request */
    SET_BIT(hspi->Instance->CFG1, SPI_CFG1_RXDMAEN);
#endif
 
    // No Tx callbacks
    hspi->hdmatx->XferHalfCpltCallback = NULL;
    hspi->hdmatx->XferCpltCallback = NULL;
    hspi->hdmatx->XferErrorCallback = NULL;
    hspi->hdmatx->XferAbortCallback = NULL;
 
    /* Enable the Tx DMA Stream/Channel  */
    if (HAL_OK != HAL_DMA_Start(hspi->hdmatx, (uint32_t) hspi->pTxBuffPtr,
                    (uint32_t) &hspi->Instance->TXDR, hspi->TxXferCount))
    {
        /* Update SPI error code */
        SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_DMA);
        errorcode = HAL_ERROR;
        hspi->State = HAL_SPI_STATE_READY;
        return errorcode;
    }
 
    // Endless transaction indicated by setting size to 0
    MODIFY_REG(hspi->Instance->CR2, SPI_CR2_TSIZE, 0UL);
 
    /* Enable Tx DMA Request */
    SET_BIT(hspi->Instance->CFG1, SPI_CFG1_TXDMAEN);
 
    /* Enable the SPI Error Interrupt Bit */
    __HAL_SPI_ENABLE_IT(hspi, (/*SPI_IT_OVR |*/SPI_IT_UDR | SPI_IT_FRE | SPI_IT_MODF));
 
    /* Enable SPI peripheral */
    __HAL_SPI_ENABLE(hspi);
 
    /* Master transfer start */
    SET_BIT(hspi->Instance->CR1, SPI_CR1_CSTART);
 
    /* Process Unlocked */
    __HAL_UNLOCK(hspi);
    return errorcode;
}

10 REPLIES 10
NPato
Associate II

I've not changed the MPU settings from default. I have both instruction and data cache enabled. I have a segment defined in the linker script to place all my DMA buffers in:

  .dma_buff : ALIGN(32) SUBALIGN(32)
  {
     *(.dmabuff*)
     
  } >RAM_D1

The ALIGN and SUBALIGN ensures that each DMA buffer is aligned to a cache line. This prevents odd corruptions that can occur if another variable occupies the same cache line as a DMA buffer and a cache invalidate is run on that cache line. Note you should also be compiling with '-fdata-sections' for this to work correctly otherwise variables within the same file may be merged into one linker section.

Then when defining a DMA buffer in the code I add something like:

__attribute__((section(".dmabuff"))) uint32_t dmaBuff[1024];

If it is an Rx DMA then once the transfer is complete you will need to invalidate the cache over the whole of the dma buffer:

SCB_InvalidateDCache_by_Addr(dmaBuff, sizeof(dmaBuff));

This will make sure that when the processor reads the memory it gets the updated values written into memory by the DMA (the DMA bypasses the cache when accessing memory).

If it is a transmit DMA you will need to 'clean' the D cache over the tx DMA buffer before starting the transmit using SCB_CleanDCache_by_Addr()