cancel
Showing results for 
Search instead for 
Did you mean: 

How to enable ADC continuous mode with DMA?

CKosa
Associate II

Hello community,

I try to configure the ADC in continuous mode so it writes the converted values via DMA continuously into a memory address in background without triggering via software.

It should be possible with : ContinuousConvMode = ENABLE and ExternalTrigConv =ADC_SOFTWARE_START.

But the function HAL_ADC_Start_DMA reads only once, writes via DMA and then stops and isn't getting triggered continuously. According to HAL manual, the function HAL_ADC_Start_DMA seems to work only in single mode. How do I get the ADC and DMA run continuously in the background? Do I need a timer? I thought it shall be possible without special timers.

main.c:

int main(void)
{
 
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
 
  uint16_t ADC_buf=0;
  uint16_t ADC_poll=0;
 
  //Start DMA stream
  HAL_ADC_Start_DMA(&hadc1,(uint32_t*) &ADC_buf,2);
 
  while(1){
 
    HAL_ADC_Start(&hadc1);
    HAL_ADC_PollForConversion(&hadc1,10);
    ADC_poll=HAL_ADC_GetValue(&hadc1);
    printf("DMA: %d \t Poll: %d\n",(int) ADC_buf, (int)ADC_poll);
 
    HAL_ADC_Stop(&hadc1);
    HAL_Delay(100);
.
.
.
static void MX_ADC1_Init(void)
{
 
  ADC_ChannelConfTypeDef sConfig;
 
    /**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
    */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.ExternalTrigConv =ADC_SOFTWARE_START ;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 2;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
 
    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
    */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
 
}
 
static void MX_DMA_Init(void) 
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();
 
  /* DMA interrupt init */
  hdma_adc1.Instance=DMA2_Stream0;
  hdma_adc1.Init.Channel = DMA_CHANNEL_0;// Channel 0, Stream 0
  hdma_adc1.Init.Direction=DMA_PERIPH_TO_MEMORY;
  hdma_adc1.Init.PeriphInc=DMA_PINC_DISABLE;
  hdma_adc1.Init.MemInc=DMA_MINC_DISABLE;
  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_HIGH;
  hdma_adc1.Init.FIFOMode=DMA_FIFOMODE_DISABLE;
  hdma_adc1.Init.FIFOThreshold=DMA_FIFO_THRESHOLD_HALFFULL;
  hdma_adc1.Init.MemBurst=DMA_MBURST_SINGLE;
  hdma_adc1.Init.PeriphBurst=DMA_PBURST_SINGLE;
 
  //Initialize DMA
  if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)    {      _Error_Handler(__FILE__, __LINE__);    }  ;
 
  /* DMA2_Stream0_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
}

stm32f7xx_it.c

void DMA2_Stream0_IRQHandler(void)
{
 
  printf("Interrupted\n");
  HAL_DMA_IRQHandler(&hdma_adc1);
}

Prints out:

Initializing
Interrupted
DMA: 4095    Poll: 4034
DMA: 4095    Poll: 4032
DMA: 4095    Poll: 4095
DMA: 4095    Poll: 4041
DMA: 4095    Poll: 4095
...

So DMA transfer runs only once. What am I doing / thinking wrong???

8 REPLIES 8
MNapi
Senior III

a few mistakes you need to declare

uint32_t ADC_buf; not uint16_t ADC_buf

you do not need the pointer &

HAL_ADC_Start_DMA(&hadc1,(uint32_t*)ADC_buf,2);

make sure in cube mx DMA is in circular mode not normal

you do not need code in while loop since the value of ADC_buf is already there

Not really. Actually, I define a uint32_t and assign it to a value. Then I send the address of it in the function(&ADC_buf). Your suggestion could be right if you would declare uint32_t* ADC_buf and send the ADC_buf.

However, it doesn't help to solve my problem. the code is correct and DMA writes the correct value into this memory address. Unfortunately only once and stops then..

The DMA is already configured to circular mode.

"you do not need code in while loop since the value of ADC_buf is already there"

I don't quite understand this. I just want to access to the value in my main loop and print it in order to debug... What's wrong with it?

 uint32_t ADC_buf=0;
  //Start DMA stream
  HAL_ADC_Start_DMA(&hadc1, &ADC_buf,1);
 
  while(1){
	  printf("DMA value: %d\n",ADC_buf);
	  HAL_Delay(100);
}

CCobo.1
Associate

I know this question was posted 2 years ago but I'm having the same problem, have you found a solution? Thanks

Don't know anymore. I solved that triggering the interrupt function. The code seems now so. ( I need only a few tens of hertz of frequency.)

https://github.com/cankosar/CTRL_Target/blob/master/hw/src/sysconfigs.cpp

https://github.com/cankosar/CTRL_Target/blob/master/hw/src/exp_pedal.cpp

But as far as I remember, I made it work with DMA. Just cannot find the older code

iforce2d
Associate III

I just wasted about 6 hours on this exact same problem. The only conclusion I came to is that this sample rate with only two entries in the buffer is not appropriate for continuous conversion:

sConfig.SamplingTime = ADC_SAMPLETIME_3CYCLES;

The ADC is so fast with that setting, that the main code has barely finished the call to Start_DMA before the conversion complete interrupt is already invoked. With continuous conversion enabled, the program will basically spend all its time servicing that interrupt. Better to only use such a short sample time for a much larger buffer which will take a reasonable time to fill.

In my case, I could get it to run as I intended with the largest sample time, which would probably be ok for the original poster too, since he only needed a slower rate. For the target I'm using this is the slowest option, which still takes 32550 readings per second (with 170MHz core).

sConfig.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;

RNiir.1
Associate

My solution for this problem is to move DMA init before ADC init. I see that it is in correct order on that code on first post, but this is maybe 4. time I check this same post and try to find reason for DMA not working.

So note for Cube team and for my self check that DMA _Init is done first, and if not change from Project Manager->Advanced Settings.

Thank you very much for your replay @RNiir.1​ 

I was on a STM32WB5MMG and configured the ADC with the following settings:

// ADC Init 
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.NbrOfConversion = 4;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_OVERWRITTEN;
hadc1.Init.OversamplingMode = DISABLE;
HAL_ADC_Init(&hadc1);
 
// DMA Init
hdma_adc1.Instance = DMA1_Channel1;
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_LOW;
HAL_DMA_Init(&hdma_adc1);

And then I started the conversion with the function HAL_ADC_Start_DMA() (after HAL_ADCEx_Calibration_Start() ). But my read array from the ADC was all '0'

After hours of debugging and changing things, I found your comment and saw that my initialization order of the CubeMX generated code was

MX_ADC1_Init();
MX_DMA_Init();

So I exchanged it and initialized the DMA before the ADC and after this it was working.

So correct way was going to CubeMX -> Project Manager -> Advanced Settings -> Generated Functions calls - resulting in the correct behavior

MX_DMA_Init();
MX_ADC1_Init():

Update to CubeIDE 1.9.0 / CubeMX 6.5.0

While writing this reply, I upgraded to CubeIDE 1.9.0 / CubeMX 6.5.0 (from CubeIDE 1.8.0, CubeMX 6.4.0) and it seems like, that it's not possible anymore that this mistake is done. DMA Init is always called before ADC Init (I tried it with an old and with a new project)

DaveBranton
Associate

I'm not sure who might come here in the future, but I'm not using CubeMX and the answer to this problem for me was that I needed to set the "DDS" bit in the CR2 register for the ADC config. Not just the "DMA" bit.