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!


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

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

      Delete
  8. Your code works great upto 12 bit on 50 hz (as your table shows)
    Can you use direct register access? Its much faster.
    see http://www.billporter.info/2010/08/18/ready-set-oscillate-the-fastest-way-to-change-arduino-pins/
    I don't know how to do it.


    // Define various ADC prescaler
    const unsigned char PS_16 = (1 << ADPS2);

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

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

      Delete
  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()
    {
    #if FASTADC
    // set prescale to 16
    sbi(ADCSRA,ADPS2) ;
    cbi(ADCSRA,ADPS1) ;
    cbi(ADCSRA,ADPS0) ;
    #endif



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

    ReplyDelete
  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

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

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

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

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

      Delete

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