Reading battery voltage using adc registers

Thread Starter

amishlol234

Joined Mar 10, 2022
32
Hi I am super confused with reading my lipo battery voltage.
1) I WANT TO CREATE A FUNCTION TO READ MY 3.7~4.2V,1200mAH BATTERY VOLTAGE THROUGH A MICROCONTROLLER (ARDUINO OR ATTINY44).
2) THEN I WANT TO CALL THE CREATED BATTERY FUNCTION IN THE FORMULA TO READ PRECISE SENSOR VALUE:
int voltage = ((CALLING BATTERY FUNCTION) * (READ SENSOR VALUE)) / 1023;
3) I AM USING INTERNAL REFERENCE VOLTAGE 1.1V.

NOTE: I want the whole system to run on lithium battery ( i am just creating a prototype on Arduino, if it works, ill be editing the code for my attiny44 using 8mhz external crystal).

Unfortunately, i tried almost all codes available on the internet but i could not read the right voltage. I believe that there is some issue with my connections.

void setup() {
Serial.begin(9600);
Serial.println("Setup completed.");
}

void loop() {

// Read external battery VCC voltage
Serial.print("Bat: ");
uint16_t batVolts = getBatteryVolts();
Serial.print(batVolts);
Serial.print(" - ");
Serial.println(getBatteryVolts2());

delay(500);
}

// One way of getting the battery voltate without any double or float calculations
unsigned int getBatteryVolts() {
//http://www.gammon.com.au/adc

// Adjust this value to your boards specific internal BG voltage x1000
const long InternalReferenceVoltage = 1100L; // <-- change this for your ATMEga328P pin 21 AREF value

// 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);

// Let mux settle a little to get a more stable A/D conversion
delay(50);

// Start a conversion
ADCSRA |= _BV( ADSC );

// Wait for conversion to complete
while ( ( (ADCSRA & (1 << ADSC)) != 0 ) );

// Scale the value - calculates for straight line value
unsigned int results = (((InternalReferenceVoltage * 1024L) / ADC) + 5L) / 10L;
return results;
}
}


circuit connection.PNG

My serial monitor displays abnormal values :
(Bat-109 -5V)


I took the code from the link down below, i know the code is ok but the issue is with my circuit i beleive,maybe i am making some wrong connection or what i dont know.

Please let me know if anybody understood the issue.
 

Ian Rogers

Joined Dec 12, 2012
1,136
Looking at that code... The Vref is set to 1.1v ergo you need a voltage divider to set 4.2V ( max battery) to sit at 1.1v on the ADC pin. Then change the scaler to use 4.2v and not 5v as this code would have been used on a 5v system.

In the scale equation there is 'internalreferencevoltage' and the constant '1024'. This could be a single constant and forgo the multiplication. The voltage divider can be set to use 1.1volts, 2.56 volts or 5v

using 2.56v you can use 620Ω on top of a 1k. This gives 2.59v at 4.2 v but its quite close.
 

Jim_PDX

Joined Nov 13, 2019
15
Since you’re going to end up using an ATTiny chip, I would suggest maybe using one of the newer Series 2 ATTinys. They have a built-in ADC that can directly read the incoming VCC. So, no need for external components for a voltage divider. Just use this function in your code:

ReadVoltage function:
float  readVoltage() {   // return the current VCC voltage
  // Using the builtin ADC
  int aReadValue = 0;
   analogReference(INTERNAL2V5);   //  Set the ADC reference voltage to be 2.5V
   aReadValue = analogRead(ADC_VDDDIV10);   // This doesn't read a pin; it reads 1/10 of the Vdd
   analogReference(VDD);   //  Set the ADC reference voltage back to Vdd
  //Calculate voltage thusly:  Since aReadValue = [(Vdd/10)/2.5V] * 1024  = Vdd * 40.96, then Vdd = aReadValue / 40.96
  return ( aReadValue / 40.96 );
}
The above function requires the megaTinyCore board manager installed in the Arduino IDE. You can get that (and lots of great instruction) here.

I’ve had good success running an ATTiny3224 directly off of a single Li-Ion battery and reading the current battery voltage with the above function. There are no external components needed for the mcu except a decoupling capacitor. It has an internal crystal as well as the ability to upload your code via UPDI (okay, this took a bit of work to set up, but once done it's a great way to upload code).
 
Last edited:

Thread Starter

amishlol234

Joined Mar 10, 2022
32
Looking at that code... The Vref is set to 1.1v ergo you need a voltage divider to set 4.2V ( max battery) to sit at 1.1v on the ADC pin. Then change the scaler to use 4.2v and not 5v as this code would have been used on a 5v system.

In the scale equation there is 'internalreferencevoltage' and the constant '1024'. This could be a single constant and forgo the multiplication. The voltage divider can be set to use 1.1volts, 2.56 volts or 5v

using 2.56v you can use 620Ω on top of a 1k. This gives 2.59v at 4.2 v but its quite close.


Hi, thank you for your response but I still have some confusion I've set my voltage divider below 1.1v it's somewhere around 0.6-0.7v. Why would I need 2.59v please explain
 

Thread Starter

amishlol234

Joined Mar 10, 2022
32
Since you’re going to end up using an ATTiny chip, I would suggest maybe using one of the newer Series 2 ATTinys. They have a built-in ADC that can directly read the incoming VCC. So, no need for external components for a voltage divider. Just use this function in your code:

ReadVoltage function:
float  readVoltage() {   // return the current VCC voltage
  // Using the builtin ADC
  int aReadValue = 0;
   analogReference(INTERNAL2V5);   //  Set the ADC reference voltage to be 2.5V
   aReadValue = analogRead(ADC_VDDDIV10);   // This doesn't read a pin; it reads 1/10 of the Vdd
   analogReference(VDD);   //  Set the ADC reference voltage back to Vdd
  //Calculate voltage thusly:  Since aReadValue = [(Vdd/10)/2.5V] * 1024  = Vdd * 40.96, then Vdd = aReadValue / 40.96
  return ( aReadValue / 40.96 );
}
The above function requires the megaTinyCore board manager installed in the Arduino IDE. You can get that (and lots of great instruction) here.

I’ve had good success running an ATTiny3224 directly off of a single Li-Ion battery and reading the current battery voltage with the above function. There are no external components needed for the mcu except a decoupling capacitor. It has an internal crystal as well as the ability to upload your code via UPDI (okay, this took a bit of work to set up, but once done it's a great way to upload code).

Thank you,

I already have attiny44A and I am using attinyCore board manager for my IDE. Do you have any solution to it.
 

BobTPH

Joined Jun 5, 2013
8,813
I would set the voltage divider to 1:4, i.e. 10K and 30K. Then 4.4V will read full scale with a 1.1V reference.

So then you get the result, and the voltage is 4.4 * reading / 255.
 

Ian Rogers

Joined Dec 12, 2012
1,136
Hi, thank you for your response but I still have some confusion I've set my voltage divider below 1.1v it's somewhere around 0.6-0.7v. Why would I need 2.59v please explain
You are reading the battery voltage.. A lithium fully charged is 4.2 volts... If you set your Vref ( internal ) to 2.56v You will need to read the ADC and reference 2.56v.. Using the internal ref that means the ADC will read 1024 when 2.56v is on the pin. So...You need to make the pin 2.56V when the battery is 4.2v.. ergo 4.2v / (620+1000) * 1000 = 2.59v which is close enough.

The ADC on a micro isn't very stable with high input impedance so I normally keep well below 10k unless your reads are miles apart. Bobs 40K divider will work if there is a good settle time before the read. I picked easy to get resistor values... Normally I stock bog standard resistors..
 

BobTPH

Joined Jun 5, 2013
8,813
You can decrease the impedance of a voltage divider for ADC by putting a 100nF capacitor across the lower resistor.
 

Thread Starter

amishlol234

Joined Mar 10, 2022
32
#define led 8
void setup() {
Serial.begin (115200);
pinMode (led, OUTPUT);
}

void loop() {
analogReference(INTERNAL);
ADCSRA |= B11000000;
// Detect end-of-conversion
while (bit_is_set(ADCSRA,ADSC));
float VA1 = 1.1 / 1024 * analogRead(A1);
VA1 = ADCL | (ADCH << 8);
float Vbat = VA1 * 5.7;
// Serial.println("battery voltage");
// Serial.println(VA1);
delay (500);


it reads 1023 when battery is on and 0v when off
 

Ian Rogers

Joined Dec 12, 2012
1,136
Okay... I don't know where the 2.56v on the arduino web site comes from.. The AREF pin can be set to anything

So use the internal 1.1 with bobs divider... The Aref pin has to have a decoupling cap fitted... Can you show a circuit.

The 2.56 is only for the mega and the new 32U4's Sorry You are stuck with 1.1..
 

Thread Starter

amishlol234

Joined Mar 10, 2022
32
Okay... I don't know where the 2.56v on the arduino web site comes from.. The AREF pin can be set to anything

So use the internal 1.1 with bobs divider... The Aref pin has to have a decoupling cap fitted... Can you show a circuit.

The 2.56 is only for the mega and the new 32U4's Sorry You are stuck with 1.1..


You know I was super confused with 2.56 and I thought there's some misunderstanding anyways, I've a code that reads internal reference voltage of 1.1v and outputs solar voltage on the serial monitor. My question is that, is it necessary to measure battery voltage as constant voltage ( I mean irrespective of what voltage is at the battery end, the code or IC reads full on value 1023 always) because as per the voltage calculations formula int sensor =( Vref/1024)*ADC; in this formula all values must be constant or else the solar value would change as the battery voltage depletes. Please clarify if its imp to measure battery voltage or we can directly substitute 1.1v in place of vref.

working code:

void setup() {
// put your setup code here, to run once:
Serial.begin (9600);
analogReference (INTERNAL);
}

void loop() {
// put your main code here, to run repeatedly:
float voltage = (1.1*analogRead(A0))/ 1024;
Serial.print ("voltage at A0:");
Serial.println(voltage);
Serial.print ("Solar panel voltage:");
Serial.println(voltage * 5.7);
delay(1000);
}


1677732018197.png

See the above picture, ive two issues now,
1) scale is very small.
2) If i connect battery to solar like this it reads 1.1v, 6.27v respectively.

NOTE : I have used LDR in place of solar panel due to the unavailability of the component in my software.
 
Last edited:

Ian Rogers

Joined Dec 12, 2012
1,136
If A1 is reading the battery voltage, it will be wrong reading a battery @ 4.2v you need to drop the 4k7 to a 3k if you can get one. ( V / total resistance) * base resistance = Vref So ( 4.2v / (3000+ 1000)) * 1000 = 1.05V THEN!

On the solar panel keep the 4k7 and a 1k then at 6V you will get 1024 bits, But it won't go over 6v so you need to play.

Use this:-
float battery= (4.2*analogRead(A1))/ 1024;

Use the other resistors for the solar panel , but look at this 1.1v = 1024 bits so you need to multipy A1 by 0.006.
Or use another read.
float solar = (6 * analogRead(A0)) / 1024; For the solar panel

To get exact figures swap the 4k7 for a 10k trimmer and set it perfectly..
 

Thread Starter

amishlol234

Joined Mar 10, 2022
32
If A1 is reading the battery voltage, it will be wrong reading a battery @ 4.2v you need to drop the 4k7 to a 3k if you can get one. ( V / total resistance) * base resistance = Vref So ( 4.2v / (3000+ 1000)) * 1000 = 1.05V THEN!

On the solar panel keep the 4k7 and a 1k then at 6V you will get 1024 bits, But it won't go over 6v so you need to play.

Use this:-
float battery= (4.2*analogRead(A1))/ 1024;

Use the other resistors for the solar panel , but look at this 1.1v = 1024 bits so you need to multipy A1 by 0.006.
Or use another read.
float solar = (6 * analogRead(A0)) / 1024; For the solar panel

To get exact figures swap the 4k7 for a 10k trimmer and set it perfectly..

okay, i tried one code

int led = 8;

void setup() {
// put your setup code here, to run once:
Serial.begin (9600);
pinMode (led, OUTPUT);
analogReference (INTERNAL);
}

void loop() {
// put your main code here, to run repeatedly:
float voltage = (analogRead(A0)/1023*(1.1)*(5.7));
int ADC_value = ADCL | (ADCH<<8);
Serial.print ("solar panel voltage");
Serial.println(ADC_value);
delay(1000);


if (ADC_value<=1023){
digitalWrite (led, HIGH);
}
else{
digitalWrite (led, LOW);

}
}

it works perfectly. Now, arduino code was just for prototyping so that i can see the output on serial monitor now, after shifting to attiny44 ic the same code is not working.


int led = 3;

void setup() {
// put your setup code here, to run once:
Serial.begin (9600);
pinMode (led, OUTPUT);
pinMode (2, INPUT);
//analogReference (INTERNAL);
}

void loop() {
// ADMUX = (1 << REFS1) | (0 << REFS0) ; // Sets ref. voltage to internal
analogReference (INTERNAL);

float voltage = (analogRead(2)/1023*(1.1)*(5.7));
ADCSRB = (1<< ADLAR);
int ADC_value = ADCL | (ADCH<<8);
Serial.print ("solar panel voltage");
Serial.println(ADC_value);

delay(1000);

if (ADC_value<=1023){
digitalWrite (led, HIGH);
}
else{
digitalWrite (led, LOW);

}
}

can you point out the error?
 

MrChips

Joined Oct 2, 2009
30,713
How many accounts do you have on AAC?
Multiple accounts will make it confusing. Tell us which account you want to use and which should be closed.
 

Ian Rogers

Joined Dec 12, 2012
1,136
How many accounts do you have on AAC?
Jesus.. I didn't even notice that..

On the tiny you have an alignment ADLAR set? But not on the Uno code... As far as I'm aware the code is 100% portable but settings can differ.

I have read that the tiny44 requires the board manager to download that specific device, But I'm not an expert here.
 

Thread Starter

amishlol234

Joined Mar 10, 2022
32
Jesus.. I didn't even notice that..

On the tiny you have an alignment ADLAR set? But not on the Uno code... As far as I'm aware the code is 100% portable but settings can differ.

I have read that the tiny44 requires the board manager to download that specific device, But I'm not an expert here.

Yes because setting ADLAR on UNO was not required, i never thought of it because it worked so perfectly.
 

Thread Starter

amishlol234

Joined Mar 10, 2022
32
This thread, yes, i am the starter
OMG! i just realized it, just rechecked, yesssssss it is me that's because due to some reasons i was not able to access that above account and that is why i had to create this new one in order to comment. Is that a violation. if yes, how can i delete top comments?
 
Top