Go Down

Topic: 12V battery meter with Arduino MEGA (Read 973 times) previous topic - next topic

johnerrington

Quote
analogReference(EXTERNAL);
value = readInput(A0);  //it will correspond to 4.1 V accurate; i.e: 4.1V == 1024 (and not to 5V, not so much accurate)
adcValue = averageRead(A0);
voltageOnPinA0 = 4.1 * adcValue / 1024;
batVoltage = ((20+51)/20) * voltageOnPinA0;  // 20k & 51k resistors on voltage divider.
work in mV;

4096/1024 =  4

voltageOnPinA0 = 4 * adcvalue;


check the voltage from the arduino - you do need 5V.

I'm not sure what your numbers below the yellow box are for - you say choose R1 = 4.3k,
then use
5.0 - 4.1 / 1.0k = 209uA which a: is wrong and b: is not 4k3

4k3 doesnt leave much headroom if the "5V" drops.
I'm trying to help. If I find your question interesting I'll give you karma. If you find my input useful please give me karma (I need it)

johnerrington

You would need to re-plot the data to give voltage vs temperature for different capacities.
Soryyou need to take sopt readings from the graph to get your information, a bit tedious but useful
I'm trying to help. If I find your question interesting I'll give you karma. If you find my input useful please give me karma (I need it)

steger

#32
Jan 14, 2021, 08:33 am Last Edit: Jan 14, 2021, 03:30 pm by steger
@ johnerrington: Thank You to pointing put the calculation error and further improvement points..
I intended to use R1= 1k ohm, but somehow I made typing mistake.

However for review, I attached the corrected schematic & full script:
Code: [Select]
const unsigned long BatVoltMax = 13500; //mV
const unsigned long BatVoltMin = 10400; //mV
const unsigned long BatRs[] = {51028, 19979};    // R2 & R3 in ohm, must be measured accurately !!!
const unsigned long InverseVoltDividerRatio = ((BatRs[0] + BatRs[1]) / BatRs[1];
const byte BatVoltPin = 0;        //Analog pin # A0
const unsigned int NrSamples = 10;   // Usually from 5 to 64 (corrected, was: 10000)
byte percentage ;

byte batteryPercentage() { 
  analogReference(EXTERNAL);
  adcValue = averageRead(BatVoltPin);
  voltageOnPinA0 = 4 * adcValue; // mV; corrected was: voltageOnPinA0 = 4096 * adcValue / 1024;
  unsigned int batVoltage = InverseVoltDividerRatio * voltageOnPinA0;  // mV; 20k & 51k resistors on voltage divider.
  Serial.print("Batery voltage [mV]: ");  Serial.println(batVoltage);
  byte percentage = 100 * (batVoltage - BatVoltMin) / (BatVoltMax - BatVoltMin);  // to be improved with temperature compensation
  Serial.print("Battery level [%]: ");  Serial.println(percentage);
  return percentage;
}
 
float averageRead(int pin)
{
  unsigned long total = 0;
  for( int i=0; i < NrSamples; i++)
  {
    total += analogRead(pin); // corrected was: (unsigned long) analogRead(pin);
  }
  total += NrSamples / 2;   // add half a bit
  return(total / NrSamples);
}




johnerrington

I'd STRONGLY recommend you use integer calcs and work in mV. That way you will not have issues misreading the precision of your result.
With a reference of 4.096V the smallest change you can read is 4mV. Integer.

Also as I already said

voltageOnPinA0 = 4096 * adcValue / 1024;

is just voltageOnPinA0 = 4 * ADCvalue; why make life hard? 

Quote
const unsigned int NrSamples = 10;   // Usually from 5 to 10000
there is no point going above 64.


Code: [Select]
 
float averageRead(int pin)   //keep it int
{
  unsigned long total = 0;
  for( int i=0; i < NrSamples; i++)
  {
    total += (unsigned long) analogRead(pin);   // I dont think you need to cast
  }
  total += NrSamples / 2;   // add half a bit
  //return(float(total) / float(NrSamples));
  return(total / NrSamples);
}



I'm trying to help. If I find your question interesting I'll give you karma. If you find my input useful please give me karma (I need it)

steger

@ johnerrington: thank You for Your advises. I corrected the script accordingly.

Now I would like to continue with the temperature dependency of the capacity of the battery.
My approach:
The battery discharge curve depending on a lot of parameters (temperature, chemistry, charge & discharge rates etc.)
The most significant from my point of view is the temperature, which can be relatively easily and accurately measured and involved in capacity calculation.
So first of all to everybody, who will follow the next steps and get accurate results, they have to have the discharge curves for they own battery and correct the figures accordingly to avoid any additional error.
Take a curve @ specific temperature, split in three or more sections. Take the values manually and fetch in tables.
Do the regression (insert trend line) with a software (Excel etc.) and make the equation for the curve visible on the graph.
Hard-code the ranges and equation in the script. Measure the temperature and pass it in the function. 
Somehow as I attached. 




steger

The script without temperature measurement:
Code: [Select]
const unsigned long BatVoltMax = 13500; //mV
const unsigned long BatVoltMin = 10400; //mV
const unsigned long BatRs[] = {51028, 19979};    // R2 & R3 in ohm, must be measured accurately !!!
const unsigned long InverseVoltDividerRatio = ((BatRs[0] + BatRs[1]) / BatRs[1];
const byte BatVoltPin = 0;            //Analog pin # A0
const unsigned int NrSamples = 10;    // Usually from 5 to 64
byte percentage ;

byte batteryPercentage(unsigned long temp) { 
  analogReference(EXTERNAL);
  adcValue = averageRead(BatVoltPin);
  voltageOnPinA0 = 4 * adcValue; // mV; 4096 / 1024 =4
  unsigned int batVoltage = InverseVoltDividerRatio * voltageOnPinA0;  // mV; 20k & 51k resistors on voltage divider.
  Serial.print("Batery voltage [mV]: ");  Serial.println(batVoltage);
 
 // *** NEW section start ****************************
  batVoltage = batVoltage / 1000; // I converted mV to V for better understanding, but You can use mV as well.

  if (temp >-15 && temp <= -5) // -10 Celsius (green) curve
  {
if (batVoltage < 12.7 && batVoltage >=12.0) percentage = -65*batVoltage*batVoltage - 1639.8*batVoltage + 10342;
    if (batVoltage < 12.0 && batVoltage >=11.85) percentage = -230*batVoltage + 2784 ;
if (batVoltage < 11.85 && batVoltage >=10.0) percentage = -6.6703*batVoltage*batVoltage + 136.85*batVoltage - 627.28 ;
percentage = 100* (75 - percentage)/75;   // as per graph @75% the battery fully depleted ! So 75% will be equvivalent to 100%
  } 
   
  else if (temp >-5 && temp <= 5)  //percentage = ? You can calculate as above approach 0 Celsius (red) curve
  else if (temp >5 && temp <= 15)  //percentage = ?...   +10 Celsius (gray) curve
  else if (temp >15 && temp <= 25) //percentage = ?... +20 Celsius (orange) curve
  else if (temp >25 && temp <= 35) //percentage = ?... +30 Celsius (lilac) curve
  else if (temp >35 && temp <= 45) //percentage = ?... +40 Celsius (light blue) curve
  else if (temp >45 && temp <= 55) //percentage = ?... +50 Celsius (black) curve
  else Serial.println("Wrong value!");       // this can happen if the battery is freshly charged
        // In this case it quickly drop from 14.6V to 13.5V or less! (characteristic curve required)
// or the cell voltage below cut-off i.e: 10 V/4 = 2.5V
// or battery temperature below -15C or over 55 C.
  analogReference(DEFAULT); // default (+5V) required for temperature sensor
  // *** NEW section end ****************************
 
  Serial.print("Battery capacity [%]: ");  Serial.println(percentage);
  return percentage;
}
 
float averageRead(int pin)
{
  unsigned long total = 0;
  for( int i=0; i < NrSamples; i++)
  {
    total += analogRead(pin);
  }
  total += NrSamples / 2;   // add half a bit
  return(total / NrSamples);
}


Important: The equations after 'if' statement to be adjusted for Your actual battery discharge curves!

johnerrington

#36
Jan 14, 2021, 05:31 pm Last Edit: Jan 14, 2021, 05:39 pm by johnerrington
I'd test the temp sensor on its own firat with this sample skecth

Quote
float averageRead(int pin)  //INT!
{
All that if ..else is a clear contender to be written as a switch - case structure

Quote
unsigned int batVoltage = InverseVoltDividerRatio * voltageOnPinA0;  // mV; 20k & 51k resistors on voltage divider.

..

batVoltage = batVoltage / 1000;   massive loss of precision

..

if (batVoltage < 12.7  //comparing an unsigned it to a float?
I'm trying to help. If I find your question interesting I'll give you karma. If you find my input useful please give me karma (I need it)

steger

#37
Jan 15, 2021, 04:00 am Last Edit: Jan 15, 2021, 06:17 am by steger
If I understood correctly, the following changes are required:
float averageRead(int pin)  //INT!
Improvement, changed to:
int averageRead(int pin)
{
...

batVoltage = batVoltage / 1000;  // massive loss of precision
Improvement: Take the data from graph only in mV and not in V.
In this case divider required (1000 0000 & 1000) in the 'percentage' equation accordingly.
So this:
if (batVoltage < 12.7  //comparing an unsigned it to a float?
Can become like this:
if (batVoltage < 12700

I don't understand fully:
Quote
All that if ..else is a clear contender to be written as a switch - case structure
This can be made for 'batVoltage' but not for 'temp', due to the value must be integer:
Code: [Select]
if (temp >-15 && temp <= -5) // -10 Celsius (green) curve
{
  switch ( batVoltage )  {
    case 12000 ... 12700:
       percentage = -65 * batVoltage * batVoltage / 1000000 - 1639.8 * batVoltage / 1000 + 10342;
       break;
    case 11850 ... 11999:
       percentage = -230 * batVoltage / 1000 + 2784;
       break;
    case 10000 ... 11849:
       percentage = -6.6703 * batVoltage * batVoltage / 1000000 + 136.85 * batVoltage / 1000 - 627.28;
       break;
    default:
       percentage = 999;   //wrong 'batVoltage' value
       break;
   }  // end switch
// ...
}

johnerrington

There must be a way to do this in math, not piecewise.  Capacity is a function of terminal voltage and temperature, so there must BE an equation C = (fv, ft)

However, have you spotted that the terminal voltage is ALSO very significantly a function of load current?

The graph you are using is for a discharge current of 0.5C - which for the 32Ah battery is 16A.

Your calculations will only be valid at all if that current is being drawn.

As for a rover the load current will be constantly changing you cant calculate the remaining capacity unless you can correct for the load current, and it gets more complicated.

MAYBE you could use the open-circuit terminal voltage and temperature to get an estimate - but dont forget a battery terminal voltage doesnt recover instantaneously when the load is removed, so it would need to be open circuit for a little while, not just a second. 
I'm trying to help. If I find your question interesting I'll give you karma. If you find my input useful please give me karma (I need it)

steger

Just further improvement purpose I'm posting the NOT complete code:
(It can be completed with the equations @ different temperatures.)
Code: [Select]
const unsigned long BatVoltMax = 13500; //mV
const unsigned long BatVoltMin = 10400; //mV
const unsigned long BatRs[] = {51028, 19979};    // R2 & R3 in ohm, must be measured accurately !!!
const unsigned long InverseVoltDividerRatio = ((BatRs[0] + BatRs[1]) / BatRs[1];
const byte BatVoltPin = 0;            //Analog pin # A0
const unsigned int NrSamples = 10;    // Usually from 5 to 64
byte percentage;
byte batPercent;
unsigned long batTemp;

// For temperature reading: https://www.hobbytronics.co.uk/ds18b20-arduino
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 2 // Data wire is plugged into pin 2 on the Arduino
// Setup a oneWire instance to communicate with any OneWire devices
// (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);

void setup(void)
{
  Serial.begin(9600);
  Serial.println("Battery voltage & capacity Demo");
  sensors.begin(); // Start up the library
}

void loop(void)
{
  // call sensors.requestTemperatures() to issue a global temperature
  // request to all devices on the bus
  sensors.requestTemperatures(); // Send the command to get temperatures
  batTemp = sensors.getTempCByIndex(0); // Why "byIndex"? You can have more than one IC on the same bus.
  Serial.println("Battery temperature [C]: ");  //0 refers to the first IC on the wire
  Serial.print(batTemp);
  batPercent = batteryPercentage(batTemp); // get the capacity for futher process
    // i.e. if batPecent <10% -> *** rover have to go to the harging station
 
}

byte batteryPercentage(unsigned long temp) {
  analogReference(EXTERNAL);
  adcValue = averageRead(BatVoltPin);
  voltageOnPinA0 = 4 * adcValue; // mV; 4096 / 1024 =4
  unsigned int batVoltage = InverseVoltDividerRatio * voltageOnPinA0;  // mV; 20k & 51k resistors on voltage divider.
  Serial.print("Batery voltage [mV]: ");  Serial.println(batVoltage);
  if (temp >-15 && temp <= -5) // -10 Celsius (green) curve
  {
switch(batVoltage){
case 12000 .. 12700:
            percentage = percentage = -65*batVoltage*batVoltage/1000000 - 1639.8*batVoltage/1000 + 10342;
            break;
case 11850 .. 11999:
            percentage = percentage = percentage = -230*batVoltage/1000 + 2784;
            break;
case 10000 .. 11849:
            percentage = -6.6703*batVoltage*batVoltage/1000000 + 136.85*batVoltage/1000 - 627.28;
            break;
default:
            percentage = 999;   //wrong value
            break;
}
percentage = 100* (75 - percentage)/75;   // as per graph @75% the battery fully depleted ! So 75% will be equvivalent to 100%
  }
  else if (temp >-5 && temp <= 5)  //percentage = ? You can calculate as above approach 0 Celsius (red) curve
  else if (temp >5 && temp <= 15)  //percentage = ?...   +10 Celsius (gray) curve
  else if (temp >15 && temp <= 25) //percentage = ?... +20 Celsius (orange) curve
  else if (temp >25 && temp <= 35) //percentage = ?... +30 Celsius (lilac) curve
  else if (temp >35 && temp <= 45) //percentage = ?... +40 Celsius (light blue) curve
  else if (temp >45 && temp <= 55) //percentage = ?... +50 Celsius (black) curve
  else Serial.println("Wrong value!");       // this can happen if the battery is freshly charged
    // In this case it quickly drop from 14.6V to 13.5V or less! (characteristic curve required)
// or the cell voltage below cut-off i.e: 10 V/4 = 2.5V
// or battery temperature below -15C or over 55 C.
  analogReference(DEFAULT); // default (+5V) required for temperature sensor
  Serial.print("Battery capacity [%]: ");  Serial.println(percentage);
  return percentage;
}
 
int averageRead(int pin)
{
  unsigned long total = 0;
  for( int i=0; i < NrSamples; i++)
  {
    total += analogRead(pin);
  }
  total += NrSamples / 2;   // add half a bit
  return(total / NrSamples);
}



steger

Quote
There must be a way to do this in math, not piecewise.  Capacity is a function of terminal voltage and temperature, so there must BE an equation C = (fv, ft)
I'm constantly searching. At least the measured capacity vs. terminal voltage @ specific temperature & @ specific discharge rage (C) (from which the curves itself made) in a big Excel table would be a great help.

Quote
However, have you spotted that the terminal voltage is ALSO very significantly a function of load current?
Yes, because as the load current go up, then due to the internal resistance of the battery, the terminal voltage will drop. But we are measuring the actual voltage on the battery. So this factor already considered in the discharge curve. (Please correct me if I was wrong.)

Quote
The graph you are using is for a discharge current of 0.5C - which for the 32Ah battery is 16A.
Correct. I had only this one. (Unfortunately the manufacturers are not sharing all infos, which are needed.)
The studied C-rate shall be near to the real average load during the operation.

Quote
Your calculations will only be valid at all if that current is being drawn.
This is the aim, so during the continuous operation, the rover can realize when the battery capacity hit the low limit (let say 10%) and can go to the charging station or trigger alarm etc.

Quote
As for a rover the load current will be constantly changing you cant calculate the remaining capacity unless you can correct for the load current, and it gets more complicated.
Yes, and this is a really disturbing factor. If the load is not stable for a certain of period of time let say 30 sec. , then the the C-rate is constantly changing and hampering the discharge curves/calculation.
To help on this, the schematic can be upgraded with an accurate current sensor. (ref my post on Jan 09.21 05:16 am) so one more factor but curves/data are required.
C = (fv, ft, fc)
C: battery capacity
fv: terminal voltage
ft: temperature
fc: discharge/c-rate

johnerrington

I'm trying to help. If I find your question interesting I'll give you karma. If you find my input useful please give me karma (I need it)

steger

#42
Jan 18, 2021, 02:59 pm Last Edit: Jan 18, 2021, 03:04 pm by steger
@ johnerrington: Thank You ! It was a very useful link (I add 1 Karma :-).

As I saw in this link there are 3 points for consideration:
1) Coulomb Counter (Battery Fuel Gauge)
   Measure the current instead of ACS712 with INA216 (datasheet attached), because it is more accurate
   Okay, I attached my schematic.

But in the same thread I dont' understand this:
Quote
An INA216 breakout board could be a better solution for your (unspecified) project.
Voltage, current, power, all in one.
Leo..
The terminal voltage not measured with INA, just volt drop on shunt. So the LM4040 AIZ-4.1 diode and voltage divider shall remain.

2) Calculate the capacity consumption as I(load)*time and store. Later subtract from nominal capacity of the battery (2200 mAh).

3) How the whatever state of the battery can be taken ? For this we have to know the actual voltage and discharge curves. (Already solved in this thread :-)
Anyhow, we need voltage value, because if the terminal voltage reach the cut-off value, we have to be re-charged.

So I would like combine the till achieved results with the new one (point 2&3):
I know the voltage & temperature, so when I start the rover, I can take a low 0.1C discharge cure as reference. -> I know the initial battery state and how much max. capacity is possible on the actual temperature (for example @ -10C (green line) only 75% of the max. Let saying 12Ah battery 12*75%= 9Ah max).
If I start the rover and measure the terminal voltage let say showing: 12 V, then it means 25% already consumed (12 Ah* 25% = 3Ah). (See on the discharge curve.)

So If I run the rover over a certain time, I can collect / calculate / integrate the I(load)*time.
Recharge required when this equation is less than zero:   9Ah - 3Ah - I(load)*time.
With this, the dependency of the discharge cure from the c-rate is resolved, but during operation the temperature shouldn't changed. C = (fv, ft)




johnerrington

@Steger, I have been thinking about this and perhaps we are looking at it from the wrong end.

The fuel guage on my car gives me an INDICATION of the remailing fuel.

Accepting that there are serious limitations on how accurately you can estimate the remaining capacity, why not look at the conditions that match 5%, 10% etc.  ?
So eg if temp = 20C and voltage = 11.3V and current drain =  3A;

not explaining very well but maybe you get the idea
I'm trying to help. If I find your question interesting I'll give you karma. If you find my input useful please give me karma (I need it)

steger

#44
Jan 18, 2021, 05:19 pm Last Edit: Jan 18, 2021, 05:20 pm by steger
@:johnerrington Yes, I got the idea:
So eg if temp = 20C and voltage = 11.3V and current drain =  3A;
I'm measuring the actual current drain of the load and we know the nominal capacity of the battery, let say: 12Ah
The discharge rate will be 3A/12Ah -> C=0.25
If the discharge curve known @ 20C temperature and @ C=0.25 (manufacturer's data sheet) and we are already measuring the actual terminal voltage, then remaining battery capacity can be easily calculated with the interpolated curves based on the curves of the datasheet. (my post on Jan 14, 2021, 03:02 pm)
The accuracy issue is coming due to:
Only few discharge curves are given from (0.1, ... 1C ...) on a discrete temperatures (-10, 0, 10, 20, ...).
So we don't known about the battery capacity for example at t=22.45C and  C=0.234.
This can be resolved, if we know the function of the Capacity = (fv, ft, fc) with 3 independent variables.
(Of course, there are more factors, which are playing role as well.)

Go Up