analogReference using a voltage higher than the Atmega 328p's power

I’m developing a custom board for use as a battery operated data logger, and to get the current down low enough, I did away with regulating the Atmega’s power, and decided to power it straight from 3 AA batteries (so around 4.5V, little more new, little less dead).

Anyway, everything has been working great, except for the ADC. The ADC has been giving me an absolute fit trying to read it properly, and I have a feeling it’s because the AREF voltage is regulated at 5V, while the voltage to the Atmega is 4.5V.

The voltage measured at the pins are:
Vcc = 4.65V (also tied to AVcc)
AVcc = 4.65V (yes, I actually measured it)
AREF = 5.00V

I have my 3.3V regulator fed into AN0 as a reference voltage, hence the reason I found it. The voltage measured from it (multimeter) is 3.31V.

The 5V regulator is tied directly to AREF, and also reads 5.00V.

The regulators are programmatically turned on and off. In order to read the ADC, the regulators must be powered up. I have a delay of 300ms for them to turn on and settle.

However, when the 3.3V regulator is read, I get an ADC value of 652 +/- 1. If I do the math, that is 3.18V, 0.12V from what I should be reading.

void setup()
{
...
...
...
  // ADC Setup
  // This is where you setup the Analog to Digital Converter inputs to measure voltages.
  analogReference(EXTERNAL); // Set to the external reference.  You'll have to wire something from the 5V power pin to the AREF pin.  
  pinMode(ADC_3V3_CAL, INPUT);
  pinMode(SMS1, INPUT);
  pinMode(SMS2, INPUT);
  for(int i = 0; i <= 3; i++) // This is to counteract a condition present in the Atmega 328p.  Refer to 23.5.2 in the datasheet for more information. 
    analogRead(i);
...
...
...
}

void loop()
{
    // Timestamp
    enterStringData(getTimestamp(), false);
    // 3.3V Cal
    enterIntData(analogRead(ADC_3V3_CAL), false);
    // SMS1 VWC
    enterIntData(analogRead(SMS1), false);
    // SMS2 VWC
    enterIntData(analogRead(SMS2), false);
    // MAX6675 Temperature
    enterFloatData(thermocouple.readCelsius(), true);
    newLine();
}

I’ve read a few things on these forums about the AREF not working, but surely they wouldn’t just include a function that does nothing, right? I looked at the wiring_analog.c, and it appears to be implemented.

Another thing. If I use a 5V supply (FTDI 5V from USB) to power the Atmega, then everything works properly, and I read 678 from the ADC, which is 3.31V, or exactly what I measure from my multimeter.

There is something strange when using battery power, or a voltage lower than the AREF to power the Arduino.

Any ideas? I can provide more clarification, you just have to request it.

You cannot use a voltage lower than AREF to power the Arduino. AREF can be lower than AVCC (see Table 29-16 in the ATmega328P datasheet) but it cannot be higher, and AVCC cannot be higher than VCC by more than 0.3V (and it's not really a recommended thing to do anyways).

-- The Ruggeduino: compatible with Arduino UNO, 24V operation, all I/O's fused and protected

RuggedCircuits: You cannot use a voltage lower than AREF to power the Arduino. AREF can be lower than AVCC (see Table 29-16 in the ATmega328P datasheet) but it cannot be higher, and AVCC cannot be higher than VCC by more than 0.3V (and it's not really a recommended thing to do anyways).

-- The Ruggeduino: compatible with Arduino UNO, 24V operation, all I/O's fused and protected

Well that would be the problem then. I didn't see that in the datasheet.

In the datasheet I'm looking at (http://www.atmel.com/Images/doc8161.pdf) Table 29-16 doesn't exist.

Anyway, I'll take your word for it. I'll add a voltage regulator with very low quiescent current to supply to the microprocessor at all times. This can simply the board design a bit, but cuts my battery life from my crazy battery life of 45 years (off of standard AA batteries, with indefinite shelf life), to just 3 years assuming a quiescent current of about 75 µA.

Any recommendations for a voltage regulator? I'm assuming a 200mA rated one would work well, as the Atmega 328p is limited to 200mA altogether as described in 28.1 of the data sheet linked above. Any less, and I risk running short of power for my board, and any more is a waste, as it would go above the rating of the board.

Currently looking at this one: http://www.analog.com/static/imported-files/data_sheets/ADP3330.pdf

Has a quiescent current of 34 µA, which will work well. Assuming the board pulls 40 µA in power down mode, that will lengthen my life basically double, to nearly 6 years. My target battery life is at least 6 months, turning on 4 times a day for about a 300ms each (and quickly polling an on-board RTC maybe twice an hour).

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

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

Why not use a lower voltage on AREF, say a 2v56 reference chip and scale down the input. Or the internal 1v1 reference.


Rob

retrolefty:
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

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

Graynomad:
Why not use a lower voltage on AREF, say a 2v56 reference chip and scale down the input. Or the internal 1v1 reference.


Rob

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.

I believe 328p doesn't have the 2v56 reference,

I meant an external reference chip.

Another solution I could do is use an Operational Amplifier,

I think that would have the same issues, ie tolerance of the components as you still need resistors.

I still couldn't just trust the values.

Some way of self calibrating?

all we have in Starkville is the infamous RadioShack

Digikey and Mouser are just a click away.

Is this a set of instructions for lay people? Is that why the forestry workers have to be involved with the components? They are going to actually build these gadgets.

If so you're right, it has to be simple.

Are you supplying any components at all, or just the instructions and some code on github?


Rob

On top of that, I still couldn't just trust the values.

Just use two resistors but measure their value and use the measured values in your calculations.

or put in a bounded pot, where series resistors on each end create boundaries or limits of operation When I needed an accurate but simple circuit to use most any two resistors to set the output of an LM317 I picked the closest value less than the required one and used a 500 ohm pot to 'adjust' the voltage to the correct point... I bought 10 dual 10k digipots the other day and I am going to have fun with them and a 14 bit D/A converter I bought from Adafruit. Back in the day I might have used a DC-DC converter a buck boost device, they're not terribly efficient But can work in the middle of the battery range to put out any desired voltage... only needs to be powered op for 50 mS or so and most I worked with could be shut off- totally, the other attractive point is besides the complexity the efficiency should be slightly better than a linear doing the same work... for that matter a switcher becomes really attractive when a board can work off 3V3 because the losses become small single digit figures when the switcher is a small part of the battery load rather than the approx 50% it is now... And they can be put to sleep and the processor put to sleep with a wake up bias from the battery that switches everything on long enough for a snapshot. or at an interrupt from some external source, I did similar things to conserve batteries in my projects... turn things on briefly rather than leaving them enabled at all times.

Doc

I stand by my method as the simplest (no external components required), cheapest (it's free!), and as accurate as any other proposed method that still uses the AVR A/D hardware. The only one time hassle is that you need to find the exact value of your specific chip's band-gap voltage. 1.1vdc is only a nominal value that AVR uses, see their tolerance value from chip to chip, something like 1.0 to 1.5 or so. Once you have the exact value down to a millivolt you are good to go.

Lefty

retrolefty: I stand by my method as the simplest (no external components required), cheapest (it's free!), and as accurate as any other proposed method that still uses the AVR A/D hardware. The only one time hassle is that you need to find the exact value of your specific chip's band-gap voltage. 1.1vdc is only a nominal value that AVR uses, see their tolerance value from chip to chip, something like 1.0 to 1.5 or so. Once you have the exact value down to a millivolt you are good to go.

Lefty

Well I got lucky and my professor/boss hasn't ordered the new PCB yet, so I'm out of the hole. I'll make a change to add the low quiescent current voltage regulator (I'll shop around a bit more to see if there are better ones), and make the needed changes on my PCB design.