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:

This Article:

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.

Update: 11 July 2014: I still plan on doing much more testing with my library, when I get the chance, and working on it to refine and validate it a lot more.  I want to know for myself, with certainty, how well it really is or isn't working, and what its limitations are.  One of the refinements I will make, for instance, will be to speed up the Arduino ADC from ~8kHz max sample rate to ~54kHz max sample rate, by changing the ADCSRA register to have an ADC prescaler of 16 instead of 128 (thanks to Simon Monk, pg. 82 of "Going Further with Sketches" for teaching me about this).  This way, I can see if reading the ADC faster affects the results produced by oversampling.  It will also be nice to just not have to wait so long for high-bit ADC reads that require tons of 10-bit samples to get one high-bit sample.  Meanwhile, if you are concerned about whether or not my library truly produces higher-precision ADC reads, you might just consider buying a 12 or 16-bit ADC from Adafruit.  They look really nice.  I will be using these myself to test my library eventually, in conjunction with an LTC1650CN 16-bit DAC to produce a signal to test.  As part of my test, I will vary the reference pin source from a noisy voltage regulator to a clean, dedicated reference IC chip.  This way, I can see how the noise affects the results.  Also, as part of my testing, I'll modify my library to introduce random noise (via software) to the analog readings, to see how that affects oversampling (It's possible that introducing just the right magnitude of random noise will increase precision of the oversampling process).  
Anyway, here's the Adafruit ADCs!  
12-bit ($10): https://www.adafruit.com/products/1083
16-bit ($15): https://www.adafruit.com/products/1085 
------------------------------------------------------------------------------------------------------------

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!

Keep reading below:


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.


My Library - eRCaGuy_analogReadXXbit:

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
#include <eRCaGuy_analogReadXXbit.h>

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

//Global constants
//constants required to determine the voltage at the pin
const float MAX_READING_10_bit = 1023.0;
const float MAX_READING_11_bit = 2046.0;
const float MAX_READING_12_bit = 4092.0;
const float MAX_READING_13_bit = 8184.0;
const float MAX_READING_14_bit = 16368.0;
const float MAX_READING_15_bit = 32736.0;
const float MAX_READING_16_bit = 65472.0;
const float MAX_READING_17_bit = 130944.0;
const float MAX_READING_18_bit = 261888.0;
const float MAX_READING_19_bit = 523776.0;
const float MAX_READING_20_bit = 1047552.0;
const float MAX_READING_21_bit = 2095104.0;

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");
  Serial.println(""); //add a line space
}

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
  float analog_reading = adc.analogReadXXbit(pin,bits_of_precision,num_samples); //get the avg. of [num_samples] 16-bit readings 
  
  //output results
  Serial.print("analog_reading = ");
  Serial.println(analog_reading);
  Serial.print("Voltage = ");
  Serial.print(5.0*analog_reading/MAX_READING_16_bit,5); //display up to 5 digits of precision
  Serial.println("V");
  Serial.println("");
  
  //wait a bit before taking another reading
  delay(500);
}

You will see several other examples in the library.

Download my Library (eRCaGuy_analogReadXXbit) on GitHub by clicking here --> then click the "Download Zip" link at the bottom of the right-hand pane.

Happy coding!


12 comments:

  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

    ReplyDelete
  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.

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

    ReplyDelete
    Replies
    1. Noah Jackson, thanks for your comments…NOT! I don’t appreciate your spam, so I have deleted your comment and reported you to Google. Hopefully your Google+ account is disabled soon. Thanks for your time.

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

    ReplyDelete
  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

    ReplyDelete
    Replies
    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.

      Delete
    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.

      Delete
    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....

      Delete
  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)

    ReplyDelete
    Replies
    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.

      Delete

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