cancel
Showing results for 
Search instead for 
Did you mean: 

Help with ADC and DMA

edwin8
Associate II

I'm using a Nucleo-H743ZI and have configured ADC3 to trigger off of TIM3. It is scanning 4 channels every 10 us. I also have BDMA configured for the ADC. The ADC end-of-scan interrupt is occurring every 10 us, but the DMA interrupt is only occurring every other ADC interrupt. They both complete in < 7 us, so it is not overrunning the 10 us period. Anyone know why the DMA occurs only every other time? Thanks for any help.

7 REPLIES 7
edwin8
Associate II

I don't think my DMA is configured correctly, but I don't know how to fix it.

The ADC is configured to scan 4 channels and trigger every 10 us based on TIM3:

  hadc3.Instance = ADC3;
  hadc3.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc3.Init.Resolution = ADC_RESOLUTION_16B;
  hadc3.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc3.Init.EOCSelection = ADC_EOC_SEQ_CONV;
  hadc3.Init.LowPowerAutoWait = DISABLE;
  hadc3.Init.ContinuousConvMode = DISABLE;
  hadc3.Init.NbrOfConversion = 4;
  hadc3.Init.DiscontinuousConvMode = DISABLE;
  hadc3.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T3_TRGO;
  hadc3.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc3.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;
  hadc3.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc3.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
  hadc3.Init.BoostMode = DISABLE;
  hadc3.Init.OversamplingMode = DISABLE;
  // Configuration of 4 channels omitted

Then I start the ADC/DMA with this:

uint32_t adcresults[4];
HAL_ADC_Start_DMA(&hadc3, adcresults, 16);

I pass in 16 to HAL_ADC_Start_DMA() as the number of bytes to be transferred to the adcresults array (4 channels * 4 bytes/channel).

Is this the correct way to DMA an ADC scan?

I get ADC3 global interrupt every 10 us, but the DMA interrupt only occurs every 20 us. The data in adcresults seem to be correct, just the timing doesn't seem right.

lokash
Associate II

How is your DMA configured?

If its data width is set to half word, it transfers two bytes per sample.

Then your DMA Buffer is twice the size needed.

edwin8
Associate II

I set the data width to word, since the ADC data register is 32 bits:

void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
 
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(hadc->Instance==ADC3)
  {
    /* Peripheral clock enable */
    __HAL_RCC_ADC3_CLK_ENABLE();
  
    __HAL_RCC_GPIOF_CLK_ENABLE();
    __HAL_RCC_GPIOC_CLK_ENABLE();
    /**ADC3 GPIO Configuration    
    PF3     ------> ADC3_INP5
    PF5     ------> ADC3_INP4
    PF9     ------> ADC3_INP2
    PC2_C     ------> ADC3_INP0 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_3|GPIO_PIN_5|GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
 
    HAL_SYSCFG_AnalogSwitchConfig(SYSCFG_SWITCH_PC2, SYSCFG_SWITCH_PC2_OPEN);
 
    /* ADC3 DMA Init */
    /* ADC3 Init */
    hdma_adc3.Instance = BDMA_Channel0;
    hdma_adc3.Init.Request = BDMA_REQUEST_ADC3;
    hdma_adc3.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc3.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc3.Init.MemInc = DMA_MINC_ENABLE;
    hdma_adc3.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
    hdma_adc3.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
    hdma_adc3.Init.Mode = DMA_CIRCULAR;
    hdma_adc3.Init.Priority = DMA_PRIORITY_VERY_HIGH;
    if (HAL_DMA_Init(&hdma_adc3) != HAL_OK)
    {
      Error_Handler();
    }
 
    __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc3);
 
    /* ADC3 interrupt Init */
    HAL_NVIC_SetPriority(ADC3_IRQn, 3, 0);
    HAL_NVIC_EnableIRQ(ADC3_IRQn);
  }
 
}

edwin8
Associate II

And just to test, I changed the number of bytes to 8 in the HAL_ADC_Start_DMA() call and I do get a DMA interrupt every 10us now instead of every 20us. I expected the adcresults[] array to only be half filled, but it is being entirely filled, and with correct ADC data. I don't understand how the 16-byte adcresults[] array is getting filled up when the DMA request is for only 8 bytes.

lokash
Associate II

In an old project for an STM32L4 in stm32h7xx_hal_adc.c it states:

"@param Length Length of data to be transferred from ADC peripheral to memory (in bytes)"

I remember that was an issue in that project, too.

In a fresh STM32H7 project the same line is:

"@param Length Number of data to be transferred from ADC peripheral to memory"

Seems somebody fixed the docs 🙂

The DMA interrupt is triggered twice per complete transfer.

Do you use HAL_ADC_ConvCpltCallback / HAL_ADC_ConvHalfCpltCallback callbacks?

edwin8
Associate II

OK, I'm confused now.

The description of Length in my project is your first one, so that's why I set Length to 16. This resulted in ADC intr every 10 us, but DMA intr every 20 us.

I changed Length to 8 just for testing, and get ADC intr and DMA intr every 10 us. This actually seems to work, though I don't know exactly what data or how much of it is transferred per interrupt. (But based on the further test below, I think it is probably the ConvHalfCplt intr and ConvCplt intr alternating every 10 us.)

Now according to the new description of Length, I should be setting Length to 4, since that is the number of channels I'm scanning. So I try 4 and I'm still getting correct ADC data, but I'm getting two DMA interrupts per ADC interrupt. I suppose one is ConvHalfCplt and the other is ConvCplt? I'm using ConvCpltCallback, but not ConvHalfCpltCallback. Though I think the ConvHalfCplt interrupt is being enabled by the HAL functions under the hood regardless.

So I guess 4 seems to be the correct Length to use?

Thanks @lokash​ for all your help.

lokash
Associate II

As adcresults is of length 4, this would be the correct length.

That said a higher buffer size decreases your CPU load, as it can process several samples at once.

Both callbacks are always called and I would use them both to avoid data corruption by the DMA.