Help with Aref reading on Mini Pro 3.3V

I'm wanting to blink an LED when the voltage from 2xAA batteries that runs a transmitter using an Arduino Mini Pro 3.3V drops to say 1.9V. So I'm experimenting first to try find the Internal voltage (around 1.1V), then I want to set it up so it reads the external voltage via AREF (battery connected directly). I assume that's the correct method??

My mini has been bootloaded with a setup for brownout level 1.8V and 8MHz (1MHz bootloader) - I'm using this to minimize power consumption and allow using voltage lower than 2.4V.

I'm currently power up via USB connection (3.303V). I've uploaded the sketch below, but when I measure across GND and AREF, I only get 0.511V. It seems to be half for some reason. Can someone please let me know what I've doing wrong?

#include <avr/power.h>

void setup() {
  clock_prescale_set(clock_div_2); // speed conversion due to Mini Pro running at 8MHz for power saving
  analogReference (INTERNAL);
  analogRead (A0);  // force voltage reference to be turned on
}
void loop () { }

Sorry, just to add some more info to this query, I "assumed" the bottom right pin was AREF, but being a clone it's RAW. And given I've removed the voltage regulator onboard to conserve power, maybe that's the reason for a weird voltage. Is there somewhere else I can measure the internal voltage.

To read the battery voltage, I use a method similar to Nick Gammon's. Do not mess with AREF.

// Read 1.1V reference against AVcc
// return battery voltage in millivolts
// must be individually calibrated for each CPU

unsigned int readVcc(void) {

  unsigned int result;

  // set the reference to Vcc and the measurement to the internal 1.1V reference

  ADMUX = (1<<REFS0) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1);
  delay(10); // Wait for Vref to settle

  ADCSRA |= (1<<ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // wait until done
  result = ADC;

  // second time is a charm

  ADCSRA |= (1<<ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // wait until done
  result = ADC;

  // calibrated for my Miniduino

  result = 1195700UL / (unsigned long)result; //1126400 = 1.1*1024*1000
  return result; // Vcc in millivolts
}

The RAW input goes to the on-board regulator, so that a minimal voltage of 4.3V is required for proper operation. Batteries should be connected to Vcc instead, consult the data sheet of your board whether bypassing the regulator requires to disconnect the regulator. Also prevent external voltage from sourcing current into the batteries.

For measuring the supply voltage two methods can be used. Either you measure the 1.1V reference against the Vcc (AREF), or you set AREF to the 1.1V reference using analogReference(), and divide down Vcc into that range by a voltage divider.

If you use Vcc for AREF (default), then 1024 corresponds to your battery voltage. Since you know that the measured ADC value for channel 14 (bandgap reference) corresponds to 1.1V, you can calculate the actual voltage for that (hypothetical) 1024 ADC value. If you e.g. read 512 (1024/2), you know that AREF=Vcc is 2*1.1V.

Just tested: analogRead(14) seems not to work, most probably is blocked by the standard library. That's why bitfiddling is required to set the analog MUX to channel 14 and take the value, see the code presented by cjcj or Nick Gammon. The magic number 1195700UL seems to reflect the exact bandgap voltage of that specific chip. Unless you have the exact value for your chip, use the 1100*1024UL constant instead.

Sorry, the theory of how to measure the voltage still doesn't make too much sense to me. Have I got this right:

  • Typically the internal voltage for my mini pro would be 1.1V. This analogue value when displayed in digital is represented from 0 to 1024 (where 0 = 0V and 1024 = 1.1V)
  • With the above, each "step" is 1.1/1024 = 0.00107421V
  • Given each processor will have a slighting different internal voltage, each step would actually be a slightly higher or lower voltage
  • I ended up finding on the spec sheet that this voltage can be measured on pin 20 of the atmega328P Au. With my digital meter, I found mine was steady at 1.087V
  • This equates to a step value of 1.087/1024 = 0.0010615V
  • So when a voltage (say battery) is applied say to pin A1, can the reading then be attained as a % of the reference (i.e. just under 300% if 3V)
  • If that's the case, a 3V supply would say it is 276% of the internal reference, so I can work out the actual value?

jremington:
To read the battery voltage, I use a method similar to Nick Gammon's. Do not mess with AREF.

I did look at Nick's before posting this thread but it was a bit confusing for my limited code knowledge (ADMUXs and ADCSRAs). I tried to run it however in a sketch with a setup and void loop, but couldn't work out how to get it to display a voltage.

DrDiettrich:
For measuring the supply voltage two methods can be used...

Unless you have the exact value for your chip, use the 1100*1024UL constant instead.

DrD, given I measure my voltage as 1.087 rather than 1.1, is the value I use 1087*1024UL = 1113088UL in JR's code above (when I get it working)?

Last two list points are incorrect.
The A/D outputs 1023 if analogue input voltage is the same or higher than Aref.
Battery voltage has to be dropped to <= Aref with a voltage divider.

The internal way might be better though.
Also look at this page.
http://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/
Leo..

cjcj:
DrD, given I measure my voltage as 1.087 rather than 1.1, is the value I use 1087*1024UL = 1113088UL in JR's code above (when I get it working)?

Right :slight_smile:

Only your percentages look strange. You'll never get 100% (1024) or more, the analog input voltages must be below the reference voltage.

Thanks all. It works.

I did some more reading based on all the above and found how to complete the code (see below). Doing some calculation, I adjusted the "1195700" figure so that the serial printout voltage matched the Vcc I measured.

/*  Thanks to the arduino gurus and specifically jremington.
    This skcetch is used to measure the Vcc voltage in this case on an Arduino Pro Mini 3.3V
    Given in my case the chip has been bootloaded as 8MHz (1MHz bootloader) and 1.8V
    brownout, avr/power library is needed as well as clock_prescale line in setup.

    To adjust the accuracy of this voltage measurement, do the following:
    1.  Measure the internal voltage of your chip (in my case it was not 1.1V but 1.087V)
    2.  On my Atmega328P Au, this was measured between GND and pin 20 (refer spec sheets)
    3.  Next is to ajdust the line below "result = 1195700UL /..." to suit your voltages
        Do the following:
        a.  Run the code and read the voltage on the serial monitor (mine was 3516 = 3.312V)
        b.  Measure the voltage across GND and Vcc - (mine was 3.306V, i.e. lower)
        c.  Measure voltage across GND and pin 20 (internal voltage) - (mine was 1.087V)
            (the orginal calculation assumes an internal voltage of 1.1V - I think)
        d.  The multiplier value below (7 digit number) needs to be changed to suit.  Use ratios:
                1195700  :  3516  (this was my original number and voltage)
                    x    :  3306  (this is the voltage it needs to display
                So x = (3306x1195700)/3516
                     = 1124359L (hence this is the value I inserted below, and it gave the
                       perfect output voltage on the serial monitor as 3.306V as I measure
                       with a mulitimeter.
*/


#include <avr/power.h> // used because I'm using a mini pro 3.3V running at 1MHz, 1.8V brownout

unsigned int readVcc() {
  unsigned int result;

  // set the reference to Vcc and the measurement to the internal 1.1V reference
  ADMUX = (1 << REFS0) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1);
  delay(10); // Wait for Vref to settle

  ADCSRA |= (1 << ADSC); // Start conversion
  while (bit_is_set(ADCSRA, ADSC)); // wait until done
  result = ADC;

  // second time is a charm

  ADCSRA |= (1 << ADSC); // Start conversion
  while (bit_is_set(ADCSRA, ADSC)); // wait until done
  result = ADC;

  // calibrated for my Min Pro 3.3V

  result = 1124285UL / (unsigned long)result; //1126400 = 1.1*1024*1000
  return result; // Vcc in millivolts
} // end readVcc


void setup() {
  Serial.begin(9600);
  clock_prescale_set(clock_div_2);  // required due to the lowered MHz of my mini pro
} // end setup

void loop() {
  Serial.println( readVcc());
  delay(1000);
  float voltage = (readVcc());
  if (voltage < 1900) // 1.9V is when I want to alarm low voltage LED
  {
    Serial.println("Voltage is LOWER than 1.900V");
  } // end if
  else
  {
    Serial.println("Voltage is HIGHER than 1.900V");
  } // end else
} // end loop

If you measure 1.087V for the bandgap reference, the factor should be 1087*1024UL.

DrDiettrich:
If you measure 1.087V for the bandgap reference, the factor should be 1087*1024UL.

That does make sense, but my figures then don't add up. The value about would = 1113088. The serial monitor then says the voltage is 3.245V rather than my measured voltage of 3.306V.

You indirectly measure the bandgap reference by figuring out what factor gives you a battery voltage on the serial monitor that agrees with your multimeter.

Taking the numbers from your most recent post: multimeter reads 3.306, but the serial monitor reads 3.245 using factor 1124285UL.

The corrected factor is 1124285UL*3.306/3.245 = 1145419UL

Of course, the multimeter has errors, too.

jremington:
You indirectly measure the bandgap reference by figuring out what factor gives you a battery voltage on the serial monitor that agrees with your multimeter... Of course, the multimeter has errors, too.

Yes, that's what I first did and makes sense. I'll re-correct it. Thanks again.

Voltages are not always constant, even if supplied by a battery. A DVM handles such fluctuations and noise in a different way than an ADC does. For increased precision the ADC sleep mode has to be used, so that fluctuations inside the processor will not affect the measured value. A 10 bit ADC also has a higher uncertainty (steps) than the ADC in a DVM. And the outside AREF may be slightly different from the internal AREF, used by the ADC.

There are many factors that can affect measured values. Take the constants that give the values you trust most :slight_smile:

DrDiettrich:
Voltages are not always constant, even if supplied by a battery. A DVM handles such fluctuations and noise in a different way than an ADC does. For increased precision the ADC sleep mode has to be used, so that fluctuations inside the processor will not affect the measured value. A 10 bit ADC also has a higher uncertainty (steps) than the ADC in a DVM. And the outside AREF may be slightly different from the internal AREF, used by the ADC.

There are many factors that can affect measured values. Take the constants that give the values you trust most :slight_smile:

Technically, what's in a DVM is still an ADC, just a different type. Most multimeters use an integrating ADC. The input signal is used to charge a capacitor for a fixed period of time, and then the capacitor is discharged down to 0 and the discharge is timed. Higher time = higher input signal. These tend to be pretty slow compared to other types of ADC, and the time it takes to convert a signal is different depending on its value.

The ATmega328P (and probably most other non-specialized microcontrollers) has a successive approximation converter. Its conversion procedure is a bit more complicated, but can work much faster and with a fixed conversion time.