cancel
Showing results for 
Search instead for 
Did you mean: 

ADC high precision challenge

ingwmeier
Senior

Most 12bit ADCs of ST are specified not very accurate in DNL, INL, and total unadjusted Error. Especially DNL allows worst case scenarios of results being non-consecutive.

I use these ADCs for several years now for slow signals and found ways in HW- and SW-design to be very accurate and reliable, with thousands of products in use. I always use SW started ADC and DMA sequences across 3..xx channels. I most of the times take 50 samples per channel and use these values for further computations.

I use an external  LM4040-2.5V reference, where absolute values matter, in many cases (Pt1000, NTC, Potentiometers etc) ratiometric results matter, because everything is relative to VDD.

Some things I found:

STM32F0xx suffer from crosstalk/charge issues if samples are taken fast, and resistance is intermediate (Potentiometers). C-loading at input pins sometimes make matters worse. What helps is starting sequences only every 2..5msec instead of 100usec for example.

Internal Temperature Sensor reads values too high, even if uController does not dissipate much heat (24MHz clock).

STM32L4xx show superior results using oversampling 2x, crosstalk/charge issues are much better. Internal Temperature Sensor reads values quite accurate. We can use <= 1msec per sequence.

I would like to issue a precision investigation challenge with following or similar scenario:

Setup a system with 32MHz on a STM32L4xx (Nucleo) Board (or new G0 / G4 boards)

Use a PWM output with a resolution of 8000cnts (=4kHz PWM)

Make a 22kOhm 22uF R-C filter to an ADC (this keeps delta voltage < 1 LSB, R x C=0.5sec)

Make another ADC with a variable input voltage.

Make a sequence with these two inputs plus internal voltage and internal temperature.

Observe the results using debugger, use stable fixed and inc/decrementing PWM to find DNL situations. Would like to discuss the results and any remarks are welcome.

static void addMeanValues(void)
{
  uint16_t i;
 
  //Driver_usecTimer(Driver_TM_START);
  for (i = 0; i < (uint16_t)AD_BUFFER_SIZE; i++)
  {
    AD_Mean[i] += aADCxConvertedValues[i];
  }
  AD_MeanCounter++;
}
 
static void checkMeanValuesResult(void)
{
  uint16_t i;
 
  if (AD_MeanCounter == 50U) // 50 x 5msec -> 250msec
  {
    //Driver_usecTimer(Driver_TM_START);
    for (i = 0; i < (uint16_t)AD_BUFFER_SIZE; i++)
    {
      AD_Result[i] = AD_Mean[i] / 2U;
      AD_Mean[i] = 0U;
    }
    NewADC = true;
    CalibRequest = true;
    AD_MeanCounter = 0U;
  }
}
 
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef * hadc)
{
  (void)hadc;
  static uint32_t adccounter = 0u;
 
  adccounter++; //lint -e550
  NewConversionReady = true;
}
 
void Timer100usec(void)
{
  static uint16_t msecdivider = 0U;
 
  switch (msecdivider)
  {
  case 0:
    TimerFlag.T1msec = true;
    if (CalibRequest)
    {
      LL_ADC_Disable(ADC1);
    }
    break;
 
  case 1:
    if (CalibRequest)
    {
      LL_ADC_StartCalibration(ADC1, ADC_SINGLE_ENDED);
    }
    break;
 
  case 3:
    if (CalibRequest)
    {
      CalibRequest = false;
      LL_ADC_Enable(ADC1);
    }
    break;
 
  case 4:
    if (Sys.ADCok)
    {
      LL_ADC_REG_StartConversion(ADC1);
    }
    break;
 
  case 5:
    if (NewConversionReady)
    {
      static uint16_t oldValue = 0;
 
      addMeanValues(); // 19usec
    }
    break;
 
  case 6:
    if (NewConversionReady)
    {
      NewConversionReady = false;
      checkMeanValuesResult(); // 11usec
    }
    break;
 
  case 9:
    SecDivider++;
    if (SecDivider >= 1000U)
    {
      SecDivider = 0;
      TimerFlag.T1sec = true;
    }
    break;
  default:
    // empty
    break;
  }
  msecdivider++;
  if (msecdivider >= 10U)
  {
    msecdivider = 0;
  }
}
 
struct
{
  uint16_t Result[20];
  uint16_t Buffer[40];
  uint16_t PWM;
  uint16_t Index;
  uint16_t MainIndex;
  uint8_t  Start;
} ADtest;
 
In main loop:
 
    if (NewADC)
    {
      NewADC = false;
        ADtest.Buffer[ADtest.Index++] = (uint16_t)(AD_Result[AD_NTC] / 25u);
        if(ADtest.Index == 20u)
        {
          ADtest.Result[ADtest.MainIndex++] = (ADtest.Buffer[19]+ADtest.Buffer[18]+ADtest.Buffer[17]+ADtest.Buffer[16]) / 2u;
          if(ADtest.MainIndex==20u)
          {
            ADtest.MainIndex=0;
          }
 
          ADtest.Index = 0;
          if(ADtest.Start==1)
          {
            ADtest.PWM++;
            if(ADtest.PWM>8000) ADtest.PWM = 0;
          }
          else if(ADtest.Start==2)
          {
            ADtest.PWM--;
            if(ADtest.PWM>8000) ADtest.PWM = 8000;
          }
          __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, ADtest.PWM);
        }
      }

2 REPLIES 2
ingwmeier
Senior

forgot to mention, 2x HW oversampling used in this example

ingwmeier
Senior

made some tests: never found a non-consecutive situation within above 14bit ADtest.Result table.

difference between 1/8000 PWM increments is always 1 or 2 counts.

At any given PWM setting, the max count deviation is 1cnt (observed for about 1min). Will now do some longterm observations.