Response of the Arduino ADC
Language: | Français • English |
---|
The Arduino analogRead()
function returns an integer
between 0 and 1023. How can this number be converted into a real
voltage? The Web provides contradictory information, where the most
common formulas are:
[math]V = V_\text{ref} \times \frac{n}{1023}[/math]
and
[math]V = V_\text{ref} \times \frac{n}{1024},[/math]
where V_{ref} is the reference voltage (typically 5 V) and n is the reading. The difference between these formulas is tiny, but is has nevertheless fueled some heated discussions. I tried to settle the problem experimentally. This experiment also provided insight into the noise level of the ADC and the usefulness of the noise for increasing the measurement resolution.
Sommaire
Usual formulas
The formulas I found on the Web all have the form
[math]V = V_\text{ref} \times \frac{n+n_0}{S},[/math]
where n_{0} is an offset (typically zero) and S is a scale factor.
An ideal 10-bit ADC converter splits the measurable voltage range into 1024 intervals, each corresponding to one possible output. The above formula is then meant to give the center of the interval corresponding to the output value n, with the possible exceptions of the first and last values (0 and 1023).
In the following, each formula is represented by the values of S and n_{0}, and in each case the splitting of the measurable range into 1024 intervals is shown graphically.
S = 1023, n_{0} = 0: Arduino tutorials
Many Arduino blogs and tutorials use this formula, including the official Arduino tutorial Read Analog Voltage. This formula splits the measurable range as follows:
0 Vref |---|-------|------- ⋅⋅⋅ ------|-------|---| ^ ^ ^ ^ 0 1 1022 1023
The top scale shows the voltage range from 0 to V_{ref}.
On the bottom scale, the voltages given by the formula for each n
are identified by the ^
character.
It can be seen that this formula makes the first and last intervals half as wide as the others.
S = 1024, n_{0} = 0: Atmel's datasheets
This is the formula given by Atmel in the ATmega328P datasheet (by the bottom of the page). This being the manufacturer's documentation, it should be considered the most authoritative.
0 Vref |---|-------|--- ⋅⋅⋅ --|-------|-----------| ^ ^ ^ ^ ^ 0 1 1022 1023 (1024)
Here, the first interval is half-width, whereas the last one is 1.5 times larger than normal. The value 1024 in parentheses is an extrapolation: a 10-bit ADC obviously cannot return 1024.
S = 1024, n_{0} = 0,5: Nick Gammon
I found this formula in Nick Gammon's site: ADC conversion on the Arduino (analogRead) – How to interpret the results. It is not widely accepted, but I like the fact that it splits the measurable range into 1024 intervals of equal width:
0 Vref |-------|-------|--- ⋅⋅⋅ --|-------|-------| ^ ^ ^ ^ 0 1 1022 1023
Measurement technique
A variable voltage is created with a potentiometer and is measured both with an Arduino and with a multimeter. Two different circuits are used for measuring voltages near zero and near V_{ref}:
near 0 near Vref Vcc Vcc ╶┬╴ ╶┬╴ ┌┴┐ ├────┐ │ │1 MΩ │ ╭─┴─╮ │ │ ┌┴┐ │ V │ └┬┘ 10 kΩ │ │ ╰─┬─╯ │ │ │←──┴─── Ain ┌┴┐ └┬┘ 10 kΩ │ │←──┬─── Ain │ │ │ ╭─┴─╮ ┌┴┐ └┬┘ │ V │ │ │ │ ╰─┬─╯ │ │1 MΩ ├────┘ └┬┘ ╶┴╴ ╶┴╴ GND GND
These circuits allow a decent resolution, both for setting the potentiometer and for reading the multimeter (the rounded rectangle with a “V” in the figure).
The ADC of the Arduino is configured in free running mode. It takes one reading every 104 µs. The readings go through a first-order low-pass filter with a time constant of 4096 measurement periods, or about 426 ms. The filter output is displayed on the serial port as a bargraph, with a resolution of 1/128 ADC steps.
Results
Here are the results from a STEMTera Breadboard, which is based on an ATmega328P and is compatible with an Arduino Uno. First, the bottom of the ADC range, from 0 to about 50 mV:
On this graph, the averaged reading is plotted as a function of the input voltage. The red crosses are the measurements. The continuous curve is a fit.
Then, the top of the range, from about V_{ref} − 50 mV to V_{ref}:
The continuous curve is a fit common to both data sets. It gives S = 1026.1 and n_{0} = 2.2, or the formula:
[math]V = V_\text{ref} \times \frac{n+2.2}{1026.1}[/math]
The fitted function has an oscillating term that models the quantization of the ADC producing a series of steps.
I repeated the experiment with an Arduino Uno rev. 2 and an Uno rev. 3, with similar results. Here is a summary of the results:
board | S | n_{0} |
---|---|---|
STEMTera | 1026,1 | 2,2 |
Uno rev. 2 | 1026,3 | 2,4 |
Uno rev. 3 | 1026,1 | 2,3 |
Discussion
The measured S and n_{0} are in principle calibration constants specific to each Arduino board. Interestingly, the constants for different boards are closer in value to one another than to their nominal values (namely 1024 and 0). From this observation, I am tempted to suggest the formula
[math]V = V_\text{ref} \times \frac{n+2}{1026}[/math]
for a non-calibrated ADC. However, this is based on only three Arduinos, which is not enough to have a reliable formula. More importantly, this shows that the question of whether one should use 1023 or 1024 is irrelevant, as the question assumes a level of accuracy that can only be achieved through a proper calibration.
These measurements show another interesting feature: the curves from the STEMTera show a series of steps. Many Web sources explain that the effective ADC resolution can be enhanced by averaging. These curves show that, even after heavy averaging (4096 data points), the original quantization is still visible. The steps are however somewhat smoothed by the combined effect of the signal noise and the averaging, but the noise here is too small to completely wash out the steps. I did no effort to minimize the noise though: I was adjusting the potentiometer with my hand, on a breadboard.
The curves measured on the two Arduino Unos (not shown) have essentially no oscillations. This shows that the intrinsic noise on those Arduinos is larger than on the STEMTera, and large enough to enable an increase of resolution through averaging.
Some attempts have been made to increase the analog resolution by adding synthetic noise to the input signal. The measurements here show that this can be useful... or not depending on the available intrinsic noise which, oddly, varies significantly from Arduino to Arduino.