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 0x3FF 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 1.52 in 5500 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.2;
//
// 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
//
//
//                aRefVoltage
//   quanta =  --------------------
//               1024 * Rl * rate
//
//   rate in this sketch is 1 Hz (1/sec).
//   Thus, the units of quanta are: volt*seconds/(Ω*samples) = amp*seconds per sample
//
//   What we really want are mA*Hours, so, scale by 1hr/3600seconds and 1000mA/A, or simply 1/3.6
//
//   So, finally:
//   quanta = aRefVoltage/(Rl*1024*3.6)  in units of mA*hours per sample

const double quanta = 0.00032305; // 2.62/(2.2*1024*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);
*/
}

Schematics, excluding the LCD display (pin-out is listed in the code).

41 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?

  • Scott Vitale

    Hi John:
    I was looking over your code/project. You put a lot of thought into the features … and its a good application for an ARDUINO.

    I’m currently working on a project that is similar to yours but is acting as a chart recorder. I am testing Li-Ion charge/discharge characteristics for another project. I had thought about using a simple resistor load but in using that method, the actual load current diminishes as the cell voltage decreases. In my humble opinion, sinking constant current increases the accuracy of your measurments and is a “more correct” way to determine discharge ratings of a battery.

    In my application, I used a constant current sink. I chose a TL431 “Adjustable Precision Shunt Regulator”. On page 29 of the Texas Instruments data sheet, there is a schematic for a “Precision Constant Current Sink”. You can power the TL431 from the ARDUINO 5V supply rail and attach the collector of the sink transistor to your battery. Since the TL431′s “REF” pin has a constant 2.495V (+/-%), it could also be used as your A/D reference instead of the resistor divider you used. The “Standard Grade” part has a +/- 2% tolerance, while the “B Grade” part sports +/- 0.5% tolerance.

    Thanks for posting your work.

    Best regards,
    Scott

    • Hi Scott;

      I too am interesting in creating a constant-current discharger/logger. Would the TL431-based constant current sink work with 1.2v nmh batteries? Would using a MosFet in place of the BJT shown on the TI page work better?

  • John

    @Scott: It’s good point, especially for LION where the discharge profile has the voltage steadily declining throughout the discharge cycle. NiMH has a fairly stable voltage profile that falls off a “cliff” quite rapidly upon depletion. Thus, I suspect you’d see minimal difference in the measured capacity for the two methods.

    Constant power discharge would probably be the best method for applications that use nonlinear voltage regulation.

  • Thanks for posting this.
    Did I miss the link to the parts list and schematic?

  • Thanks for the schematic. :)

    What did you use for Q1? What are the required specs for Q1?

    • I didn’t exactly engineer it and just pieced it together to get the discharge current (drain-source ON resistance) I was looking for. I had a hand-full of unspecified low-current transistors sitting around and I just put them all in parallel to minimize the resistance.

      Really, most any N-channel FET that’s solidly saturated when Vgs>=4 V should work with a continuous current rating of >500mA. You’ll want to account for the resistance of the FET and adjust accordingly to get an accurate measurement.

      Perhaps this one would work fine for you.

      Another way to do it is to use a relay, but that’s more costly. Alternatively, you could use nothing and just be ready to pull the battery out when it hits the low-voltage threshold.

  • Thanks for the info. I do hobby electronics (passives, transistors, op-amps, etc.) , but I’m not really up on MosFets. I want to take your idea (and thank you for posting it) and modify it to make the load constant-current by measuring the voltage drop across the resistor and varying the gate voltage to keep the current constant, regardless of the voltage. And of course, shut off when the voltage on the battery gets to the safe minimum.

    Saw a circuit using an Op-Amp to drive the gate and keep the current constant, but not sure if I need the op-amp since I can use 2 of the A/D inputs to measure the voltage drop directly and calculate the current draw. I’d use one of the PWM outputs and a cap to send a voltage to the gate.

    Any suggestions on how I would go about choosing a suitable MosFet?

    Thanks for your help. :)

  • poperbu

    Hi, I’ve “built” it and it works perfect with low voltage batteries, but I need to test batteries from 9 to 12v, and it shows me always 2.62 volts. Could you help me please?

    • The design is only intended for 1.5 volt batteries (nominal). There are a couple of ways you could adapt it for 12V batteries. The biggest problem is that I doubt the analog input nor AREF input are suitable for usage above 5 volts. I could be wrong on that, so, check the Atmel spec.

      The simplest method to work around this is to use a voltage divider such that the measurement voltage is within the usable range of the analog input. You would then have to scale the formula based on the percentage drop the measurement is taking compared to that of the actual voltage of the battery. You’ll lose some amount of precision, but, it will probably prove to still be acceptably accurate.

  • Zuazua

    Hello:
    I do not know much about electronics and circuitry and stuff, but I would like to make this tester. I have an Arduino and I just follow the instructions that people supply for these projects. I can solder and all that stuff and I have the LCD display, just like the one in the picture here. The only thing that I think I need is how to connect the Arduino to the display. Could you please show a diagram or a description of how to connect it please?
    Thank you

  • Safy Saeed

    I am also building a similar project, can you tell me how would i be able to log the data measured over time in a txt file? I need to do this in a remote location so serial connection is not an option, can you tell me how would i be able to log the data in a removable memory device which can be put in computer later to plot graph of the discharge curve ?

  • cole

    i built this on a breadboard, but i didn’t have a 2.2 ohm resistor so i put 4 10 ohm ones in parallel to get 2.5 ohms, and i didn’t have a 0.1W 10k ohm resistor so i used 1/8W one. but when i turned it on, it gives me 2.62 volts constantly even with no battery inside. i am just measuring an AA battery and i built everything else perfectly. please help soon I need this for a school project.

    • That’s not much info to go on for debugging. Nothing you’ve described sounds like any problem. The first place I would look is to test the the analog input to make sure it’s working correctly. Make a voltage divider to get a known voltage (measure it with a volt meter), then disconnect the analog input wire, and apply the known voltage to the analog input pin you are using. The voltage displayed by the program should match the value you measured. If it doesn’t, you’ll need to figure out why.

      If analog input test works, then there is some problem with your battery measurement circuit and where it connects to the analog input.

      Hope that’s helpful. Good luck!

  • Wildan

    From that circuit diagram of Arduino, it can be used for tester Lead Acid Battery capacity? or just used for AA batteries? Sorry for my English. I’m newbie.. :-)

    • As I mentioned in the comments above, the analog input is only good for a max of 5 volts. Most lead-acid batteries are greater than 5v, so, you’ll have to scale the analog-input to some voltage between 0-5 volts and adjust the formula for that scaling factor.

      Also, the higher voltage will require a different discharge circuit to handle greater power dissipation.

  • Ankit

    hi,i’m doing a project named as LEAD ACID BATTERY HEALTH TESTER so please guide me through the project as it is similar to the one which you have made.
    thanking you!!

  • Michael Illingby

    Just wondering if you have thought about modifying this into a 4 or 8 simultaneous battery version?

    I have about a hundred AA batteries, and doing this one at a time would be painful.

    would love a upscaled version of this.

    Also, would you be able to engineer a charging circuit into this? That way, you could charge, then test, then charge then test again, and see if cycling improves performance.

    Also an arduino newbie btw, but love the idea you have come up with here.

    • The arduino has 8 analog inputs (IIRC), so, in theory, you should be able to scale this up quite simply. It’d be a good exercise to learn about the arduino for someone just starting out ;)

  • Steve

    There are several descrepancies between the code on this page and the downloaded text file. Whole chunks of code appear in one, but not the other, and vice-versa. For example, the downloaded file will not work because there is no provision for turning the FET on!
    Also, you explain how you arrive at “quanta” but then you calculate it differently – it is even different between the two pages – without explanation of how or why.
    I have managed to reconcile the two pages of code, but I cannot fathom how you arrived at the figures for “quanta”. I would love to build this project but I have a different reference voltage (1.62V) and different load resistor (1 Ohm) and using your formula, the “quanta” figure is obviously not correct.
    I have added PWM drive for the FET to give a constant current load which could improve accuracy – provided the “quanta” figure can be corrected.
    Otherwise this is a nice project.

    • OK, fine — guilt me into to fixing a 4 year old post :)

      Admittedly, I did a quick hand patch up of the code so it wasn’t interpreted as HTML instead of using a machine generated version. Thus, when I added the FET later, I never went back and fixed the code in the post. Thanks for pointing it out. They now are from the same source.

      Also, I added a little more clarification as to what “quanta” is. Hope that makes more sense now.

  • Steve

    You’re welcome John :-)

    Seriously, thanks for helping out a mathematically challenged old duffer!

    I have built a version of your battery tester now and it works perfectly.

    I use a 33k resistor as R2, which gives a reference voltage of 1.62 Volts, and a 1 Ohm resistor as R1.

    Instead of simply switching the fet on, I use analogWrite with a value of 207 (fet is a 2n7000) and this gives an (almost) constant current of 100mA.

    I also monitor the voltage across R1 and nudge the PWM value up or down to keep it at 0.100 Volts

    Although the 2n7000 spec is 200mA max continuous current, it seems to run quite happily at 300mA, only getting slightly warm. The resistor is 1/3 watt and runs cold.

    Now I can characterise that box of cheapo batteries I got off Ebay!

    Thanks for a nice project!

  • Ro

    I plan to make this circuit to measure the capacity of three series AA battery. So, I have a total of 4.5V. It does not exceed 5V of Arduino safety input. Is this circuit and code compatible with it? Or do I need another AREF value? Please help. Any suggestion would be very helpful to me.

    • You’ll need to use an AREF that’s larger than the max voltage the 3 series batteries. On a 5V aurdino, you get 5V reference with the AREF pin left open (floating).

      Adjust the “quanta” calculation as needed for your AREF and Rl (load resistance), and you should be good to go.

  • Irvin

    Hi john, may I ask something, I’m using the Arduino Uno R3 board, after I paste your code into my software my lcd doesn’t show anything. Is it the datasheet different?

  • Sir, sry for bothering u, I not really understand this sentences, can explain details a little bit more? =C
    “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!”

  • Jonn

    Hi John, Im doing a project similarly like battery capacity tester, But im using the Li-ion battery which is 4V, and resulting the lcd display 2.62volts and capacity has no value too. I saw yours comment on the others guy to change the AREF value. But If i use 4v battery and i using 16 ohm for Rl is it able to work? Please guide me master =C, Thanks you

    • Yes, 4v Li-ion will work with some minor changes. Remove the resistor connected to AREF so you have the default 5v analog reference voltage. Calculate the value of quanta accordingly for the new AREF value and different Rl value.

      • Thanks for yours reply, just 1 more question , then the resistor which is R1 in the FET no need to change it if im using 4v? sorry for my poor english .. Thganks for the help !!

        • R1 can be whatever value you like that results in an appropriate discharge current for your battery. Just remember, the quanta calculation uses the ‘effective load resistance’ on the battery: R1 + ‘resistance of the FET while on’.

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>