Pages: [1] 2   Go Down
Author Topic: Static Accuracy Tests of the Arduino Internal ADC.  (Read 3762 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Edison Member
*
Karma: 67
Posts: 1656
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Several people have asked about the DC accuracy of the Arduino ADC when used in my data logging applications at slow sample rates.

Here are my results of some "hobby level" measurements of the Arduino ADC.

One question is how important is the ADC clock rate.  I did measurements for an ADC clock rate of 125 kHz to 2MHz.

Another question is how much does Noise Reduction Mode help.  I did a series of measurements using this mode.

Noise Reduction Mode only reduced the mean absolute error slightly.

I do calibration to remove Offset Error and Gain Error.  Calibration is very important for good accuracy.

These tests depend on the Arduino voltage regulator providing a stable voltage during the tests.  The Arduino ADC reference voltage is Vcc for these tests.  This may not be realistic for practical applications

Integral Non-linearity (INL) is the main remaining source of error.

Here are my results for static (DC) tests of the internal ADC for three UNOs.

The Arduinos are powered by a high quality nine volt power supply.

These tests measure a DC level so do not include problems due to time jitter, S/H time, and other dynamic errors.
There are several studies of the dynamic behavior of the Arduino ADC that determine ENOB (Effective Number Of Bits).

I used a shield with a 12-bit MCP4921 DAC to generate voltage levels. This DAC has an output buffer so it provides a very low impedance source.

I measured the voltage of the DAC with a calibrated 18-bit MCP3422 ADC on the shield.

I used DAC levels from 20 to 4075 to avoid zero offset errors at low voltages and DAC buffer problems at high voltages.

Each series of measurements has 4056 data points.

This is a voltage range of about 0.023 to 4.972 volts.

I calibrated the Arduino ADC for each series of measurements with a linear fit of the form.

v = a + b*adcValue

Errors are the difference between the value measured with the 18-bit ADC and the calibrated value measured with the Arduino ADC.

I also show the results for no calibration, the NoCal column, using the datasheet formula.

Vin = Vref*adcValue/1024


The columns in the tables are:

Ideal - results for a perfect 10-bit ADC for comparison.

NoCal - datasheet formula (5/1024)*adcValue with Noise Reduction Mode.

NR128 - Noise Reduction mode with Prescaler of 128 (ADC clock of 125 kHz).

PS64 - analogRead with Prescaler of 128 (ADC clock of 125 kHz).

PS64 - analogRead with Prescaler of 64 (ADC clock of 250 kHz).

PS32 - analogRead with Prescaler of 32 (ADC clock of 500 kHz).

PS16 - analogRead with Prescaler of 16 (ADC clock of 1 MHz).

PS8 - analogRead with Prescaler of 8 (ADC clock of 2 MHz).


The rows in the tables tables are.

Min - minimum error in millivolts

Max - maximum error in millivolts

MAE - mean absolute error in millivolts


Results for three UNO Arduinos (sorry about the ragged tables, I haven't mastered table insertion)

    First Arduino - Error Millivolts
      
       Ideal   NoCal  NR128  PS128   PS64   PS32   PS16    PS8
Min   -2.44  -2.43    -3.72   -4.01  -3.88  -4.53  -6.57  -27.18
Max   2.44  11.69     3.74    4.24    4.15   5.17   8.69   23.21
MAE   1.22   5.02     1.33    1.38    1.37   1.44   1.96     4.11

     Second Arduino - Error Millivolts

       Ideal   NoCal  NR128  PS128   PS64   PS32   PS16    PS8
Min   -2.44  -9.24    -4.87   -4.86  -5.05  -5.34  -6.52  -24.04
Max   2.44  11.62     3.95    4.64    4.69   5.71    8.41   21.29
MAE   1.22   5.33      1.41    1.43   1.44   1.53     2.02    4.05

     Third Arduino - Error Millivolts

       Ideal   NoCal  NR128  PS128   PS64   PS32   PS16    PS8
Min  -2.44   -7.88    -4.12   -4.40   -4.32  -4.41  -6.97  -26.93
Max  2.44   12.53     3.80    4.04     4.18   5.27   8.84   24.59
MAE  1.22    4.85     1.29     1.33    1.34    1.42   1.91    4.10
« Last Edit: August 30, 2012, 12:40:14 pm by fat16lib » Logged

Offline Offline
Edison Member
*
Karma: 8
Posts: 1341
If you're not living on the Edge, you're taking up too much space!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

This explains why we can sample audio accurately at 40khz. 
How long does it take to sample 1x at PS16?
Logged

If you fall... I'll be there for you!
-Floor

Skype Brighteyes3333
(262) 696-9619

0
Offline Offline
Shannon Member
****
Karma: 215
Posts: 12467
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Be interesting to see the accuracy drop when running at higher speeds with 10k source impedance and alternating between different voltages on different analog pins.

Oh - and retest at both extremes of the temperature range.
Logged

[ I won't respond to messages, use the forum please ]

0
Offline Offline
Edison Member
*
Karma: 67
Posts: 1656
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It is possible to record 8-bit 44 ksps audio at PS16. Voice is not bad, like a cheap recorder.  I wrote a library to do it, WaveRP, http://code.google.com/p/waverp/downloads/list.

I also did a 8-bit 100,000 sample per second logger, AnalogIsrLogger20120810.zip http://code.google.com/p/beta-lib/downloads/list.  The Arduino gets an ENOB of about 7.5 at this speed.  You need a low impedance source.  I did a lot of work to reduce time jitter.

MarkT,

These tests are not appropriate for dynamic ADC performance. 

Here is a good article http://www.openmusiclabs.com/learning/digital/atmega-adc/.

You just can't use the AVR Arduino at high sample rates with high impedance sources.  If the ADC clock is 1MHz, the sample and hold time is on the order of a microsecond.

I like to have 10 time constant, RC, periods to charge the sample and hold cap.  10 K Ohms is just too large.

Calibration would change with temperature since gain and offset change.

Integral Nonlinearity is usually less dependent on temperature.

Atmel doesn't publish much about the properties of the AVR ADC so you can't be sure without tests.  Doing tests of ADC properties as a function of temperature for the AVR ADC would be a pain.

The Arduino ADC is not bad for most hobby use with low impedance sources.
Logged

Offline Offline
Edison Member
*
Karma: 8
Posts: 1341
If you're not living on the Edge, you're taking up too much space!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I like these 2 libraries!  I remember reading about it back in Sep.  Is the documentation in the Zip file?
How did you solve the problem of writing to SD?  Allocating a new cluster takes 100's of msecs.
Did you preallocate all the clusters in advance?
How do you know the size?

I have created something similar, but I throw away data when allocating a new cluster.
I'd like to redo this to send via Wifly, but it seems impossible because of the slow throughput speed.

Logged

If you fall... I'll be there for you!
-Floor

Skype Brighteyes3333
(262) 696-9619

0
Offline Offline
Edison Member
*
Karma: 67
Posts: 1656
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

There is documentation in the zip files.

I originally designed SdFat for recording audio and fast data logging.  I added features similar to the POSIX real-time extensions for files.

Large contiguous files can be created quickly.  There is a method to do raw writes to the SD using an efficient multi-block mode.  When a file is closed, the unused space can be truncated.

This allows very low latency writes to high quality SD cards.  The best cards are standard cards (2 GB or less) such as the SanDisk Extreme III.  Manufacturers have stopped producing many of these consumer grade cards so I am now experimenting with industrial grade cards like those made by Swissbit.

The write time for a 512 byte block is about 820 usec with good cards.

I have had some luck with SDHC cards like the 4GB and 8 GB SanDisk Extreme cards but manufacturers assume devices that use these cards have lots of RAM for buffering and can tolerate an occasional long write latency.

Download the 100,000 sample per second logger for an example that uses of these features.
Logged

Offline Offline
Edison Member
*
Karma: 8
Posts: 1341
If you're not living on the Edge, you're taking up too much space!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Can I sample at 40ksps, average 2 samples so it's only 20, then slow down your 100k library?
Or just use WaveRP.  Which is better?  Easier?
Logged

If you fall... I'll be there for you!
-Floor

Skype Brighteyes3333
(262) 696-9619

0
Offline Offline
Edison Member
*
Karma: 67
Posts: 1656
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Both WaveRP and AnalogIsrLogger have configurable sample rates.  You need to look at the programs and documentation for more details.
Logged

Offline Offline
Full Member
***
Karma: 0
Posts: 179
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Code:
Vin = Vref*adcValue/1024

Just a knit pick but this is a discussion about calibration: FSD is 1023 and corresponds to the Aref voltage.

I'm pretty sure the data sheet indicates dividing by 1023.

The doc says you can sample channel 9 for Aref and 15 for ground (though I have not tried it) I have started to change the pin=(pin & 7) lines to make this possible.

I also did a quick "hobby" quality linearity etc. check as a first test at intervals of 1/6 fsd. The lowest was off by 2LSB on the first chip. Since I had a spare Atmel , I swapped it into the same board and only got 1LSB error on the same test.

Seems select on test can be worth while. Save the less accurate chips for non analogue uses.

Noise and stability seemed excellent. sampling two channels connected to lead acid batteries ( via a preamp board ! ) did not show one bit of change (ie zero LSB movement).

Having provided the Arduino with a bandgap ref of 5.00V as a reference my  readings were
Code:
# sample 125/128
ch_0 = 0 = 0.0000000000 current=0.0000000000
ch_1 = 825 = 26.1755523681 power=0.0000000000
ch_2 = 743 = 12.9945077896 power=0.0000000000
ch_3 = 0 = 0.0000000000
ch_4 = 0 = 0.0000000000
ch_5 = 0 = 0.0000000000

checking ch2 with a good quality (3,5 digit accuracy) DVM showed it flickering between 12.99 and 13.00V

So it looks  like I have a good chip but I'm also favourably impressed with the hardware design of the Uno.
Logged

0
Offline Offline
Shannon Member
****
Karma: 215
Posts: 12467
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset


MarkT,

These tests are not appropriate for dynamic ADC performance. 

Here is a good article http://www.openmusiclabs.com/learning/digital/atmega-adc/.

You just can't use the AVR Arduino at high sample rates with high impedance sources.  If the ADC clock is 1MHz, the sample and hold time is on the order of a microsecond.


My point is to see the real-world degradation of performance (perhaps at several impedance levels), I'm sure the datasheet isn't far wrong, but it would be nice to see "typical" behaviour for faster operation at high impedance - explore the error against the 2 dimensions of speed and input impedance...
Logged

[ I won't respond to messages, use the forum please ]

Offline Offline
Full Member
***
Karma: 0
Posts: 179
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

speed is really the same thing as the impedance problem. It is the impedance of the input capacitance (parasitic and S/H cap) that changes the input impedance , hence the relative limit of the impedance you need to drive it.

This will affect the extremes more than the mid range values since the other side of the s/h is at Vref/2

If you want "typical" values you'll need to specify what you mean by that, then buy one arduino and suitably large sample of replacement atmega chips (preferably from a variety of sources).

I found a notable difference in the two I had available.
Logged

0
Offline Offline
Edison Member
*
Karma: 67
Posts: 1656
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

ardnut,
I would have expected 1023 also but the datasheet and Atmel app notes say 1024.
Quote
ADC = VIN ⋅ 1024/VREF
Data sheets for most SAR ADCs use 2^n where n is the number of bits.  I often use 2^n-1 since that is full scale.

MarkT,

Here is a demonstration of how the ADC MUX and S/H are affected by external resistance.

I connected analog pin 0 to ground and analog pin 1 to 5V through a resistor.  I used resistor values from 10K to 470K.

I used  ADC prescalers values from 16 to 128 which results in an ADC clock from 1MHz to 125kHz in this sketch.
Code:
const uint8_t ADC_PS_16  = (1 << ADPS2);
const uint8_t ADC_PS_32  = (1 << ADPS2) | (1 << ADPS0);
const uint8_t ADC_PS_64  = (1 << ADPS2) | (1 << ADPS1);
const uint8_t ADC_PS_128 = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
const uint8_t ADC_PS_BITS = ADC_PS_128;

void setup() {
  Serial.begin(9600);
  ADCSRA &= ~ADC_PS_BITS;
  ADCSRA |= ADC_PS_128;    
}
void loop() {
  uint16_t v0 = analogRead(0);
  uint16_t v1 = analogRead(1);
  Serial.print(v0);
  Serial.write(',');
  Serial.println(v1);
  delay(500);
}
Here are the results for the value of v1.  It should be 1023.

10K22K33K47K68K100K220K330K470K
PS16970920880840805770727710704
PS3210231015997970932883797760745
PS641023102210191010990950858808785
PS128102310231023102310221016964913874

Note that even with zero external resistance, PS16 has an error.  This is because the ADC input has more than 10K of internal resistance.

I then ran the test after adding a line to throw away the first reading after changing the channel to analog pin 1.  This is recommended by Atmel for more accuracy in an app note.

Here is the sketch.
Code:
const uint8_t ADC_PS_16  = (1 << ADPS2);
const uint8_t ADC_PS_32  = (1 << ADPS2) | (1 << ADPS0);
const uint8_t ADC_PS_64  = (1 << ADPS2) | (1 << ADPS1);
const uint8_t ADC_PS_128 = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
const uint8_t ADC_PS_BITS = ADC_PS_128;

void setup() {
  Serial.begin(9600);
  ADCSRA &= ~ADC_PS_BITS;
  ADCSRA |= ADC_PS_128;    
}
void loop() {
  uint16_t v0 = analogRead(0);
  analogRead(1);
  uint16_t v1 = analogRead(1);
  Serial.print(v0);
  Serial.write(',');
  Serial.println(v1);
  delay(500);
}

Here are the results.

10K22K33K47K68K100K220K330K470K
PS1610231023102210191012999947901870
PS32102310231023102310221017992966941
PS641023102310231023102310221010997983
PS128102310231023102310231023102210161011

If you have a very high impedance sensor you can add a one ms delay after the first analogRead(1) with PS128.  Then even a 1Meg resistor will result in 1022 or 1023.
Code:
 analogRead(1);
  delay(1);
  uint16_t v1 = analogRead(1);

« Last Edit: August 31, 2012, 11:47:03 am by fat16lib » Logged

0
Offline Offline
Edison Member
*
Karma: 67
Posts: 1656
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

After more thought, I am puzzled why Atmel specifies the formula
Quote
v = (Vref/1024)*n

for converting an ADC value, n, to voltage v.

This formula, that I sometimes use, is no better.

Quote
v = (Vref/1023)*n

An ideal 10-bit ADC returns the value n for voltages between (Vref/1024)*n and Vref/1024)*(n+1).

It seems like you should use the average value of voltage for bin n.  The formula for the average bin voltage is:
Quote
v = (Vref/1024)*(n + 0.5)

The Arduino ADC is anything but ideal so I calibrate it, as above, for accurate measurements. 

The problem with calibration is that non-linearity is a ragged function so you can't just do a few measurements to get a good calibration.

I measure the response of the ADC for over 4000 voltages.  I use a linear fit to these measurements.  This gives very good results for almost all ATmega chips.

That brings up the question of how can you select the best chip based on a few measurements?

ADC theory articles often show smooth curves for integral non-linearity but for real parts the plot is a very ragged/jagged line.  A calibration based on just a few measurements is an improvement but nowhere near optimal.

A comment on the S/H impedance measurements.  These measurements should not be relied on for real situations.  Stray capacitance becomes a key factor for high impedance sensors. 

High impedance sensors are not likely to output high frequency signals so you can improve accuracy by adding a 2-20nf cap between the sensor's analog pin and ground.

When the ADC MUX switches to this pin, the charge in the pin cap will be used to charge the S/H cap. The S/H cap is about 14pf so the S/H voltage will be accurate to 1% if you use a 2nf cap and 0.1% for a 20nf cap.

Of course the best solution is to use a good op-amp and filters to condition the signal from high impedance sensors.
Logged

Offline Offline
Full Member
***
Karma: 0
Posts: 179
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
I measure the response of the ADC for over 4000 voltages.  I use a linear fit to these measurements.  This gives very good results for almost all ATmega chips.

The biggest error is usually around change over in the MSB. Do you see evidence of that in the 4000 readings?

 If a simple linear correction is to be made , it may be better to do a separate fit for each half of the range, or the same slope would suffice but with a different offset for each half of the scale.

WRT 1023 vs 1024, I realised after thinking about the succ. approx technique that 2^N is the correct factor not 2^N-1 . The approx is done by halving the interval to find each bit, so it is based on full scale of Vref. The implication is that the ADC will never measure Vref , only Vref-1LSB as its max value .

This ties in to your +0.5 suggestion. You are correct that would be statistically more accurate.

Considering a 1 bit S-A conversion , it will only return 0 or 1 ie 0% or 50% Vref. Clearly 25% or 75% would be a more balanced representation of the whole input range.

Logged

0
Offline Offline
Edison Member
*
Karma: 67
Posts: 1656
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
The biggest error is usually around change over in the MSB. Do you see evidence of that in the 4000 readings?

No, the change from 511 to 512 is not an unusual step.  One of the big challenges in fabricating an ADC is to make that true so the part will meet the DNL spec.

To maintain a DNL (Differential Non-Linearity) of under 1/2 LSB, the bin from 511 to 512 must not be less than 0.5 LSB or more than 1.5 LSB wide.

You will gain a small bit of accuracy by fits with more parameters than a single line but I don't consider the improvement worth the effort.

In the past, most ADCs used resistor ladders.  Precision ADCs were expensive because it was necessary to laser trim the larger valued resistors to meet the DNL requirement.

Modern high precision ADCs use capacitor arrays.  It is easier to maintain the proper tolerance in fabrication of capacitors with lithography.  The array has caps with values C, C/2, C/4, C/8, ...

Since the large caps have a large area on the chip, it is easier achieve the required tolerance so laser trimming is not requied. 
Logged

Pages: [1] 2   Go Up
Jump to: