cancel
Showing results for 
Search instead for 
Did you mean: 

Using HAL to configure ADC with double buffered DMA on STM32F765VIT

BobbyBeta
Associate III

I'm modifying some code to start using double buffered DMA. Everything in the code currently uses the HAL so I'd like to continue this but it seems ill suited for using double buffering with the ADCs.

This code gets me close:

 

 

        HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_samples, total_samples);
        HAL_DMA_Abort(&hdma2_stream4);
        // Clearing this does nothing ADC1->SR = 0;

        // Clear buffers to make sure they're getting filled
        memset(adc_samples, 0xff, sizeof(adc_samples));

        // Start the DMA with double buffering
        bobbyerror = HAL_DMAEx_MultiBufferStart(&hdma2_stream4, // *hdma
                                                    (uint32_t) (&(hadc1.Instance->DR)), // SrcAddress
                                                    (uint32_t)(&adc_samples[0]), // DstAddress
                                                    (uint32_t)(&adc_samples[1]), // SecondMemAddress
                                                    total_samples // DataLength
                                                    );

 

 

 

 
The issue after this code is that as soon as HAL_DMAEx_MultiBufferStart() sets EN in S4CR, one word gets copied from ADC1->DR and S4NTDR decrements by one. ADC1 is configured for software to start the conversion so this loop does show both buffers successfully receiving the samples from ADC1, they are just off by one memory location.
 

 

        for(uint8_t bobbyidx = 0; bobbyidx < NUM_BUFFERS * SAMPLES_PER_CYCLE; bobbyidx++) {
            bobbyerror = HAL_ADC_Start(&hadc1);
            uint32_t counter = 1000UL * (ADC_STAB_DELAY_US * (SystemCoreClock / 1000000));
            while (counter != 0) {
                counter--;
            }
        }

 

8 REPLIES 8
BobbyBeta
Associate III

In case anyone else find this helpful some day, this is how I needed to use the HAL to configure the DMA Stream to use double buffering with the ADC. It looks like a mess but at least it works.

After this S4NTDR, S4PAR, S4M0AR, and S4M1AR are all configured correctly and ready for when my ADC starts to receive trigger.

 

        HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_samples, total_samples);
        HAL_DMA_Abort(&hdma2_stream4);
        HAL_ADC_Stop(&hadc1);

        // Clear buffers to make sure they're getting filled
        memset(adc_samples, 0xff, sizeof(adc_samples));

        // Start the DMA with double buffering
        // Turn on to not include this delay in sample time
        __HAL_ADC_ENABLE(&hadc1);
        uint32_t counter = (ADC_STAB_DELAY_US * (SystemCoreClock / 1000000));
        while (counter != 0) {
            counter--;
        }        
        __HAL_ADC_CLEAR_FLAG(&hadc1, ADC_SR_EOC | ADC_SR_STRT | ADC_SR_OVR);
        bobbyerror = HAL_DMAEx_MultiBufferStart(&hdma2_stream4, // *hdma
                                                    (uint32_t) (&(hadc1.Instance->DR)), // SrcAddress
                                                    (uint32_t)(&adc_samples[0]), // DstAddress
                                                    (uint32_t)(&adc_samples[1]), // SecondMemAddress
                                                    total_samples // DataLength
                                                    );

 

 It'd be really nice if the HAL had a HAL_ADC_DMA_MultiBufferStart() or something like that.

MasterT
Senior III

Why do you need this two lines:

HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_samples, total_samples);
        HAL_DMA_Abort(&hdma2_stream4);

?

Starting than stopping  definitely would generate a mess in dma-fifo buffers.

BobbyBeta
Associate III

Without that it doesn't set up the ADC up for DMA. HAL_DMAEx_MultiBufferStart() only sets up the DMA stream, not the peripheral.

I got the hint to do this messy solution in this link -> https://stackoverflow.com/questions/65966997/stm32-adc-dma-double-multi-buffer-example

The solution there was for memory-to-peripheral though so I needed a slightly different mess to make it work for the ADC.

Can you elaborate on "Without that it doesn't set up the ADC up for DMA.".

I thought ADC configured to run along with dma in the (example CubeMX)

void HAL_ADC_MspInit(ADC_HandleTypeDef *hadc)
{
  GPIO_InitTypeDef          GPIO_InitStruct;
  static DMA_HandleTypeDef         DmaHandle;
  
  /*##-1- Enable peripherals and GPIO Clocks #################################*/
  /* Enable GPIO clock ****************************************/
  __HAL_RCC_GPIOA_CLK_ENABLE();
  /* ADC Periph clock enable */
  ADCx_CLK_ENABLE();
  /* ADC Periph interface clock configuration */
  __HAL_RCC_ADC_CONFIG(RCC_ADCCLKSOURCE_CLKP);
  /* Enable DMA clock */
  DMAx_CHANNELx_CLK_ENABLE();
  
  /*##- 2- Configure peripheral GPIO #########################################*/
  /* ADC Channel GPIO pin configuration */
  GPIO_InitStruct.Pin = ADCx_CHANNEL_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(ADCx_CHANNEL_GPIO_PORT, &GPIO_InitStruct);
  /*##- 3- Configure DMA #####################################################*/ 

  /*********************** Configure DMA parameters ***************************/
  DmaHandle.Instance                 = DMA1_Stream1;
  DmaHandle.Init.Request             = DMA_REQUEST_ADC1;
  DmaHandle.Init.Direction           = DMA_PERIPH_TO_MEMORY;
  DmaHandle.Init.PeriphInc           = DMA_PINC_DISABLE;
  DmaHandle.Init.MemInc              = DMA_MINC_ENABLE;
  DmaHandle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
  DmaHandle.Init.MemDataAlignment    = DMA_MDATAALIGN_HALFWORD;
  DmaHandle.Init.Mode                = DMA_CIRCULAR;
  DmaHandle.Init.Priority            = DMA_PRIORITY_MEDIUM;
  /* Deinitialize  & Initialize the DMA for new transfer */
  HAL_DMA_DeInit(&DmaHandle);
  HAL_DMA_Init(&DmaHandle);
  
  /* Associate the DMA handle */
  __HAL_LINKDMA(hadc, DMA_Handle, DmaHandle);

  /* NVIC configuration for DMA Input data interrupt */
  HAL_NVIC_SetPriority(DMA1_Stream1_IRQn, 1, 0);
  HAL_NVIC_EnableIRQ(DMA1_Stream1_IRQn);  
}

> It'd be really nice if the HAL had a HAL_ADC_DMA_MultiBufferStart() or something like that.

For "double buffer" - it has. Is called : circular buffer, because you want "endless" continuous stream - right?

Using it , is very easy : just in Cube set circular buffer, inc. memory, source&destination size (byte, word..), and enable callbacks in "Advanced settings" .

AScha3_0-1704388366581.png

+

AScha3_1-1704388436514.png

And in main : on half- and full buffer callbacks move data , wherever, to use it.

I show you, here for my H743 getting data from ESP8266 , SPI rx -> DMA -> circular (=double) buffer :

use callbacks:

 

  HAL_SPI_RegisterCallback(&hspi4, HAL_SPI_RX_HALF_COMPLETE_CB_ID, HAL_SPI_RxHalfCpltCallback);
  HAL_SPI_RegisterCallback(&hspi4, HAL_SPI_RX_COMPLETE_CB_ID, HAL_SPI_RxCpltCallback);
  HAL_SPI_RegisterCallback(&hspi4, HAL_SPI_ERROR_CB_ID,  HAL_SPI_ErrorCallback );

 

 

! also for error - if it ever should happen... !

then: init

 

fresult = HAL_DMA_Init(&hdma_spi4_rx);

 

and start it:

 

fresult = HAL_SPI_Receive_DMA(&hspi4, ESPinbuf , (sizeof(ESPinbuf)));	// ESP input-loop start

 

 

now its running...you need to use the data ... copy...whatever :

 

void HAL_SPI_RxHalfCpltCallback(SPI_HandleTypeDef *hspi4)
{
 .... do here ... use the data...

	inbuf_A_ready =1;
}
// and...
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi4)
{
... and do it here...

	inbuf_B_ready =1;
}

void  HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi4)
{
	fresult = HAL_SPI_Abort(hspi4);
	printf("SPI2: restart \n"); // if this ever happens...
	drawText(118, 92, "SPI2e" , RED ,  0, 1); // warning / info
	fresult = HAL_SPI_Receive_DMA(hspi4, ESPinbuf , (sizeof(ESPinbuf)));	// ESP input-loop start again
}

 

 

+ the inbuf_x_ready  are just my "semaphores" , info in main-loop:  this data is "ready to use" now.

Thats it.

&

This is without cache management, so set i-cache ON and d-cache OFF (in Cube), and global :

volatile uint16_t inbuf_A_ready, inbuf_B_ready ;

If you feel a post has answered your question, please click "Accept as Solution".

You can use a circular buffer with the half xfer complete interrupt to have the same effect as using the double buffer built into the DMA streams but it's less efficient. The STM32 DMA streams have double buffering solely for this reason. Look up the DMA_SxM0AR and DMA_SxM1AR registers in the reference manual. Without using interrupts you should be able to look at the CT bit in the CR and know which buffer is being used by the DMA and which is available for processing the results.

Honestly my HAL_ADC_MspInit() called by HAL_ADC_Init() looks very different from yours, but it's also from a common library in my company that is supposed to be generalized. The one in your picture assumes you're always using DMA1_Stream1 for any ADC handle passed to it. Also, did you make no changes after MX generated this at all? For example some of the comments in your function don't look like something that MX would make and it's missing all of the assert_param() statements checking the handle parameters.

It seems like you possibly could call HAL_DMAEx_MultiBufferStart() after this HAL_ADC_MspInit() although you'd need to use the link in the hadc handle to get to the handle for the stream. Honestly though, I'd need to run it and look at the registers to be sure that it's actually set up to handle the double buffered DMA correctly.

Code I posted is out of CubeMX examples for stm32H743zi2 , all comments as it is. You can download it here:

https://www.st.com/en/development-tools/stm32cubemx.html

Regarding DMA1_Stream1 depends on the uCPU, some have dma channels "hard-wired" to peripheral (F4 and older) , but using modern product like H7 series is allow to link any dma to any peripheral - there is a MUX, so software defined map.

A few years ago I developed multy-buffer dma-spi driver. My line of work was to take "properly written by ST experts" HAL_SPI_TransmitReceive_DMA than replace  if (HAL_OK != HAL_DMA_Start_IT(hspi->hdmarx, (uint32_t)&hspi->Instance->RXDR, (uint32_t)hspi->pRxBuffPtr,   

for

if (HAL_OK != HAL_DMAEx_MultiBufferStart_IT(hspi->hdmarx, (uint32_t)&hspi->Instance->RXDR, (uint32_t)pData, (uint32_t)pData2, Size))

You can do same thing with adc.

Here is my re-work:

HAL_StatusTypeDef dbuff_SPI_Receive_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint8_t *pData2, uint16_t Size)
{
  HAL_StatusTypeDef errorcode = HAL_OK;

  /* Check Direction parameter */
  assert_param(IS_SPI_DIRECTION_2LINES_OR_1LINE_2LINES_RXONLY(hspi->Init.Direction));

  if ((hspi->Init.Direction == SPI_DIRECTION_2LINES) && (hspi->Init.Mode == SPI_MODE_MASTER))
  {
    hspi->State = HAL_SPI_STATE_BUSY_RX;
    /* Call transmit-receive function to send Dummy data on Tx line and generate clock on CLK line */
    return HAL_SPI_TransmitReceive_DMA(hspi, pData, pData, Size);
  }

  /* Process Locked */
  __HAL_LOCK(hspi);

  if (hspi->State != HAL_SPI_STATE_READY)
  {
    errorcode = HAL_BUSY;
    __HAL_UNLOCK(hspi);
    return errorcode;
  }

  if ((pData == NULL) || (Size == 0UL))
  {
    errorcode = HAL_ERROR;
    __HAL_UNLOCK(hspi);
    return errorcode;
  }

  /* Set the transaction information */
  hspi->State       = HAL_SPI_STATE_BUSY_RX;
  hspi->ErrorCode   = HAL_SPI_ERROR_NONE;
  hspi->pRxBuffPtr  = (uint8_t *)pData;
  hspi->RxXferSize  = Size;
  hspi->RxXferCount = Size;

  /*Init field not used in handle to zero */
  hspi->RxISR       = NULL;
  hspi->TxISR       = NULL;
  hspi->TxXferSize  = (uint16_t) 0UL;
  hspi->TxXferCount = (uint16_t) 0UL;

  /* Configure communication direction : 1Line */
  if (hspi->Init.Direction == SPI_DIRECTION_1LINE)
  {
    SPI_1LINE_RX(hspi);
  }

  /* Packing mode management is enabled by the DMA settings */
  if (((hspi->Init.DataSize > SPI_DATASIZE_16BIT) && (hspi->hdmarx->Init.MemDataAlignment != DMA_MDATAALIGN_WORD))    || \
      ((hspi->Init.DataSize > SPI_DATASIZE_8BIT) && ((hspi->hdmarx->Init.MemDataAlignment != DMA_MDATAALIGN_HALFWORD) && \
                                                     (hspi->hdmarx->Init.MemDataAlignment != DMA_MDATAALIGN_WORD))))
  {
    /* Restriction the DMA data received is not allowed in this mode */
    errorcode = HAL_ERROR;
    __HAL_UNLOCK(hspi);
    return errorcode;
  }

  /* Clear RXDMAEN bit */
  CLEAR_BIT(hspi->Instance->CFG1, SPI_CFG1_RXDMAEN);

  /* Adjust XferCount according to DMA alignment / Data size */
  if (hspi->Init.DataSize <= SPI_DATASIZE_8BIT)
  {
    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->hdmarx->Init.MemDataAlignment == DMA_MDATAALIGN_WORD)
    {
      hspi->RxXferCount = (hspi->RxXferCount + (uint16_t) 1UL) >> 1UL;
    }
  }
  else
  {
    /* Adjustment done */
  }

    HAL_DMA_RegisterCallback(hspi2.hdmarx,   HAL_DMA_XFER_CPLT_CB_ID, SPI_RxCpltM0);
    HAL_DMA_RegisterCallback(hspi2.hdmarx, HAL_DMA_XFER_M1CPLT_CB_ID, SPI_RxCpltM1);

  /* Enable the Rx DMA Stream/Channel  */
//  if (HAL_OK != HAL_DMA_Start_IT(hspi->hdmarx, (uint32_t)&hspi->Instance->RXDR, (uint32_t)hspi->pRxBuffPtr, hspi->RxXferCount))
//  if (HAL_OK != HAL_DMAEx_MultiBufferStart_IT(hspi->hdmarx, (uint32_t)&hspi->Instance->DR, (uint32_t)pData, (uint32_t)pData2, Size))
  if (HAL_OK != HAL_DMAEx_MultiBufferStart_IT(hspi->hdmarx, (uint32_t)&hspi->Instance->RXDR, (uint32_t)pData, (uint32_t)pData2, Size))
  {
    /* Update SPI error code */
    SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_DMA);
    errorcode = HAL_ERROR;
    hspi->State = HAL_SPI_STATE_READY;
    return errorcode;
  }

  /* Set the number of data at current transfer */
  if (hspi->hdmarx->Init.Mode == DMA_CIRCULAR)
  {
    MODIFY_REG(hspi->Instance->CR2, SPI_CR2_TSIZE, 0UL);
  }
  else
  {
    MODIFY_REG(hspi->Instance->CR2, SPI_CR2_TSIZE, Size);
  }

  /* Enable Rx DMA Request */
  SET_BIT(hspi->Instance->CFG1, SPI_CFG1_RXDMAEN);

  /* Enable the SPI Error Interrupt Bit */
  __HAL_SPI_ENABLE_IT(hspi, (SPI_IT_OVR | SPI_IT_FRE | SPI_IT_MODF));

  /* Enable SPI peripheral */
  __HAL_SPI_ENABLE(hspi);

  if (hspi->Init.Mode == SPI_MODE_MASTER)
  {
    /* Master transfer start */
    SET_BIT(hspi->Instance->CR1, SPI_CR1_CSTART);
  }

  /* Process Unlocked */
  __HAL_UNLOCK(hspi);
  return errorcode;
}