Can an Arduino measure the voltage of it's own power source?

I'm planning on running a 3v Arduino Pro Mini on 2 AA batteries and I want the Arduino to turn on an LED when the batteries get too low.

I was able to find hundreds of examples on how to measure a battery, but these methods all relied on the Arduino being powered by a fixed voltage. In my case however, the Arduino runs on whatever voltage the battery is currently running on.
I'm also planning on removing the voltage regulator and the power LED (because it's said to reduce power usage).

What would be the easiest way to achieve this?

Also, sorry in advance, in case this question has been asked before. Everything I could find required a fixed voltage for the Arduino.

It is quite straightforward to check the battery voltage but it is is done in a rather un-intuitive way. The Arduino has an internal 1.1v voltage reference and by "measuring" that with the battery voltage as VREF you can calculate the battery voltage.

Have a look at the code in this link

...R

You have done more homework before asking than many members!
I don't know the mini but on some controllers there is a 1.1 volt internal reference. Hopefully that reference can be used. If it varies with the voltage supplied, 3.3 or 5, maybe You can calibrate it for the situation "close to dead battory".

Brilliant! I had no idea it would be that simple! My Pro Mini has an ATMEGA328P I think, so I guess it will work. :slight_smile:
Thank you so much.

I use the same code as Robin2 described, only simplified:

long readVcc() {
 
long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1126400L / result; // Back-calculate AVcc in mV
  return result;
}

It returns the current battery level in mV (milliVolts). Works great on Pro Mini 5V and 3.3V.

Regards!

I was able to find hundreds of examples on how to measure a battery, but these methods all relied on the Arduino being powered by a fixed voltage.

It's a cultural deficit. People have forgotten how important it is to RTFM. Because of that, everyone is copying everyone else's flaws. It's fully documented on this same site, in the language reference section.

https://www.arduino.cc/reference/en/language/functions/analog-io/analogreference/

One defect of the documentation, it doesn't explain the significance of the command (regards improving accuracy vs using VCC).

AFAIK the 1.1 Ref is used for digital LOW.

felic:
I'm planning on running a 3v Arduino Pro Mini on 2 AA batteries and I want the Arduino to turn on an LED when the batteries get too low.

I was able to find hundreds of examples on how to measure a battery, but these methods all relied on the Arduino being powered by a fixed voltage. In my case however, the Arduino runs on whatever voltage the battery is currently running on.
I'm also planning on removing the voltage regulator and the power LED (because it's said to reduce power usage).

What would be the easiest way to achieve this?

Also, sorry in advance, in case this question has been asked before. Everything I could find required a fixed voltage for the Arduino.

Not a fixed voltage, there is +/- .5V at 5V and 3.3V can usually by 3V to 3.7V.

Get a boost converter to run the Arduino at 5V. Run a wire from the pre-boost +V to a digital pin and when the combined batteries get to 1V the pin will go low.

It is often desirable to measure the supply before it gets to the board or external regulator... then you can determine if the supply is high, low, falling etc before it becomes critical.

There are several discussions on this topic, but basically, isolate the supply from the circuit with a diode, .add a modest electrolytic capacitor for enough CPU continuity to measure when the supply fails.
Put a voltage divider at to bring the supply voltage into the ADC pin’s range...
If you poll the ADC frequently enough, you’ll have time to save EEPROM etc before the world ends.

e.g. I use 47K over 10K, and feed via 2K2 into the ADC for a nominal 24V supply before the regulators.

GoForSmoke:
Get a boost converter to run the Arduino at 5V.

It is more efficient to run directly off 2 x AA cells as the OP proposes.

...R

Railroader:
I don't know the mini but on some controllers there is a 1.1 volt internal reference.

So you don't know what a Pro Mini actually is?

This discussion is getting very confused. The OP describes powering the Pro Min by two AA (alkaline) cells, so there is no need to introduce other components.

GoForSmoke:
AFAIK the 1.1 Ref is used for digital LOW.

I I have no idea what that is attempting to say! :roll_eyes:

Paul__B:
I I have no idea what that is attempting to say! :roll_eyes:

Digital pins switch state with hysteresis:
Digital LOW is comparator < 1.1V or <= 1.1V … easily tested
Digital HIGH on 5V Uno’s seems to be 2.8V … also testable

If you bring the pin HIGH and then say it has a 220K pulldown to ground, the pin will go LOW at 1.0 or 1.1V. 2 new AA’s should drive the pin HIGH, make sure grounds are connected.

If the board is powered by a boost converter and 2 AA’s in series, the un-boosted battery V will drop even though board Vcc remains at 5V until the batteries drop below 0.9V with the boost converters I bought years ago. Shop around.

Why spend time on even 8-bit ADC when any digital pin can read in 1 cycle or trigger a pin-change interrupt?

After playing around with this for a while, I can say that using the internal 1.1v reference yields results that are way too inaccurate for what I'm intending to do (calculate the capacity).

Are there any other ways to do this? Some of you mentioned a boost converter, but I feel like that would completely destroy my battery life. I spent a lot of time getting the whole thing to only use 203.4nW in sleep mode.

What would be the most power efficient way to read the voltage?
Btw I should explain that the Arduino is in deep sleep 99% of the time, and I only want to read the voltage once every time I press a certain button which wakes the Arduino up for a few milliseconds.

felic:
After playing around with this for a while, I can say that using the internal 1.1v reference yields results that are way too inaccurate for what I'm intending to do (calculate the capacity).

I suspect there is no problem with the Arduino's measurement (assuming your code and connections are correct) - only a problem with your understanding of what's going on.

How would you approach the problem if the readings you are getting are actually correct?

If you are comparing results with a multimeter be aware that the multimeter averages its values and the Arduino does not.

...R

Robin2 is correct :

The internal reference is fine , but you may need to calibrate as although its a fixed and accurate voltage , it’s voltage is “around” 1.1volts and may vary from processor to processor . Wake up , let it settle , take a reading , sleep

I have used that “secret” internal voltmeter Utilising the 1.1v reference with great success - it works well !!!!! The voltage resolution will be around 3mV , more than good enough.
Forget all the other stuff about boost converters and digital outputs using the reference for low type things , this has got a bit out of hand and causes confusion and some of it wrong

If you wanted a different route there are battery monitor IC’s out there .

Bear in mind the battery voltage you measure will vary with its load , if you have a pulsing load, then as Robin states , you may not get the result you want due to the point at which the Arduino samples the voltage

Accuracy to 3mV would be fantastic. How long should I let it settle? Are we talking about a few milliseconds, multiple seconds or minutes?

Take a couple of readings and throw the first away .

Oh are you saying I don’t need delay at all? I can just read the voltage 10 times and then use the last result?

Like this?

void setup() {
  Serial.begin(9600);
  Serial.println(getAccurateVoltage());
}

void loop() {}

int getAccurateVoltage() {
  int i;
  for (i=0; i<10; i++) {
    getVoltage();
  }
  return getVoltage();
}

// Read the voltage of the battery the Arduino is currently running on (in millivolts)
int getVoltage(void) {
  #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
    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;
    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
  ADCSRA |= _BV( ADSC ); // Start a conversion 
  while( ( (ADCSRA & (1<<ADSC)) != 0 ) ); // Wait for it to complete
  int results = (((InternalReferenceVoltage * 1024L) / ADC) + 5L) / 10L; // Scale the value; calculates for straight line value
  return results*10; // convert from centivolts to millivolts
}

felic:
I can just read the voltage 10 times and then use the last result?

You need to experiment and see what works for you.

It would be a good idea to get your Arduino to measure a stable voltage that is NOT its own power supply. Perhaps a 3v battery with a potentiometer attached so you can vary the voltage. Then, because the battery voltage will be stable (at least for a few minutes) you can compare your Arduino measurements with your multimeter. They should match pretty closely. And that will give you confidence that the Arduino and software is working properly.

...R

After playing around with this for a while, I can say that using the internal 1.1v reference yields results that are way too inaccurate for what I'm intending to do (calculate the capacity).

I believe the spec says between 1.0 and 1.2V but it should be stable (for any given chip) so once you know what it is you can make a calibration adjustment in software.