DIY Arduino Battery Capacity Tester - V2.0

43,841

187

62

Introduction: DIY Arduino Battery Capacity Tester - V2.0

About: I am a DIY hobbyist by passion and Power Engineer by profession. Most of my works are related to Solar Energy and Arduino. Apart from Electronics I love 3D printing, Woodworking and to make crafts from used …

Nowadays fake Lithium and NiMH batteries are everywhere which is sold by advertising with higher capacities than their true capacity. So it is really difficult to distinguish between a real and a fake battery. Similarly, it is difficult to know the capacity retained in the salvaged 18650 laptop batteries. So, a device is required to measure the true capacity of the batteries.

In the year 2016, I have written an Instructable on " Arduino Capacity Tester - V1.0 " which was a very straight forward and simple device. The earlier version was based on Ohms Law. The battery to be tested is discharged through a fixed resistor, current and time duration is measured by Arduino and capacity is calculated by multiplying both the readings ( Discharge current and time ).

The drawback of the earlier version was that during the testing, as as the battery voltage decrease, the current also decreases which make the calculations complex and inaccurate. To overcome this, I have made the V2.0 which is designed in such a way that the current will remain constant throughout the discharging process. I made this device by inspiring the original design by MyVanitar

The main features of Capacity Tester V2.0 are :

1. Capable of measuring the capacity of AA / AAA NiMh / NiCd, 18650 Li-ion, Li-Polymer, and Li FePO4 battery. It is suitable for almost any kind of battery rated below 5V.

2. Users can set the discharge current by using the pushbuttons.

3. OLED user Interface

4. The device can be used as an Electronic Load

Update on 02.12.2019

Now you can order the PCB and components together in a kit from PCBWay

Disclaimer:Please note that you are working with Li-Ion battery which is highly explosive and dangerous. I cannot be held responsible for any loss of property, damage, or loss of life if it comes to that. This tutorial was written for those who have knowledge of rechargeable lithium-ion technology. Please do not attempt this if you are a novice. Stay Safe.

Supplies:

Components Used

Now Order PCB and all the components to build this project in a kit from PCBWay

1.PCB: PCBWay

2. Arduino Nano: Amazon / Banggood

3. Opamp LM358: Amazon / Banggood

4. 0.96" OLED display : Amazon / Banggood

5. Ceramic Resistor: Amazon / Banggood

6. Capacitor 100nF : Amazon / Banggood

7. Capacitor 220uF: Amazon / Banggood

8. Resistors 4.7K & 1M : Amazon / Banggood

9. Push Button: Amazon / Banggood

10. Push-Buttons Cap: Aliexpress

11. Screw Terminal: Amazon / Banggood

12. Prototype Board: Amazon / Banggood

13. PCB Stand-off: Amazon / Banggood

14. Heatshrink Tubing: Amazon/ Banggood

15. Heatsink: Aliexpress

Tools Used

1. Soldering Iron: Amazon / Banggood

2. Clamp Meter: Amazon / Banggood

3. Multimeter: Amazon / Banggood

4. Hot Air Blower: Amazon / Banggood

5. Wire Cutter: Amazon / Banggood

6. Wire Stripper: Amazon / Banggood

Step 1: Schematic Diagram

The whole schematic is divided into to followings sections:

1. Power Supply Circuit

2. Constant Current Load Circuit

3. Battery Voltage Measurement Circuit

4. User Interface Circuit

5. Buzzer Circuit

1. Power Supply Circuit

The power supply circuit is consists of a DC Jack ( 7-9V) and two filter capacitors C1 and C2. The power output (Vin) is connected to the Arduino pin Vin. Here I am using the Arduino on-board voltage regulator to step down the voltage to 5V.

2. Constant Current Load Circuit

The core component of the circuit is Op-amp LM358 which contains two operational amplifiers. The PWM signal from the Arduino pin D10 is filtered by a low-pass filter ( R2 and C6 ) and fed to the second operational amplifier. The output of the second op-amp is connected to the first op-amp in voltage follower configuration. The power supply to LM358 is filtered by a decoupling capacitor C5.

The first op-amp, R1, and Q1 build a constant current load circuit. So now we can control the current through the load resistor (R1) by changing the PWM signal pulse width.

3. Battery Voltage Measurement Circuit

The battery voltage is measured by the Arduino analog input pin A0. Two capacitors C3 and C4 are used to filter out the noises coming from the constant current load circuit which can degrade the ADC conversion performance.

4. User Interface Circuit

The user interface circuit is consists of two push-buttons and a 0.96" I2C OLED display. The Up and Down push-button is to increase or decrease the PWM pulse width. R3 and R4 are pull-up resistors for the Up and Down push-buttons. C7 and C8 are used to debounce the push-buttons. The third push-button (RST) is used for resetting the Arduino.

5. Buzzer Circuit

The buzzer circuit is used to alert the starting and end of the test. A 5V buzzer is hooked to Arduino digital pin D9.

Step 2: How Does It Work?

The theory is based on the voltage comparison of the inverting (pin-2) and the non-inverting (pin-3) inputs of the OpAmp, configured as a unity amplifier. When you set the voltage applied to the non-inverting input by adjusting the PWM signal, the output of the opamp opens the gate of MOSFET. As the MOSFET turn-on, the current runs through R1, it creates a voltage drop, which provides negative feedback to OpAmp. It controls the MOSFET in such a way that the voltages at its inverting and non-inverting inputs are equal. So, the current through the load resistor is proportional to the voltage at the non-inverting input of the OpAmp.

The PWM signal from the Arduino is filtered by using a low pass filter circuit ( R2 and C1). To test the PWM signal and filter circuit performance, I hooked up my DSO ch-1 at the input and ch-2 at the output of the filter circuit. The output waveform is shown above.

Step 3: Capacity Measurement

Here Battery is discharged to its low-level threshold voltage ( 3.2V).

Battery Capacity (mAh) = Current ( I ) in mA x Time (T ) in Hours

From the above equation it is clear that to calculate battery capacity (mAh), we have to know the current in mA and time in Hour. The designed circuit is a constant current load circuit, so the discharge current remains constant throughout the testing period.

The discharge current can be adjusted by pressing the Up and Down Button. The time duration is measured by using a timer in the Arduino code.

Step 4: Making the Circuit

In the previous steps, I have explained the function of each of the components in the circuit. Before jump to make the final board, test the circuit on a breadboard first. If the circuit works perfectly on the breadboard, then move to solder the components on the prototype board.

I used the 7cm X 5cm prototype board.

Mounting the Nano: First cut two rows of female header pin with 15 pins in each. I used a diagonal nipper to cut the headers. Then solder the header pins. Be sure the distance between the two rails fits the Arduino nano.

Mounting OLED Display: Cut a female header with 4pins. Then solder it as shown in the picture.

Mounting the terminals and components: Solder the remaining components as shown in pictures.

Wiring: Make the wiring as per the schematic. I used colored wires to make the wiring so that I can identify them easily.

Step 5: OLED Display

To display the Battery Voltage, discharge current and capacity, I used a 0.96" OLED display. It has 128x64 resolution and uses an I2C bus to communicate with the Arduino. Two pins SCL (A5), SDA (A4) in Arduino Uno are used for communication.

I am using the Adafruit_SSD1306 library to display the parameters.

First, you have to download the Adafruit_SSD1306. Then installed it.

The connections should be as follows

Arduino --> OLED

5V --->VCC

GND -->GND

A4----> SDA

A5----> SCL

Step 6: Buzzer for Warning

To provide alerts during the start and competition of the test, a piezo buzzer is used. The buzzer has two terminals, the longer one is positive and the shorter leg is negative. The sticker on the new buzzer has also " + " marked to indicate the positive terminal.

As the prototype board doesn't have enough room to place the buzzer, I have connected the buzzer to the main circuit board by using two wires. To insulate the bare connection, I have used heat shrink tubing.

The connections should be as follows

Arduino --> Buzzer

D9--> Positive terminal

GND--> Negative terminal

Step 7: Mounting the Standoffs

After soldering and wiring, mount the standoffs at 4 corners. It will provide sufficient clearance to the soldering joints and wires from the ground.

Step 8: PCB Design

I have drawn the schematic by using EasyEDA online software after that switched to the PCB layout.

All of the components you added in the schematic should be there, stacked on top of each other, ready to be placed and routed. Drag the components by grabbing on its pads. Then place it inside the rectangular borderline.

Arrange all the components in such a way that the board occupies minimum space. Smaller the board size, the cheaper will be the PCB manufacturing cost. It will be useful if this board has some mounting holes on it so that it can be mounted in an enclosure.

Now you have to route. Routing is the most fun part of this entire process. It’s like solving a puzzle! Using the tracking tool we need to connect all the components. You can use both the top and the bottom layer for avoiding overlap between two different tracks and making the tracks shorter.

You can use the Silk layer to add text to the board. Also, we are able to insert an image file, so I add an image of my website logo to be printed on the board. In the end, using the copper area tool, we need to create the ground area of the PCB.

You can order it from PCBWay.

Sign up PCBWay now to get a US $5 coupon. That means your first order is free of cost only you have to pay the shipping charges.

When you place an order, I will get 10% donation from PCBWay for a contribution to my work. Your little help may encourage me to do more awesome work in the future. Thank you for your cooperation.

Step 9: Assemble the PCB

For Soldering, you will need a decent Soldering Iron, Solder, Nipper and a multimeter.It is good practice to solder the components according to their height. Solder the lesser height components first.

You can follow the following steps to solder the components :

1. Push the component legs through their holes, and turn the PCB on its back.

2. Hold the tip of the soldering iron to the junction of the pad and the leg of the component.

3. Feed solder into the joint so that it flows all around the lead and covers the pad. Once it has flowed all around, move the tip away.

Step 10: Software & Libraries

First, download the attached Arduino Code. Then download the following libraries and install them.

Libraries:

Download and install the following libraries:

1. JC_Button: https://github.com/JChristensen/JC_Button

2. Adafruit_SSD1306: https://github.com/JChristensen/JC_Button

In the code, you have to change the following two things.

1. Current Arrays values: This can be done by connecting a multimeter in series with the battery. Press the up button and measure the current, the current values are the elements of the array.

2. Vcc: You use a multimeter to measure the voltage at the Arduino 5V pin. In my case it is 4.96V.

Updated on 20.11.2019

You can change the Low_BAT_Level value in the code as per the battery chemistry. It is better to take a little margin over the cut-off voltage stated below.

Here is the discharge rates and cutoff voltages for various Lithium-Ion Battery chemistries:

1. Lithium Cobalt Oxide: Cut-off Voltage = 2.5V at 1C discharge rate

2. Lithium Manganese Oxide: Cut-off Voltage = 2.5V at 1C discharge rate

3. Lithium Iron Phosphate: Cut-off Voltage = 2.5V at 1C discharge rate

4. Lithium Titanate: Cut-off Voltage = 1.8V at 1C discharge rate

5. Lithium Nickel Manganese Cobalt Oxide: Cut-off Voltage = 2.5V at 1C discharge rate

6. Lithium Nickel Cobalt Aluminum Oxide: Cut-off Voltage = 3.0V at 1C discharge rate

Updated on 01.04.2020

jcgrabo, suggested some changes to the original design to improve the precision. The changes are listed below:

1. Add a precision reference (LM385BLP-1.2) and connected it to A1. During setup, read its value which is known to be 1.215 volts, and then calculate Vcc thereby eliminating the need to measure Vcc.

2.Replace the 1 ohm 5% resistor with a 1 ohm 1% power resistor thereby reducing errors that depend on the value of the resistance.

3. Rather than using a fixed set of PWM values for each current step (in increments of 5) create an array of desired current values that used to compute the necessary PWM values to achieve those current values as close as possible. He followed that by calculating the actual current values that will be achieved with the computed PWM values.

By considering the above changes, he revised the code and shared it in the comment section. The revised code is attached below.

Thank you so much jcgrabo for your valuable contribution to my project. I hope this improvement will be helpful for many more users.

Step 11: Conclusion

To test the circuit, first I charged a good Samsung 18650 battery using my ISDT C4 Charger. Then connect the battery to the battery terminal. Now set the current to according to your requirement and long-pressed the “UP” button. Then you should hear a beep and the test procedure starts. During the test, you will monitor all the parameters on the OLED display. The battery will discharge until its voltage reaches its low-level threshold (3.2V). The test process will be finished by two long beeps.

Note: The project is still under development stage. You can join me for any improvements. Raise comments if any mistakes or errors. I am designing a PCB for this project. Stay connected for more updates to the project.

Hope my tutorial is helpful. If you like it, don't forget to share :) Subscribe for more DIY projects. Thank You.

4 People Made This Project!

Recommendations

  • Toys & Games Contest

    Toys & Games Contest
  • Big vs Small Challenge

    Big vs Small Challenge
  • Fix It Challenge

    Fix It Challenge

62 Comments

3
jcgrabo
jcgrabo

1 year ago

After assembling V2.0 I decided to make some changes to improve its precision.

1. I added a precision reference (LM385BLP-1.2) and connected it to A1. During setup I read its value which I know to be 1.215 volts and then calculate Vcc thereby eliminating the need to measure Vcc.

2. I replaced the 1 ohm 5% resistor with a 1 ohm 1% power resistor thereby reducing errors that depend on the value of the resistance.

3. Rather than using a fixed set of PWM values for each current step (in increments of 5) I created an array of desired current values that I used to compute the necessary PWM values to achieve those current values as close as possible. I followed that by calculating the actual current values that will be achieved with the computed PWM values.

The above steps resulted in measured current values within a couple of milliamps of the ones the program is using to measure capacity.

0
opengreenenergy
opengreenenergy

Reply 1 year ago

Congratulations on your build.
I would like to see some pictures.
Please share the schematic and codes that you have done. I would like to include it in this instructable.

1
jcgrabo
jcgrabo

Reply 1 year ago

My notes along with images of the bottom of the board with the Band Gap Reference attached in a dead bug fashion. The top of the board shows the replacement of the 1 ohm 5% power resistor with a Dale 1 ohm 1% 7.5 watt power resistor. I am not familiar how to upload the modified sketch.

My sketch follows:

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// ARDUINO BATTERY CAPACITY TESTER
//Version-2.0
//by deba168,INDIA // The code is taken from Hesam Moshiri ( https://www.pcbway.com/blog/technology/Battery_capacity_measurement_using_Arduino.html )
//Dated : 20/10/2019
//
//Dated : 29/02/2020
//Added code to read a band gap reference connected to analog pin A1,
// thus allowing measurement of Vcc and avoiding the need to measure it using a multimeter
//Replaced 1 ohm 5% resistor with 1 ohm 1% power resistor
//Modified code to compute actual current draw based on desired values. Measurements
// using ammeter show computed values within 2 milliamps of measured
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

#include<JC_Button.h>
#include <Adafruit_SSD1306.h>

#define DEBUG 0 // Binary Treatment 0=Nothing, 1=Early, 2=Running Voltages, 4=Finish
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

void timerInterrupt();
void Display_UP_DOWN();//Debugging support
void Print_DEBUG_4();//Debugging support

const float Low_BAT_level = 2.80;// Threshold for stopping test
//Desired Current steps with a 3R load (R7)
int Current [] = {0,50,100,200,300,400,500,600,700,800,900,1000};
int PWM [] = {0, 2, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50};
int Array_Size;// calculated during setup
const byte PWM_Pin = 10;
const byte Buzzer = 9;
const int BAT_Pin = A0;
const int Vref_Pin = A1;//Pin to which Band Gap Reference is attached
int Current_Value = 0; // Selected Current value for test
int PWM_Value = 0;// Value of PWM during test
int PWM_Index = 0;// Index into PWM array during test
unsigned long Capacity;
float Capacity_f;
int ADC_Value = 0;
float Vref_Voltage = 1.215; // LM385BLP-1.2 Band Gap Reference voltage
float Vcc = 5.32; // Voltage of Arduino 5V pin ( Measured during setup using Band Gap Reference )
float BAT_Voltage = 0;
float Resistance = 1.0; // Value of R3 Power Resistor
float sample = 0;
byte Hour = 0, Minute = 0, Second = 0;
bool calc = false, Done = false, Report_Info = true;
Button UP_Button(2, 25, false, true);
Button Down_Button(3, 25, false, true);

// string values for reporting intermediate information
const int VAL_MAX = 10;
char val_0[VAL_MAX]={""};
char val_2[VAL_MAX]={""};

void setup () {//Setup

Serial.begin(38400);
pinMode(PWM_Pin, OUTPUT);
pinMode(Buzzer, OUTPUT);
analogWrite(PWM_Pin, PWM_Value);
UP_Button.begin();
Down_Button.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(12,25);
display.print("Open Green Energy");
display.display();
delay(3000);
display.clearDisplay();
display.setTextSize(2);
display.setCursor(2,15);
display.print("Adj Curr:");
display.setCursor(2,40);
display.print("UP/Down:");
display.print("0");
display.setCursor(2,55);
display.setTextSize(1);

#if (DEBUG == 1 || DEBUG == 5)
Serial.println("\nStart of calculations");
#endif

Array_Size = sizeof(Current)/2;
//Read voltages of Band Gap Reference and use it to calculate actual Vcc
float fTemp=0.0;
for(int i=0;i< 100;i++)
{
fTemp=fTemp+analogRead(Vref_Pin); //read the Band Gap Reference voltage
delay (2);
}
fTemp=fTemp/100.0;
Vcc = Vref_Voltage * 1024.0 / fTemp;

// Convert desired current levels to PWM using actual Vcc
// Convert PWM values back to actual current levels
// While measuring actual current I discovered that the actual draw is
// (PWM + 1)/256 rather than PWM/255 as indicated in Arduino documentation
int iTemp;
for (int i=0;i<Array_Size;i++)
{
iTemp = int( Resistance * Current[i] * 256.0 / Vcc / 1000.0 - 1.0 + 0.5 ); // desired current to nearest PWM
iTemp = min(iTemp,255);
iTemp = max(iTemp,0);
Current[i] = int( (iTemp+1) * Vcc / 256.0 / Resistance * 1000.0); // actual current for PWM
PWM[i] = iTemp;//Save PWM in array
}

//Include Threshold and Vcc in startup display
dtostrf(Low_BAT_level, 5, 3, val_0);
dtostrf(Vcc, 5, 3, val_2);

display.print("Thr=");
display.print(val_0);
display.print("v, Vcc=");
display.print(val_2);

display.display();
display.setTextSize(2);

}//Setup

//************************* End of Setup function *******************************

void loop() {

if (Report_Info)
{//Report_Info
Serial.flush();
#if (DEBUG == 1 || DEBUG == 5)
// Serial.println("Measured Vcc Voltage: " + String(Vcc) + " volts");
Serial.print("Threshold: ");
Serial.print(Low_BAT_level,3);
Serial.println(" volts");
Serial.print("R3 Resistance: ");
Serial.print(Resistance,3);
Serial.println(" ohms");
Serial.print("Measured Vcc Voltage: ");
Serial.print(Vcc,4);
Serial.println(" volts");
sample = 0.0;

Serial.println("Index Actual(mA) PWM");
for (int i=0;i<Array_Size;i++)
{
// Serial.println( "["+String(i)+"]="+String(Current[i])+" mA PWM["+String(i)+"]="+String(PWM[i]) );
Serial.print("[");
Serial.print(i);
Serial.print("]");
Serial.print(" ");
Serial.print(Current[i],DEC);
Serial.print(" ");
Serial.print(PWM[i],DEC);
Serial.println(" ");
}
#endif
Report_Info = false;
}//Report_Info

UP_Button.read();
Down_Button.read();
if (UP_Button.wasReleased() && PWM_Index < (Array_Size-1) && calc == false)
{
PWM_Value = PWM[++PWM_Index];
analogWrite(PWM_Pin,PWM_Value);

Display_UP_DOWN();
}

if (Down_Button.wasReleased() && PWM_Index > 0 && calc == false)
{
PWM_Value = PWM[--PWM_Index];
analogWrite(PWM_Pin,PWM_Value);

Display_UP_DOWN();
}

if (UP_Button.pressedFor (1000) && calc == false)
{
digitalWrite(Buzzer, HIGH);
delay(100);
digitalWrite(Buzzer, LOW);
display.clearDisplay();
timerInterrupt();
}
}

//************************* End of Loop function *******************************

void timerInterrupt(){
calc = true;
while (Done == false) {//(Done == false)
Second ++;
if (Second == 60) {
Second = 0;
Minute ++;
}
if (Minute == 60) {
Minute = 0;
Hour ++;
}

//************ Measuring Battery Voltage ***********

for(int i=0;i< 100;i++)
{
sample=sample+analogRead(BAT_Pin); //read the Battery voltage
delay (2);
}
sample=sample/100;
BAT_Voltage = sample * Vcc / 1024.0;
//*********************************************

display.clearDisplay();
display.setTextSize(2);
display.setCursor(20,5);
// display.print(String(Hour) + ":" + String(Minute) + ":" + String(Second));
display.print(Hour);
display.print(":");
display.print(Minute);
display.print(":");
display.print(Second);

display.setTextSize(1);
display.setCursor(0,25);
display.print("Disch Curr: ");
// display.print(String(Current[PWM_Index])+"mA");
display.print(Current_Value);
display.print("mA");

display.setCursor(2,40);
// display.print("Bat Volt:" + String(BAT_Voltage)+"V" );
display.print("Bat Volt:");
display.print(BAT_Voltage,3);
display.print("V");

// When total seconds is greater than 32767 the statement below did not work until the byte values
// Hour, Minute and Second were cast to an unsigned long. Apparently the compiler cast the byte
// values to an "int" first which cannot represent 32768 correctly
Capacity = ((unsigned long)Hour * 3600) + ((unsigned long)Minute * 60) + (unsigned long)Second;
Capacity_f = ((float)Capacity * Current_Value) / 3600.0;

display.setCursor(2, 55);
// display.print("Capacity:" + String(Capacity) + "mAh");
display.print("Capacity:");
display.print(Capacity_f,1);
display.print("mAh");
display.display();

#if (DEBUG == 4 || DEBUG == 2)
Print_DEBUG_4();
#endif

if (BAT_Voltage < Low_BAT_level)
{//BAT_Voltage < Low_BAT_level

#if (DEBUG == 4 || DEBUG == 5)
Serial.println("\nCurrent_Value PWM_Value");
Serial.print(Current_Value);
Serial.print(" ");
Serial.println(PWM_Value);
Serial.println("\nHour Minute Second PWM_Index");
Serial.print(Hour);
Serial.print(" ");
Serial.print(Minute);
Serial.print(" ");
Serial.print(Second);
Serial.print(" ");
Serial.println(PWM_Index);
#endif

// When total seconds is greater than 32767 the statement below did not work until the byte values
// Hour, Minute and Second were cast to an unsigned long. Apparently the compiler cast the byte
// values to an "int" first which cannot represent 32768 correctly
Capacity = ((unsigned long)Hour * 3600) + ((unsigned long)Minute * 60) + (unsigned long)Second;

#if (DEBUG == 4 || DEBUG == 5)
Serial.println("Capacity HMS");
Serial.println(Capacity);
#endif

Capacity_f = ((float)Capacity * Current_Value) / 3600.0;

#if (DEBUG == 4 || DEBUG == 5)
Serial.println("Capacity HMS*PWM");
Serial.println(Capacity_f,1);
#endif

display.clearDisplay();
display.setTextSize(2);
display.setCursor(2,15);
display.print("Capacity:");
display.setCursor(2,40);
// display.print(String(Capacity) + "mAh");
display.print(Capacity_f,1);
display.print("mAh");
display.display();
Done = true;
PWM_Value = 0;
analogWrite(PWM_Pin, PWM_Value);
digitalWrite(Buzzer, HIGH);
delay(100);
digitalWrite(Buzzer, LOW);
delay(100);
digitalWrite(Buzzer, HIGH);
delay(100);
digitalWrite(Buzzer, LOW);
delay(100);
}//BAT_Voltage < Low_BAT_level
delay(1000);
}//(Done == false)

}// timerInterrupt

void Display_UP_DOWN()
{//Display_UP_DOWN()
Current_Value = Current[PWM_Index];

display.clearDisplay();
display.setCursor(2,25);
display.print("Curr:");
display.print(Current_Value);
display.print("mA ");
display.setCursor(2,40);
display.print("PWM=");
display.print(PWM_Value);
display.display();
}//Display_UP_DOWN()

void Print_DEBUG_4()
{//Print_DEBUG_4()
Serial.print(Hour);
Serial.print(":");
Serial.print(Minute);
Serial.print(":");
Serial.print(Second);
Serial.print(" ");
Serial.print(BAT_Voltage,3);
Serial.print("v ");
Serial.print(Capacity_f,1);
Serial.println("mAh");
}//Print_DEBUG_4()

BT2_0_Notes.jpgBT2_0_PPR_3.jpgBTV2_0_BGR_3.jpg
0
opengreenenergy
opengreenenergy

Reply 5 weeks ago

Hi,
Thank you so much for your valuable contribution to this project. I have added this update to my Instructables today. You can see it on Step-10.

0
MarkusM9
MarkusM9

Reply 9 months ago

Great add-on, thanks for sharing. For those who would prefer to save on a cheaper 5% resistor or already have some (and they have a multimeter) they could measure the resistance of what they have and make adjustments in the Arduino code to compensate for the variation in resistance.

0
zym1
zym1

Reply 2 months ago

does it work with lm385z-1.2?

0
jcgrabo
jcgrabo

Reply 2 months ago

Yes. What is important is to have a known reliable reference to eliminate the need to measure the actual DC power being used to power the board. The actual current depends on the resistance being used and the DC voltage. USB ports are allowed to provide power over a wide DC range from 5.25 to 4.40 volts although the nominal is 5.0.

0
ansmandar
ansmandar

Reply 7 months ago

Very nice additions. Nicely done. Thank you very much for sharing.

I haven't created it yet, but planning to build one. I will definitely try these additions.

One doubt though. How did you find the value as 1.215 as reference in the calculation? Should it be typical voltage mentioned in the datasheet specs like 1.235 here as you mentioned in your hand written sheet?

If there are any calculations to find the value could you please share it.. That will be very helpful in understanding it.

0
jcgrabo
jcgrabo

Reply 7 months ago

I bought a number of the band gap references LM385BLP-1-2 (cost $0.44). Although their specified voltage range is nominally 1.235 (1.223 to 1.247) the ones I received were all 1.215 even when testing at different current levels. I suppose my digital multimeter is incorrect but I decided to use 1.215V.

0
ansmandar
ansmandar

Reply 7 months ago

Oh.. k That clarifies my doubt. Thanks for your reply.

0
ansmandar
ansmandar

7 months ago

Very nice project, nice presentation and I appreciate the efforts.Thank you so much for sharing... :-)

I have some doubts regarding the code.
I think the call to analogWrite must be called when we actually start the test. If my understanding is correct the PWM signal is started immediately when we increase or decrease the current which in turn starting the discharging process.

This function call --> analogWrite(PWM_Pin,PWM_Value); should be placed inside the if condition --> if (UP_Button.pressedFor (1000) && calc == false) inside the loop() function.

Can someone light me up on this?

here is the existing code of loop function for reference--->
void loop() {
UP_Button.read();
Down_Button.read();
if (UP_Button.wasReleased() && PWM_Value < 55 && calc == false)
{
PWM_Value = PWM_Value + 5;
analogWrite(PWM_Pin,PWM_Value); display.clearDisplay();
display.setCursor(2,25);
display.print("Curr:");
display.print(String(Current[PWM_Value / 5])+"mA");
display.display();
// Serial.println(String(Current[PWM_Value / 5]));
}
if (Down_Button.wasReleased() && PWM_Value > 1 && calc == false)
{
PWM_Value = PWM_Value - 5;
analogWrite(PWM_Pin,PWM_Value); display.clearDisplay();
display.setCursor(2,25);
display.print("Curr:");
display.print(String(Current[PWM_Value / 5])+"mA");
display.display();
// Serial.println(String(Current[PWM_Value / 5]) + "mA");
}
if (UP_Button.pressedFor (1000) && calc == false)
{
//Isn't this the correct place to call this function? digitalWrite(Buzzer, HIGH);
delay(100);
digitalWrite(Buzzer, LOW);
display.clearDisplay();
timerInterrupt();
}
}

0
pavot1
pavot1

Reply 5 weeks ago

Hello, you can post the entire revised code.


This function call --> analogWrite(PWM_Pin,PWM_Value); should be placed inside the if condition --> if (UP_Button.pressedFor (1000) && calc == false) inside the loop() function. ???

Thanks


0
ansmandar
ansmandar

Reply 5 weeks ago

Here is a modified code:
I have also implemented the suggestions from jcgrabo in his comments using LM385BLP-1.2.

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// ARDUINO BATTERY CAPACITY TESTER
//Version-2.0
//by deba168,INDIA // The code is taken from Hesam Moshiri ( https://www.pcbway.com/blog/technology/Battery_capacity_measurement_using_Arduino.html )
//Dated : 20/10/2019
//
//Dated : 29/02/2020
//Added code to read a band gap reference connected to analog pin A1,
// thus allowing measurement of Vcc and avoiding the need to measure it using a multimeter
//Replaced 1 ohm 5% resistor with 1 ohm 1% power resistor
//Modified code to compute actual current draw based on desired values. Measurements
// using ammeter show computed values within 2 milliamps of measured
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include <JC_Button.h>
#include <Adafruit_SSD1306.h>
#define DEBUG 0 // Binary Treatment 0=Nothing, 1=Early, 2=Running Voltages, 4=Finish
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
void timerInterrupt();
void Display_UP_DOWN();//Debugging support
void Print_DEBUG_4();//Debugging support
const float Low_BAT_level = 3.0;// Threshold for stopping test
//Desired Current steps with a 3R load (R7)
int Current [] = {0,50,100,200,300,400,500,600,700,800,900,1000};
int PWM [] = {0, 2, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50};
int Array_Size;// calculated during setup
const byte PWM_Pin = 10;
const byte Buzzer = 9;
const int BAT_Pin = A0;
const int Vref_Pin = A1;//Pin to which Band Gap Reference is attached
int Current_Value = 0; // Selected Current value for test
int PWM_Value = 0;// Value of PWM during test
int PWM_Index = 0;// Index into PWM array during test
unsigned long Capacity;
float Capacity_f;
int ADC_Value = 0;
float Vref_Voltage = 1.215; // LM385BLP-1.2 Band Gap Reference voltage
float Vcc = 5.32; // Voltage of Arduino 5V pin ( Measured during setup using Band Gap Reference )
float BAT_Voltage = 0;
float Resistance = 1.0; // Value of R3 Power Resistor
float sample = 0;
byte Hour = 0, Minute = 0, Second = 0;
bool calc = false, Done = false, Report_Info = true;
Button UP_Button(2, 25, false, true);
Button Down_Button(3, 25, false, true);
// string values for reporting intermediate information
const int VAL_MAX = 10;
char val_0[VAL_MAX]={""};
char val_2[VAL_MAX]={""};
void setup () {//Setup
Serial.begin(38400);
pinMode(PWM_Pin, OUTPUT);
pinMode(Buzzer, OUTPUT);
analogWrite(PWM_Pin, PWM_Value);
UP_Button.begin();
Down_Button.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(12,25);
display.print("Open Green Energy");
display.display();
delay(3000);
display.clearDisplay();
display.setTextSize(2);
display.setCursor(2,15);
display.print("Adj Curr:");
display.setCursor(2,40);
display.print("UP/Down:");
display.print("0");
display.setCursor(2,55);
display.setTextSize(1);
#if (DEBUG == 1 || DEBUG == 5)
Serial.println("\nStart of calculations");
#endif
Array_Size = sizeof(Current)/2;
//Read voltages of Band Gap Reference and use it to calculate actual Vcc
float fTemp=0.0;
for(int i=0;i< 100;i++)
{
fTemp=fTemp+analogRead(Vref_Pin); //read the Band Gap Reference voltage
delay (2);
}
fTemp=fTemp/100.0;
Vcc = Vref_Voltage * 1024.0 / fTemp;
// Convert desired current levels to PWM using actual Vcc
// Convert PWM values back to actual current levels
// While measuring actual current I discovered that the actual draw is
// (PWM + 1)/256 rather than PWM/255 as indicated in Arduino documentation
int iTemp;
for (int i=0;i<Array_Size;i++)
{
iTemp = int( Resistance * Current[i] * 256.0 / Vcc / 1000.0 - 1.0 + 0.5 ); // desired current to nearest PWM
iTemp = min(iTemp,255);
iTemp = max(iTemp,0);
Current[i] = int( (iTemp+1) * Vcc / 256.0 / Resistance * 1000.0); // actual current for PWM
PWM[i] = iTemp;//Save PWM in array
}
//Include Threshold and Vcc in startup display
dtostrf(Low_BAT_level, 5, 3, val_0);
dtostrf(Vcc, 5, 3, val_2);
display.print("Thr=");
display.print(val_0);
display.print("v, Vcc=");
display.print(val_2);
display.display();
display.setTextSize(2);
}//Setup
//************************* End of Setup function *******************************
void loop() {
if (Report_Info)
{//Report_Info
Serial.flush();
#if (DEBUG == 1 || DEBUG == 5)
// Serial.println("Measured Vcc Voltage: " + String(Vcc) + " volts");
Serial.print("Threshold: ");
Serial.print(Low_BAT_level,3);
Serial.println(" volts");
Serial.print("R3 Resistance: ");
Serial.print(Resistance,3);
Serial.println(" ohms");
Serial.print("Measured Vcc Voltage: ");
Serial.print(Vcc,4);
Serial.println(" volts");
sample = 0.0;
Serial.println("Index Actual(mA) PWM");
for (int i=0;i<Array_Size;i++)
{
// Serial.println( "["+String(i)+"]="+String(Current[i])+" mA PWM["+String(i)+"]="+String(PWM[i]) );
Serial.print("[");
Serial.print(i);
Serial.print("]");
Serial.print(" ");
Serial.print(Current[i],DEC);
Serial.print(" ");
Serial.print(PWM[i],DEC);
Serial.println(" ");
}
#endif
Report_Info = false;
}//Report_Info
UP_Button.read();
Down_Button.read();
if (UP_Button.wasReleased() && PWM_Index < (Array_Size-1) && calc == false)
{
PWM_Value = PWM[++PWM_Index];
Display_UP_DOWN();
}
if (Down_Button.wasReleased() && PWM_Index > 0 && calc == false)
{
PWM_Value = PWM[--PWM_Index];
Display_UP_DOWN();
}
if (UP_Button.pressedFor (1000) && calc == false)
{
analogWrite(PWM_Pin,PWM_Value);
digitalWrite(Buzzer, HIGH);
delay(100);
digitalWrite(Buzzer, LOW);
display.clearDisplay();
timerInterrupt();
}
}//loop
//************************* End of Loop function *******************************
void timerInterrupt(){
calc = true;
while (Done == false) {//(Done == false)
Second ++;
if (Second == 60) {
Second = 0;
Minute ++;
}
if (Minute == 60) {
Minute = 0;
Hour ++;
}
//************ Measuring Battery Voltage ***********
for(int i=0;i< 100;i++)
{
sample=sample+analogRead(BAT_Pin); //read the Battery voltage
delay (2);
}
sample=sample/100;
BAT_Voltage = sample * Vcc / 1024.0;
//*********************************************
display.clearDisplay();
display.setTextSize(2);
display.setCursor(20,5);
// display.print(String(Hour) + ":" + String(Minute) + ":" + String(Second));
display.print(Hour);
display.print(":");
display.print(Minute);
display.print(":");
display.print(Second);
display.setTextSize(1);
display.setCursor(0,25);
display.print("Disch Curr: ");
// display.print(String(Current[PWM_Index])+"mA");
display.print(Current_Value);
display.print("mA");
display.setCursor(2,40);
// display.print("Bat Volt:" + String(BAT_Voltage)+"V" );
display.print("Bat Volt:");
display.print(BAT_Voltage,3);
display.print("V");
// When total seconds is greater than 32767 the statement below did not work until the byte values
// Hour, Minute and Second were cast to an unsigned long. Apparently the compiler cast the byte
// values to an "int" first which cannot represent 32768 correctly
Capacity = ((unsigned long)Hour * 3600) + ((unsigned long)Minute * 60) + (unsigned long)Second;
Capacity_f = ((float)Capacity * Current_Value) / 3600.0;
display.setCursor(2, 55);
// display.print("Capacity:" + String(Capacity) + "mAh");
display.print("Capacity:");
display.print(Capacity_f,1);
display.print("mAh");
display.display();
#if (DEBUG == 4 || DEBUG == 2)
Print_DEBUG_4();
#endif
if (BAT_Voltage < Low_BAT_level)
{//BAT_Voltage < Low_BAT_level
#if (DEBUG == 4 || DEBUG == 5)
Serial.println("\nCurrent_Value PWM_Value");
Serial.print(Current_Value);
Serial.print(" ");
Serial.println(PWM_Value);
Serial.println("\nHour Minute Second PWM_Index");
Serial.print(Hour);
Serial.print(" ");
Serial.print(Minute);
Serial.print(" ");
Serial.print(Second);
Serial.print(" ");
Serial.println(PWM_Index);
#endif
// When total seconds is greater than 32767 the statement below did not work until the byte values
// Hour, Minute and Second were cast to an unsigned long. Apparently the compiler cast the byte
// values to an "int" first which cannot represent 32768 correctly
Capacity = ((unsigned long)Hour * 3600) + ((unsigned long)Minute * 60) + (unsigned long)Second;
#if (DEBUG == 4 || DEBUG == 5)
Serial.println("Capacity HMS");
Serial.println(Capacity);
#endif
Capacity_f = ((float)Capacity * Current_Value) / 3600.0;
#if (DEBUG == 4 || DEBUG == 5)
Serial.println("Capacity HMS*PWM");
Serial.println(Capacity_f,1);
#endif
display.clearDisplay();
display.setTextSize(2);
display.setCursor(2,15);
display.print("Capacity:");
display.setCursor(2,40);
// display.print(String(Capacity) + "mAh");
display.print(Capacity_f,1);
display.print("mAh");
display.display();
Done = true;
PWM_Value = 0;
analogWrite(PWM_Pin, PWM_Value);
digitalWrite(Buzzer, HIGH);
delay(100);
digitalWrite(Buzzer, LOW);
delay(100);
digitalWrite(Buzzer, HIGH);
delay(100);
digitalWrite(Buzzer, LOW);
delay(100);
}//BAT_Voltage < Low_BAT_level
delay(1000);
}//(Done == false)
}// timerInterrupt
void Display_UP_DOWN()
{//Display_UP_DOWN()
Current_Value = Current[PWM_Index];
display.clearDisplay();
display.setCursor(2,25);
display.print("Curr:");
display.print(Current_Value);
display.print("mA ");
display.setCursor(2,40);
display.print("PWM=");
display.print(PWM_Value);
display.display();
}//Display_UP_DOWN()
void Print_DEBUG_4()
{//Print_DEBUG_4()
Serial.print(Hour);
Serial.print(":");
Serial.print(Minute);
Serial.print(":");
Serial.print(Second);
Serial.print(" ");
Serial.print(BAT_Voltage,3);
Serial.print("v ");
Serial.print(Capacity_f,1);
Serial.println("mAh");
}//Print_DEBUG_4()

0
pavot1
pavot1

Reply 5 weeks ago

Thank you for the modified code

0
mariotovar768
mariotovar768

Reply 7 weeks ago

thats correct the discharging starts inmediately.........your fix is correct

0
pavot1
pavot1

Reply 5 weeks ago

Hello, you can post the entire revised code.

This function call --> analogWrite(PWM_Pin,PWM_Value); should be placed inside the if condition --> if (UP_Button.pressedFor (1000) && calc == false) inside the loop() function. ???

Thanks

0
ansmandar
ansmandar

Reply 5 weeks ago

Revised code:

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// ARDUINO BATTERY CAPACITY TESTER
//Version-2.0
//by deba168,INDIA // The code is taken from Hesam Moshiri ( https://www.pcbway.com/blog/technology/Battery_ca... )
//Dated : 20/10/2019
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include <JC_Button.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET 4 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
const float Low_BAT_level = 3.0;
//Current steps with a 3R load (R7)
const int Current[] = { 0, 110, 210, 300, 390, 490, 580, 680, 770, 870, 960, 1000 };
const byte PWM_Pin = 10;
const byte Buzzer = 9;
const int BAT_Pin = A0;
int PWM_Value = 0;
unsigned long Capacity = 0;
int ADC_Value = 0;
float Vcc = 4.96; // Voltage of Arduino 5V pin ( Mesured by Multimeter )
float BAT_Voltage = 0;
float sample = 0;
byte Hour = 0, Minute = 0, Second = 0;
bool calc = false, Done = false;
Button UP_Button(2, 25, false, true);
Button Down_Button(3, 25, false, true);
void setup()
{
//
Serial.begin(9600);
pinMode(PWM_Pin, OUTPUT);
pinMode(Buzzer, OUTPUT);
analogWrite(PWM_Pin, PWM_Value);
UP_Button.begin();
Down_Button.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(12, 25);
display.print("Open Green Energy");
display.display();
delay(3000);
display.clearDisplay();
display.setTextSize(2);
display.setCursor(2, 15);
display.print("Adj Curr:");
display.setCursor(2, 40);
display.print("UP/Down:");
display.print("0");
display.display();
}
//************************* End of Setup function *******************************
void loop()
{
UP_Button.read();
Down_Button.read();
if (UP_Button.wasReleased() && PWM_Value < 55 && calc == false) {
PWM_Value = PWM_Value + 5;
display.clearDisplay();
display.setCursor(2, 25);
display.print("Curr:");
display.print(String(Current[PWM_Value / 5]) + "mA");
display.display();
// Serial.println(String(Current[PWM_Value / 5]));
}
if (Down_Button.wasReleased() && PWM_Value > 1 && calc == false) {
PWM_Value = PWM_Value - 5;
display.clearDisplay();
display.setCursor(2, 25);
display.print("Curr:");
display.print(String(Current[PWM_Value / 5]) + "mA");
display.display();
// Serial.println(String(Current[PWM_Value / 5]) + "mA");
}
if (UP_Button.pressedFor(1000) && calc == false) {
analogWrite(PWM_Pin, PWM_Value);
digitalWrite(Buzzer, HIGH);
delay(100);
digitalWrite(Buzzer, LOW);
display.clearDisplay();
timerInterrupt();
}
}
//************************* End of Loop function *******************************
void timerInterrupt()
{
calc = true;
while (Done == false) {
Second++;
if (Second == 60) {
Second = 0;
Minute++;
}
if (Minute == 60) {
Minute = 0;
Hour++;
}
//************ Measuring Battery Voltage ***********
for (int i = 0; i < 100; i++) {
sample = sample + analogRead(BAT_Pin); //read the Battery voltage
delay(2);
}
sample = sample / 100;
BAT_Voltage = sample * Vcc / 1024.0;
//*********************************************
display.clearDisplay();
display.setTextSize(2);
display.setCursor(20, 5);
display.print(String(Hour) + ":" + String(Minute) + ":" + String(Second));
display.setTextSize(1);
display.setCursor(0, 25);
display.print("Disch Curr: ");
display.print(String(Current[PWM_Value / 5]) + "mA");
display.setCursor(2, 40);
display.print("Bat Volt:" + String(BAT_Voltage) + "V");
Capacity = (Hour * 3600) + (Minute * 60) + Second;
Capacity = (Capacity * Current[PWM_Value / 5]) / 3600;
display.setCursor(2, 55);
display.print("Capacity:" + String(Capacity) + "mAh");
display.display();
if (BAT_Voltage < Low_BAT_level) {
Capacity = (Hour * 3600) + (Minute * 60) + Second;
Capacity = (Capacity * Current[PWM_Value / 5]) / 3600;
display.clearDisplay();
display.setTextSize(2);
display.setCursor(2, 15);
display.print("Capacity:");
display.setCursor(2, 40);
display.print(String(Capacity) + "mAh");
display.display();
Done = true;
PWM_Value = 0;
analogWrite(PWM_Pin, PWM_Value);
digitalWrite(Buzzer, HIGH);
delay(100);
digitalWrite(Buzzer, LOW);
delay(100);
digitalWrite(Buzzer, HIGH);
delay(100);
digitalWrite(Buzzer, LOW);
delay(100);
}
delay(1000);
}
}

1
WayneSUK
WayneSUK

6 weeks ago

Good project, thanks for sharing, I built including the addition of the precision voltage ref. However I did not see the value I was expecting from a cell of known standard. After a bit of head scratching I realised the time counter was running too slow. In the loop there is a 1000ms delay, reducing this to 730ms has the time counting correctly.

1
zym1
zym1

Question 2 months ago

irlz44n alternative?

0
neg3ntropy
neg3ntropy

Question 2 months ago on Introduction

I don't see the mosfet in the BOM. Is that a mistake?