Arduino oversampling and Decimation (O & D) is a method you can use to increase the resolution of any ADC. Fundamental equations [1] show that if you increase the number of samples by a factor of four, then the bit resolution of the ADC increases by an extra bit!
You can take the humble Arduino 10-bit ADC and turn it into a 14-bit
ADC (or more!) just by sampling the analogue input 256 times and
shifting the
result right by 4 bits! this does sound like a bit of magic but it does
work. Of course you don't get these extra bits for free; The bandwidth
of the input signal is reduced and it takes more time to gather the
samples.
The mathematics [1] show that for n bits of extra resolution, you sample the input 4n
times, and for decimation (the output) you shift the result right by n
bits. The following table shows what you can do with the Arduino ADC:
New Bits |
Extra samples |
Decimation (right shift) |
Bit resolution achieved |
1 |
4 |
1 |
11 |
2 |
16 |
2 |
12 |
3 |
64 |
3 |
13 |
4 |
256 |
4 |
14 |
5 |
512 |
5 |
15 |
6 |
1024 |
6 |
16 |
It is really a fancy way to do some averaging with the mathematics
showing that you get the benefit of an extra bit (or more bits depending
on how many samples you read). However you don't divide by the number
of samples taken - you decimate instead (or shift right).
The method does not average the samples for noise reduction. You can
still do that on top of this method - by averaging.
It does sound a bit too good to be true but there is a cost and that cost comes in the form of:
Every factor of four increase in the number of samples reduces the
bandwidth that is readable by four (due to the Nyquist theorem - since
the sampling rate is reduced by a factor of 4), so Arduino ADC
oversampling does not come for free. The benefit is you can increase the
resolution of any ADC without adding any more hardware!
The following table shows the Nyquist frequency (usable bandwidth)
and voltage resolution achievable with the Arduino ADC running at 125kHz
(ADC clock see the Arduino ADC) - the normal ADC Sample rate is 9.615kHz See this page :
New Bits |
Voltage resolution |
Bandwidth |
Bit resolution achieved |
0 |
4.883mV | 4810Hz |
10 |
1 |
2.441mV | 1202.5Hz | 11 |
2 |
1.221mV | 300.63Hz |
12 |
3 |
610.352uV | 75.16Hz |
13 |
4 |
305.176uV | 18.79Hz |
14 |
5 |
152.588uV | 4.697Hz |
15 |
6 |
76.294uV | 1.174Hz |
16 |
The other requirement is quite strange and it is...
...Gaussian noise is required for the method to work.
In fact, to work properly, you need electronic noise on the input
signal entering the ADC chip (it is needed for toggling the lowest bit) -
without it the average around the lowest bit can not be seen. Adding an
artificial noise signal is known as dithering.
If you have made a really good system with very low noise then you will be in trouble! You have to have noise and there are recommended circuits to inject noise into the ADC input signal! (its probably not a problem working with Arduino on the bench!).
The noise required is between:
0.5 LSB and 2 LSB and should be at least 1 LSB [2].
The output values can not be of the same range as the original ADC
output. You don't get an Oversampled and Decimated reading returned as a
10bit value that you started with; instead you have:
New Bits |
Voltage resolution | Bit resolution achieved | Decimated output |
0 |
4.883mV | 10 |
0 ~ 1023 |
1 |
2.441mV | 11 | 0 ~ 2047 |
2 |
1.221mV | 12 | 0 ~ 4095 |
3 |
610.352uV | 13 | 0 ~ 8191 |
4 |
305.176uV | 14 | 0 ~ 16383 |
5 |
152.588uV |
15 |
0 ~ 32767 |
6 |
76.294uV |
16 |
0 ~ 65535 |
In the decimated output, one is subtracted from the output range to account for representation of zero.
Since this does sound "too good to be true" lets do some testing to find out.
For the tests, you need to setup the Arduino to make measurements
using the internal voltage reference - The tests below use an Arduino
Nano and a DAC (the MCP4725).
Note the internal reference has a large offset (due to manufacturing) but is fairly stable - find out more here
- if you want ~1% accuracy then you can calibrate it as described in
the link. However, the tests below do not require you to calibrate the
reference since you only need to see the difference between decimated
and undecimated output.
The idea is to generate a voltage from the MCP4725 that is fed into
the Arduino ADC for testing. You can compare the undecimated output to
the n-decimation output and check that the method works by viewing the
output on a spreadsheet.
An external DAC is used to generate the input signal to A0 and the
internal bandgap reference used for more accurate measurement. Both
decimated and raw ADC values are output by the program.
For testing Arduino Oversampling use an Arduino Nano (or Uno) and connect it as follows:
Arduino | MCP4725 |
---|---|
5V |
VDD |
GND |
GND |
A5 |
SCL |
A4 |
SDA |
GND |
A0 _ this the MCP4725 address |
A0 - Arduino Analogue input |
Output to the A0 input. |
This sketch outputs DAC values from 2500 to 2559 which are read by the Arduino analog input pin A0.
// ADC oversampling
#include <Adafruit_MCP4725.h>
Adafruit_MCP4725 dac;
void simpleTestA0withDacAndDecimate(byte n) {
char buf[20];
byte adsGain;
analogReference(INTERNAL); // 1V1 ref = less noise?
for(int lp=0;lp<50;lp++)
analogRead(A0); // Throw away 1st readings to allow ADC to settle, 10 too low.
delay(2); // Let settle
float fSamples=pow(4,(float) n);
int samples = (int)(fSamples+0.5); // solves : when n = 3 get get pow(4,n) of 63
Serial.print("Extra bits ");
Serial.print(n);
Serial.print(" samples ");
Serial.println(samples);
byte numPrint = 60;
for(int i=2500;i<2500+numPrint;i++) {
// Dac output voltage ~ mid range
dac.setVoltage(i, false);
Serial.print(itoa(i,buf,10));
// Arduino ADC A0 normal reading, averaged.
//
long v = 0;
for (byte j=0;j<10;j++) v+= analogRead(A0); // analogRead(A0);
v/=10;
Serial.print("\t");
itoa(v,buf,10);
Serial.print(buf);
Serial.print("\t");
// Decimation for Arduino ADC A0, averaged then decimated.
long dv=0;
for (byte avg=0;avg<10;avg++)
for (int j=0;j<samples;j++) dv+= analogRead(A0); // analogRead(A0);
dv=(dv/10);
dv = dv>>n;
ltoa(dv,buf,10);
Serial.println(buf);
if (Serial.available()) return; // Allow keyboard exit
}
analogReference(DEFAULT);
}
void setup(void) {
Serial.begin(115200);
dac.begin(0x61);
simpleTestA0withDacAndDecimate(4);
}
void loop(void) {
}
I used OpenOffice for viewing the output from the program.
Simply copy the three lines of data into the spreadsheet - they are tab separated so are put into three columns.
The first column is the DAC value used. The second is the Arduino ADC
without decimation and the third is the Arduino output with decimation.
The reference voltage (measured with an ADS1115) is : 1.070125V
The output for the first and last measurements is:
2500 751 12001
2501 750 12006
2502 750 12009
2503 749 12014
2504 751 12018
2505 751 12023
2506 751 12027
2507 751 12032
2508 751 12036
...
2552 764 12241
2553 764 12247
2554 765 12252
2555 765 12257
2556 766 12261
2557 766 12266
2558 766 12270
2559 767 12274
The data for the above results is in this file.
From the table above (for 4 bit decimation) resulting in a 14 ADC
output the maximum value ADC is 16383 pow(2,14)-1. and the resolution is
Vref/(pow(2,14) = 1.070125/pow(2,14) = 65.3uV
This is different to the data in the table above since the table assumes Vcc as the reference.
The actual voltage output depends on the voltage source and the
divider output (and associated errors in the DAC and ADC and resistor
tolerance).
For the test setup the VCC source for the MCP4725 DAC is 2.5V (MOSFET level shifters were used) and the
output is divided by a resistive divider of 10k:10k so the maximum
output is 1.25V.
The output of the test program runs from 2500 to 2559 (keeping the output in the mid range of the DAC). So voltages range from
DACMinV = 1.25 * 2500/pow(2,12) = 0.762V
DACMaxV = 1.25 * 2559/pow(2,12) = 0.781V
Theoretical difference is 0.019V
Taking the minimum decimated output gives
1.070125 * (12001/pow(2,14)) = 0.783V
1.070125 * (12274/pow(2,14)) = 0.801V
Actual difference is 0.801-0.783 = 0.018V
The actual outputs are different to theoretical outputs because Arduino
ADC and DAC have offsets which are not calibrated out by this
procedure, also the resitive divider uses 10% resistors. The difference
between the maximum and minimum is nearly identical showing range of
voltage measured is correct.
From the graphs below you can see that decimation is working i.e the decimated output graph shows finer resolution capability.
Paste the your results (three columns of data) into an OpenOffice
spreadheet, then select B-column and the choose
Menu->Insert->Chart. This brings
up a plot of the data which shows the (noisy) raw ADC value of ADC A0
(although this waas filtered using an averaging filter with averages of
10 samples.
You can see the stepped nature of the output which is defined by the "normal" resolution of the ADC.
Normal Arduino ADC operation
Select C-column and the choose Menu->Insert->Chart. This brings up a plot of the decimated data. Amazingly the steps have disappeared and a very nice linear line is shown.
Depending on noise in the system you may get a more bumpy output.
There are many noise sources including ,the power supply (USB is noisy),
The Nano layout itself (not analogue friendly).
This shows that the ADC is now capable
of finer resolution - and that is just by doing a bit of averaging and
shifting!
Arduino Oversampling ADC operation
Too much noise can give a less linear output - but too little won't allow the Arduino oversampling method to work.
Note: On the Arduino Nano, since the analogue ground and analog Vcc (AVCC) are both
connected to the normal GND, and VCC respectively, Arduino Nano analogue
measurement is very noisy.
[1] Silabs application note on oversampling.
https://www.silabs.com/documents/public/application-notes/an118.pdf
[2] Microchip application note AVR121
http://ww1.microchip.com/downloads/en/AppNotes/doc8003.pdf
Comments
Have your say about what you just read! Leave me a comment in the box below.
Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.