Measuring the source voltage using internal 1.1V reference

Hi,

I am developing a little autonomous robot project (with Arduino Nano, Atmega 328), powered by a single cell lipo battery, and I want to periodically measure the voltage of that battery, and once it drops below 3.5V, shut everything down to prevent damage to that battery (I've got the shut-down part covered).

I have one analog pin remaining for this task, but I was wondering if this measurement could be taken by measuring the voltage that comes into the Vin pin, using internal 1.1V reference. I am using the following code:

float readVcc()
{
	const float V_BAND_GAP = 1.1;     // typical
	ADMUX = _BV(REFS0)    // ref = Vcc
		| 14;           // channel 14 is the bandgap reference
	ADCSRA |= _BV(ADSC);   // start conversion
	loop_until_bit_is_clear(ADCSRA, ADSC);  // wait until complete
	return V_BAND_GAP * 1024 / ADC;
}

This code sort of works, but unfortunately, the readings are off by a certain amount. I am powering Arduino from a custom-made PSU, and displaying the voltage readings on an LCD screen, so no USB cable to interfere with anything.

With 5V source coming into the Vin, this function returns 4.15V. At 4V, it displays 3.12V. Of course, this isn't the end of the world, I can still test what this function thinks the voltage is when it is actually 3.5V, and use that for shut-down reference, but I don't like not knowing what is causing such a large discrepancy...

To correct the measurements, I would have to set reference voltage to 1.3V... Which can't be right. And unfortunately, that only fixes the voltage reading at 5V - at other voltages, numbers don't match again, so it's clearly a scaling issue, not just an offset. Can anyone tell why these numbers don't match?

I am also considering using a boost converter to bring 4.2V from the lipo to 6V in order to power motor driver, so I could power Arduino from these 6V as well, and take measurements of the lipo voltage by connecting to the positive rail between the lipo and the boost converter - that way, the converter will keep the 6V stable even when lipo gradually discharges, and I'll be able to use that 6V as a reference to measure the lipo voltage with a simple analogRead. Is this assumption correct?

Or are there any other better ways of measuring the voltage of my battery?

Thanks in advance! :slight_smile:

1 Like

I've never tried it... I've never directly used the internal registers, but here is a similar project.

The 1.1V reference does have a tolerance (although it should be very stable) so you might have to measure the reference or otherwise calculate a correction factor.

1 Like

DVDdoug:
I've never tried it... I've never directly used the internal registers, but here is a similar project.

The 1.1V reference does have a tolerance (although it should be very stable) so you might have to measure the reference or otherwise calculate a correction factor.

Thanks, I tried that code, and it produces pretty much the same error... As for the correction factor, I figured it is about 1.18 (turning 1.1V to 1.3V), but that can't be right... I can't imagine that the 1.1V would so much off. And also, as I said, that correction doesn't scale correctly when source voltage changes.

According to the ATmega328 data sheet, the "1.1V" bandgap reference voltage can be anywhere between 1.00 and 1.20 V, although I would expect the occasional outlier.

The usual approach is to measure Vcc with an accurate voltmeter, and calculate the value of the bandgap reference that gives the correct answer.

Here is the routine that I've been using for several years now:

unsigned int readVcc(void) {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  ADMUX = (1<<REFS0) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= (1<<ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring
  unsigned int result = ADC;
  //custom scale factor, processor specific
  result = 1125300UL / (unsigned long)result; // Calculate Vcc (in mV); 1125300 = 1.1*1024*1000
  return result; // Vcc in millivolts
}
1 Like

jremington:
According to the ATmega328 data sheet, the "1.1V" bandgap reference voltage can be anywhere between 1.00 and 1.20 V, although I would expect the occasional outlier.

Well, as I said, the correction needs to place that voltage at 1.3V, which is certainly out of bounds, and it doesn't scale correctly (at other voltages the real source voltage and the voltage readings differ).

Is there some mistake in my approach?

Try the routine I posted, which works fine for me, and see if you get a different result. There is a delay that may be important.

You did not post ALL your code. When switching ADC channels, it is often necessary to make two readings and discard the first.

Do these experiments with nothing else connected to the Arduino and powering the Arduino via Vcc (the 5V output), not by the barrel jack or RAW input.

I could power Arduino from these 6V as well

Not through Vcc, and it would be a waste of power to run it through the RAW input.

jremington:
Try the routine I posted, which works fine for me, and see if you get a different result. There is a delay that may be important.

You did not post ALL your code. When switching ADC channels, it is often necessary to make two readings and discard the first.

Do these experiments with nothing else connected to the Arduino and powering the Arduino via Vcc (the 5V output), not by the barrel jack or RAW input.
Not through Vcc, and it would be a waste of power to run it through the RAW input.

I tried your routine. This is the full code:

#include <LiquidCrystal.h>

LiquidCrystal lcd(3, 2, 4, 5, 6, 7); // Creates an LC object. Parameters: (rs, enable, d4, d5, d6, d7) 


unsigned int readVcc3(void) {
	// Read 1.1V reference against AVcc
	// set the reference to Vcc and the measurement to the internal 1.1V reference
	ADMUX = (1 << REFS0) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1);
	delay(2); // Wait for Vref to settle
	ADCSRA |= (1 << ADSC); // Start conversion
	while (bit_is_set(ADCSRA, ADSC)); // measuring
	unsigned int result = ADC;
	//custom scale factor, processor specific
	result = 1125300UL / (unsigned long)result; // Calculate Vcc (in mV); 1125300 = 1.1*1024*1000
	return result; // Vcc in millivolts
}


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

void loop() { 

	lcd.clear();
	lcd.print(readVcc(), DEC);
	Serial.println(readVcc(), DEC);
	delay(1000);
}

And unfortunately, the measurements are way off. At 7V input to Vin, it prints 5161 on the lcd screen. At 6V, it prints 4748. At 5V, it prints 3640. And so on.

Is there some mistake in the way I'm implementing this routine?

P.S. Just a correction, I'm using Arduino Nano, it doesn't have a barrel jack.

1 Like

P.S. Just a correction, I'm using Arduino Nano, it doesn't have a barrel jack.

Are you powering through the 5V output or Vin?

It sounds like you are doing the latter, and the regulator dropout voltage is causing the problem.

jremington:
Are you powering through the 5V output or Vin?

It sounds like you are doing the latter, and the regulator dropout voltage is causing the problem.

I am powering through the Vin... Isn't that how I'm supposed to power it, especially if my source is 7V?

That was the problem all along.

The code measures the voltage supplied to the processor (and the ADC reference), not Vin. There is a voltage drop across the voltate regulator connected to Vin, which depends on whatever regulator is installed on your board.

If you want to calibrate the code, you can continue to supply power to Vin, but the correct voltage to measure (for calibration purposes) appears on the 5V output.

In the future, please supply correct information. Your opening post does not mention 7V. A single cell LiPo is 3.4 - 3.7V nominal, and you would NOT connect that to Vin.

I am developing a little autonomous robot project (with Arduino Nano, Atmega 328), powered by a single cell lipo battery

laukejas:
I am developing a little autonomous robot project (with Arduino Nano, Atmega 328), powered by a single cell lipo battery...

You shouldn't run a 16Mhz Nano on a single LiPo cell.
A 16Mhz 328 is only guaranteed to work reliably above ~3.85volt.
Leo..

jremington:
That was the problem all along.

The code measures the voltage supplied to the processor (and the ADC reference), not Vin. There is a voltage drop across the voltate regulator connected to Vin, which depends on whatever regulator is installed on your board.

If you want to calibrate the code, you can continue to supply power to Vin, but the correct voltage to measure (for calibration purposes) appears on the 5V output.

In the future, please supply correct information. Your opening post does not mention 7V. A single cell LiPo is 3.4 - 3.7V nominal, and you would NOT connect that to Vin.

Wawa:
You shouldn't run a 16Mhz Nano on a single LiPo cell.
A 16Mhz 328 is only guaranteed to work reliably above ~3.85volt.
Leo..

Sorry for the confusing info on my part, I was still experimenting with various power supplies. Right now I'm running off a custom-made PSU (to test various voltages and try to measure them with Arduino), but when I'll be using the single-cell LiPo, I will run it through a boost converter to bring that up to 7V first. But I still want to measure the voltage that comes into that boost converter, so I can shut down the system if the LiPo voltage drops too much. I am now simulating this with my PSU.

Considering that the boost converter is keeping the voltage supplied to Arduino at stable 7V, it should be a simple matter of using any of the analog pins, connecting it to the positive wire between the LiPo/PSU and the Arduino Vin, and getting a number between 0 and 1023, and then mapping it between 0 and 5 to get a voltage reading. But for some reason, these measurements are erratic and not at all accurate. Is my method flawed here?

I know I went a little astray from the original question of the topic, sorry about that. I can create a new post with more detailed, correct info and a schematic if needed.

You will need a voltage divider to measure a voltage greater than 5V, using the analog input.

For a battery powered robot, most people want to know the battery voltage. Why would the boosted voltage be of interest?

Sir, I think you misunderstood me. I want to measure the voltage of the one-cell LiPo BEFORE it goes into the boost converter. Since a fully charged LiPo is never above 4.2V, I don't think I need a divider, right?

This quote, taken from your most recent post

it should be a simple matter of using any of the analog pins, connecting it to the positive wire between the LiPo/PSU and the Arduino Vin,

clearly indicates a connection to Vin, which is generally above 5V and requires a voltage divider.

Good luck with your project, I'm out.

jremington:
This quote, taken from your most recent post

clearly indicates a connection to Vin, which is generally above 5V and requires a voltage divider.

Good luck with your project, I'm out.

Sorry... Wrote that in a hurry. I meant between LiPo/PSU and boost converter. Thanks for the help you provided so far!