cancel
Showing results for 
Search instead for 
Did you mean: 

Unusual high and nonlinear ADC offset

EE_21
Visitor

Hello everyone,

I've been struggling with the ADC of a STM32G474QE on a custom board for some while now. I initially saw a discrepancy between an onboard NTC thermocouple and its measured ADC value. The measured Value (calculated to mV) was off by an average of 50mV over three boards. The impedance of the NTC circuit is reasonable low with 500ohm, which should result in no issues with this rather infrequent measurement with a a high sampling time. 

I then started seeing more of that behavior with other ADCs and their respective channels. There was always some offset in the measurement. To be able to quantify the offset, I ran a test, where i applied a dc voltage to the other channels, measured the voltage at the pin and compared it to the ADC values. (see attachment meas offset.png). I had very similar behavior on several boards. 

I then looked into ways to compensate this and started to implement measurements to Vrefint as injected samples after rerunning the ADC-calibration. (I always ran the calibration at startup, but was cautious about unstable supply voltage at that stage). And what I've found was, that using __LL_ADC_CALC_VREFANALOG_VOLTAGE, each board had a calculated VREF voltage ~100mV lower then the supplied 3.3V. From the previous measurement series, I took an average offset and came to the conclusion, that those ~100mV was closer to 37mV but simply scaled by 3.3V/1.2V = 2.75.

I also attached the analog supply schematics and layout of the chip to this post. N_REF (basically GND) and 3V3 are present as a powerplane on a mid-layer respectfully. The system is supplied by TLV761 LDO. With the filtering there is still some ripple of nearly 10mV, not enough to account for the errors. 

So to conclude, my ADCs reading are always off by an average off 37mV across three boards. Is this to be expected with a specified accuracy of 6.9 LSB (100 mV in my case)? https://www.st.com/resource/en/datasheet/stm32g474cb.pdf p. 143 Any ideas what can be done to compensate more of the false readings?

If looked at the errate and RM, I take two samples, if the last one was taken longer than 1ms. 
Here some (pseudo) code from my zephyr project. I use the LL library. 

/**
  * @brief ADC1 Initialization Function
  * @PAram None
  * @retval None
  */
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};

  LL_GPIO_InitTypeDef GPIO_InitStruct = {0};

  LL_RCC_SetADCClockSource(LL_RCC_ADC12_CLKSOURCE_SYSCLK);

  /* Peripheral clock enable */
  LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_ADC12);

  LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOC);
  LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA);
  /**ADC1 GPIO Configuration
  PC3   ------> ADC1_IN9
  PA0   ------> ADC1_IN1
  PA3   ------> ADC1_IN4
  */
  GPIO_InitStruct.Pin = Voltage_Meas_L1_Pin;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  LL_GPIO_Init(Voltage_Meas_L1_GPIO_Port, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = Voltage_Meas_L2_Pin;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  LL_GPIO_Init(Voltage_Meas_L2_GPIO_Port, &GPIO_InitStruct);

  GPIO_InitStruct.Pin = Voltage_Meas_L3_Pin;
  GPIO_InitStruct.Mode = LL_GPIO_MODE_ANALOG;
  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;
  LL_GPIO_Init(Voltage_Meas_L3_GPIO_Port, &GPIO_InitStruct);

  /* 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_HIGH);

  LL_DMA_SetMode(DMA1, LL_DMA_CHANNEL_1, LL_DMA_MODE_CIRCULAR);

  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_EXT_TIM3_TRGO;
  ADC_REG_InitStruct.SequencerLength = LL_ADC_REG_SEQ_SCAN_ENABLE_3RANKS;
  ADC_REG_InitStruct.SequencerDiscont = LL_ADC_REG_SEQ_DISCONT_DISABLE;
  ADC_REG_InitStruct.ContinuousMode = LL_ADC_REG_CONV_SINGLE;
  ADC_REG_InitStruct.DMATransfer = LL_ADC_REG_DMA_TRANSFER_UNLIMITED;
  ADC_REG_InitStruct.Overrun = LL_ADC_REG_OVR_DATA_OVERWRITTEN;
  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);
  LL_ADC_REG_SetTriggerEdge(ADC1, LL_ADC_REG_TRIG_EXT_RISING);

  /* 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_9);
  LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_9, LL_ADC_SAMPLINGTIME_247CYCLES_5);
  LL_ADC_SetChannelSingleDiff(ADC1, LL_ADC_CHANNEL_9, LL_ADC_SINGLE_ENDED);

  /** Configure Regular Channel
  */
  LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_2, LL_ADC_CHANNEL_1);
  LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_1, LL_ADC_SAMPLINGTIME_247CYCLES_5);
  LL_ADC_SetChannelSingleDiff(ADC1, LL_ADC_CHANNEL_1, LL_ADC_SINGLE_ENDED);

  /** Configure Regular Channel
  */
  LL_ADC_REG_SetSequencerRanks(ADC1, LL_ADC_REG_RANK_3, LL_ADC_CHANNEL_4);
  LL_ADC_SetChannelSamplingTime(ADC1, LL_ADC_CHANNEL_4, LL_ADC_SAMPLINGTIME_247CYCLES_5);
  LL_ADC_SetChannelSingleDiff(ADC1, LL_ADC_CHANNEL_4, LL_ADC_SINGLE_ENDED);
  /* USER CODE BEGIN ADC1_Init 2 */

  /* USER CODE END ADC1_Init 2 */

}


LL_ADC_SetCommonPathInternalCh( ADC12_COMMON, LL_ADC_PATH_INTERNAL_VREFINT );     
LL_ADC_INJ_SetSequencerLength( ADC1, LL_ADC_INJ_SEQ_SCAN_DISABLE );    
LL_ADC_INJ_SetSequencerRanks( ADC1, LL_ADC_INJ_RANK_1, LL_ADC_CHANNEL_VREFINT );   
LL_ADC_SetChannelSamplingTime( ADC1, LL_ADC_CHANNEL_VREFINT, LL_ADC_SAMPLINGTIME_640CYCLES_5 );
LL_ADC_INJ_SetTriggerSource( ADC1, LL_ADC_INJ_TRIG_SOFTWARE );
LL_ADC_INJ_SetTriggerEdge( ADC1, LL_ADC_REG_TRIG_EXT_RISING );

if ( ( LL_ADC_IsEnabled( ADC1) != 0 ) )
{
    StopADC_and_DMA( );
    k_usleep(300);
}

LL_ADC_StartCalibration( ADC1, LL_ADC_SINGLE_ENDED );
while ( ( LL_ADC_IsCalibrationOnGoing( ADC1 ) != 0 ) );
k_usleep(300);

if ( !LL_ADC_IsEnabled( pLine_p->config.adc.reg ) )
{
   LL_ADC_Enable( pLine_p->config.adc.reg );
   while ( !LL_ADC_IsActiveFlag_ADRDY( pLine_p->config.adc.reg ) )
}
k_usleep(300);

LL_ADC_ClearFlag_JEOC( ADC1 );
LL_ADC_ClearFlag_JEOS( ADC1 );
LL_ADC_INJ_StartConversion( ADC1 );
while ( !LL_ADC_IsActiveFlag_JEOS( ADC1) );
vref_raw = LL_ADC_INJ_ReadConversionData12(ADC1, LL_ADC_INJ_RANK_1 );
vdda_mV = __LL_ADC_CALC_VREFANALOG_VOLTAGE( vref_raw, LL_ADC_RESOLUTION_12B );

Configure DMA and start Timer. 

 

2 REPLIES 2
MasterT
Lead

Try to install capacitors close to inputs:

G474 adc decoupler.png

Also use differential mode if possible, and lower ADC internal clock - G474 has ASYNC inputs from PLL-P, something like 10-15 MHz instead of 42

Igor Cesko
ST Employee

Hello,

 

  I think that the problem is in the calibration process. The ADC itself cannot have such high offset error after calibration. Please check the behavior without calibration and with the calibration - to check if the calibration process has effect - if not then there is some mistake in the code. 

  I hope that there is no offset caused by some voltage difference between grounds of MCU and the sensor.

  Regards

                      Igor