Actually there is a method to have your cake (run directly on batteries) and have your cake also (an accurate A/D conversion even though the Vcc is decreasing over time, without having to have a regulator or external reference source. It involves having a software function that can calculate the actual Vcc by measuring the internal band-gap voltage by switching the A/D mux to read it’s value. The reading taken is used to ‘back’ calculate the Vcc value which can then be used as a calibration value to apply to any voltage reading on the analog input pins via simple mapping function. That probably sounds more complicated then it really is. Anyway it’s been posted about several times in this forum, once even in the last month or two. If stumble across it I will post the link.
Here is an example that Coding Badly and I worked out a couple of years ago, as a proof of concept. It probably needs to be updated a bit for the serial print command changes now in IDE 1.x ?
// 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
Serial.print(“volts X 100”);
Serial.println( “\r\n\r\n” );
battVolts=getBandgap(); //Determins what actual Vcc is, (X 100), based on known bandgap voltage
Serial.print("Battery Vcc volts = ");
Serial.print("Analog pin 0 voltage = ");
Serial.println(map(analogRead(0), 0, 1023, 0, battVolts));
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);
// 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);
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
Why not use a lower voltage on AREF, say a 2v56 reference chip and scale down the input. Or the internal 1v1 reference.
Hmm… seems like I actually kind of did this a few weeks ago. I had the Sparkfun 16 channel MUX to take in voltages. I wanted to measure voltages up to 5V, but I was using the 1v1 reference (BTW Rob, I believe 328p doesn’t have the 2v56 reference, and that’s a 2560 feature). I created a simple voltage divider using 2 resistors, and calibrated to my 5V regulator voltage and 3.3V regulator voltage. In the end, it worked great. Unfortunately, the RAM space is extremely limited on the 328p, and having just the SD card seems to use up the majority of the RAM. The combination of SD card, the RTC library, and my ADC MUX library caused weird problems that turned out to be a lack of free RAM, so I had to scrap the MUX setup. Such a shame… I made a nice library for it. It assumed the voltage regulators were a ‘gold standard,’ which for the most part, is true. I could accurately read voltages from 0-5V using this setup, and had the ability to take in 13 channels (GND, 3.3V, and 5.0V were all ‘gold standards’ fed into the MUX, and could not be used for measuring sensors or whatever.
retrolefty’s idea seems to be good (especially since that’s basically what I did when I calibrated my ADC MUX stuff). I’ll look around for that tomorrow between classes.
Rob, not that your idea is a bad one, the voltage divider (scale down circuitry), needs to be pretty simple for what I’m doing. I’m creating a guide on how to use the Arduino platform for data logging in the field on battery power. This is geared towards people in the Forestry department at my school (Mississippi State University). Forestry researchers are great at what they do, but not at circuitry and programming, so everything I explain needs to be very basic. Basic as in a 2 resistor voltage divider.
Unfortunately, as you likely know, resistors have a tolerance. The common tolerance is 5%. While 5% tolerance is pretty good in most cases, Forestry researchers use a sensor called a soil moisture sensor. The one I was asked to look at provides a voltage from 0-2V. They also have a piecewise function that goes along with them. The tolerance for error using these sensors is very low, less than 0.5%, before it throws off the calculations.
I could get low tolerance resistors, but then those are special order components (all we have in Starkville is the infamous RadioShack). On top of that, I still couldn’t just trust the values.
Another solution I could do is use an Operational Amplifier, but that complicates the circuit, and I wouldn’t feel comfortable with a non-technical person dealing with ICs.