cancel
Showing results for 
Search instead for 
Did you mean: 

STM32G4: First ADC samples are not correct

Pete Dietl
Associate II

Summary: I am sampling the internal voltage reference 5 times using DMA with the ADC set to continuous mode with LL_DMA_LIMITED. I know about the errata that says to ignore the first ADC value, but the other 4 values only become consistent if I use a

HAL_Delay(1)

after enabling the ADC. Am I missing something?

Relevant code without the delay:

#include "main.h"
#include <stdio.h>
#include <string.h>
#include <assert.h>
 
#define ARRAY_SIZE(x) (sizeof (x) / sizeof *(x))
 
static uint16_t adc_data[5];
static volatile int dma_interrupt_done = 0;
 
// runs inside main
void main_user_code(void) {
	printf("Now booting!\r\n");
 
	// Setup DMA
	LL_DMA_SetPeriphAddress(DMA1, LL_DMA_CHANNEL_1,
			LL_ADC_DMA_GetRegAddr(ADC1, 0));
	LL_DMA_SetMemoryAddress(DMA1, LL_DMA_CHANNEL_1, (uintptr_t) &adc_data);
	LL_DMA_EnableIT_TC(DMA1, LL_DMA_CHANNEL_1);
	LL_DMA_EnableIT_TE(DMA1, LL_DMA_CHANNEL_1);
 
	// Setup ADC
	LL_ADC_StartCalibration(ADC1, LL_ADC_SINGLE_ENDED);
	while (LL_ADC_IsCalibrationOnGoing(ADC1)) {}
	// Delay needed between end of calibration and beginning of enabling. 100ms is overkill.
	HAL_Delay(100);
	LL_ADC_ClearFlag_ADRDY(ADC1);
	LL_ADC_Enable(ADC1);
	while (!LL_ADC_IsActiveFlag_ADRDY(ADC1)) {}
 
	// HAL_Delay(1);
 
	while (1) {
		// To change the data length, which must be reset after every time DMA finishes, we must first disable the relevant DMA channel.
		LL_DMA_DisableChannel(DMA1, LL_DMA_CHANNEL_1);
		LL_DMA_SetDataLength(DMA1, LL_DMA_CHANNEL_1, ARRAY_SIZE(adc_data));
		LL_DMA_EnableChannel(DMA1, LL_DMA_CHANNEL_1);
 
		LL_ADC_REG_StartConversion(ADC1);
 
		HAL_Delay(2000);
 
		while (!dma_interrupt_done) {
		};
 
		printf("ADC Results:\r\n");
		for (size_t i = 0; i < ARRAY_SIZE(adc_data); ++i) {
			printf("  ADC sample [%u] = %u\r\n", i, adc_data[i]);
		}
 
		uint16_t cal = *(uint16_t*) VREFINT_CAL_ADDR;
		static_assert(ARRAY_SIZE(adc_data) > 1, "We ignore the first sample per the errata list.");
		float vref = (VREFINT_CAL_VREF * cal / (float) adc_data[1]) / 1000;
		printf("VREF+ (using ADC result index 1)= %.3fV\r\n", vref);
 
		memset(adc_data, 0, sizeof adc_data);
		dma_interrupt_done = 0;
	}
}
 
// runs inside DMA handler
void dma_interrupt_handler_code(void) {
	dma_interrupt_done = 1;
	LL_DMA_ClearFlag_TC1(DMA1);
}

Terminal output below. Do you see how samples 1..3 are a bit high and then start lowering until they are consistent?

Now booting!
ADC Results:
  ADC sample [0] = 1514
  ADC sample [1] = 1519
  ADC sample [2] = 1511
  ADC sample [3] = 1503
  ADC sample [4] = 1499
VREF+ (using ADC result index 1)= 3.267V
ADC Results:
  ADC sample [0] = 0
  ADC sample [1] = 1473
  ADC sample [2] = 1492
  ADC sample [3] = 1491
  ADC sample [4] = 1490
VREF+ (using ADC result index 1)= 3.369V
ADC Results:
  ADC sample [0] = 0
  ADC sample [1] = 1473
  ADC sample [2] = 1493
  ADC sample [3] = 1492
  ADC sample [4] = 1492
VREF+ (using ADC result index 1)= 3.369V
ADC Results:
  ADC sample [0] = 0
  ADC sample [1] = 1474
  ADC sample [2] = 1493
  ADC sample [3] = 1491
  ADC sample [4] = 1491
VREF+ (using ADC result index 1)= 3.366V
ADC Results:
  ADC sample [0] = 0
  ADC sample [1] = 1475
  ADC sample [2] = 1494
  ADC sample [3] = 1493
  ADC sample [4] = 1492
VREF+ (using ADC result index 1)= 3.364V
ADC Results:
  ADC sample [0] = 0
  ADC sample [1] = 1471
  ADC sample [2] = 1491
  ADC sample [3] = 1491
  ADC sample [4] = 1490
VREF+ (using ADC result index 1)= 3.373V

// Generated ADC init code
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_ADC_CommonInitTypeDef ADC_CommonInitStruct = {0};
 
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
 
  /** Initializes the peripherals clocks
  */
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC12;
  PeriphClkInit.Adc12ClockSelection = RCC_ADC12CLKSOURCE_SYSCLK;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
 
  /* Peripheral clock enable */
  LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_ADC12);
 
  /* ADC1 DMA Init */
 
  /* ADC1 Init */
  LL_DMA_SetPeriphRequest(DMA1, LL_DMA_CHANNEL_1, LL_DMAMUX_REQ_ADC1);
 
  LL_DMA_SetDataTransferDirection(DMA1, LL_DMA_CHANNEL_1, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
 
  LL_DMA_SetChannelPriorityLevel(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PRIORITY_LOW);
 
  LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_NORMAL);
 
  LL_DMA_SetPeriphIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PERIPH_NOINCREMENT);
 
  LL_DMA_SetMemoryIncMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MEMORY_INCREMENT);
 
  LL_DMA_SetPeriphSize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_PDATAALIGN_HALFWORD);
 
  LL_DMA_SetMemorySize(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MDATAALIGN_HALFWORD);
 
  /* USER CODE BEGIN ADC1_Init 1 */
 
  /* USER CODE END ADC1_Init 1 */
 
  /** Common config
  */
  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);
  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_LIMITED;
  ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_PRESERVED;
  LL_ADC_REG_Init(ADC1, &ADC_REG_InitStruct);
  LL_ADC_SetGainCompensation(ADC1, 0);
  LL_ADC_SetOverSamplingScope(ADC1, LL_ADC_OVS_DISABLE);
  ADC_CommonInitStruct.CommonClock = LL_ADC_CLOCK_SYNC_PCLK_DIV4;
  ADC_CommonInitStruct.Multimode = LL_ADC_MULTI_INDEPENDENT;
  LL_ADC_CommonInit(__LL_ADC_COMMON_INSTANCE(ADC1), &ADC_CommonInitStruct);
 
  /* Disable ADC deep power down (enabled by default after reset state) */
  LL_ADC_DisableDeepPowerDown(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_VREFINT);
  LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SAMPLINGTIME_2CYCLES_5);
  LL_ADC_SetChannelSingleDiff(ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SINGLE_ENDED);
  LL_ADC_SetCommonPathInternalCh(__LL_ADC_COMMON_INSTANCE(ADC1), LL_ADC_PATH_INTERNAL_VREFINT);
  /* USER CODE BEGIN ADC1_Init 2 */
 
  /* USER CODE END ADC1_Init 2 */
 
}
// Generated DMA init code
static void MX_DMA_Init(void)
{
 
  /* Init with LL driver */
  /* DMA controller clock enable */
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMAMUX1);
  LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA1);
 
  /* DMA interrupt init */
  /* DMA1_Channel1_IRQn interrupt configuration */
  NVIC_SetPriority(DMA1_Channel1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(),0, 0));
  NVIC_EnableIRQ(DMA1_Channel1_IRQn);
 
}

1 ACCEPTED SOLUTION

Accepted Solutions
raptorhal2
Lead

Vref and Temperature signals are high impedance sources. Increase the ADC Sampling Time.

ST has an Application Note on ADC Accuracy that explains the effect of signal impedance on sampling accuracy.

Edit: see G4 Datasheet 5.3.4 Table 20.

Cheers, Hal

View solution in original post

2 REPLIES 2
raptorhal2
Lead

Vref and Temperature signals are high impedance sources. Increase the ADC Sampling Time.

ST has an Application Note on ADC Accuracy that explains the effect of signal impedance on sampling accuracy.

Edit: see G4 Datasheet 5.3.4 Table 20.

Cheers, Hal

ravelx
Associate

I've got the similar (or the same) issue, when I was porting the a curve-tracer program(with ADC/DAC) from stm32f3 to stm32g4. Stmf3 just get a satisfactory results, with low noise of the curves, while stm32g4 did not. The noise was so wierd. Even I tired the input to gnd, glitches still exist. stm32f3 was far better in this sense.
Hope that there are some solutions or explainations for such differences.
Thank you~~