cancel
Showing results for 
Search instead for 
Did you mean: 

DMA stops due to ADC Overrun

RPape.1
Associate II

Hi,

I have a problem with the DMA seemingly being shut down due to ADC overrun.

What I want to achieve:

Three ADC channels should be read indefinitely and the values should be written to memory by DMA as soon as they are ready.

What's currently happening:

ADC and DMA work in the beginning, so my buffer fills up with values, but shortly after the start, the values in the buffer are not updated anymore and the DMA2_Stream0_IRQHandler is not called anymore either. I suspect the problem is an overrun, as the OVR bit is set in the ADC_ISR register (value is 0x101F) and ErrorCode is set to 2.

The ADC_DR registers value still changes, so the ADC itself seems to be operational, and only the DMA stopped working.

When the DMA does not work anymore, the DMA_CR register value alternates between 0x440 and 0x457. NDTR is some value over 140, and the addresses in M0AR and M1AR keep changing.

My code for the ADC/DMA is as follows:

DMA Initialization:

static void MX_DMA_Init(void)
{
  /* DMA controller clock enable */
  __HAL_RCC_DMAMUX_CLK_ENABLE();
  __HAL_RCC_DMA2_CLK_ENABLE();
 
  /* DMA interrupt init */
  /* DMA2_Stream0_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}

ADC Config:

// ADC configuration: 
AdcManager::AdcManager(ADC_TypeDef* adc, ADC_HandleTypeDef* hadc) {
	adcHandle = hadc; // this is a pointer to ADC_HandleTypeDef hadc1; 
 
	/* auto-generated by IOC file - use IOC file to change config and copy-paste it here */
	/** Common config
	*/
	adcHandle->Instance = adc; // this is ADC1
	adcHandle->Init.ClockPrescaler        = ADC_CLOCK_ASYNC_DIV2;
	adcHandle->Init.Resolution            = ADC_RESOLUTION_12B;
	adcHandle->Init.ScanConvMode          = ADC_SCAN_ENABLE;
	adcHandle->Init.EOCSelection          = ADC_EOC_SEQ_CONV;
	adcHandle->Init.LowPowerAutoWait      = DISABLE;
	adcHandle->Init.ContinuousConvMode    = ENABLE;
	adcHandle->Init.NbrOfConversion       = NUM_ADC_INSTANCES;
	adcHandle->Init.DiscontinuousConvMode = DISABLE;
	adcHandle->Init.NbrOfDiscConversion   = 1;
	adcHandle->Init.ExternalTrigConv = ADC_SOFTWARE_START;
	adcHandle->Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
	adcHandle->Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_CIRCULAR;
	adcHandle->Init.Overrun               = ADC_OVR_DATA_OVERWRITTEN;
    adcHandle->Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
	adcHandle->Init.OversamplingMode      = DISABLE;
}
 
// this is called to add a new channel:
void AdcManager::configureChannel(uint32_t channel, uint32_t rank, uint32_t sampleTime)
{
    /** Configure Regular Channel
    */
    ADC_ChannelConfTypeDef sConfig = {0};
    sConfig.Channel = channel;
    sConfig.Rank = rank;
    sConfig.SamplingTime = sampleTime;
    sConfig.SingleDiff = ADC_SINGLE_ENDED;
    sConfig.OffsetNumber = ADC_OFFSET_NONE;
    sConfig.Offset = 0;
 
    HAL_ADC_ConfigChannel(adcHandle, &sConfig);
}
 
void AdcManager::init(void)
{
	ASSERT(HAL_ADC_DeInit(adcHandle) == HAL_OK);
    ASSERT(HAL_ADC_Init(adcHandle) == HAL_OK);
 
    /** Configure the ADC multi-mode
    */
    ADC_MultiModeTypeDef multimode = {0};
    multimode.Mode = ADC_MODE_INDEPENDENT; // multi-mode disabled
    ASSERT(HAL_ADCEx_MultiModeConfigChannel(adcHandle, &multimode) == HAL_OK);
 
    /* Run the ADC calibration in single-ended mode */
    ASSERT(HAL_ADCEx_Calibration_Start(adcHandle, ADC_CALIB_OFFSET_LINEARITY, ADC_SINGLE_ENDED) == HAL_OK);
 
    SET_BIT(adcHandle->Instance->IER, ADC_IER_EOSIE);
 
}
 
void AdcManager::start()
{
	ASSERT(HAL_ADC_Start_DMA(this->adcHandle, (uint32_t*) &samplingData[0], NUM_ADC_INSTANCES) == HAL_OK); // NUM_ADC_INSTANCES is the number of channels (3 in this case)
}
 

Msp:

void HAL_ADC_MspInit(ADC_HandleTypeDef* hadc)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
  if(hadc->Instance==ADC1)
  {
  /* USER CODE BEGIN ADC1_MspInit 0 */
 
  /* USER CODE END ADC1_MspInit 0 */
  if(IS_ENGINEERING_BOOT_MODE())
  {
 
  /** Initializes the peripherals clock
  */
    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
    PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PLL4;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
    {
      Error_Handler();
    }
 
  }
 
    /* Peripheral clock enable */
    __HAL_RCC_ADC12_CLK_ENABLE();
 
    __HAL_RCC_GPIOC_CLK_ENABLE();
    /**ADC1 GPIO Configuration
    PC2     ------> ADC1_INP12
    PC3     ------> ADC1_INP13
    */
    GPIO_InitStruct.Pin = GPIO_PIN_2|GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
 
    /* ADC1 DMA Init */
    /* ADC1 Init */
    hdma_adc1.Instance = DMA2_Stream0;
    hdma_adc1.Init.Request = DMA_REQUEST_ADC1;
    hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
    hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
    hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_adc1.Init.Mode = DMA_CIRCULAR;
    hdma_adc1.Init.Priority = DMA_PRIORITY_VERY_HIGH;
    hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
    {
      Error_Handler();
    }
 
    __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc1);
 
    /* ADC1 interrupt Init */
    HAL_NVIC_SetPriority(ADC1_IRQn, 1, 0);
    HAL_NVIC_EnableIRQ(ADC1_IRQn);
  /* USER CODE BEGIN ADC1_MspInit 1 */
 
  /* USER CODE END ADC1_MspInit 1 */
  }
 
}

What I also tried:

  • Using a 1 kHz timer to trigger the ADC conversion, hoping it would be slow enough to prevent an overrun. This did not change the behavior though. I only changed these settings and left the rest as it was:
    • adcHandle->Init.ExternalTrigConv = ADC_EXTERNALTRIG_T2_TRGO; adcHandle->Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  • Setting adcHandle->Init.LowPowerAutoWait to ENABLE. With this setting, the OVR bit is not set, but the DMA stops working nevertheless. I know that it is advised not to use this setting with Circular Mode, but I wanted to try whether it makes a difference.

Any help that points me in the right direction would be highly appreciated.

8 REPLIES 8
Joe WILLIAMS
ST Employee

Your question has been routed to the online support team and a case had been create in your name we'll be contacting you back directly. 

Which STM32? What hardware, what clocks setting, what ADC settings?

If ADC indicates overrun, it's probably because it overruns. DMA is no magic, it has its latency, and that depends on many factors - other DMA channels/streams activity, bus activity, target memory latencies.

JW

RPape.1
Associate II

STM32MP153.

You can find the ADC settings in the code above.

The ADC should be clocked at 25 MHz from HSE. HSI is at 64 MHz. I am not sure about the DMA clock, though.

I understand that there is probably an actual overrun. I'm just trying to understand why it overruns even when using a rather slow timer to trigger the ADC. There's probably something misconfigured from my side...

The STM32MPxxx are somewhat outside of scope of this (mcu) forum... and the details do matter.

Assuming sampling time is minimal, ADC conversions churn out at the rate of 2MHz. If the M4 domain (including DMA2) runs at cca 200MHz that's cca 100 cycles per conversion which indeed sounds like quite a lot. DMA's latency/transfer time should be below a dozen of cycles, unless there are some other channels/streams in DMA which would prevent the given channel/stream to transfer timely. The second major potential problem source is latency of destination memory, i.e. the buffer into which DMA stores data - if it's SRAM in the M4 domain it shouldn't be an issue, but if it's beyond the inter-domain interconnect it may be slow - so check the location of that buffer. Other potential issue may be some heavy bus load conflicting with the ADC-to-DMA or DMA-to-SRAM transfer, that shouldn't occur in a simplistic test program which does nothing but the ADC.

I might've overlooked something - as I've said, this is somewhat above my paygrade.

JW

RPape.1
Associate II

Thanks for your help, Jan.

I meant to create the topic in the MPU forum, I probably clicked the wrong link.

The buffer into which the DMA stores is in the SRAM of the M4, so this should not be a limitation.

I also found the main problem, with the help of the ST support: The ClockPrescaler of the ADC was set to ADC_CLOCK_ASYNC_DIV2.

Changing this to ADC_CLOCK_SYNC_PCLK_DIV2 fixes the problem, at least for short sampling times.

For longer sampling times (387.5 cycles and longer) the problem still exists like before. I would assume that longer sampling times would give the DMA more time to transfer the data, so this seems a bit counter-intuitive for me.

Interestingly, chapter 14.3.3 of the STM32F4 Reference Manual (STM32F469xx and STM32F479xx advanced Arm®-based 32-bit MCUs - Reference manual) kind of mentions that only PCLK is available. I did not find any information about this in the STM32MP1 reference manual, though, and CubeIDE selects the ASYNC Prescaler which did not work for me as a default.

> The buffer into which the DMA stores is in the SRAM of the M4, so this should not be a limitation.

Oh.

>: The ClockPrescaler of the ADC was set to ADC_CLOCK_ASYNC_DIV2.

Changing this to ADC_CLOCK_SYNC_PCLK_DIV2 fixes the problem, at least for short sampling times.

What are the absolute frequencies for these two options, i.e. for the dedicated ADC clock frequency and the given APB bus frequency?

With asynchronous clock, some synchronization time is involved, too, but that should be in the order of one or two APB cycles, so nothing that dramatic.

> For longer sampling times (387.5 cycles and longer) the problem still exists like before. I would assume that longer sampling times would give the DMA more time to transfer the data, so this seems a bit counter-intuitive for me.

This indeed does not make much sense.

I'm not sure what's the proper way to debug this. I would try to reduce the application omitting unrelated parts, until the problem disappears. If problem persists in an absolute minimal application (i.e. nothing else just ADC and DMA running on the M4), then I'd try to wiggle the clocks setups, to try to gain understanding of how those influence the problem.

JW

RPape.1
Associate II

I think I'm getting closer 🙂

It seems the size of the buffer I was writing the values into via DMA was a problem.

I was initially converting three channels as a sequence into a buffer with size 3 and then, in the DMA2_Stream0_IRQHandler(), copying these values into separate arrays of different sizes. This was probably not a good idea to begin with.

Right now, I'm only converting one channel and write the values into a buffer of size 400 via DMA, and it works with any sample time (except for when I set a breakpoint - then the DMA stops again...).

I use this call to start the ADC/DMA:

HAL_ADC_Start_DMA(this->adcHandle, (uint32_t*) &buffer[0], 400);

The question is now: How do I add the other two channels?

Let's say I want 400 samples per millisecond for channel 1 and only one sample per millisecond for both channel 2 and 3.

How can I tell the ADC to do that?

I guess I would have to set

adcHandle->Init.NbrOfConversion = 3;

and probably

HAL_ADC_Start_DMA(this->adcHandle, (uint32_t*) &buffer[0], 402);

but how can I tell the ADC that it should do 400 samples of channel 1 and only one of channel 2 and 3?

Chris21
Associate III

I don't think the ADC can operate at different rates for different channels (although I do not know the specific details of the STM32MP153).

Perhaps you should use the second ADC for the low rate channels.