Lithium-Ion Battery Charger

Following is the tutorial of a DIY Lithium-Ion battery charger implemented on Arduino with several advanced features like state-of-charge estimation, EEPROM logging and command-line interface. It uses the Constant Current Constant Voltage (CC-CV) charging method with end-of-charge detection based on multiple criteria. The same design can be used for charging Lithium-Polymer (LiPo) batteries.

The rationale behind this project was to upgrade the depleted battery pack and charger of an old cordless drill from Nickel-Cadmium (NiCd) to Lithium-Ion (Li-Ion) technology.

Warning: Lithium-Ion batteries are hazardous devices. Overcharging, short-circuiting or otherwise abusing Lithium-Ion batteries may result in a fire and/or a violent explosion. The author of this page neither takes any responsibility nor can be held liable for any damage caused to human beings and things due to the improper handling of Lithium-Ion batteries. Please be aware that the current design has not been certified for safety, consequently it is not suitable for commercial applications and must be implemented at your own risk. Last but not least, it is imperative to equip each Lithium-Ion battery pack with its dedicated battery protection board (or Battery Management System aka BMS).

Theory of Operation

The following sub-sections cover the theoretical and mathematical aspects of Lithium-Ion (Li-Ion) battery charging. The exact same principles apply to Lithium-Polymer (LiPo) batteries.

CC-CV Charging

Li-Ion batteries must be charged using the Constant Current Constant Voltage (CC-CV) charging method. This method consists of charging the battery at a constant current I_{chrg} until a certain voltage threshold V_{max} = 4.19 V/cell is reached, then gradually reducing the charging such that the constant cell voltage V_{max} is not exceeded. Charging is terminated once the current reaches a certain minimum threshold I_{full} of typically 50..150 mA.

Additional end-of-charge (EoC) criteria have been implemented for safety reasons. These include the time-based and capacity-based EoC detection. When a battery is connected, the charger measures the voltage at its terminals and roughly estimates its state of charge SoC in %. The SoC value is used for calculating the remaining capacity C_{max} and charge duration T_{max}. Charging is terminated if either of these values has been reached. The SoC calculation details are described in the following sections.

Control Loop

The battery “+” terminal is connected to the positive power supply through a power MOSFET (field-effect transistor). The battery “-” terminal is connected to the power supply ground through a low-value shunt resistor R_{shunt}.

The charging current is regulated by means of Pulse Width Modulation (PWM), where the MOSFET cyclically turned on and off by the Arduino at an approximate frequency of 1KHz. The charging current is controlled by gradually adjusting the PWM duty cycle which is the ratio between the MOSFET on and off duration.

V_1 is the voltage measured at the battery “+” terminal and V_2 is the voltage measured at the battery “-” terminal. Both voltages are measured relative to the power supply ground and are used for calculating the voltage V across the battery pack and the charging current I as follows:

V (V) = V_1 - V_2

I (A) = \frac{V_2}{R_{shunt}}

Two separate ADC channels on the Arduino are used for measuring the above voltages. The Arduino continuously monitors V and I and adjusts the PWM duty cycle in order to achieve the desired constant current or constant voltage regulation.

State-of-Charge Estimation

The state of charge SoC is estimated by reading the battery voltage V and comparing it to a series of values stored in a lookup table L = [l_0,l_1,l_2,l_3,l_4,l_5,l_6,l_7,l_8]. The threshold voltages are derived from the particular discharge curve shown below for the LG 18650 HE4 cells used in this project (source:

The red discharge curve corresponding to 0.2A discharge current has been used, whereas the values of L were assigned such that:

  • l_0 = V@2.25Ah
  • l_1 = V@2.00Ah
  • l_2 = V@1.75Ah
  • l_8 = V@0.25Ah

SoC is calculated as follows:

  • V < l_0: SoC = 0\%
  • l_0 < V < l_1: SoC = 10 \%
  • l_1 < V < l_2: SoC = 20 \%
  • l_8 < V: SoC = 90 \%

The remaining capacity C_{max} and charge duration T_{max} are derived as follows:

C_{max}(mAh) = C_{full} \cdot (100 - SoC) \cdot 1.3

T_{max}(s) = 3600 \cdot \frac{C_{full}}{I_{chrg}} \cdot (90 - SoC) + 45 \cdot 60

Where C_{full} is the battery design capacity and I_{chrg} is the nominal charging current. Note that C_{max} is increased by 30% and T_{max} is increased by 45 minutes in order to to account for resistive losses and SoC estimation inaccuracy.


The charger implements several safety features. These include undervoltage, overvoltage, short circuit and open circuit detection.

The typical voltage range where a Li-Ion battery can safely operate is between V_{min} = 2.5 V/cell and V_{max} = 4.19 V/cell. Operating outside this range is likely to cause permanent damage to the Li-Ion cells and may even result in a catastrophic failure such as an explosion or fire.

The battery pack is additionally protected by a battery protection board (or Battery Management System aka BMS). The BMS measures the voltages of the individual battery cells as well as the charge/discharge current flowing through the battery. The BMS uses a solid-state switch to disconnect the battery as soon as the voltage or current values become outside of the specified limits.

For the most part, the BMS is completely transparent and does not interfere with the charging process, except for the case where the BMS disconnects the depleted battery in order to prevent over-discharge. In this case, the voltage of the depleted battery is still present across the BMS terminals through a high value resistor placed in series with the battery. This high value resistor causes a much lower voltage value to be measured at the charger terminals. Consequently, the charger must ignore the V_{min} lower limit and start charging at a much lower value of as low as V_{start} = 0.5 V/cell.

When presented with a depleted battery, the charger would start charging at a reduced safety current I_{safe} = I_{chrg} / 10 until the battery voltage reaches V_{safe} = 2.8 V/cell, afterwards it would apply the full charging current I_{chrg}. Once the voltage reaches this threshold, it is no longer allowed to drop below the V_{min}. A voltage below V_{min} would raise an “undervolt error” which is may caused by either a short circuit or a battery open circuit.

Open circuit is also detected if the charging current stays equal to zero while the PWM duty cycle increases beyond a specific threshold. This condition would raise an “open circuit error”.

Overvoltage is detected whenever the battery pack voltage momentarily exceeds V_{surge} = 2.25 V/cell. Exceeding this value would raise an “overvolt error”.

Trickle Charging

Once the end-of-charge (EoC) criteria has been met, the charger would cut-off the charging current and switch to an idle mode where it will continuously monitor the battery voltage. Once the voltage drops below a specific threshold of V_{trickle\_start} = 4.10 V/cell, a new charging cycle will be initiated using the following parameters:

V_{max} (V/cell) = V_{trickle\_max} = 4.15V/cell

C_{max} (mAh) = C_{full} \cdot 0.3 + C

T_{max} (s) = 20 \cdot 60 + T

Where C_{full} is the battery design capacity. C and T are the accumulated charge capacity and charge time since the battery has been connected, including the initial charge and all of the subsequent trickle charge cycles.

Given the above formulas, the trickle charge cycle uses a reduced V_{max} and allows for charging up to a maximum of 3% of the battery design capacity during a maximum duration of 20 minutes.


The following sub-sections describe the hardware design aspects of the Li-Ion charger.

Mechanical Design

The following image gallery shows the mechanical design of both battery pack and charger.

The original 12 NiCd cells have been removed from the battery pack and replaced by 4 LG 18650 HE4 Li-Ion cells and a battery protection board (or Battery Management System aka BMS). Despite the increased capacity, the modern Lithium-Ion cells use significantly less space which leaves plenty of room for the BMS and and the required wiring.

The original battery charger has been retrofitted with the custom PCB containing the Arduino and required circuitry. A 19.5V / 3.33A notebook power supply has been used for powering the whole system.

Battery Protection Board

Despite the safety features described above, it is imperative to use a dedicated battery protection board for each of the battery packs. This provides an additional layer of protection to prevent an overcharge or over-discharge condition due to a software or hardware bug.

The following figures show the particular 4S / 30A (4S means 4 cells in series) battery protection board (or BMS) that has been used in this project. It has been acquired for less than 10€ on Ebay and serves its purpose very well (source:

In the above figure one can see the wiring diagram for connection the 4 Li-Ion cells with the BMS.

This particular BMS includes the cell balancer feature. If the voltage of one or more cells becomes higher than the rest of the pack, the BMS would actively discharge those cells to ensure that all the cells of the battery pack share the exact same voltage.

Circuit Diagram

The the following figure shows the Li-Ion charger circuit diagram.

Lithium-Ion battery charger circuit diagram (click to enlarge)

The above schematic, the 19.5V of the power supply are stepped-down to 5V by the 7805 voltage regulator U1. The 5V is used for powering the Arduino board.

The Arduino Pro Mini compatible board U2 hosts an ATmega 328P microcontroller running at 16MHz clock frequency and is used as the main processing unit for the device.

The Lithium-Ion battery is connected across the B+ and B- terminals. The battery charging current is regulated by switching P-Channel MOSFET (field-effect transistor) Q1 via pulse-width modulation (PWM).

The PWM-enabled digital output pin 9 on the Arduino generates a PWM signal which drives the gate of the MOSFET Q1 through the NPN transistor Q2. The voltage divider formed by R1 and R2 ensures that the gate-source voltage of the MOSFET stays within the specified limits.

A current-sensing shunt resistor connects the B- terminal with ground. It consists of two 1Ω/3W resistors R8 and R9 connected in parallel. This results in a total resistance of 0.5Ω. At a charging current of I = 2A, the voltage across the shunt will be exactly 1V; which is slightly below the 1.1V internal voltage reference of the Arduino thus corresponds to the full range of the Arduino’s analog-to-digital converter (ADC).

The analog pin A1 on the Arduino is used for measuring the voltage V_1 between B+ and 0V. Analog pin A0 is used for measuring V_2 between B- and 0V.

B+ is connected to pin A1 through a voltage divider consisting of R4 and R7, the ratio has been chosen such that the maximum battery pack voltage of 16.8V would result in slightly less than the Arduino’s internal reference voltage of 1.1V at A1. Please note that the value of R4 needs to be adapted to the number of cells in use. For example, using a 1 cell setup would require reducing the value of R4 to 39KΩ.

B- is connected to A0 through a current-limiting resistor R5; a voltage divider is not required for measuring V_2 as its value stays below the Arduino’s ADC internal reference voltage.

Two 10µF Tantalum capacitors C4 and C5 are used for blocking the high-frequency noise caused by the PWM from reaching the analog inputs, an essential measure for smooth ADC readings.

The Diode D1 protects the 7805 regulator from a reverse power supply polarity. The diode D2 protects the battery from a reverse polarity; it also prevents the battery from feeding power back into the Arduino in case the main power supply has been disconnected.

A LED indicator D3 and its dropper resistor R6 are connected to Arduino’s digital pin 13.

Charging Different Numbers of Cells

The following values for R2, R4 and the power supply voltage need to be chosen in order to charge different numbers of Cells:

N_{cells} Power SupplyR2R4
15V – 10V220Ω39KΩ
210V – 15V220Ω82KΩ
314V – 20V1KΩ120KΩ
418.5V – 20V2.2KΩ180KΩ

PCB Layout

All of the components are of through-hole type and are mounted on a stripboard PCB. The following figures show the PCB layout of the Li-Ion charger. Please click on the respective image in order to see a larger version.

The MOSFET (TO-220 device in the top right corner) and large green-colored shunt resistors will get pretty hot so adequate ventilation needs to be assured. The following measures has been taken to avoid overheating:

  • The shunt resistors are raised by around 5mm from the PCB in order to assure adequate cooling.
  • A series of holes has been drilled in the bottom of the enclosure in order to allow for a better air flow.
  • The charging current I_{chrg} has been limited to 1.5A.

The electrolytic capacitor towards the top center of the board is in a sub-optimal position due to its location between two hot components – the 7805 regulator and the MOSFET. High temperatures reduce the lifespan of electrolytic capacitors thus the must be kept away from heat sources.

Note that the 3A BY255 diode D2 shown on the schematic has been added after these PCB pictures were taken, thus the front PCB view still shows shows 3 smaller 1A Schottky diodes soldered in parallel (top right corner). Connecting diodes in parallel is generally prone to failure due to the fact that their junction voltage might be slightly different which may lead to an unbalanced current distribution,whereas the diode with the lowest junction voltage would take most of the current.

The pin header located at the top right corner is used for connecting all the external wires. Following is the pinout assuming that pin 1 is at the top right corner and pin 10 is towards the middle of the board.

1LED + (*)
2LED – (*)
3, 4Power supply +
5, 6Battery +
7, 8Power supply –
9, 10 Battery –

* The LED dropper resistor is located on a separate PCB together with the LED itself.

User Interface

The following sections describe the user interface of the Lithium-Ion charger. It consists of a LED indicator and a Command-Line Interface (CLI).

LED Indicator

The charging status is displayed by means turning on or blinking a single LED as follows:

  • LED turns on for half a second every 2 seconds: ready, waiting for the battery to be connected
  • LED is solid on: charging
  • LED turns on for 0.1 second every 2 seconds: battery fully charged
  • LED blinks quickly: error

Command-Line Interface

This Lithium-Ion battery charger features a Command-Line Interface (CLI) that can be accessed via the Arduino’s RS232 serial port. The easiest way to connect to the CLI is to open the serial monitor of the Arduino IDE while connected to the charger. Please ensure that the Baud rate is set to 115200.

Once up and running, the charger will display a welcome message on the serial monitor, show the current firmware version and present with the list of available commands as shown in the following list.

Some of these CLI commands need to be provided with arguments. Thus, one needs to enter a the command followed by one or two arguments separated by a white space.

cShow the list of calibration parameters that are stored within EEPROM.
[v1, v2]
Activate the voltage calibration mode. Issuing this command for the first time will enter the calibration mode and display two reference voltage values: V_{1,ref} and V_{2,ref}. Once in the calibration mode, you may call this command again with the option v1 or v2 in order to calibrate the respective voltage. The V_1 and V_2 calibration values will then be stored into the EEPROM.
More on this in the following section.
cfull <integer>Set the battery design capacity C_{full} in mAh. The value provided as an argument will be validated and stored in EEPROM.
ichrg <integer>Set the battery charging current I_{chrg}. The value provided as an argument will be validated and stored in EEPROM.
ifull <integer>Set the end-of-charge current I_{full} in mA. The value provided as an argument will be validated and stored in EEPROM.
lut <index> <voltage>Configure the state-of-charge lookup table (LUT). This command takes and index i =0,1,2,\dots,8 and the reference voltage l_i in mV as arguments. Each time this command is called, a new reference voltage value l_i is populated into the LUT and stored into EEPROM. More on this in the following section.
ncells <integer>Set the total number of cells within the battery pack N_{cells}. The value provided as an argument will be validated and stored in EEPROM.
rshunt <integer>Set the shunt resistor value R_{shunt} in mΩ. The value provided as an argument will be validated and stored in EEPROM.
tShow the contents of the trace circular buffer.
.Display the real-time parameters, like the charge duration T, charge capacity C, battery voltage V, charging current I, maximum charge duration T_{max}, maximum charge capacity C_{max}, maximum charging voltage V_{max}, maximum charging current I_{max}, PWM duty cycle, voltages V_1, V_2 and their raw ADC values.
hHelp – show an overview of the available commands and their brief descriptions.

Calibration Procedure

This section provides an example on how to perform the first-time calibration of the Lithium-Ion battery charger using the CLI over the serial monitor.

The calibration values are stored into the Arduino’s Electrically Erasable Programmable Read-Only Memory (EEPROM). A Cyclic Redundancy Check (CRC) checksum is appended to the configuration parameters set and stored into EEPROM as well. All configuration parameters are validated and out-of-range values are automatically replaced with the corresponding failsafe values.

The current example assumes a system consisting of N_{cells} = 4 connected in series having a design capacity of C_{max} = 2500mAh charged using a current of I_{chrg} = 1500mA . In order to perform the calibration, please execute the command sequence as listed below:

Having performed the above initial step, please proceed for calibrating the ADC readings for the voltages V_1 and V_2 as follows:

  1. Enter the command cal into the serial monitor, this will activate the calibration mode and display two reference voltages; V_{1,ref} = 4 * 4200mV = 16800mV and V_{2,ref}=1000mV. Please note that the value of V_{1,ref} depends on the number of cells in the battery pack N_{cells} = 4. The calibration mode ensures that the charger does not initiate a charging cycle while the external calibration voltage sources are being connected to the B+ and B- terminals as described in the subsequent steps.
  2. Connect a constant voltage source of exactly 1000mV between the B- terminal and the power supply ground (0V). You may want to temporarily disconnect the shunt resistors R8 and R9 in order to avoid excessive current flow.
  3. Enter the command cal v2 into the serial monitor. The value of V_{2,cal} will be displayed upon the successful calibration of V_2.
  4. Connect a constant voltage source of exactly 16800mV (4200mV per cell) between the B+ terminal and the power supply ground (0V).
  5. Enter the command cal v1 into the serial monitor. The value of V_{1,cal} will be displayed upon the successful calibration of V_1.
  6. Enter the command cal in order to exit the voltage calibration mode.
  7. Verify the voltage calibration by applying a known voltage to each of B+ and B- (relative to 0V), then enter the “.” (dot) command and check the displayed values for V_1 and V_2 which must match the measured voltages at B+ and B- as closely as possible. Needless to say, please use a reasonably accurate digital multimeter for measuring the voltages.
  8. Verify the current calibration by measuring the current through the shut resistors using a digital multimeter, then enter the “.” (dot) command and check the displayed value for I which must match the measured current as closely as possible.

Important: please ensure that the voltage calibration procedure has been properly executed and verified prior to attempting to charge a Lithium-Ion battery. It is mandatory to connect a good quality battery protection board between the charger and battery. Failing to observe these precautions may lead to permanent damage or even explosion of the Lithium-Ion cells.

Trace Buffer

The Lithium-Ion battery charger logs the events that occur during the charging process into a circular buffer within the available EEPROM space. The contents of the trace buffer are dumped using the t command. Following is a sample trace log output for a complete charging cycle:

The trace messages have the format of <timestamp>: <event> <value>. Whereas the timestamp counts the minutes elapsed since the beginning of the charging process. The following table shows available events and their descriptions:

*Beginning of the charging cycle, indicates the maximum battery voltage V_{max} in V
%Initial state-of-charge in %
TMaximum allowable charge duration T_{max} in minutes
CMaximum allowable charge capacity C_{max} in mAh
SSafety charge in progress, indicates I_{safe} in mA
INormal charge in progress, indicates I_{chrg} in mA
vInstantaneous battery voltage V=V_1-V_2 in V
iInstantaneous battery current I in mA
FBattery full, indicates the end-of-charge condition (1 = I_{full} reached, 2 = C_{max} reached, 3 = T_{max} reached)
tActual charge duration T in minutes
cActual charged capacity C in mAh
EError (1 = overvolt, 2 = undervolt, 3 = open circuit, 99 = CRC fail)


Below you can find GitHub download links for the Arduino firmware source code, Eagle schematic source files and bill of material. All of the source code is distributed under the GNU General Public License v3.0.

Please note that the current implementation uses the watchdog timer functionality which requires the customized Arduino bootloader found under the link below. For more details, please follow the installation instructions found within the README file on GitHub.

Customized Arduino Bootloader

Lithium-Ion Charger Firmware

Eagle Schematic Source Files

Bill of Material

Last updated on June 19, 2019

14 thoughts on “Lithium-Ion Battery Charger”

  1. Hello! Great Project! Is there any way we can interface an LCD to show the real-time SoC?

      1. Thanks for the reply, Karim. Can you also tell me how can I implement this project from the arduino CLI? Can’t this be done using the IDE?

        1. Hi Aqib, do you refer to the Command Line Interface (Cli) class in the firmware source code? Cli sends and receives messages through the Arduino’s Serial port. Once you load the firmware, you need to open the Serial Monitor of your Arduino IDE; there you will see a welcome message and a list of available commands you can execute by typing them into the Serial Monitor. You will need to set the Serial Baud rate to 115200.

          1. I see! This is cool. Thanks a lot Karim. Also, in which class do I have to make certain changes so that I might get to plot the SoC real time (while the battery is charging).

          2. You are welcome. The state of charge is calculated by calcTmaxCmax() in li-charger.ino line 205. In order to plot the SoC in real time, you would need to take the initial SoC, multiply it by the battery design capacity Nvm.cFull and add the result to the the charged capacity G.c which being calculated in li-charger.ino line 469. Note that the soc variable from line 205 needs to be made global by moving it to the G struct (line 103). You have either the option to directly print the result to the serial console using Cli.xprintf() or trace it in to the EEPROM circular buffer by calling Trace.log(). The circular buffer can be then dumped by typing the “t” command into the Serial Monitor. Please note that the unit of Nvm.cFull is mAh (milliampere-hour) while G.c is mAs (milliampere-second), so first you would need to convert G.c to mAh by dividing it by 3600. Its a bit complicated but i hope it helps.

  2. And also, if I calibrate values in the serial monitor and when I trace, I get “E=99” and iI don’t get the “harging” message. Is this because of some crc error?

    1. Hi Aqib, CRC check error E=99 is normal upon initial start as the EEPROM still contains some garbage. The error will disappear once you configure the charger via the Serial Monitor for the first time. I will post some additional information on how to configure and calibrate the charger via the Serial interface.

      1. Hi karim, Great Project! can you help me for realising this circuit please? i realy need your help thank u .

        1. Hi Raouf,

          thanks for your message, I am happy that you find this project interesting. I would be happy to help, however please bear in mind that this project requires some basic skills in electric circuits, soldering, using measurement equipment and C++ programming. At the moment I have very limited free time, so I would be happy to answer some advanced questions and provide clarifications for advanced details that are not well described within this article. What I can’t really do is provide explanations on basic things like Ohm’s law, using a multimeter, C++ tutorials and the like.

          Kind Regards, Karim

  3. thanks for the replay karim , i want to ask you if we can use this regulator with photovoltaïque generator , and if we can charge just one battery of phone type (lithium-ion) and how to put an LCD to show the real time SoC , i have a thesis and i must to return it before june, i am very interested with your project i am greatful to you karim thank you so much.

    1. Hi Raouf, answering your questions:
      – Yes, you can power this circuit from a solar cell, however with some modifications, as the Arduino board would need to be powered by the Li-Ion cell. A 3.3V/8MHz Arduino Pro Mini can be directly powered from the Li-Ion cell, however you must use a battery protection board in order to avoid over-discharging the cell.
      – Yes, you can use this charger to charge a 1s LiPo cell for this you would need to reduce the value of R4 to 39KOhm, reduce the value of R2 to 220Ohm and program the value of N_Cells = 1.
      – Yes, you may use an LCD by following .
      Nevertheless, this would not be the most efficient solar charger due to losses occurring within the shunt resistors R8 and R9 and the diode D2. This can be improved by using an “Arduino current sensor” board and an “ideal diode” (please google the terms).

      1. Thank you karim I did what you told me, but I still found problems:
        1.the output voltage is still=18.5 v I can’t supply one li-ion battery with this voltage
        2. Solar panel sizing (if only one battery is powered, the input voltage should be 19.5 or less) input voltage regulation circuit, because the panel produces a variable voltage.

        1. Hi Raouf, my replies:
          1. The voltage will automatically be reduced to the required value by means of a reduced PWM duty cycle, so 18.5 V should be fine even for 1s. IMPORTANT: please use an off-the-shelf battery protection board, otherwise you may risk to blow-up the battery in the event of a bug!
          2. If i understand the question correctly, in order to charge 1s, the solar panel should deliver at least 5v to 6v. 19.5v would be required for 4s.
          3. The 7805 linear regulator already provides adequate regulation for the Arduino, it can accept anything between 7V and 25v. The MOSFET PWM control takes care of regulating the battery voltage and current.

Leave a Reply

Your email address will not be published. Required fields are marked with an *.