2024-08-02 08:21 AM
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:
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
2024-08-04 01:25 PM
Hello,
I tried to use HAL library. I exported this settings:
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
2024-08-04 03:50 PM - edited 2024-08-04 05:06 PM
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 .
2024-08-04 11:28 PM
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.
2024-08-05 12:15 AM - edited 2024-08-05 12:26 AM
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:
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..)
2024-08-05 12:26 AM
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?
2024-08-05 12:38 AM
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.
2024-08-05 01:13 AM
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.)
2024-08-05 03:24 AM
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?
Is necessary to have 160 Cycles?
Sampling time about 160 Cycles means on 64 MHz conversion time around (1/64meg * 160) = 2,5 us?
2024-08-05 03:52 AM - edited 2024-08-05 04:11 AM
The cycles here are on adc-clock , not cpu-clock. So if adc at 32M -> 5us .
+
from ds:
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.)