cancel
Showing results for 
Search instead for 
Did you mean: 

Correct ADC settings for DMA

Stastny.Petr
Associate III

Hello, I need help with ADC and DMA settings. My application is VERY EASY but I do not understand the setup parameters. The application is that I just start CPU and the ADC raw value should independently transfer via DMA to variable. When I need the value (randomly) I just read the internal variable. I saw several youtube manuals but I still have 0 on output even when there is 1V. I use PA0 pin (ADC1, chan 0) on very easy CPU STM32G031. I need to use DMA Channel 4 (Channel 1 and 2 are used for USART_RX and TX)

My setup:

StastnyPetr_0-1722611177464.png

 

StastnyPetr_1-1722611224695.png

 

The generated code is like this (I use LL):

 

static void MX_ADC1_Init(void)
{

  /* USER CODE BEGIN ADC1_Init 0 */

  /* USER CODE END ADC1_Init 0 */

  LL_ADC_InitTypeDef ADC_InitStruct = {0};
  LL_ADC_REG_InitTypeDef ADC_REG_InitStruct = {0};

  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* Peripheral clock enable */
  LL_APB2_GRP1_EnableClock(LL_APB2_GRP1_PERIPH_ADC);

  LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
  /**ADC1 GPIO Configuration
  PA0   ------> ADC1_IN0
  */
  GPIO_InitStruct.Pin = LL_GPIO_PIN_0;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* ADC1 DMA Init */

  /* ADC1 Init */
  LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_4, LL_DMAMUX_REQ_ADC1);

  LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_4, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);

  LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_4, LL_DMA_PRIORITY_LOW);

  LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_4, LL_DMA_MODE_CIRCULAR);

  LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_4, LL_DMA_PERIPH_NOINCREMENT);

  LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_4, LL_DMA_MEMORY_INCREMENT);

  LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_4, LL_DMA_PDATAALIGN_WORD);

  LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_4, LL_DMA_MDATAALIGN_WORD);

  /* USER CODE BEGIN ADC1_Init 1 */

  /* USER CODE END ADC1_Init 1 */
  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  */

   #define ADC_CHANNEL_CONF_RDY_TIMEOUT_MS ( 1U)
   #if (USE_TIMEOUT == 1)
   uint32_t Timeout ; /* Variable used for Timeout management */
   #endif /* USE_TIMEOUT */

  ADC_InitStruct.Clock = LL_ADC_CLOCK_SYNC_PCLK_DIV2;
  ADC_InitStruct.Resolution = LL_ADC_RESOLUTION_12B;
  ADC_InitStruct.DataAlignment = LL_ADC_DATA_ALIGN_RIGHT;
  ADC_InitStruct.LowPowerMode = LL_ADC_LP_MODE_NONE;
  LL_ADC_Init(ADC1, &ADC_InitStruct);
  LL_ADC_REG_SetSequencerConfigurable(ADC1, LL_ADC_REG_SEQ_CONFIGURABLE);

   /* Poll for ADC channel configuration ready */
   #if (USE_TIMEOUT == 1)
   Timeout = ADC_CHANNEL_CONF_RDY_TIMEOUT_MS;
   #endif /* USE_TIMEOUT */
   while (LL_ADC_IsActiveFlag_CCRDY(ADC1) == 0)
     {
   #if (USE_TIMEOUT == 1)
   /* Check Systick counter flag to decrement the time-out value */
   if (LL_SYSTICK_IsActiveCounterFlag())
     {
   if(Timeout-- == 0)
         {
   Error_Handler();
         }
     }
   #endif /* USE_TIMEOUT */
     }
   /* Clear flag ADC channel configuration ready */
   LL_ADC_ClearFlag_CCRDY(ADC1);
  ADC_REG_InitStruct.TriggerSource = LL_ADC_REG_TRIG_SOFTWARE;
  ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_DISABLE;
  ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
  ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_CONTINUOUS;
  ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;
  ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_PRESERVED;
  LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
  LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE);
  LL_ADC_SetTriggerFrequencyMode(ADC1, LL_ADC_CLOCK_FREQ_MODE_HIGH);
  LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_1, LL_ADC_SAMPLINGTIME_1CYCLE_5);
  LL_ADC_SetSamplingTimeCommonChannels(ADC1, LL_ADC_SAMPLINGTIME_COMMON_2, LL_ADC_SAMPLINGTIME_1CYCLE_5);
  LL_ADC_DisableIT_EOC(ADC1);
  LL_ADC_DisableIT_EOS(ADC1);

   /* Enable ADC internal voltage regulator */
   LL_ADC_EnableInternalRegulator(ADC1);
   /* Delay for ADC internal voltage regulator stabilization. */
   /* Compute number of CPU cycles to wait for, from delay in us. */
   /* Note: Variable divided by 2 to compensate partially */
   /* CPU processing cycles (depends on compilation optimization). */
   /* Note: If system core clock frequency is below 200kHz, wait time */
   /* is only a few CPU processing cycles. */
   uint32_t wait_loop_index;
   wait_loop_index = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);
   while(wait_loop_index != 0)
     {
   wait_loop_index--;
     }
  /** Configure Regular Channel
  */
  LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_1, LL_ADC_CHANNEL_0);
  LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_0, LL_ADC_SAMPLINGTIME_COMMON_1);
  /* USER CODE BEGIN ADC1_Init 2 */

  /* USER CODE END ADC1_Init 2 */

}

static void MX_DMA_Init(void)
{

  /* Init with LL driver */
  /* DMA controller clock enable */
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);

  /* DMA interrupt init */
  /* DMA1_Channel1_IRQn interrupt configuration */
  NVIC_SetPriority(DMA1_Channel1_IRQn, 0);
  NVIC_EnableIRQ(DMA1_Channel1_IRQn);
  /* DMA1_Channel2_3_IRQn interrupt configuration */
  NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0);
  NVIC_EnableIRQ(DMA1_Channel2_3_IRQn);
  /* DMA1_Ch4_5_DMAMUX1_OVR_IRQn interrupt configuration */
  NVIC_SetPriority(DMA1_Ch4_5_DMAMUX1_OVR_IRQn, 0);
  NVIC_EnableIRQ(DMA1_Ch4_5_DMAMUX1_OVR_IRQn);

}

 

 

But how can I setup the variable where is the ADC result saved?

How can I start the ADC?

I tried to use ADC1->CR |= ADC_CR_ADSTART; and  reading ADC1->DR; but only zeroes...

Thank you

 

 

14 REPLIES 14
Stastny.Petr
Associate III

Hello,

I tried to use HAL library. I exported this settings:

StastnyPetr_0-1722802952957.png

StastnyPetr_1-1722802984701.png

 

And added 

uint16_t ADCACTValue;
  HAL_ADC_Start_DMA (&hadc1, (uint32_t *)ADCACTValue, 1);

but the result is still 0.

 

If I use for testing this in main while cycle, result is correct:

      HAL_ADC_Start(&hadc1);
      HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
      ADCACTValue = (uint16_t)HAL_ADC_GetValue(&hadc1);

 

What is wrong in my settings? Or do I need to add more code tu run this?

Thank you

 

 

This line caught my eye.

 

 

uint16_t ADCACTValue;
HAL_ADC_Start_DMA (&hadc1, (uint32_t *)ADCACTValue, 1);

 

 

HAL_ADC_Start_DMA(...) takes a pointer as the 2nd argument. ADCACTValue is not an array.  The 2nd argument should be (uint32_t*)&ADCACTValue  .

Stastny.Petr
Associate III

Thank you, you are right, but I tested all. If I use (uint32_t*)&ADCACTValue then program code stuck.

I exported the code from CubeMX and when I use HAL_ADC_Start_DMA, nothing happens.

I tested again on NUCLEO kit with HAL and same problem. I am not able to use STM32G031K8 with ADC on DMA. I must say that I spent many hours on this and no result. I really do not know where is the problem - on STM32F103 it worked, but not on STM32G031.

Hi,

1. set pointer :  (uint32_t*)&ADCACTValue

2. DMA with transfer 1 sample is totally nonsense, as "circular" = in two blocks, how should this work with 1 sample ???  -> so make an array and transfer 100 samples or whatever, maybe circular with callbacks:

 

uint16_t sampels[120];

HAL_ADC_Start_DMA(&hadc1, (uint32_t *) &sampels, 100);

 

- Dont set circular - for only 1 transfer.

-- or dont use DMA , if you really want only one sample.

3. set half-word access:

AScha3_0-1722841475597.png

4. Use HAL , not LL . (Anyway same speed : if DMA used, this is the speed of the DMA then. HAL or LL or bare metal will change just the setup/call time , maybe 1 x 1 us more, when using HAL. This is nothing to think about..)

 

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

I do understand, but what is the best solution when I only need to permanently send the actual data to one variable and when I need the data (on EXTI from external signal) I just read this variable in EXTI interrupt?

In interrupt should be only short executions so it does not make a sense to read ADC value and wait for conversion.

Maybe you can tell me the better solution?

Ok, so you want 1 sample after EXTI event ; and your right > it does not make a sense to read ADC value and wait for conversion <  , so we can use DMA or just INT .

i would go the simple way : in exti INT , start ADC with INT :  HAL_ADC_Start_IT(..)

 * @brief  Enables ADC, starts conversion of regular group with interruption.
  *         Interruptions enabled in this function:
  *          - EOC (end of conversion of regular group) or EOS (end of 
  *            sequence of regular group) depending on ADC initialization 
  *            parameter "EOCSelection" (if available)
  *          - overrun (if available)
  *         Each of these interruptions has its dedicated callback function.
  * @note:  Case of multimode enabled (for devices with several ADCs): This 
  *         function must be called for ADC slave first, then ADC master. 
  *         For ADC slave, ADC is enabled only (conversion is not started).  
  *         For ADC master, ADC is enabled and multimode conversion is started.
  * @PAram  hadc ADC handle
  * @retval HAL status
  */

Then get an INT from ADC, when your value is ready. Thats it.

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

btw

About timing, INT etc. : it depends on your ADC setting, which way is better/faster:

- if your ADC is set to average hi speed, lets say 1us conversion time, calling the conversion with INT or DMA return,

might need the same/or more time, than just blocking/wait for conversion ready .

Just think: calling an INT needs saving and restoring all cpu registers (or the 12 , as standard call doing),

so if the cpu running at 50MHz, it might need about (30..40 cycles) 1 us to execute the INT -

but this is almost same time, or more, the ADC doing its conversion.

So in this case it might be same speed or faster, to just convert 1 sample and "wait" for result, than using an INT or DMA + INT . Depending, on : how fast you can set the ADC (and dont forget ADC input impedance - need low source  impedance for fast and precise result.)

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

This is very good point, I did not realize it! Thank you!

Output impedance of my source is around 1 kiloohm, so not very small. Am I correct that in this case I need to setup as sampling time as possible?

 

StastnyPetr_0-1722853218926.png

Is necessary to have 160 Cycles?

Sampling time about 160 Cycles means on 64 MHz conversion time around (1/64meg * 160) = 2,5 us?

The cycles here are on adc-clock , not cpu-clock. So if adc at 32M -> 5us .

+

from ds:

AScha3_0-1722856082596.png

need about 200ns for < 2kohm input , so at 32M adc clock 7.5 sam.cyc. setting should be ok; 

Tconv then 20 cyc /32M -> 0.6 us .

+

How fast is your input signal ? (Because if not fast, like a pot , adding a 100nF cap at the input gives low impedance, to use short sampling time.)

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