Skip to main content
Graduate
January 3, 2024
Question

Using HAL to configure ADC with double buffered DMA on STM32F765VIT

  • January 3, 2024
  • 3 replies
  • 5216 views

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--;
 }
 }

 

    This topic has been closed for replies.

    3 replies

    BobbyBetaAuthor
    Graduate
    January 4, 2024

    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.

    Super User
    January 4, 2024

    > 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 ;

    BobbyBetaAuthor
    Graduate
    January 4, 2024

    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.

    Explorer
    January 4, 2024

    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.

    BobbyBetaAuthor
    Graduate
    January 4, 2024

    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.

    Explorer
    January 4, 2024

    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); 
    }
    BobbyBetaAuthor
    Graduate
    January 4, 2024

    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.