Introduction: Yet Another Battery Capacity Tester
Why one more capacity tester
I read through a lot of different tester build instructions but none of them seem to fit my needs. I wanted to be able to test also more than just singe NiCd/NiMH or Lion cells. I wanted to be able to test a power tool battery without taking it to part first. So, I decided to have a closer look on the matter and design one of my own. One thing leads to another and I finally decided to write an instructable myself. I also decided not to go in all the details of how to actually build the tester because everyone can decide on certain choices like what size resistor to use or if a PCB is needed or is Veroboard enough and there is also a ton on instructables how to install eagle or how to make a PCB. In other words, I will concentrate on the schematics and the code and how to calibrate the tester.
Step 1: History - Version 1
Above is the first version with below mentioned over 10V input support added (R12&R17&Q11&Q12).
The first version was more or less taken from an instructable by deba168 (unfortunately I cannot find his instructable to provide a link). Only some minor changes were made. In this version I had one 10 ohm load resistor controlled by a mosfet. This brought some problems though. When testing one NiCd or NiMH cell the needed time was easily measured in hours if not days. A 1500mAh battery took over 12 hours (the current was only 120mA). On the other hand, the first version could test only batteries under 10V. And a fully charged 9.6V battery can actually be up to 11.2V which could not be tested due to the 10V limit. Something needed to be done. First, I just added a couple of mosfets and resistors to make the voltage dividers to be able to allow more than 10V. But this on the other hand brought up another problem. A 14.4V battery fully loaded can have up tp 16.8V which with 10 ohm resistor meant 1.68A current and of course a power dissipation from the load resistor of almost 30W. So, with low voltage too long test time and with high voltage too high current. Clearly it was not an adequate solution and further development was needed.
Step 2: Version 2
I wanted a solution where the current would stay in certain limits regardless of the battery voltage. One solution would have been to use PWM and just one resistor, but I preferred having a solution without pulsating current or to have the need to dissipate mosfet heat. Thus, I created a solution with 10 voltage slots, each 2V wide, using 10 3.3ohm resistors and a mosfet for each resistor.
Step 3: This Is How It Turned Out
Comments on the circuit
One could argue the voltage loss over the mosfet is negligible because the resistance of the mosfet is so low, but I have left the choice of the mosfet to the reader and thus the resistance can go even over 1 ohm where it begins to matter. In version one choosing correct mosfet would remove the need of lower point measuring but on version 2 I decided to measure the voltage over one resistor only which then makes it important to actually have two measuring points. And the reason behind the choice was the simplicity in wiring the Veroboard. This does add some accuracy error since the measured voltage across one resistor is significantly smaller than measuring over all resistors. On the component selection I decided to use what I either already had handy or what I could easily obtain. This led to following BOM:
- Arduino Pro Mini 5V !IMPORTANT! I used 5V version and all is based on it
- 128x64 I2C OLED display
- 10 x 5W 3.3 Ohm resistors
- 3 x 2n7000 mosfets
- 10 x IRFZ34N mosfets
- 6 x 10 kOhm resistors
- 2 x 5 kOhm resistors
- 16V 680uF capacitor
- 1 old CPU fan
I have not added the following in the schematics
- pullup resistors on I2C lines, which I noticed made the display more stable
- power lines
- capacitor in 5V line which also stabilized the display
While testing I noticed the load resistors would get quite hot especially if they were all in use. The temperature raised to over 100 degrees Celsius (which is over 212 degrees Fahrenheit) and if the whole system is to be closed in a box there should be some kind of cooling provided. Resistors I used are 3.3 ohm / 5W and the maximum current should occur with about 2V per resistor giving 2V / 3.3 = 0.61A which results in 1.21W. I ended up in adding a simple fan in the box. Mostly because I happened to have some old CPU fan around.
It is quite straight forward and self explanatory. The battery to be tested is connected to the series of the resistors and ground. The voltage measurement points are the battery connection and the first resistor. The voltage dividers are used then to drop the voltage to a level which better suites Arduino. One digital output is used to select either 10V or 20V range of the dividers. Each resistor in the load can individually be grounded using the mosfets, which are driven directly by Arduino. And finally, the display is connected to Arduino I2C pins. Not much to say about the schematic J
Step 4: The Code
Above can be seen the rough functionality of the code. Let’s have a closer look at the code then (the arduino ino files are attached). There are a number of functions and then the main loop.
When measuring is ready the results are shown, and the execution ends there. If the measuring is not yet done, then first is checked which battery type is selected and then the voltage across input. If the voltage exceeds 0.1V there must be at least some kind of battery connected. In this case a subroutine is called to try to figure out how many cells it is in the battery to decide how to test. The number of cells is more or less information which could be better utilized but, in this version, it is reported through serial interface only. If all is good the discharge process is started and on every round of main loop the battery capacity is calculated. At the end of the main loop the display is populated with known values.
Procedure for showing results
The showResults function simply sets the lines to be shown on the display and also the string to be sent to serial interface.
Procedure for measuring voltages
In the beginning of the function the Vcc of Arduino is measured. It is needed to be able to calculate the voltages measured using analog inputs. Then battery voltage is measured using 20V range to be able to decide which range to use. Then both battery voltage and resistor voltage is calculated. Battery voltage measurements take advantage of DividerInput class which has methods reading and voltage to give the raw reading or the calculated voltage of the analog input in question.
Procedure for selecting used values
In selectUsedValues function the number of cells is guessed and the high and low limits for the battery is set to be used with the discharge procedure. Also the measuring is marked as started, The limits for this procedure are set in the beginning of the as global variables. Though they could be constant, and they could also be defined inside the procedure since they are not used globally. But hey there is always something to improve :)
Procedure for calculating the battery capacity
The discharge function takes care of actually counting the capacity of the battery. It gets the low and high limits of the voltages for the battery under testing as parameters. The high value is not used in this version, but the low value is used to decide when to stop the testing. In the beginning of the function the number of resistors to use is found out by using a function created for this purpose. The function returns number of resistor and at the same time starts the discharge and resets counter. Then the voltages are measured and used together with known resistor value to calculate the current. Now that we know voltage and current and the time from it has been since last measurement, we can calculate capacity. In the end of the discharge process battery voltage is compared to low limit and if it has gone below the limit the discharge phase stops, mosfets are closed, and the measuring is flagged as ready.
Procedure for finding the number of resistors to use
In the selectNumOfResistors function a simple comparison of voltage to preset values is done and upon result the number of resistors to be used is decided. The appropriate mosfet is opened to skip some of the resistors. The voltage slots are selected so that the maximum current any time during the discharge will stay slightly over 600mA (2V/3.3Ohm=606mA). The function returns the number of resistors used. Because the fan gets driven from the same line as the first mosfet it has to be always opened when discharge is going on.
Step 5: Calibrating the Meter
To have the meter calibrated I created another app (attached). It uses the same hardware. In the beginning the correction divider values are all set to 1000.
const int divCorrectionB10V = 1000; // divider correction multiplier in range 10V
const int divCorrectionR10V = 1000; // divider correction multiplier in range 10V
const int divCorrectionB20V = 1000; // divider correction multiplier in range 20V
const int divCorrectionR20V = 1000; // divider correction multiplier in range 20V
in the readVcc() function the resulting Vcc voltage depens on setting the value on last line of the function before return. Usually you can find in the internet a value of 1126400L to be used in calculation. I noticed the result was not correct.
- Load the measurement app to Arduino.
- You can see in the Arduino (and in the serial output and if the fan is rotating) if load is on. If it is turn the battery type selection switch.
- Adjust the value in readuVCC() to have correct result. Take the value the function gives (which is in millivolts) and divide the long value with it. You will get the raw value of the internal reference. Now measure the actual supply voltage in millivolts with a multimeter and multiply it with the previously calculated value and you get the new corrected long value. In my case the function returned 5288mV when the actual Vcc was 5.14V. Calculating 1126400/5288*5140=1094874 which I finetuned by trial. Put the new value in the code and upload it again to Arduino.
- Adjusting the analog input resistor divider correction values happens by using an adjustable power source which is used to feed the input of the meter. Simplest is to use voltages from 1V to 20V with 1V steps and recording the results to a spreadsheet. In the spreadsheet average is taken. The corrected values are calculated with the following formula: “raw_value*range*Vcc/Vin” where raw_value is the value in 10VdivB, 10VdivR, 20VdivB or 20VdivR depending on which correction is to be calculated.
See the spreadsheet how it looked for me. The averages are calculated only from the values that are to be on the range and those values are then set in the actual meter app.
const int divCorrectionB10V = 998; // divider correction divider in range 10V
const int divCorrectionR10V = 1022; // divider correction divider in range 10V
const int divCorrectionB20V = 1044; // divider correction divider in range 20V
const int divCorrectionR20V = 1045; // divider correction divider in range 20V
Adjusting the resistor value can be done by providing some voltage to the input (i.e. 2V), switching the bat type switch (to get load on) and measuring the current going in and the voltage across the first resistor and dividing the voltage with the current. For me 2V gave 607mA which gives 2/0.607=3.2948 ohms which I rounded to 3.295 ohms. So now the calibration is done.
Step 6: Last NOTE!
One important note here. It is imperative to have all connections in prime condition from the battery to the resistors. I had one bad connection and was wondering why did I get 0.3V less volts in the resistor grid than on the battery. This meant that the measurement process ended up almost immediately with 1.2V NiCd cells because the lower limit of 0.95V was reached quickly.