How to Increase the Resolution of analogRead()! (from 10-bits up to 21-bits)

This really has me curious and willing to do a similar test with the Arduino Due, where I would generate the signal through DAC0 and connect this to the A0 input. Since this signal would be both generated and measured by using the same clock source, I could test the condition where the sampling frequency is in the same domain and is a multiple or at the same input frequency.

I could try a 60-point sine wave table to generate a 60-Hz sine wave on DAC0 and measure each cycle with 60 samples (3600 samples per second). The purpose would be to measure the effect of oversampling with respect to each sample point on a sine wave. These sample points will not travel along the waveform (they'll always be at the same point in time).

I won't get the chance to do this for a few weeks and really can't make any promises ... I'll re-check this thread when I get back.

Anyone interested in doing a "synchronized" test, where everything originates from the same clock?
This is where I suspect the resolution improvement will be limited to the DAC bit resolution (12-bit max in this case).

Just a comment on the general libary. In my oppinion it is poor because it uses global public variables to communicate.
There should be a call to set the resoloution, and a call that returns a reading. There also should be some way to set what analogue port to use.

Note that the input must remain stable throught the whole reading, if you are measuring an unknown voltage that means it is going to change but it must change at a lower rate than the least significant bit over the reading.

Due to the faulty sample code I have not managed to test any resoloution higher than 16 bits. However as mentioned the resoloution of the floating point variable is going to affect things. Already in the sample code floating point constants are defined that exceed the resoloution of a floating point number. Note that in the arduino float and double are the same size.

The distribution of the values in that chart you have printed there is rather interesting. The effect of the "noise" on the adc readings you are getting, is strongly emphasised on the downside.

It would be of more interest to analyze the distribution of the integer results of the decimation, at various "precision" settings.

The analogReadXXbit method returns a float, so will need modify it to return unsigned long, or put debug prints in the library.

float avg_reading = (float)reading_sum/(float)num_samples_to_avg;
return avg_reading;

Grumpy_Mike:
Just a comment on the general libary. In my oppinion it is poor because it uses global public variables to communicate.
There should be a call to set the resoloution, and a call that returns a reading. There also should be some way to set what analogue port to use.

I'm not sure what you mean. In eRCaGuy_analogReadXXbit.cpp, all input parameters are local to one function only.

Grumpy_Mike:
Due to the faulty sample code I have not managed to test any resoloution higher than 16 bits. However as mentioned the resoloution of the floating point variable is going to affect things. Already in the sample code floating point constants are defined that exceed the resoloution of a floating point number. Note that in the arduino float and double are the same size.

Goodness, so many unverified accusations! I understand that floating point numbers don't always store what you input in the code, if you use too many decimal digits. If my "faulty sample code" really was exceeding the resolution of a floating point number, don't you think that the const float values I defined would be altered once stored?

Run this code:

//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(MAX_READING_10_bit,10);
  Serial.println(MAX_READING_11_bit,10);
  Serial.println(MAX_READING_12_bit,10);
  Serial.println(MAX_READING_13_bit,10);
  Serial.println(MAX_READING_14_bit,10);
  Serial.println(MAX_READING_15_bit,10);
  Serial.println(MAX_READING_16_bit,10);
  Serial.println(MAX_READING_17_bit,10);
  Serial.println(MAX_READING_18_bit,10);
  Serial.println(MAX_READING_19_bit,10);
  Serial.println(MAX_READING_20_bit,10);
  Serial.println(MAX_READING_21_bit,10);
  
  Serial.println("");
  
  Serial.println((unsigned long)MAX_READING_10_bit);
  Serial.println((unsigned long)MAX_READING_11_bit);
  Serial.println((unsigned long)MAX_READING_12_bit);
  Serial.println((unsigned long)MAX_READING_13_bit);
  Serial.println((unsigned long)MAX_READING_14_bit);
  Serial.println((unsigned long)MAX_READING_15_bit);
  Serial.println((unsigned long)MAX_READING_16_bit);
  Serial.println((unsigned long)MAX_READING_17_bit);
  Serial.println((unsigned long)MAX_READING_18_bit);
  Serial.println((unsigned long)MAX_READING_19_bit);
  Serial.println((unsigned long)MAX_READING_20_bit);
  Serial.println((unsigned long)MAX_READING_21_bit);
}

void loop(){}

I see no alterations. Here's my output; exactly what I input:

1023.0000000000
2046.0000000000
4092.0000000000
8184.0000000000
16368.0000000000
32736.0000000000
65472.0000000000
130944.0000000000
261888.0000000000
523776.0000000000
1047552.0000000000
2095104.0000000000

1023
2046
4092
8184
16368
32736
65472
130944
261888
523776
1047552
2095104

For more understanding of floats, you might try reading about how the exponent & mantissa are stored, according to the IEEE standard. Google "ieee float exponent mantissa" to start.

To All:
Be careful when running my code to verify my claimed resolution values. In the following code:
float analog_reading1 = adc.analogReadXXbit(pin,bits_of_precision,num_samples);
be sure to set "num_samples" to 1. If you leave it as 10, or anything but 1, it will take that many samples at the specified resolution, then return the avg of all those samples. This doesn't test just resolution, then, but rather it smoothes the readings too, which will change your results. So, set num_samples to 1.

Now, why did I choose a float? Answer: because A) returning an unsigned long isn't measurably faster (I checked repeatedly), B) if you want an unsigned long you can simply use:

unsigned long analog_reading1 = adc.analogReadXXbit(pin,bits_of_precision,num_samples);

instead, and the returned value will be automatically truncated in order to cast it from a float to an unsigned long; And C) If you make num_samples > 1, you can actually get an avg. reading back, which I wanted back as a float for extra precision. So, if you set num_samples to 2, and set bits_of_precision to 10, the two readings done by the ADC might be 998 and 999, for instance, and I wanted to get back 998.5 as the answer, rather than the truncated 998. That's why I chose a float.

dlloyd:
Sorry panther3001, I didn't read through your links and information thoroughly, but I'll have to say that I'm siding with the engineer that wrote this: Precision, Accuracy, and Resolution

Of note is this: •Precision is the fineness to which an instrument can be read repeatably and reliably.

dlloyd, I agree. I appologize for blending the two words, precision and resolution. What I am claiming is higher "resolution," NOT precision. However, I think precision may increase too. It certainly will if you set my "num_readings" parameter to anything > 1, since that simply does data smoothing by returning the avg. reading of [num_readings] readings.

dlloyd:
So without testing, I would say that your library could never achieve repeatable precision greater than what the resolution is for the device under test (ADC).

I disagree, but only if you make num_readings > 1 (see above). Otherwise I think you may be right. Further testing will tell.

Hackscribble:
And just to clarify ... This technique is designed to improve the resolution, i.e. the ability to distinguish smaller changes in the signal under test? Not to improve the accuracy of the readings in absolute terms?

Correct.

Hackscribble:
Hello panther3001

I think I've got a sense of the statistical process in the Atmel paper. Having just built a device with added outboard ADCs, I'll be a bit miffed if it turns out I could have done it in software :slight_smile:

There are also limitations set out in that paper and your posts: the need for random noise, the extended sample period, the assumption that the signal under test does not vary during the sample period.

So I guess the proof of whether this is a practical technique will come through testing.

Personally, I'm willing to invest in an ADC and time to do some testing. I think it's important to define the test method and expected results in advance.

Maybe you could suggest what the testing should involve, and others can comment. I'll probably start with the MCP4725, since I've used it recently.

All the best

Ray

Absolutely! Suggestions coming up soon.

Hackscribble:
I have done a bit of analysis on the readings in Grumpy_Mike's spreadsheet.

The 328 ADC readings cover 21 discrete values, ranging from 1.447030780 to 1.447183510. The gap between each value averages 7.630 uV. This is 10% of the "uV per sample for 16 bits" value in the spreadsheet.

The frequency distribution of the readings is shown in the attached graph. The mode is 1.447152990.

Grumpy_Mike, thanks for doing the background reading and testing my library! That's what I like to see. I hate it when "smart people" attack others' work without even trying it out. If my work is lousy, sorry. That's why it's under the GNU license. It's not guaranteed to do anything useful, like the license says. If my work is deemed lousy by some "smart" person, without them even testing it, that's unfair and ignorant. If my work is actually useful, but imperfect, that's good enough for me and my purposes, and it's good enough to be useful to many others too.

Now, as for your results: I'm very pleased with them. They very clearly indicate that the 16-bit resolution results truly are 16-bit resolution results. Again, let me refrain from using "precision" and "resolution" interchangeably. I was wrong. The 10-bit ADC as a 10-bit ADC doesn't even have 10-bit precision. It has 10-bit resolution. Even with a pot on a fixed setting it can occasionally vary a little when using analogRead().

Hackscribble, thanks a ton for looking at the results and making note of the above items. You said, "The gap between each value averages 7.630 uV. This is 10% of the "uV per sample for 16 bits" value in the spreadsheet."
-This is very clearly indicative of what could be expected from a 16-bit-resolution reading, where each reading is actually the avg. of 10 16-bit readings, which is what Grumpy_Mike did. He had num_samples set to 10, so each value returned was the avg. of 10 16-bit readings. Therefore, it makes sense that the gap between each value, or the resolution, is 10% of the expected resolution. Looking at my spreadsheet found here, you can see that the expected 16-bit resolution, or minimum distinguishable step-size between readings, is 76.4uV. Hackscribble made note of 7.630uV, which is 10% of 76.4 due to the 10-sample avg thing mentioned above. So, 7.63x10 = the expected 16-bit resolution, which matches my prediction. These results make me happy. They indicate the whole concept of oversampling is valid, and for at least this case, they are good. I plan on utilizing my library a lot in the future, on pretty much any project I do where I need fine steps in resolution.

__Don't be fooled though, you still need to meet the criteria in the last two columns of my table, or else what you're doing is useless, as your data will be highly aliased. __

dlloyd:
This really has me curious and willing to do a similar test with the Arduino Due, where I would generate the signal through DAC0 and connect this to the A0 input. Since this signal would be both generated and measured by using the same clock source, I could test the condition where the sampling frequency is in the same domain and is a multiple or at the same input frequency.

I could try a 60-point sine wave table to generate a 60-Hz sine wave on DAC0 and measure each cycle with 60 samples (3600 samples per second). The purpose would be to measure the effect of oversampling with respect to each sample point on a sine wave. These sample points will not travel along the waveform (they'll always be at the same point in time).

I won't get the chance to do this for a few weeks and really can't make any promises ... I'll re-check this thread when I get back.

Anyone interested in doing a "synchronized" test, where everything originates from the same clock?
This is where I suspect the resolution improvement will be limited to the DAC bit resolution (12-bit max in this case).

I am interested in your proposed tests, dlloyd, for sure. Meanwhile, I think I may be able to recruit Hackscribble to do some testing, too, with his Arduino and a separate MCP4725 12-bit DAC, such as this: MCP4725 Breakout Board - 12-Bit DAC with I2C Interface [STEMMA QT / qwiic] : ID 935 : Adafruit Industries, Unique & fun DIY electronics and kits. This should demonstrate the viability and limitations of my library nicely (though not to the very high precision levels, of course, since it's only a 12-bit DAC), and I'll submit some suggested testing methods shortly. Since this DAC is stand-alone, it should be using a separate clock and be good to go.

Grumpy_Mike:
Further more when I run the Ultra basic demo and apply 1V to the input and change the resolution I get the following readings for voltage:-
14 bits - 0.2468V
15 bits - 0.493V
16 bits - 0.98665
17 bits and above 0.0V
All with the same input voltage. The only thing I changed was the number in this line:-

int bits_of_precision = 16; //must be a value between 10 and 21

Also with the full demo on 16 bits I get zero out for a voltage of 0.011V in and anything below.

Edit:
Grumpy Mike, with regards to your faulty values in your 14-bit, 15-bit, and 17-bit and above resolutions, you made a mistake in the application of my library. Please see the following posts for clarification.

Original content:
Grumpy_Mike, with regards to your comment: ("Also with the full demo on 16 bits I get zero out for a voltage of 0.011V in and anything below."), this is a very useful contribution. Thank you. This indicates the limitations of oversampling near the upper and lower bounds. I think this is because near the limits (ex: for 10-bit readings, near 0 and 1023), the noise is constrained on one side. Constraining the noise is a limiting factor on increasing resolution in that area. What I mean by constrained can be described as follows:
Unconstrained noise distribution would be, for example: if the true reading is 500, then that means the mean reading is 500, but there is noise evenly distributed about this value, so the ADC might return 498, 499, 500, 501, 502, at random. This is an even noise distribution, so oversampling can enhance resolution.
**A constrained noise distribution would be, however: ** if the true reading is 0, then that means the avg reading will NOT be 0. This is because the readings (due to noise fluctuations) want to be -2, -1, 0, 1, 2, at random, but they are constrained to 0, 1, 2, and -2 and -1 are not possible. This means that if the true reading is 0, you might actually get a result that says it is, at average, 1 (or if you use a floating point value to take the avg. of many readings, it would at least be skewed to the right [>0], even if the true mean should be 0 if noise were not constrained). This constrained noise distribution prevents oversampling from increasing resolution wherever noise is constrained. So, I would expect that near the upper and lower limits, resolution is either not improved by oversampling, or improved only marginally by oversampling. Also, strange cutoff values like you demonstrated via testing are not surprising, but are rather informative, to help us better understand (and believe) what I describe just above.

So, I thank you very much for your results.

As the application note AVR121 clearly states, under certain conditions, even in theory this procedure cannot extend the resolution. Yet it should give at least the same result as the 10 bit measurement. However, according to Mike's Ultra basic demo results, at 1.0 volts input (nowhere near the lower bound of the measurement range) it isn't even close.

...
14 bits - 0.2468V
15 bits - 0.493V
16 bits - 0.98665
...

Your "constrained noise" proposal cannot explain these observations.

In looking through the code, I wonder about the wisdom of the "right shift" operation recommended by the AVR121:

for (unsigned long j=0; j<oversample_num; j++)
{
inner_sum += analogRead(analogPin); //take a 10-bit reading on the Arduino ADC
}
unsigned int reading = inner_sum >> rightShift; //see AVR121 Application Note; this converts the analogRead into the higher-bit resolution reading
reading_sum += reading;
}

This is an integer division that drops the lowest bits of inner_sum and AVR121 does not give a theoretical justification for doing so. This ALWAYS results in rounding the result down, which seems unlikely to be correct. To properly round an integer division, one uses procedures like those discussed here: c++ - Dividing two integers and rounding up the result, without using floating point - Stack Overflow

Maybe the OP can confirm this, but those readings of (roughly) 1V, 0.5V and 0.25V may be explained as follows.

Grumpy_Mike said he changed one line in the program, to select resolution. The outputs in Volts are calculated by multiplying the ADC reading by a defined constant. I think this also needs to be changed to match the max reading at a given resolution.

Hackscribble:
Maybe the OP can confirm this, but those readings of (roughly) 1V, 0.5V and 0.25V may be explained as follows.

Grumpy_Mike said he changed one line in the program, to select resolution. The outputs in Volts are calculated by multiplying the ADC reading by a defined constant. I think this also needs to be changed to match the max reading at a given resolution.

That is correct. Refer to my Example file called "analogReadXXbit_full_demo.ino" and you will see the following, for example:

analog_reading = adc.analogReadXXbit(pin,bits_of_precision,num_samples);
V = analog_reading/MAX_READING_16_bit*5.0; //voltage

Notice the MAX_READING_16_bit constant. For other resolution readings, this constant is as follows:

//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;

He simply forgot to change that constant when changing the "bits_of_precision" parameter.

It can easily be explained as follows: if you leave that 16-bit constant in there, but use a 14-bit resolution setting, for instance, you can expect that a 1V reading would return a 14-bit reading of 16368/5V = 3273. So, it returned ~3273. When he then erroneously divided that by MAX_READING_16_bit, and multiplies by 5, he gets 3273/65472*5 = 0.24995V, which is close to his value of 0.2468V. It's user error.

Like any library, to use it correctly you must carefully read the examples and use the library appropriately. This is simple folks: analogRead() doesn't return a voltage, it returns a 10-bit reading. You must use your knowledge of that to calculate a voltage. Similarly, adc.analogReadXXbit(pin,bits_of_precision,num_samples) doesn't return a voltage, it returns an analog reading, with the precision you specify by "bits_of_precision." However, due to some nuances of how oversampling works, you must divide by my constants specified above to get the right answer (rather than dividing by 2^n, for instance, where n is the bits_of_precision).

Grumpy_Mike made a mistake in this, then later implied oversampling doesn't work and my library is broken, when in actuality it is user error.

Thanks Hackscribble for carefully reading my code, well enough to understand the examples, and catching that.

Grumpy_MIke, I hope that despite your large number of posts you can respect that others with fewer posts know things too. I could be misreading you, but it seems you spend a lot of time posting things and feel I may have "encroached" upon your territory. I'm not a computer scientist, I'm an an aerospace engineer with a deep love of, and a lot of knowledge in, electrical engineering and computer science, and I am a pretty meticulous guy and I really like to learn. Let's just all get along and learn from each other please.

And yes, your 20 min. test was very useful to me, and yes, it did prove to me that the 16-bit oversampling produced by the library, according to AVR121, really is returning discrete values with the resolution expected for a 16-bit sample (thanks to Hackscribble [Ray] for his plots and for pointing that out in Reply #18). This is looking good. I do appreciate both of your contributions like these.

Sincerely,
Gabriel Staples

Grumpy_Mike made a mistake in that

Grumpy Mike dis what it said in the example code. He was perfectly aware that this was crap as soon as the results came out.

When you write a library it is vital that the example codes is accurate. So when there is a line that says:-

int bits_of_precision = 16; //must be a value between 10 and 21

That is what you do.
There was absolutely no comment in the line:-

Serial.print(5.0*analog_reading/MAX_READING_16_bit,5); //display up to 5 digits of precision

Saying that this had to be changed.
So this is a fault in documentation, that is what I was pointing out.

Like any library, to use it correctly you must carefully read the examples and use the library appropriately.

No, the examples must be crystal clear.

As I said before it is a very poor implementation of a library:-

  1. You can not change the input port
  2. Global variables are used to pass data out of the library
  3. Global variables are used to set parameters and process results.

The point about a library is that it should be easy to use. Having to know how it works and manipulating several variables outside the library defeats the object of having one.

The library should have:-

  1. A call to set the resolution
  2. Include the port number in the call to do the digitising
  3. The call should return the float value of the voltage

Study other quality libraries.

Hackscribble:
Hello panther3001

I think I've got a sense of the statistical process in the Atmel paper. Having just built a device with added outboard ADCs, I'll be a bit miffed if it turns out I could have done it in software :slight_smile:

There are also limitations set out in that paper and your posts: the need for random noise, the extended sample period, the assumption that the signal under test does not vary during the sample period.

So I guess the proof of whether this is a practical technique will come through testing.

Personally, I'm willing to invest in an ADC and time to do some testing. I think it's important to define the test method and expected results in advance.

Maybe you could suggest what the testing should involve, and others can comment. I'll probably start with the MCP4725, since I've used it recently.

All the best

Ray

Ray, here's the testing method I recommend. It will take just under 23 hrs per test, at a maximum of 16-bit ADC resolution, with 20 readings per DAC step, unless you modify my code to do fewer readings per step, or fewer steps.

Here it is:
-All you need to do is add in the part that writes to the DAC.

//create flat-toothed sawtooth output signal, and sample it via oversampling (using eRCaGuy_analogReadXXbit library)
//By Gabriel Staples
//Code to test my "eRCaGuy_analogReadXXbit" library, and the concept of oversampling, according to AVR121 application note.
//http://electricrcaircraftguy.blogspot.com/2014/05/using-arduino-unos-built-in-16-bit-adc.html
//17 May 2014

//include the library
#include <eRCaGuy_analogReadXXbit.h>

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

//Global constants
const uint8_t pin = A0; //analogRead pin
//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;

unsigned long output_dt_des = 10 * 1e6; //us, desired output time step; 10 seconds; GS Note: this output time step was chosen purposefully
//so that we can get ~20 readings per time step, even with a resolution setting up to 16-bit.  Note that according to my table,
//a single 16-bit reading takes ~0.5 sec, so getting 20 readings takes ~0.5x20 = 10 seconds, or 10,000,000 us. Table found here: http://electricrcaircraftguy.blogspot.com/2014/05/using-arduino-unos-built-in-16-bit-adc.html

void setup()
{
  Serial.begin(115200);
  //print test info
  Serial.print("output_dt_des (time per DAC output step) = "); Serial.print(output_dt_des / 1e6); Serial.println(" sec");
  Serial.print("Estimated time to run test = "); Serial.print(output_dt_des / 1e6 * 4096 * 2 / 3600.0); Serial.println(" hours");
  Serial.println("");
  //print data header
  Serial.println("time_stamp(ms),DAC_output,Vout_commanded,ADC_reading,Vin_ADC_reading");
}

void loop()
{
  //----------------------------------------------------------------------------------
  //DAC OUTPUT
  //----------------------------------------------------------------------------------
  static int output = 0; //DAC output value from 0-4095 for a 12-bit DAC
  static int8_t d_output = 1; //output change, per output time step
  static unsigned long t_start_out = micros();

  unsigned long t_now = micros();
  if (t_now - t_start_out >= output_dt_des) //the time step period has elapsed, so let's change the DAC output value
  {
    t_start_out = t_now; //update
    //SET YOUR DAC OUTPUT HERE; HOWEVER THAT IS DONE
    //write_to_DAC(output);
    output += d_output;
    //constrain DAC output values to those possible for a ********12-bit DAC**********
    if (output > 4095)
    {
      d_output = -1;
    }
    else if (output < 0)
    {
      d_output = 1;
      Serial.println("END OF TEST");
      while (1) {}; //go into infinite loop here, to stop test, since one full cyle has now been completed
    }
  }

  //----------------------------------------------------------------
  //ADC INPUT, & PRINTING DATA TO SERIAL MONITOR FOR PLOTTING
  //----------------------------------------------------------------
  static uint8_t bits_of_resolution = 10;  //<--------THIS IS THE VALUE YOU WILL HAVE TO CHANGE EACH RUN, TESTING 10-BIT, 11, 12, 13, 14, 15, AND 16-BIT RESOLUTION SETTINGS
  static unsigned long input_dt_des = output_dt_des / 20; //us, desired time step for taking analog readings; we want the time-step to be ~20x shorter than the output
  //time-step for the DAC, so that we can get ~20 ADC readings per discretized DAC output step
  static unsigned long t_start_in = micros();

  t_now = micros();
  if (t_now - t_start_in >= input_dt_des) //the time step period has elapsed, so let's change the DAC output value
  {
    t_start_in = t_now; //update
    unsigned long t_now_ms = millis(); //grab a time stamp in ms, so that I don't have to worry about overflows every ~70 min on micros(), during long tests
    unsigned long reading = adc.analogReadXXbit(A0, bits_of_resolution, 1); //take a single reading at specified resolution
    float Vin = (float)reading / MAX_READING_10_bit * 5.0; //V read on the pin;  <--------------DON'T FORGET TO CHANGE MAX_READING_XX_bit here too, according to your setting
    float V_commanded = (float)output / 4095.0 * 5.0; //V commanded on DAC, assuming *************12-bit DAC*************
    //print data to serial monitor, for plotting in Excel:
    Serial.print(t_now_ms); Serial.print(","); Serial.print(output); Serial.print(","); Serial.print(V_commanded, 8); Serial.print(","); Serial.print(reading); Serial.print(","); Serial.println(Vin, 8);
  }

} //end of loop()

What you'll need to do is run the code with various ADC resolutions from 10 to 16-bit. I recommend you do them all. Be sure to change this setting in two places in the code. One, you must set bits_of_resolution to the correct value, and two: you must set the constant divisor (ex: MAX_READING_10_bit) to the correct value. My code above also assumes you are using a 12-bit DAC. If you ever use a higher resolution DAC, you'll need to change the code in a handful of places, but I'm sure you'll read the code carefully and see that anyway.

Here is what the commanded DAC output "sawtooth" wave looks like, for one cycle:

Here is what it looks like zoomed in. It would be really useful to see how the different sampling resolutions affect how close the ADC gets to actually picking out these discrete steps. Sampling this at 10-bits, for example, would show that many of the steps are indistinguishable, so effectively the sampling would show a staircase with larger stairs than what you see here. Of course there will be noise, so precision (as we have learned it from dlloyd [see reply #27 on pg 2]) will not be as good as the sampling resolution, but if the sampling resolution works as expected, each individual staircase will be able to be detected, once the selected input level of resolution is >= the output resolution. Ex: to detect every stair on this 12-bit staircase, I predict that the ADC resolution will have to be set to 12-bit or higher. I also predict, however, that the higher the resolution you choose, the less noise you will see in the readings. This is all to be seen, however, by your testing, if you are able to do it.

Here is the code I used to generate the sawtooth:

//create flat-toothed sawtooth output signal
//By Gabriel Staples
//17 May 2014

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  static int output = 0; //DAC output value from 0-4095 for a 12-bit DAC
  static int8_t d_output = 1; //output change

  while (1)
  {
    //SET YOUR DAC OUTPUT HERE; HOWEVER THAT IS DONE
    //write_to_DAC(output);
    for (int i=0; i<20; i++){
      Serial.println(output);
    }
    output += d_output;
    //constrain DAC output values to those possible for a ******12-bit DAC********
    if (output > 4095)
    {
      d_output = -1;
    }
    else if (output < 0)
    {
      d_output = 1;
      Serial.println("END OF TEST");
      while (1) {}; //go into infinite loop here, to stop test, since one full cyle has now been completed
    }
  }
}

To plot the data you'll need to use FreeMat or MATLAB or something similar, as Excel can't handle it. It's 163,860 rows of data.

FreeMat is available for download here: http://freemat.sourceforge.net/

My FreeMat code I used to generate the plots above is attached. The data text file is also attached. Put it in the same directory as the .m file.

Note that FreeMat is a MATLAB clone, and most functions in FreeMat are the same as MATLAB, so feel free to copy MATLAB tutorials when learning FreeMat. The FreeMat documentation can otherwise be found here:

http://freemat.sourceforge.net/help/index.html

Let me know if you have problems.

plot_sawtooth.m (1.42 KB)

sawtooth_single_cycle.txt (917 KB)

Grumpy_Mike:
Study other quality libraries.

Will do. This is my first library I've ever written. I'll get better. When I get the chance I'll make a few clarifications, but for now I'll leave it. Personally, I'm really proud of this library and think it's an excellent first library for how much I have had to learn to accomplish it. My purpose in starting this thread was simply to share it and help others. But like I said, I'll get better at writing libraries. Gotta run.

OK, I'll take a look at the code and do some testing this week.