## Tuesday, May 13, 2014

### Using the Arduino Uno’s built-in 10-bit to 21-bit ADC (Analog to Digital Converter)

By Gabriel Staples Written: 13 May 2014
Last Updated: 11 July 2014 - added an update in green below

A Few Other Articles to Check Out:

Using the Arduino Uno’s built-in 10-bit to 21-bit ADC (Analog to Digital Converter)???
--Wait, what did you say!? I thought that Arduinos only had a 10-bit ADC!  How can you get, for example, 16-bit resolution out of a 10-bit ADC?  Well, the answer is oversampling.  Atmel has written a really good article about it called "AVR121 Enhancing ADC resolution by oversampling."

Before I continue, I'd like to give a very special thanks to user "fat16lib," on the Adafruit Forums, who first made this technique known unto me by his post right here, thereby inspiring me to write this, my first ever, library.

Now on to the library:

------------------------------------------------------------------------------------------------------------
27 May 2014: Update: With a ton of help from Ray Benitez, of Hackscribble, I am still evaluating the practicality & legitimacy of oversampling, via experimental data collection & analysis, in order to see if it really is increasing the precision to the degree I am claiming/hoping.

------------------------------------------------------------------------------------------------------------

Download my Library (eRCaGuy_analogReadXXbit) on GitHub by clicking here --> then click the "Download Zip" link at the bottom of the right-hand pane.  Using my library, you can actually choose any precision you want between 10 and 21 bits, when using the analogReadXXbit() function!

Basically, here's how oversampling works:

For every n extra bits of precision that you want, you have to take 4^n samples at the base 10-bit precision.  You then sum all of the readings, and divide the sum by 2^n, by doing a bit-shift on the sum, to the right, n places (ie: sum >> n).  10-bit precision has an analogRead() range of 0-1023.  11-bit precision has an analogRead() range of 0-2046, 12-bit has a range of 0-4092, etc.

Each time you increase the resolution by 1-bit, you double the precision.  So, if you want 11-bit precision, that's 1 bit more than the standard 10-bits, so you have to take 4^1, or 4 samples at the standard 10-bit resolution.  You then sum the samples and divide by 2^1, or 2.  This now gives you a maximum value of 1023*4/2 = 2046.  If you want 16-bit precision, n = 6, so you need 4^6 = 4096 samples at the standard 10-bit resolution.  You sum them and divide by 2^6 = 64.  Your analogRead() max reading possible is now 4096*1023/64 = 65472.

Here's a nice table to sum it up.  You can find this spreadsheet in my library folder when you download it.

Using this concept, I have written a nice little library to tidy it all up and demonstrate oversampling.  I call it "eRCaGuy_analogReadXXbit."  The main function is:

analogReadXXbit(uint8_t analogPin, uint8_t bits_of_precision, unsigned long num_samples_to_avg)

I named it after the standard Arduino function, analogRead(), but I added the "XXbit" to indicate that you can specify the # of bits of precision you want from the ADC when doing the read.  You can input a bits_of_precision value from 10 to 21!

Here is a simple sample code:

```/*
Circuit:
We need to read an analog voltage on A0, so place a pot with the outer legs going to 5V and GND, respectively, and the wiper (middle leg) going to A0
-make sure to set your Serial Monitor to 115200 baud rate
*/

//include the library

//instantiate an object of this library class; call it "adc"

//Global constants
//constants required to determine the voltage at the pin

void setup()
{
Serial.begin(115200);
Serial.println(F("Oversampling example to get 10-bit to 21-bit resolution using a 10-bit ADC on an Arduino"));
Serial.println("Ultra Basic demo");
}

void loop()
{
//local variables
int pin = A0; //analogRead pin
int bits_of_precision = 16; //must be a value between 10 and 21
int num_samples = 1;

//take a reading on pin A0

//output results
Serial.print("Voltage = ");
Serial.println("V");
Serial.println("");

//wait a bit before taking another reading
delay(500);
}
```

You will see several other examples in the library.

Happy coding!

1. Hi Gabriel, kudos to such an awesome article. I have learned many things from you so thank you very much for sharing your knowledge. I have a couple of questions - will or should your code run on the Arduino Yun? I monitor a fluid pressure sensor as part of a weight change system for a honeybee hive. I am very interested in the analog precision potential and came across your site as I was researching bits and mV's and I would like to add a few bits to my sensors precision.
I am wondering what is better - oversampling to get to 16 bit or using one of these - ADS1115 16-Bit ADC - 4 Channel with Programmable Gain Amplifier chips from adafruit? I would be very interested if you could share some of your thoughts - regarding the debate or conversation from May 27 with Ray, that sounds very very interesting and maybe I can learn something from that also?

Thank you again, and warm regards,
Stephen
stephen@stephensapiary.com

2. Stephen, I'm glad I can help! Thanks for the support. Yes, the library should run fine on the Yun, since all it requires is the analogRead() command, and does not access any ports or registers directly. However, I don't have a Yun to verify this. It is definitely better to go with the ADS1115 from Adafruit. It is going to be much more predictable and accurate.

Regarding the debate on the Arduino forums, Ray is the only one that I can tell who is willing to research stuff instead of just criticizing me. Ray and I have been looking at the utility of my library, and I think we still need to keep looking at things, as so far, it seems that it may not be increasing the precision as predicted, when taking a single sample. However, when taking and averaging multiple samples, I think we may see more promising results. I haven't done any extensive testing personally yet, and at the moment it's not high on my priority list, but I think my library as it is now is simply the *start* of a final product, as I think it needs a lot more research to prove its value (or the lack thereof). Anyway, def. go with the ADS1115 from adafruit for now, though, as it will just work, without having to wonder if it is working properly or if oversampling isn't adequate for your given situation. Oversampling does have its limitations, and I am still discovering what those are. One of those, however, seems to be noise, and I hypothesize that if the signal is too clean, oversampling won't increase precision, but if the sample is nice and noisy, it will. Weird stuff.

3. This comment has been removed by a blog administrator.

4. This comment has been removed by a blog administrator.

1. …same to you Michel Cole.

5. Also you'r library is object oriented for no reason why would you use an empty class with a single non static function anyway just makes it slower? either declare it as static and remove the constructor or get rid of the class

1. I'm an aeronautical engineer, and I don't have a computer science background (but I'm learning, and I wish I did), and this library is my first time ever really using C++. It's my first library ever, and I have virtually no formal programming training. I don't know how to do it another way. I simply looked at some other libraries and was happy to even make it work. Feel free to fork the library on Github and demonstrate precisely what you mean, as I am certainly striving to improve.

2. I am familiar with the concept that static class functions are shared among all instantiated objects of the class, but how do you access a static function if not through an object? With this "-->" or something? And you're saying that using a static function accessed through the main class, rather than accessed through an object of the class, is faster? Also, if I remove the constructor and get rid of the class, how do I make a shareable Arduino library at all? Because you're right....originally, I just made it a stand-alone function in an Arduino .ino file, but how do I make the function universally accessible by any code I write (without copying the .ino file to every place I write a new sketch) without making it a class? Thanks for any help/info by the way. I'm a smart guy, but I am teaching myself a new field and need help along the way of course. Once I finish another dozen projects or so (who knows when that will be), I plan on buying Alex Allain's "Jumping Into C++" to help me learn a great deal more ([url="http://www.amazon.com/Jumping-into-C-Alex-Allain/dp/0988927802"]Jumping into C++[/url]). Meanwhile, I'm mostly self-taught, using forums and online tutorials & things.

3. FYI, for anyone else wondering: http://www.tutorialspoint.com/cplusplus/cpp_static_members.htm
-I should have made the member function static, then called it like this: "eRCaGuy_analogReadXXbit::analogReadXXbit()". I would have been better off calling the function this way *instead of* calling it through a (constructed) object of this class.

I need to get back to this code and do a ton of testing on it someday anyway....

6. Would you mind owerwrite the name eRCaGuy_analogReadXXbit-master to eRCaGuy_analogReadXXbit on Github download section. It can not be install directly, beacuse of the "-master" text. (Win7 - Arduino 1.0.5)

1. Sorry, I can't do that. Github adds the "-master" name to the folder automatically, after I upload the files. All you have to do is manually fix the folder name. Ex, after I extract the files from my download I get the following directory, "\eRCaGuy_analogReadXXbit-master\eRCaGuy_analogReadXXbit-master." Simply move the inner directory up one folder, and rename it to remove "-master." Now I have the whole library under "\eRCaGuy_analogReadXXbit". Now move it into your Arduino "libraries" folder and you're good to go.

7. Thank You. I think someone who have reached to this point would be able to solve this problem. But again, it would be more elegant to install from the IDE with a few mouseclick.
I use your library for measuring temperature with Pt100 resistance thermometer (RTD). This library's advantage (beside of the higher resolution) is a smoother line on the graph - and of course avoid an ADC chip.
In my case 14 bit resolution is the best. 15bit is almost the same as 14bit; with 13bit the graph line is acceptable, but the temperature resolution is more than 0.1°C.

1. Szőcs, thanks for your comment! I am very interested in your code and plots of your data results, if you don't mind sharing. Honestly, I have been involved in many projects simultaneously and have spent very little time myself using this library. Would you mind sharing your code and plots, so I can see how well the oversampling is working? If you are willing to share, please put on Google Drive or Dropbox, and post your link here, *or* email me the results at rcflyyer (email at sign) gmail. Thanks!

8. Your code works great upto 12 bit on 50 hz (as your table shows)
Can you use direct register access? Its much faster.
I don't know how to do it.

const unsigned char PS_16 = (1 << ADPS2);

1. kiwibird, great suggestion! I'll add it to my to-do list. I think I know how to use direct register access and that is a good idea. If I am wrong about knowing how to do it now, I can always figure it out, and I'll look at your link. Also, I'll see about adding a feature to speed up the ADC by a factor of 8, by changing the prescaler, since this is also possible. Hopefully I can come out with version 2 of this code and repost.

2. Question for you: how did you test my code at 12 bit and 50 Hz. Do you have a signal generator by chance? if so, I'd love for you to send me plots of your input signal and the values read by my library.

9. Im using ac sine wave with some noise on it.
Would love your direct access code when you have it.

My first prescaler code didn't work.
however code below worked great.

void setup()
{
// set prescale to 16
#endif

I used this code with good results, but (later) need to make some comments on it.
looop=8;
while (looop-- >0)
{
}
sampleI=sampleI*multval;

10. I think this code uses direct access for analogue read
https://courses.cit.cornell.edu/ee476/FinalProjects/s2008/cj72_xg37/cj72_xg37/index.html
Also they point out that fixed point as faster than floating point
pre-scaler is set very fast. Not sure if accuracy is good at this level
. see line 297

11. Can you also tell me conceptually if the c++ is much faster than standard arduno code?

1. kiwibird, I still haven't been able to get around to doing this, but I can answer this question quickly. There is a common misconception that Arduino has or is its own language. This is just not true. The "Arduino language" is simply a collection of C++ libraries, written in C++. As far as I can tell (my only wondering is whether or not the Arduino code may use Assembly language in parts of it), *all* Arduino code is just C++ (or C) code. However, in order to maximize simplicity and the ability to universally apply the Arduino code, it oftentimes exchanges efficiency and speed for ease of use and broad application. This means that if you get into the 660 pg ATmega328 datasheet and learn to access ports and pins and registers and things directly, you will nearly always be able to make your code run faster by *not* using some of the Arduino functions or libraries directly. For example, according to Simon Monk's "Going Further With Sketches" book on pg. 79 [see links at bottom of my post here: http://electricrcaircraftguy.blogspot.com/2014/01/the-power-of-arduino.html#.VHjPA4vF_6d]), using direct port access (ex: via "PORTB") is 46 times faster than the Arduino function "digitalWrite". Similarly, digital input can be sped up dramatically by accessing the input ports (ex: PINB), rather than using "digitalRead", and analog input can be sped up by at least 6.5 times as well. If you open up the Arduino source code and look at how digitalRead, for example, is implemented, you'll see that each time you call it it has to determine how to access the correct register that corresponds to the pin you want. This is where the slow-down occurs. By hard-coding in which register you want to access, knowing the pin before-hand rather than having the code determine the register during run-time, you get dramatic speed improvements. Having said this, I still intend to expand this library and add the functionality to optionally improve speed ~10x (hopefully), but I still haven't done it....

12. Hi Gabriel,
thank you for your reply. Its well documented that direct port access is much faster. Whats not documented is If there is difference in general arduino code compared to C. I would love to see some tests on this.
Im keen to see the 10x speed improvement as the library has been very useful to me, but I still prefer more speed.

1. kiwibird, can you elaborate on this statement please? "Whats not documented is If there is difference in general arduino code compared to C. I would love to see some tests on this."
--I personally don't understand what you mean, as Arduino code is C.

Thanks for your comment or question! If it is a question, I will try to get back to you quickly. Sincerely, Gabriel Staples.