Arduino battery capacity tester

My first Arduino project was to build a battery capacity tester. I’ve got a box of rechargeable AA batteries, and it seams they’ve been less and less effective. Since most applications require 4 batteries, invariably one problem battery makes the rest of them look bad.

The Atmel ATMega328 microcontroller has 6 analog inputs with 10-bit A-to-D converters and a external AREF that allows you to define what voltage 0×3FF represents. In other words, it’ll give you ~1.4mV precision measuring 0-1.5V when given a 1.5V analog reference. Plenty accurate for a battery capacity measurement.

The principle is fairly simple. Apply a known load to a battery, record the voltage periodically while the battery discharges, stop recording at some point, and integrate to arrive at the area under the curve in order to derive the amp-hours delivered from the battery.

Enough theory, let’s see how it works. The UI starts with a helpful message:

Insert Battery

NIKON D70, ISO 500, ƒ/2.0, 1/20sec, 50mm focal L.

While the battery discharges, the UI prints the voltage, real-time capacity measured thus far, and the duration that the measurement has been taking place.

Battery In

NIKON D70, ISO 500, ƒ/2.0, 1/25sec, 50mm focal L.

When the cut-off voltage of 0.9V has been reached, the “usable” capacity is saved at the top. The real-time data continues so the capacity below 0.9V is measured. For the NiMH batteries I’ve tested, the capacity below 0.9V is minimal (~100mAH).

After the cut-off is reached, an LED starts blinking to attract attention that the test is effectively over.

Done

NIKON D70, ISO 500, ƒ/2.0, 1/20sec, 50mm focal L.

Here’s the code (click to download as text rather than the questionable HTML translated version pasted below):

/*  Battery Characterization Tool

     11/7/2009 John Terry

     Compiled on Aurdino 0017 in 4930 bytes.

 This uses the analog in to measure the voltage of a battery under a known load and
 integrates the area under the curve to arrive at the useful capacity of the
 batterin in mAH.
*/

#include <LiquidCrystal.h>

// Set some constants -- these will need to be adjusted for your setup
//
// Connect a ~10K Ω (Rr) resistor from 3.3V supply pin to the AREF pin,
//   creating a voltage divder with the internal 32KΩ resistor on AREF.
//      aRefVoltage = supply_voltage * 32K / Rr
//   Alternatively, measure the actual AREF voltage applied with a good DMM:
//  my measured aRefVoltage = 2.62;
const float     aRefVoltage = 2.62;

// Connect a load resistor (Rl) to the battery. ~2.2 Ω restistor gives ~500mA drain which
//   is about right for a battery rated at 2500mAH.  Note, this should be a >=1W resistor!
//   Dont trust your DMM to meausre such a low resistance accurately. I measured the current
//   and back-calculated the resistance. Best just to trust rated resistance.
//   my  resistance = 2.18;
//
// The integrator works by accumulating the sampled voltage values from the start until
//   hitting a pre-determined low-voltage threshold. Since they are sampled at a
//   known period, the number of samples taken cancels out and the accumulation of all samples
//   simply needs to be scalled by a factor
//
//   Let's make some definitions to show the derivation of how this is so:
//   I = current
//   V = load voltage
//   Rl = load resistance
//   samples = number of voltage samples made
//   sensor = value read out of analog A->D
//   rate = number of samples taken per second
//
//                                     ave(V)
//   capacity = ∫I ~= ave(I) * time = -------- * time
//                                       Rl
//
//   Where:
//                ∑(V)     ∑(sensor) * aRefVoltage/1024
//     ave(V) = ------- = ---------------------------------
//              samples              samples
//
//      time  =  samples/rate
//
//   Thus:
//               ∑(sensor) * aRefVoltage     samples
//   capacity = ------------------------- * ---------
//                samples * 1024 * Rl         rate
//
//               ∑(sensor) * aRefVoltage
//   capacity = ------------------------- = ∑(sensor) * quanta
//                  1024 * Rl * rate
//
//           quanta = aRefVoltage/(1024 * Rl * rate ) * 1000/3600   // scaled for mA Hours
const double quanta = 0.00030179; // 2.62/1024/2.355/3.6

// Define low voltage threshold where any remaining capacity is "unusable"
//        lowThreshold = 0.9V * 1024/aRefVoltage
const int lowThreshold = 354;

int sensorPin          = 0;    // select the analog input pin for the voltage measurement
int ledPin             = 13;   // select the pin for the LED
int fetGatePin         = 8;    // select the pin for the LED
int sensorValue        = 0;    // unscaled sensor output
float voltage          = 0;    // measured voltage
double mAH             = 0;    // Calculated current
long accumulator       = 0;    // sum of all unscalled sensor values sampled
int epoch              = 0;    // seconds since battery was inserted
int lowVolts           = 0;    // debounce the low voltage threshold
boolean done           = false;// Voltage has dropped below threshold
boolean batteryIn      = false;// Battery present

// initialize the the LCD library with the numbers of the interface pins
//  LCD Pins  --  RS, EN, D4, D5, D6, D7
LiquidCrystal lcd(12, 11,  5,  4,  3,  2);

void setup() {
  // declare the ledPin and fetGatePin as an OUTPUT:
  pinMode(ledPin,     OUTPUT);
  pinMode(fetGatePin, OUTPUT);  

  // set analog reference to external
  analogReference(EXTERNAL);

  // set up the LCD's number of rows and columns:
  lcd.begin(16, 2);

  // Print a helpful start-up message to the LCD.
  lcd.print("Insert battery");

  // DEBUG initialize serial communications at 9600 bps:
  // Serial.begin(9600);
}

void loop() {

  sensorValue = analogRead(sensorPin);    

  if (!batteryIn && (sensorValue > 100)) {
    // Initialize upon the insertion of a "fresh" battery
    batteryIn   = true;
    done        = false;
    epoch       = 0;
    accumulator = 0;
    lowVolts    = 0;
    digitalWrite(ledPin, LOW);
    digitalWrite(fetGatePin, HIGH);
    // clear out when LCD when starting over
    lcd.setCursor(0, 0);
    lcd.print("vlts  mAH  Time ");
    lcd.setCursor(0, 1);
    lcd.print("                ");
  }
  else if (batteryIn && done && (sensorValue <= 20)) {
    // consider the battery removed only after finishing the last measurement to
    // debounce a glitchy concact
    batteryIn=false;
  }
  else if (batteryIn) {
    // Running state during discharge state

    voltage = sensorValue*aRefVoltage/1024.0;

    // print voltage
    lcd.setCursor(0, 1);
    lcd.print(voltage);

    if (!done) {
      accumulator += sensorValue;
      // print mAH
      mAH = accumulator*quanta;
      if      (mAH <   10) { lcd.setCursor(8, 1); }  // adjust to make it perdy
      else if (mAH <  100) { lcd.setCursor(7, 1); }
      else if (mAH < 1000) { lcd.setCursor(6, 1); }
      else                 { lcd.setCursor(5, 1); }
      lcd.print(int(mAH));

    }

    // print current time
    lcd.setCursor(11, 1);
    lcd.print(epoch/60.0);
    lcd.setCursor(15, 1);
    lcd.print("m");

    if (!done) {

      // lowVolts requires 10 seconds in the last 20 before being done
      if (sensorValue < lowThreshold) { lowVolts++;}
      else if (lowVolts > 0)          { lowVolts--;}  

      // If it's below threshold for 10 of 20 samples, bail out
      if (lowVolts > 10) {
        done=true;
        digitalWrite(fetGatePin, LOW);
        lcd.setCursor(0, 0);
        lcd.print("    mAH in     ");
        lcd.setCursor(1, 0);
        lcd.print(int(mAH));

        // put the time, in minutes in the upper right
        lcd.setCursor(11, 0);
        lcd.print(epoch/60.0);
        lcd.setCursor(15, 0);
        lcd.print("m");

        // Clear out the bottom line
        lcd.setCursor(4, 1);
        lcd.print("v      ");
      }
    }

    epoch++;
  } // batteryIn -- main routine

  if (done) {
    // When done, flash the LED to get attention
    digitalWrite(ledPin, HIGH);
    delay(500);
    digitalWrite(ledPin, LOW);
    delay(499);
  } else {
    // Since the processing takes some time prior to the delay, we'll assume 1mS
    // This could stand to be improved with an interrupt routine that is kicked off
    // before all the processing starts for each loop
    delay(999);
  }

/* DEBUG

  Serial.print("\nsensor = " );
  Serial.print(sensorValue);
  Serial.print("\t lowvolrs = " );
  Serial.print(lowVolts);
*/

}

Compiles into 4930 bytes.

7 comments to Arduino battery capacity tester

  • extremesanity

    Thanks for the info!

  • MichaelDallas

    I am wondering about your code. It seems to have errors such as the #include not listing any files. The compiler gives me a few more errors as well. Is this your actual code?

  • MichaelDallas

    Okay. The two things that you need to change to make the code run is to change “#include” to “#include ” and to change “else if (batteryIn && done && (sensorValue < = 20)) {" to "else if (batteryIn && done && (sensorValue <= 20)) {" (i.e. change "< =" to "<=" by removing the space.)

    Thanks for posting this project.

  • MichaelDallas

    After the #include you need to list the LiquidCrystal.h file. This site won’t print the code for some reason.

  • Thanks Michael — Didn’t even notice that the cut-paste of code was partially interpreted as HTML.

    Fixed.

    In any case, the “Here’s the code” has always been a link to the text — much better than cutting/pasting from the browser :)

  • MichaelDallas

    I’ve constructed a version of this. I noticed your comments about not trusting the VOM on the low R (ohm) readings and to measure the current and calculate backwards. You may want to reconsider. I assume you said this because you had problems making the calibration numbers balance (V/I=R). My calculation seemed to indicate an additional .7R to .8R in the system (in addition to the 2.1R resistors). I finally decided to measure the VOM which was measuring current with another VOM for any R that the (first) VOM might be adding. Sure enough, the VOM measuring current added .7R to .8R. While .7R doesn’t mean much in a high impedence system, it makes a big difference when added to 2R of the load resistors.

    Thanks again for posting your example. I had independently thought of building this gadget. Your code saved me a ton of time.

  • Hector Albornoz

    Thank you for this exciting project.
    I have to test the batteries of 16V or 12V.
    I could indicate how you made the connections or a Schematic of tester?

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Spam protection by WP Captcha-Free