of Atmega ADC and supply reference

I'm making a project and hit this slight question i'm exhibiting here on the hopes someone could help me guide my train of thought....
(The board i'm using is arduino pro mini), letme start by the beginning:
Taking VDc measurements with the circuit powered by USB, i found that different computers have wildly different variations, on one i saw 4.9, on other 4.85, they're all inside the 5% range of course and can be negligible... but...
That same source is being used as an ADC reference by default, and that 0.15V of difference means 31 codes of difference, now... this wouldn't be an issue if all my circuit where based off the same source as the ratio would be 100%(for example, if i put the ADC input to Vcc, the resulting code will be 1023, since Vcc=Vref, doesn't matters what the absolute value is -somewhat, bear with me here-).
But what if i need the utmost precision?, let's take the above example, if i do any calculations assuming 5Vcc, the result will be off by "0.15V"/31 codes

So my question is, do any of you guys use the internal 1.1Vref to read the real Vcc and then map the ADC reads based on that value?

another option would be to power the board with a precision 5Vref, but those Refs aren't made for power draw, and the board has a lot of devices connected that draw power....

another option is to use a precision/low noise linear reg(i have no idea the line reg of the arduino pro mini, has 150mA output which isn't enough for all the system)?
or split several regs, for power and for the avr itself.... umfortunately the board does not give me full access to the atmega pins(or i could use a precision 0.1% reg ext 5vref on the AREF pin)

What is the range of voltages you want to read?

If it is above the 1.1V internal you can't unless you use a voltage divider.

i forgot to mention it in the first post because it was implied that i'll be using a divider to calibrate the Vcc from the bandgap reference.

Elimi:

You have to look at it as a system.

  1. What are you supplying to the sensor.
  2. What is your reference.

If you are only considering the built in sensors then presumably the internal reference is the same as the supply to the sensors.

If you are using external sensors, then you have to deal with the sensor as well as the reference in supplying matched voltages -- AREF to External supply -- as measured at the (analog) sensor.

Some senors like the BMA180 are more accurate when supplied with 2.4V -- go figger.

So bottom line --

  1. are you only talking about the built in Arduino ADC sensors?; or,
  2. Is this a general question? -- including things like an external Analog sensor...

I think there might be different answers depending on what you are doing...

So my question is, do any of you guys use the internal 1.1Vref to read the real Vcc and then map the ADC reads based on that value?

Yes!

There is a method to make your A/D measurements independent with variation of the Vcc used as the A/D reference. The trick is to read the bandgap voltage (not use it as the reference) and come up with a correction factor to utilize in mapping your analog input readings. This is especially useful if you are powering the processor directly with batteries. Anyway give it a reading and see if you might get some ideas.

Edit:

PS: Keep in mind that this doesn't solve the electrical problem that an input voltage to a analog input pin must not be above the Vcc +.5vdc specification when Vcc is being used as the normal default reference voltage, because the positive input pin clamping protection diode will start to conduct with possible pin damage resulting. So make sure your analog input signals being read can never exceed the lowest Vcc value you expect your power source to provide.

// Function created to obtain chip's actual Vcc voltage value, using internal bandgap reference
// This demonstrates ability to read processors Vcc voltage and the ability to maintain A/D calibration with changing Vcc
// Now works for 168/328 and mega boards.
// Thanks to "Coding Badly" for direct register control for A/D mux
// 1/9/10 "retrolefty"

int battVolts;   // made global for wider avaliblity throughout a sketch if needed, example a low voltage alarm, etc

void setup(void)
    {
     Serial.begin(38400);
     Serial.print("volts X 100");
     Serial.println( "\r\n\r\n" );
     delay(100);
    }
    
void loop(void)
    {
     battVolts=getBandgap();  //Determins what actual Vcc is, (X 100), based on known bandgap voltage
     Serial.print("Battery Vcc volts =  ");
     Serial.println(battVolts);
     Serial.print("Analog pin 0 voltage = ");
     Serial.println(map(analogRead(0), 0, 1023, 0, battVolts));
     Serial.println();    
     delay(1000);
    }

int getBandgap(void) // Returns actual value of Vcc (x 100)
    {
        
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
     // For mega boards
     const long InternalReferenceVoltage = 1115L;  // Adjust this value to your boards specific internal BG voltage x1000
        // REFS1 REFS0          --> 0 1, AVcc internal ref. -Selects AVcc reference
        // MUX4 MUX3 MUX2 MUX1 MUX0  --> 11110 1.1V (VBG)         -Selects channel 30, bandgap voltage, to measure
     ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR)| (0<<MUX5) | (1<<MUX4) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
  
#else
     // For 168/328 boards
     const long InternalReferenceVoltage = 1056L;  // Adjust this value to your boards specific internal BG voltage x1000
        // REFS1 REFS0          --> 0 1, AVcc internal ref. -Selects AVcc external reference
        // MUX3 MUX2 MUX1 MUX0  --> 1110 1.1V (VBG)         -Selects channel 14, bandgap voltage, to measure
     ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
       
#endif
     delay(50);  // Let mux settle a little to get a more stable A/D conversion
        // Start a conversion  
     ADCSRA |= _BV( ADSC );
        // Wait for it to complete
     while( ( (ADCSRA & (1<<ADSC)) != 0 ) );
        // Scale the value
     int results = (((InternalReferenceVoltage * 1023L) / ADC) + 5L) / 10L; // calculates for straight line value 
     return results;
 
    }

Lefty

retrolefty,
i see, interesting code there...
so you use the Vcc as reference but select the bandgap as source, since BG is a known fixed value(thermal variations notwithstanding) you then use the measured difference to infer the real Vcc.
I like it, a lot!, it's much more elegant than my idea of using a external divider and analog pin, and much more precise.
I have some questions about it:

  1. I don't quite follow the mapping equation you did, why are you using *1023, shouldn't it be *1024 since you have the number of steps there?, why the +5 then /10?(maybe if put differently i could understand it, maybe it's my particular math view hehehe
  2. about the bitshifts in ADMUX, aren't they backwards?, i mean, how to you shift a 0 by a value?, couldn't you just use a bitfield directly?(admux = 0101110b), since those macros correspond directly to bits, also, couldn't you use the _BV macro?

I'd forgotten all about the direct MUX access for the ADC(and i even twiddled with it once to use the internal temp sensor, but gave up as it's horribly inaccurate and needs calibration), guess too much "analogread" dulled my AVR-fu (incidentally, it would be very nice if base arduino analogread included this calibration, maybe an "analogreadcal" or a callibration flag, or "analogcal()" function).
Hav you considered adding it to the playground?

Eliminateur:
retrolefty,
i see, interesting code there...
so you use the Vcc as reference but select the bandgap as source, since BG is a known fixed value(thermal variations notwithstanding) you then use the measured difference to infer the real Vcc.
I like it, a lot!, it's much more elegant than my idea of using a external divider and analog pin, and much more precise.
I have some questions about it:

  1. I don't quite follow the mapping equation you did, why are you using *1023, shouldn't it be *1024 since you have the number of steps there?, why the +5 then /10?(maybe if put differently i could understand it, maybe it's my particular math view hehehe

Coding Badly came up with that math and it is effective as I've tested it with a precision adjustable power supply and found the compensation to be very good over quite a wide Vcc variation, 3.5 to 5.5 as I recall. In the original posting of this, as CB and I were developing it he did a great job of explaining the math. It's more then might meet the eye but I can't re-explain it as well as CB did (in his reply #13 in the link below). I just can't locate the original postings in the old site at the moment.
EDIT: Finally found the original posting thread: http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1294478456/all

  1. about the bitshifts in ADMUX, aren't they backwards?, i mean, how to you shift a 0 by a value?, couldn't you just use a bitfield directly?(admux = 0101110b), since those macros correspond directly to bits, also, couldn't you use the _BV macro?

Again CB provided that, and it is correct. You can use admux = 0101110b, but using << and predefined bit names is the style used in most low level code on the Arduino platform.

I'd forgotten all about the direct MUX access for the ADC(and i even twiddled with it once to use the internal temp sensor, but gave up as it's horribly inaccurate and needs calibration), guess too much "analogread" dulled my AVR-fu (incidentally, it would be very nice if base arduino analogread included this calibration, maybe an "analogreadcal" or a callibration flag, or "analogcal()" function).

Keep in mind that for this method to get best accuracy results one needs to 'tweek' the actual bandgap voltage for the specific chip in service. While the bandgap voltage seems to be very stable it's actual nominal voltage is a pretty wide specification. Think of the "const long InternalReferenceVoltage = 1115L;" statment as where you would trim the function's actual results based on independent measurement/testing. CB came up with a independent sketch that would allow the bandgap voltage to appear on the Aref pin where one could read it with a good DVM and then use that value in this sketch. However I found it more fun to just 'adjust' the value while testing with real world voltages.

Hav you considered adding it to the playground?

No, I have never tried to post anything in the playground. While I do search the playground site out from time to time, I get very fustration trying to actually locate something in that section even when I know it's in there somewhere. It's layout leaves a lot to be desired in my opinion. Anyway, Coding Badly really was the key to getting the task simplified and understandable with the mux commands and compensation math, for this software challenged hardware person. I just originated the posting asking about if and how it could be done on an Arduion and then wrapped it around a simple sketch after testing it with decent voltage sources and test equipment. I first read of the method on the AVRfreaks site but there example code was not real Arduino sketch friendly. Anyone is free to put it on the playground if they wish and know how to.

Lefty

Thanks again, i've been reading the thread of the old forum and whilst CB explains the math, it's too much for my fried monday brain hahaha
"However I found it more fun to just 'adjust' the value while testing with real world voltages."--> how do you do this?, you assume the Vbg from the datasheet, feed a known-stable voltage to A0 pin and then based on the readout you change your assumed value?.
wouldn't that mean you have to upload a lot of sketches until you get things right?

too bad CB doesn't explains the bitshift bit settings for the bit macros, i couldn't find any info about them(this is pure curiosity of course), more than a bitshift they look like an assignment

For my original system, i'm going to go with a low-noise precision LDO(don't care about the LDO part.. but it comes as it is) that has <1.5% regulation, as a normal 7805 has 5%!!!, that's a huge variation for the precision i need(which is 0.1% give or take), even then i'll use the code you pasted to calibrate either everytime i make a read or every x seconds...

i wonder how much does the adjusted code takes compared to a normal one, i quite like the sleep version as well due to the extra precision, but i wonder how it will impact the rest of my code as i'm also using external interrupts

Eliminateur:
Thanks again, i've been reading the thread of the old forum and whilst CB explains the math, it's too much for my fried monday brain hahaha

CB is a software god in my eyes. :wink:

"However I found it more fun to just 'adjust' the value while testing with real world voltages."--> how do you do this?, you assume the Vbg from the datasheet, feed a known-stable voltage to A0 pin and then based on the readout you change your assumed value?.

Yes, that is how I did it. Not as time consuming as you only need one data point as the whole thing is quite linear.
wouldn't that mean you have to upload a lot of sketches until you get things right?

Yes, but that goes really fast with just edit the value, press upload, open serial monitor, check results. Maybe 10 seconds for each iteration. Didn't take but a min or two to 'zero in' on the best value. I love the Arduino IDE for it's simple and fast operation, but I know the software power users want it to be more complex and feature rich. :wink:

too bad CB doesn't explains the bitshift bit settings for the bit macros, i couldn't find any info about them(this is pure curiosity of course), more than a bitshift they look like an assignment

You could ask him for a tutorial explaination. I see that method used extensivly in the Arduino library codes and AVR library codes, and all the register and bit names are 'official' AVR defines. I think it has become a standard method, something about better then hidding information if you just use 'magic number' assignments. In my own coding I have no problems using 'magic numbers', I just try and leave comments to explain the source of the number used.

For my original system, i'm going to go with a low-noise precision LDO(don't care about the LDO part.. but it comes as it is) that has <1.5% regulation, as a normal 7805 has 5%!!!, that's a huge variation for the precision i need(which is 0.1% give or take), even then i'll use the code you pasted to calibrate either everytime i make a read or every x seconds...

Another method would be to use one of those precision 3 terminal 2.5vdc voltage reference chips. Just wire it to one of the analog input pins and use that value as a 'correcting source'. They are pretty cheap and at least 1% or better accuracy is avalible I think, and you could probably easily solder it directly onto a arduino board so it's avalible for all your projects. However it does take one analog input pin out of play. My 'method' was more to deal with direct battery powered applications where the A/D accuracy would constantly degrade as battery voltage decreases and where one wants to prevent overdischarging a li-po battery cell to prevent cell damage.

i wonder how much does the adjusted code takes compared to a normal one, i quite like the sleep version as well due to the extra precision, but i wonder how it will impact the rest of my code as i'm also using external interrupts

Hey playing around with ideas and testing ideas is where all the fun come in. Just jump in and try things and report whatever jems you come up with.

Lefty

Another method would be to use one of those precision 3 terminal 2.5vdc voltage reference chips. Just wire it to one of the analog input pins and use that value as a 'correcting source'.
As you say, that would take an analog pin and i'm out of them already(big project, pin constrained in all sides).
And there would be no need to do that if i have the bandgap correcting the input

i'll have to make some tests/exalibrations once the device is more than a jumbled prototype, once i finish building a prototype of the analog part that needs the precision readings i'll do some tests.
Maybe i won't even need the code when i finish building and callibrating(but i doubt it) since i'm having at least...:
+-18Vdc opamp supply(probably 7818+7918)
+12V fan line (simple 7812)
+5v precision DAC supply
+5V "general line" for AVR, LCD+logic, DAC logic, LEDs, buzzer, digitemp sensor
the 5V precision will be derived from the 18V high rail, the general from the rectified bridge output of the bridge.

so yeah, a mess of different rails all over the place :smiley:

On the subject of DACs, I recently received a 2 channel SPI interface 12 bit D/A module. It works fine in initial testing so I'm quite happy with it even thought I don't have an immediate project in mind for it.

http://cgi.ebay.com/INBOARD-12-Bits-DAC-Board-MCP4922-PIC-AVR-ARM-/270632079322?pt=LH_DefaultDomain_0&hash=item3f02edcfda

It's based on this chip: http://ww1.microchip.com/downloads/en/devicedoc/21897a.pdf

Lefty

Microchip makes fine devices, too bad i can't get them as easily as i'd like here.

The one i'm using: DAC6573 data sheet, product information and support | TI.com
no module, only the chip
10 bit quad DAC, i chose I2C specifically over SPI as SPI needs 3 wires and I2C is already builtin in the Atmega(albeit hardwired and you lose 2 analog pins), so i'm using several I2C devices in my project, makes everything much simpler.

now on what project to use it... good question... i'm using it for a programmable window comparator and current sink, thus 3 outputs..., no need for more than 10 bits as well.

and another thing i hate is how all these i2c devices are SMD!, ohhh i hate SMD with passion....

and another thing i hate is how all these i2c devices are SMD!, ohhh i hate SMD with passion....

Not my favorite either as at my age eyesite is just not real compatible with them. :wink:

Lefty