Skip to content

Battery capacity tester for Lithium, NiMH, and NiCd batteries with Arduino

BATTERY (LITHIUM, NIMH, NICD) CAPACITY TESTER USING ARDUINO

The growing global interest in IoT and electric vehicles is driving a surge in the utilization of Lithium-Ion/Lithium-Polymer/NiCd/NiMH batteries. These batteries are being increasingly employed across various devices and applications due to their high energy storage capacity relative to size. However, this heightened demand is also giving rise to an influx of batteries with misleading ampere-hour ratings in the market. Such false ratings pose a significant risk, potentially resulting in project failures, particularly in IoT endeavors where developers rely on battery ratings for on-time calculations.

To address the risks associated with this issue, today’s tutorial focuses on constructing a battery capacity tester. This tester is designed to accurately determine the energy storage capacity of Lithium-Ion/Lithium-Polymer/NiCd/NiMH batteries with voltages below 5 volts, thereby mitigating the potential failures caused by inaccurate battery specifications.

There are numerous battery testing projects available on the internet, each with its own unique approach. However, for today’s tutorial, we will focus on the work of Instructables user Sam Moshiri. His project stands out for its high-quality build and its compact, standalone design. Moshiri aimed to create a device that could easily measure the capacity of nearly any type of battery (< 5V) using an adjustable constant load setup with an LCD for displaying battery capacity.

The concept behind the constant load current setup is straightforward. By drawing a consistent current from a battery over a specific duration, it becomes possible to calculate the true ampere-hour capacity of the battery based on the voltage drop observed during that time. To achieve this constant load current, Moshiri utilized a resistor network featuring an LM358 operational amplifier and a MOSFET. The setup includes two push buttons (for adjusting load current) and a third push-button to reset the board for testing another battery. The battery’s voltage is fed into one of the analog pins on an Arduino Nano, which monitors the voltage drop corresponding to the preset current draw, calculates the battery capacity, and displays the result on a 16×2 LCD display.

By the end of this tutorial, you will not only understand how to determine battery capacity but also how to design for constant load/current draw and incorporate a 16×2 LCD display with an Arduino.

Required components

The necessary components for this project include an Arduino Nano, a 16×2 LCD display, an LM358N operational amplifier, various resistors (4.7kΩ (2), 47Ω (2), 1MΩ, 10kΩ, 3R), capacitors (100nF (6), 100uF-16V, 1000uF-16V), three tactile switches, an IRF3710 MOSFET, jumper wires, a battery holder, and a variable power supply. While the project was implemented on a PCB for compactness, all components used are of the DIP type to facilitate soldering for individuals of varying soldering proficiency levels. The Arduino Nano was chosen for its ease of soldering onto a PCB, though alternative boards could have been utilized for similar purposes.

Schematics

schematic-BATTERY-CAPACITY-TESTER-USING-ARDUINO

Below is a comprehensive Bill of Materials (BOM) illustrating how each component corresponds to the schematics provided above:

For PCB creation, Sam utilized the SamacSys component libraries due to their adherence to industrial IPC standards and their availability as free resources. These libraries were installed using the Altium Library plugin. The developed PCB is depicted in the image below, alongside the assembled version of the board.

Code

The code for this project is relatively simple. Essentially, we’ll monitor the time it takes for a battery to reach a predefined “low” value, then calculate the battery’s capacity using the depletion rate and the preset constant load current it experienced. This entire process will be displayed interactively on an LCD.

To streamline the code, we’ll utilize the Arduino LiquidCrystal library along with the JCButton library. The LiquidCrystal library, which comes pre-installed on Arduino, will handle interactions with the 16×2 LCD. Meanwhile, the JCButton library, downloadable from the provided link, will manage the state of the tactile buttons, detecting presses, long presses, etc., and also handling debounce.

As customary, I’ll provide a brief explanation of how the code operates, focusing on sections that may be challenging to grasp.

The code begins by importing the two libraries that will be utilized.

#include <LiquidCrystal.h>
#include <JC_Button.h>

Afterward, we establish a variable to store the minimum allowable battery level and several other variables to hold various values. The “Current” array comprises a sequence of values corresponding to the rotation of the R7 potentiometer, which dictates the load current.

const float Low_BAT_level = 3.2;
//Current steps with a 3R load (R7)
const int Current[] = {0, 37, 70, 103, 136, 169, 202, 235, 268, 301, 334, 367, 400, 440, 470, 500, 540};

const byte RS = 2, EN = 3, D4 = 4, D5 = 5, D6 = 6, D7 = 7;
const byte PWM_Pin = 10;
const byte Speaker = 12;
const int BAT_Pin = A0;
int PWM_Value = 0;
unsigned long Capacity = 0;
int ADC_Value = 0;
float BAT_Voltage = 0;
byte Hour = 0, Minute = 0, Second = 0;
bool calc = false, Done = false;

Following that, we instantiate the Liquid Crystal library by specifying the Arduino pins to which the LCD is connected, including EN (Enable), RS (Register Select), and other necessary pins, as arguments.

LiquidCrystal lcd(RS, EN, D4, D5, D6, D7);

We also create instances of the button library for the two major buttons (up and down).

Button UP_Button(16, 25, false, true);
Button Down_Button(15, 25, false, true);

With this done, we move to the void setup() function. We start the function by setting the pinMode of the buzzer and PWM pin as output.

void setup() {

pinMode(PWM_Pin, OUTPUT);
pinMode(Speaker, OUTPUT);

After this, we initialize the two buttons we created along with the LCD.

UP_Button.begin();
Down_Button.begin();
lcd.setCursor(0, 0);
lcd.begin(16, 2);

Upon initializing the LCD, display a splash screen featuring the project name and version. After a 3-second delay, clear the display and prompt the user with the “Load Adj: up/Down” button, indicating it is ready for the user to adjust the load current setting.

lcd.print(“Battery Capacity”);
lcd.setCursor(0, 1);
lcd.print(“Measurement v1.0”);
delay(3000);
lcd.clear();
lcd.print(“Load Adj:UP/Down”);
lcd.setCursor(0, 1);
lcd.print(“0”);

}

Next is the void loop() function. We start the function by reading the tact buttons

void loop() {
UP_Button.read();
Down_Button.read();

Each time the Up button is pressed, it adds 5 to the Pwm_Value variable which is used as the load current indicator. The inverse is true for the Down button. For each of the button pressed, the user is provided with visual feedback of the increase or decrease in the PWM_value, on the Display.

if (UP_Button.wasReleased() && PWM_Value < 80 && calc == false) { PWM_Value = PWM_Value + 5; analogWrite(PWM_Pin, PWM_Value); lcd.setCursor(0, 1); lcd.print(” “); lcd.setCursor(0, 1); lcd.print(String(Current[PWM_Value / 5]) + “mA”); } if (Down_Button.wasReleased() && PWM_Value > 1 && calc == false)
{
PWM_Value = PWM_Value – 5;
analogWrite(PWM_Pin, PWM_Value);
lcd.setCursor(0, 1);
lcd.print(” “);
lcd.setCursor(0, 1);
lcd.print(String(Current[PWM_Value / 5]) + “mA”);
}

If the Up button is long pressed (indicated by 1000ms), the system switches mode as it assumes the user has selected a load current that satisfies them. The mode switch involves invoking the timer interrupt() function which handles all the calculations involved with determining the capacity of the battery. This cycle is repeated as the loop continues.

if (UP_Button.pressedFor(1000) && calc == false)
{
digitalWrite(Speaker, HIGH);
delay(100);
digitalWrite(Speaker, LOW);
lcd.clear();
timerInterrupt();
}

The timerInterrupt() function handles most of the project’s heavy lifting. It continuously monitors the battery voltage and as long as it’s not yet at the preset Low_BAT_Level, it increments the time. As soon as the measured battery voltage becomes less than the Low_BAT_Level, it stops the time increment and uses it to estimate the capacity of the battery. At this point, the buzzer is turned on and off in a pattern to indicate the completion of the process.

void timerInterrupt() {
calc = true;
while (Done == false) {
Second ++;
if (Second == 60) {
Second = 0;
Minute ++;
lcd.clear();
}
if (Minute == 60) {
Minute = 0;
Hour ++;
}
lcd.setCursor(0, 0);
lcd.print(String(Hour) + “:” + String(Minute) + “:” + String(Second));
lcd.setCursor(9, 0);
ADC_Value = analogRead(BAT_Pin);
BAT_Voltage = ADC_Value * (5.0 / 1024);
lcd.print(“V:” + String(BAT_Voltage));
lcd.setCursor(0, 1);
lcd.print(“BAT-C: Wait!…”);

if (BAT_Voltage < Low_BAT_level)
{
lcd.setCursor(0, 1);
lcd.print(” “);
lcd.setCursor(0, 1);
Capacity = (Hour * 3600) + (Minute * 60) + Second;
Capacity = (Capacity * Current[PWM_Value / 5]) / 3600;
lcd.print(“BAT-C:” + String(Capacity) + “mAh”);
Done = true;
PWM_Value = 0;
analogWrite(PWM_Pin, PWM_Value);
digitalWrite(Speaker, HIGH);
delay(100);
digitalWrite(Speaker, LOW);
delay(100);
digitalWrite(Speaker, HIGH);
delay(100);
digitalWrite(Speaker, LOW);
}

delay(1000);
}
}

The complete code for the project is available below and also attached under the download section.

#include #include

const float Low_BAT_level = 3.2;

//Current steps with a 3R load (R7)
const int Current[] = {0, 37, 70, 103, 136, 169, 202, 235, 268, 301, 334, 367, 400, 440, 470, 500, 540};

const byte RS = 2, EN = 3, D4 = 4, D5 = 5, D6 = 6, D7 = 7;
const byte PWM_Pin = 10;
const byte Speaker = 12;
const int BAT_Pin = A0;
int PWM_Value = 0;
unsigned long Capacity = 0;
int ADC_Value = 0;
float BAT_Voltage = 0;
byte Hour = 0, Minute = 0, Second = 0;
bool calc = false, Done = false;

LiquidCrystal lcd(RS, EN, D4, D5, D6, D7);

Button UP_Button(16, 25, false, true);
Button Down_Button(15, 25, false, true);

void setup() {

pinMode(PWM_Pin, OUTPUT);
pinMode(Speaker, OUTPUT);

analogWrite(PWM_Pin, PWM_Value);

UP_Button.begin();
Down_Button.begin();

lcd.setCursor(0, 0);
lcd.begin(16, 2);
lcd.print(“Battery Capacity”);
lcd.setCursor(0, 1);
lcd.print(“Measurement v1.0”);
delay(3000);
lcd.clear();
lcd.print(“Load Adj:UP/Down”);
lcd.setCursor(0, 1);
lcd.print(“0″);

}

void loop() {
UP_Button.read();
Down_Button.read();

if (UP_Button.wasReleased() && PWM_Value < 80 && calc == false) { PWM_Value = PWM_Value + 5; analogWrite(PWM_Pin, PWM_Value); lcd.setCursor(0, 1); lcd.print(” “); lcd.setCursor(0, 1); lcd.print(String(Current[PWM_Value / 5]) + “mA”); } if (Down_Button.wasReleased() && PWM_Value > 1 && calc == false)
{
PWM_Value = PWM_Value – 5;
analogWrite(PWM_Pin, PWM_Value);
lcd.setCursor(0, 1);
lcd.print(” “);
lcd.setCursor(0, 1);
lcd.print(String(Current[PWM_Value / 5]) + “mA”);
}
if (UP_Button.pressedFor(1000) && calc == false)
{
digitalWrite(Speaker, HIGH);
delay(100);
digitalWrite(Speaker, LOW);
lcd.clear();
timerInterrupt();
}

}

void timerInterrupt() {
calc = true;
while (Done == false) {
Second ++;
if (Second == 60) {
Second = 0;
Minute ++;
lcd.clear();
}
if (Minute == 60) {
Minute = 0;
Hour ++;
}
lcd.setCursor(0, 0);
lcd.print(String(Hour) + “:” + String(Minute) + “:” + String(Second));
lcd.setCursor(9, 0);
ADC_Value = analogRead(BAT_Pin);
BAT_Voltage = ADC_Value * (5.0 / 1024);
lcd.print(“V:” + String(BAT_Voltage));
lcd.setCursor(0, 1);
lcd.print(“BAT-C: Wait!…”);

if (BAT_Voltage < Low_BAT_level)
{
lcd.setCursor(0, 1);
lcd.print(” “);
lcd.setCursor(0, 1);
Capacity = (Hour * 3600) + (Minute * 60) + Second;
Capacity = (Capacity * Current[PWM_Value / 5]) / 3600;
lcd.print(“BAT-C:” + String(Capacity) + “mAh”);
Done = true;
PWM_Value = 0;
analogWrite(PWM_Pin, PWM_Value);
digitalWrite(Speaker, HIGH);
delay(100);
digitalWrite(Speaker, LOW);
delay(100);
digitalWrite(Speaker, HIGH);
delay(100);
digitalWrite(Speaker, LOW);
}

delay(1000);
}
}

Demo

Once the code is finalized and your PCB is prepared, connect the Arduino Nano to your computer and upload the code. Upon successful upload, the LCD should display the splash screen as depicted in the image below.

After uploading the code, the next step is to power the board using an external power supply and connect a battery to it, as demonstrated in the image below. It’s important to note that, according to the current design, the board should only be powered with a maximum of 9V.

With the power supply and battery connected, set your desired load Current using the up and down buttons then press and hold the up button till you hear the buzzer beeps, to kick start the process.

As the process proceeds, an update will be provided on the screen, showing the time that has elapsed and the voltage of the battery.

At the end of the process, the battery’s true capacity is displayed as shown in the image below.

That’s it. For the demo, a battery rated 8800mAh was used but at the end of the test, the battery was discovered to only have an energy capacity of 1190mAh.

That’s it for this tutorial guy’s thanks for reading. As usual, you can reach out to me via the comment section if you have any questions or difficulties replicating the project.

Source : electronics-lab.com (CC BY-SA 4.0)

Don't lag behind the latest technological trends!
This is default text for notification bar