cancel
Showing results for 
Search instead for 
Did you mean: 

Not the best ADC accuracy of STM32F0 even after calibration

Christian Wächter
Associate III
Posted on June 19, 2018 at 14:13

Hello everyone,

I'm currently trying to get as accurate ADC values as possible from a STM32F072. I'm using the NUCLEO-F072RB and wanted to use the internal stored reference voltage value for calibration. But I'm already off when I use this to calculate my actual VDDA for further ADC measurement. I double checked all my voltages with a calibrated multimeter and oscilloscope directly on the pins and with smalles possible ground loops. There are no spikes/drops in the voltage due to the ADC measurement and the ripple and noise is at around 5 mVpp. I also added 2x 10 nF and 1x 10 uF to C30 (100 nF) on AVDD on the nucleo. I tried all sampling times for the ADC without any change in the results and of course use 12-Bit. I do not want to read the value of the internal Vref-Voltage as it is not accurate (min 1.2V, typ 1.23V, max 1.25V).

So even after taking care of any possible error sources I'm getting a wrong value for my calculated AVDD voltage. I measured this with two multimeters to be 3.310V at 25�C. The calculated value from the STM32 is ~3.296 V, which is 14 mV off. This might be too much for measurements of voltages after a voltage divider, even when considering the 1% tolerance of the resistors. I read about a accuracy of ~1 mV (without voltage divider), so I'm curious how to achieve this when the reference value is not accurate.

This are parts of the code from the used test program:

// ADC self calibration, has to be done before any ADC Start/Enable

while(HAL_ADCEx_Calibration_Start(&hadc) != HAL_OK);

// values for calculation of actually VDDA-supply and reference voltage

volatile uint32_t VREFINT_CAL, VREF_3V3 = 330000;

const uint16_t *p = (uint16_t *)0x1FFFF7BA; // 2 Byte at this address is VRefInt @3.3V/30�C

VREFINT_CAL = *p; // read the value at pointer address

// Start ADC with DMA support

volatile uint16_t adc_value[4] = {0, 0, 0, 0};

HAL_ADC_Start_DMA(&hadc, (uint32_t*)adc_value, 4);

 

while (1)

{

   //toggle LED

   HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);

   HAL_Delay(50);

   // calculate 3.3 V supply voltage with the factory stored VREFINT_CAL value

   // adc_value[2] contains the raw ADC_IN17 value of the internal voltage reference (~1530)

   VREF_3V3 = (330000 * VREFINT_CAL) / adc_value[2];

   // VREFINT_CAL = 1529

   // adc_value[2] = 1530

   // VREF_3V3 = ~329568 (~3.29568 V calculated)

}

#accuracy #nucleo-f072rb #adc #calibration #stm32f0 #internal-voltage-reference
21 REPLIES 21
T J
Lead
Posted on June 20, 2018 at 01:45

there is sample time and ADC clock, both have to be 1 or 2 steps from Fastest possible.

Do you have any 5V pins connected to your processor ?, this will affect any A/D result.

there is an app note on the issues for ADC accuracy  AN2834

henry.dick
Senior II
Posted on June 20, 2018 at 02:06

1. make sure you have good grounding;

2. slow down the adc to the slowest possible timing;

3. adc a known good source, like the temperature sensor, voltage reference, Vcc, GND, ...

...

Christian Wächter
Associate III
Posted on June 20, 2018 at 09:41

Thank you for your replies, but...

- @T J suggests fast sampling,

henry.****

‌ slow sampling. I got the better result (=closer to the actual value) with 239 cycles sampling time

- I do not have any 5V on my microcontroller

- I know and followed AN28 What I did not do yet is to add a known AC voltage onto the value to be measured, so the error of the LSB can be eliminated. This is not even possible for the measurement of the internal voltage reference

- grounding is as good as it gets, this and the voltages for the adc have been checked with a calibrated oscilloscope

- I double checked the voltage source with two calibrated multimeters and a calibrated oscilloscope, so I assume this is good known

T J
Lead
Posted on June 20, 2018 at 13:34

do you have a USB connection ?

is that a 5V source ?

Posted on June 20, 2018 at 12:10

 ,

 ,

can you measure the voltage difference from the pins on the processor ?

Vss and Vssa and any other Vss

also all Vdds to Vdda

I just checked my F091 solution:

the Vdda calc is flashing between 3.297 / 8

the meter across the cap next to pins 13/14 is 3.297V

processor Temp reads 36.8 in an ambient temp of 20C

my code recalibrates the ADC every minute.

void readProcessorTemperature(void) {

♯ define TEMP110_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFFF7C2)) , // calibrated at 3.3V +-10mV , , @110C +/- 5C

 ,

♯ define TEMP30_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFFF7B8)) , , // calibrated at 3.3V +-10mV , , @ 30C +/- 5C

 ,

♯ define VREFINT_CAL , , , , ((uint16_t*) ((uint32_t) 0x1FFFF7BA)) , , // calibrated at 3.3V +-10mV , , @ 30C +/- 5C

 ,

♯ define VDD_CALIB ((uint16_t) (3300))

 ,

♯ define VDD_APPLI ((uint16_t) (3295))

 ,

 , ,  ,

 ,

 ,  , ,  ,int32_t AveTemperatureADC = ADC_Channel_AVE[VTemp_Ad16], , , , , // 16x over sampled, needs /16

 , , , double processorTemp, , , , , , , , , , , , , , , , , , , , // the temperature in degree Celsius

 ,

 , ,  , , ,  ,

 ,

 , , , processorTemp , = , (double) AveTemperatureADC / 16 - *TEMP30_CAL_ADDR,

 ,

 , , , processorTemp *= , 110 - 30,

 ,

 , , , processorTemp /= , ((double)*TEMP110_CAL_ADDR) - *TEMP30_CAL_ADDR,

 ,

 , , , processorTemp += , , 30,

 ,

 , , , processorTemperature = , (int32_t)(processorTemp * 100),

 ,

 , , , // , ,  , , ,  ,sprintf(string,'AveTemperatureADC %04X, ADC_CalibrationFactor %04X, *TEMP30_CAL_ADDR %04X, processorTemperature %.2f, processorTemp %.3f \n\r',AveTemperatureADC,ADC_CalibrationFactor,(int32_t) *TEMP30_CAL_ADDR,((float)processorTemperature/100),processorTemp),

 ,

 , , , // , ,  , , ,  ,puts (string),

 , , , Vdda = (double)16 * 3.3 * *VREFINT_CAL,

 ,

 , , , Vdda /= ADC_Channel_AVE[VRef_Ad17],

 ,

 ,

 ,

 , ,  ,

 ,

 , , , char string[64],

 ,

 , , , setCursor1(0, 90),

 , , , sprintf(string, 'processorTemperature %.1f , , ', ((float)processorTemperature) / 100),

 ,

 , , , puts1(string),

 ,

 , , , sprintf(string, 'Vdda as %.3f , \n\r', Vdda),

 ,

 , , , puts1(string),

 ,

 , ,
Posted on June 20, 2018 at 13:14

It is crazy to think you can get better performance out of your adc by speeding it up. It can only get worse.

Posted on June 20, 2018 at 13:26

it was a concept typo, thought it to difficult to edit and explain,

but 'at the most' set your ADC rate a few levels below maximum rate.

i also use the slowest rate 239 cycles with DMA

Posted on June 20, 2018 at 13:54

Thank you for your detailed answer. But as far as I can see, there is no difference between your and my calibration, or are you using the internal temperature value for further compensation of the VDDA value anywhere else?

I already measured the voltage directly on the STM32 pins 12 and 13 to be stable 3.310 V

Posted on June 20, 2018 at 13:56

Yes, the Nucleo board is powered via the +5V of the USB Port. But of course there is a linear voltage regulator LD39050PU33R inbetween and the generated 3.3V look clean and without noise (< 5mVpp)