Go Down

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

Koepel

The ACS712 requires indeed the DEFAULT voltage reference of 5V, because it does not output a voltage, it outputs a certain fraction of the 5V.
steger, those ACS712 modules are very inaccurate. Assume they are 20% inaccurate as a start. Maybe it can be improved later.

johnerrington

Thanks @Koepel, I didnt spot that.
In which case would it be sensible to calibrate the default against the internal reference using the "magic voltmeter" and do everything with the default reference - assuming Vcc is stable?
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)

Koepel

In which case would it be sensible to calibrate the default against the internal reference using the "magic voltmeter" and do everything with the default reference - assuming Vcc is stable?
That is possible, but changing the reference voltage is made for this situation. Since there is a 100nF at AREF, some time is needed and the first ADC value might not be valid.
The 5V is probably not really stable. It will be less stable than the internal reference.

With a Arduino Mega 2560 board + Ethernet shield I switch the reference voltage all the time, and I also run the "magic voltmeter" all the time. Returning to normal after the "magic voltmeter" took more effort. I had to increase the delay to 20ms and do more than one dummy analogRead(). I made a mistake in my project. I put everything on a prototype shield and now the current to the Mega+Ethernet causes a offset to my voltage measurements. That is because R2 of the voltage divider is connected to the GND on the prototype shield. The offset is only 6mV, but it is noticeable.

septillion

@steger Again, did you notice the easy calibrate option ;)

Code: [Select]

float averageRead(int pin)

Why is that float so damn tempting ;) Keep it integer :)

Code: [Select]

#define BatVolt_pin = 0;     //Anlaoge pin # A0

What's wrong with a type ave integer!?

I would still wait a bit for the ref to settle after changing it. Now you include the settling into the averaging.

Code: [Select]
byte BattVoltage() {
[...]
return percentage;

Seems rather confusing to me ;)


Also, try
          to
  keep
    the
indentation
                      in check

And about percentage. Yeah, you can take temperature and current all in consideration, but do you really need to now it that accurate in that many situation? I can tell you, most consumer electronics does not bother. And are you actually downing that much current? And do you expect it to set in the freezing cold?

Code: [Select]
const unsigned long BatRs[] = {51028, 9979};    // as long as they have the same unit
const unsigned long BatVref = 2560;            //mV ; this must be measured on the AREF pin accurately with DMM
//after initiating 'analogReference(INTERNAL2V56);' and change here.
const unsigned long BatResolution = 1024;
const unsigned long BatRatio = ((BatRs[0] + BatRs[1]) * BatVref) / BatRs[1];
const unsigned long BatVoltMax = 13500; //mV
const unsigned long BatVoltMin = 10400; //mV
const byte BatVoltPin = A0;     //Analog pin # A0
const unsigned int NrSamples = 1000;   // Usually from 5 to 10000

byte batteryPercentage() {
  analogReference(INTERNAL2V56);   // set the reference voltage level from 5V to 2.56V
  delay(2); //I would still wait for it to settle in

  // unsigned int adcValue = analogRead(BatVoltPin);      // read the divided battery voltage on analog pin # 0
  // unsigned int batVoltage = (adcValue + 0.5) * BatRatio / BatResolution; // add them up
  // take the average of few readings for better accuracy:
  // https://gist.github.com/Koepel/f7d625a6e5c0481fc4c7a9c530c643ef   :
  unsigned int adcValue = averageRead(BatVoltPin);  // read the average of the divided battery voltage on analog pin # 0
  analogReference(DEFAULT); //switch back for later use
  unsigned int batVoltage = adcValue * BatRatio / BatResolution;
 
  Serial.print("Batery voltage [mV]: ");
  Serial.println(batVoltage);
  byte percentage = 100 * (batVoltage - batVolt_Min) / (batVolt_Max - batVolt_Min);  // to be improved
  Serial.print("Battery level [%]: ");  //<= statement ends, next line please!
  Serial.println(percentage);
 
 
  return percentage;
}


unsigned int averageRead(byte pin){
  unsigned long total = 0;
  for(unsigned int i = 0; i < NrSamples; i++)
  {
    total += analogRead(pin);
  }
  total += nSamples / 2;   // add half a bit
  return (total / NrSamples);
}
Use fricking code tags!!!!
I want x => I would like x, I need help => I would like help, Need fast => Go and pay someone to do the job...

NEW Library to make fading leds a piece of cake
https://github.com/septillion-git/FadeLed

Koepel

#19
Jan 09, 2021, 06:34 pm Last Edit: Jan 09, 2021, 06:39 pm by Koepel
I prefer calculations in the source code with numbers that are in real units (liters, meters, volt, ampere, kPa, kg, and so on).
With float calculations there is no trouble with overflow or divisions, it is just as fast (maybe 50% slower, that's peanuts) and most important: the source code is more readable.
Using the 10-bit ADC value for a calculation with 32-bit single precision float is just fine. Nothing bad will happen.

septillion, my averageRead() function is supposed to return a float. That is the whole point of that function. It uses noise to get a little more information than the 10-bit from the ADC.

Don't sink, stay float ;)

steger, when Arduino users disagree and both solutions are good, then you can pick what seems best for you :D

steger

#20
Jan 10, 2021, 05:30 am Last Edit: Jan 10, 2021, 06:29 am by steger
Thank You for You suggestions and update on the sketch.
The batteries can discharge in -20 / +55 deg. If the rover operate on the field, then the temperature can make a significant impact on battery capacity.

Just to make the study complete:
For external reference voltage version, I followed this link and made a schematic for 12V version.
Is used for  Analog Pin A1. So analogReference(INTERNAL2V56);  obsoleted.
The data sheet for LM 4040 is attached. The script can be figured out.


Now I turned my attention to septillion's easy calibration proposal.
Let say initially I have 12.1V on battery (measured with DMM).
After voltage divider: UA0 = 12.1V * 10k * (10k + 47k) = 2.122V
If I run separately analogRead(BatVoltPin) this will give somewhere adcValue = 434.
If during the operation, I get let say adcValue' = 378, due to linearity:
12.1V/434 = Vcc/378 -> Vcc = 12.1V * 378 / 434 = 10.538 V  (actual battery voltage during operation)


The updated sketch for Your kind consideration and review:
Code: [Select]
const unsigned long BatVIni = 12100;   //mV  measure only one time, when connected to the circuit
const unsigned long AdcValueIni = 434; //  to get, run once separately analogRead(BatVoltPin),
                                       // in the same time when BatVini measured. Only one time required.
const unsigned long BatRatio = BatVIni / AdcValueIni;
const unsigned long BatVoltMax = 13500; //mV
const unsigned long BatVoltMin = 10400; //mV
const byte BatVoltPin = A0;     //Analog pin # A0
const unsigned int NrSamples = 1000;   // Usually from 5 to 10000

byte batteryPercentage() {
  unsigned int adcValue = averageRead(BatVoltPin);  // let say it is 378

  unsigned int batVoltage = adcValue * BatRatio;  //mV; let say it is 10538 mV
 
  Serial.print("Batery voltage [mV]: ");
  Serial.println(batVoltage);
  byte percentage = 100 * (batVoltage - batVolt_Min) / (batVolt_Max - batVolt_Min);  // to be improved
  Serial.print("Battery level [%]: ");  //<= statement ends, next line please!
  Serial.println(percentage);
   
  return percentage;
}

unsigned int averageRead(byte pin){
  unsigned long total = 0;
  for(unsigned int i = 0; i < NrSamples; i++)
  {
    total += analogRead(pin);
  }
  total += nSamples / 2;   // add half a bit
  return (total / NrSamples);
}



steger

#21
Jan 10, 2021, 06:51 am Last Edit: Jan 10, 2021, 12:29 pm by steger
I just one note to the external Vref with LM4040.
Is it possible, that the diode is not so accurate ?
According this (John Errington's Experiments with an Arduino):
link
link for sample code
In the script mentioned the following:
Quote
// for voltage conversion
float vRef = 3.390; // the accurate value of your reference eg 4.096
As per data sheet, the typical is: 4.096 V
Can be the difference: 3.390 / 4.096 = 0.82 -> 18% ?
In this case (despite the diode) the voltage on the diode must be measured and hard-coded in the script!

septillion

Which grade do you use? But I don't believe it will ever be that low (if biased correctly). It would also be wiser to bias it from 5V to keep the bias current more stable.

But why not use it as the voltage reference instead of measuring it?
Use fricking code tags!!!!
I want x => I would like x, I need help => I would like help, Need fast => Go and pay someone to do the job...

NEW Library to make fading leds a piece of cake
https://github.com/septillion-git/FadeLed

steger

For LM4040 the following ranges can be used:
2.048 V +/- 0.1%
2.5 V +/- 0.1%
3.0 V +/- 0.1%
4.096 V +/- 0.1%
I preferred 2.048 V version, because it give better resolution after ADC.


As per this link, somewhere in the middle:
Quote
Here is a sketch that does all this for you, and averages the readings for better resolution and noise reduction.
This script is called. He used LM4040 4.096 V version, but at the end of the day the reference voltage on the diode measured and hard-coded:.

Quote
// for voltage conversion
float vRef = 3.390; // the accurate value of your reference eg 4.096
/* *** change the value to match the reference voltage you are using *** */

septillion

@steger There are way more versions (grades) than that. Both in static accuracy and temperature coefficient. And yeah, it doesn't matter which version you use, as long as you know what you use and reflect that in the software. But after a quick scan of the datasheet, 3.390V instead of 4.096V seems outside any spec you may expect from it.

And again, why not use it as the actual reference instead of measuring it and compensate the other reading?
Use fricking code tags!!!!
I want x => I would like x, I need help => I would like help, Need fast => Go and pay someone to do the job...

NEW Library to make fading leds a piece of cake
https://github.com/septillion-git/FadeLed

steger

#25
Jan 11, 2021, 05:09 pm Last Edit: Jan 11, 2021, 05:10 pm by steger
septillion: I got Your point. The voltage remain the same, no single change, just feed with min. 60 uA as the datasheet showing. I thought it is slightly changing, that is why the author of the script put the corrected value 3.390 V instead of 4.096 V.

steger

#26
Jan 12, 2021, 01:18 pm Last Edit: Jan 12, 2021, 01:20 pm by steger
After so many improvements, I would like to involve the temperature in the battery capacity (percentage) calculation.
For case study with LiFePO4 battery, I selected this source: link and the attached picture (discharge).

-> If Your have better source, like with numbers/ (%&Volt) data, which generating the graph itself, then kindly share with us, because those data can be used for non-linear regression. (Instead of manually figure out the points from the below graph.)



johnerrington

#27
Jan 13, 2021, 09:10 am Last Edit: Jan 13, 2021, 10:09 am by johnerrington
@steger I must apologise, I'm editing that page and you have been caught in the maelstrom.

My suggestion was you use a 4.096 as an EXTERNAL reference, not as a source for calibration.
Then change your divider to match that voltage range.

eg as @septillion says
Quote
And again, why not use it as the actual reference instead of measuring it and compensate the other reading?
Many "arduinos" dont provide a connection for an external reference, so if you were using one of those you could use the calibration source approach as in my sketch.

However in that sketch
Code: [Select]
// for voltage conversion
float vRef = 3.390; // the accurate value of your reference eg 4.096
/* *** change the value to match the reference voltage you are using *** */


means put in the value of your chosen reference.
So if you are using a 2.3653V reference thats what you put in.

Your misunderstanding has been very useful feedback, and I've amended my page.

http://www.skillbank.co.uk/arduino/measure.htm
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

#28
Jan 13, 2021, 02:57 pm Last Edit: Jan 13, 2021, 02:58 pm by steger
@ johnerrington: Thank You very much for Your guidance.
So my external reference shall be as high as possible, because I already would like to measure more than 5V.
I selected your proposal :-) LM4040 AIZ -4.1V
The voltage on the divider shall be approx 0.3V less. (because the 4.1V will be the '100%')
So it will come: 3.8V. I changed the resistors accordingly. (20k & 51k receptively.)
The diode will get the supply form +5V out from MAEGA. (Not from 12V.)
For this, I have to select R1 protection resistor accordingly, so min.60 μA, max. 15 mA can flow through the diode.
I attached the modified schematics. I hope it is fine.

If I write:
Code: [Select]
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.

I hope the above code is fine, after I will post the full script just for the others for better understanding.

I added a Kamra for You. (Now I know, what is it for :-)


steger

#29
Jan 13, 2021, 06:49 pm Last Edit: Jan 13, 2021, 06:50 pm by steger
@ johnerrington: my "interesting" ;) question:
How can we consider / compensate the estimated battery capacity with the temperature ?
(I'm planning to use the battery on a rover, outdoor.)

Go Up