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.

Please note that the documentation provided on this page always refers to the latest firmware release found on GitHub. Older firmware versions may or may not work as described within this article.

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 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} = \SI{4.2}{\volt/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 a frequency of 31.250 kHz. 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.2 A discharge current has been used, whereas the values of L were assigned such that:

  • l_0 = V@\SI{2.25}{Ah}
  • l_1 = V@\SI{2.00}{Ah}
  • l_2 = V@\SI{1.75}{Ah}
  • l_8 = V@\SI{0.25}{Ah}

SoC is calculated as follows:

  • V < l_0: SoC = \SI{0}{\percent}
  • l_0 < V < l_1: SoC = \SI{10}{\percent}
  • l_1 < V < l_2: SoC =\SI{20}{\percent}
  • l_8 < V: SoC = \SI{90}{\percent}

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} = \SI{2.5}{V/cell} and V_{max} = \SI{4.2}{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 overdischarge. 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} = \SI{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} = \SI{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} = \SI{4.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} = \SI{4.10}{V/cell}, a new charging cycle will be initiated using the following parameters:

V_{max} (V/cell) = V_{trickle\_max} = \SI{4.15}{V/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 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 the required wiring.

The original battery charger has been retrofitted with the custom PCB containing the Arduino and required circuitry. A 19.5 V / 3.33 A 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 malfunction.

The following figures show the particular 4 S / 30 A (4 S 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.

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.5 V of the power supply are stepped-down to 5 V by the 7805 voltage regulator U1. The 5 V is used for powering the Arduino board.

The Arduino Pro Mini compatible board U2 hosts an ATmega 328P microcontroller running at 16 MHz 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 Ω / 3 W resistors R8 and R9 connected in parallel. This results in a total resistance of 0.5 Ω. At a charging current of I = \SI{2}{\ampere}, the voltage across the shunt will be exactly 1 V; which is slightly below the 1.1 V internal voltage reference of the Arduino thus corresponds to the full range of the Arduino’s analog-to-digital converter (ADC).

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

B+ is connected to pin A0 through a voltage divider consisting of R4 and R7, the ratio has been chosen such that the maximum battery pack voltage of 16.8 V would result in slightly less than the Arduino’s internal reference voltage of 1.1 V at A0. 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 39 KΩ.

B- is connected to A1 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 100 nF 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.

Important: The battery terminals in the circuit diagram are labeled as B+ and B-. It is important to connect these terminals to the P+ and P- terminals of the Battery Management System (BMS) depicted in the picture. The BMS has its own set of B+ and B- terminals that must be connected directly to the battery terminals. It is crucial to avoid connecting the charger’s B+ and B- terminals to the B+ and B- terminals of the BMS, as this would bypass the BMS and prevent it from safeguarding the battery against overcharging.

Different Number 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
1 *5 V-6 V220 Ω *39 KΩ
210 V – 15 V100 Ω82 KΩ
314 V – 20 V220 Ω120 KΩ
418.5 V – 20 V220 Ω180 KΩ

* When charging 1 cell, the following circuit modifications must be performed:

  • Remove the voltage regulator U1 and capacitor C3 and power the Arduino directly from the output of D1
  • Replace Q1 with a IRLML2244 MOSFET
  • Increase R1 to 10 KΩ
  • Remove Q2 and R3
  • Connect R2 directly to Arduino digital pin 9
  • Modify the code in li-charger.ino to invert the PWM signal by subtracting the PWM duty cycle from 255 within all instances of analogWrite() using one of the following statements:
analogWrite (MOSFET_PIN, 255 - G.dutyCycle);  // Replaces analogWrite (MOSFET_PIN, G.dutyCycle)
analogWrite (MOSFET_PIN, 255);                // Replaces analogWrite (MOSFET_PIN, 0)

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 (click to enlarge).

The MOSFET Q1 (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 R8 and R9 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.5 A.

The electrolytic capacitor C1 towards the top center of the board is in a suboptimal 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.

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.

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

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

† Two pins are connected in parallel in order to increase their current capacity.

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 charger status is displayed by means turning on or blinking a single LED as shown in the following table.

Blinking PatternMeaning
On for half a second every 2 secondsReady, waiting for the battery to be connected
Solid onBattery charging
On for 0.1 second every 2 secondsBattery fully charged
Blinking fast (0.4 s period)Error
Blinking very fast (0.2 s period)Calibration mode

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 using a FTDI USB to Serial converter. 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 shown in the following table.

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.

hHelp – show the list of available commands
.Display the real-time parameters, including 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 V_{1,raw} and V_{2,raw}
rShow the list of calibration constants that are stored within EEPROM
tShow the contents of the trace circular buffer
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
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
rshunt <integer>Set the shunt resistor value R_{shunt} in mΩ, The value provided as an argument will be validated and stored in EEPROM
cal <start| stop| v1| v2> [mv]The voltage calibration mode is entered by calling cal start and exited by calling cal stop.
V_{1} is calibrated using cal v1 <mv>.
V_{2} is calibrated using cal v2 <mv>.
<mv> is the measured voltage level in millivolts. Please refer to the next section for more details about the calibration procedure.

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} = \SI{2500}{mAh} charged using a current of I_{chrg} = \SI{1500}{mA} .


  • Do not connect the battery during the calibration procedure unless instructed otherwise.
  • Ensure that the voltage calibration procedure has been properly executed and verified prior to attempting to connect 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.

Initial Configuration

A a first step, the initial configuration parameters need to be loaded into EEPROM by executing the command sequence below:

ncells 4
cfull 2500
ichrg 1500
ifull 150
rshunt 500
lut 0 3200
lut 1 3450
lut 2 3530
lut 3 3610
lut 4 3650
lut 5 3710
lut 6 3825
lut 7 3920
lut 8 4020

A confirmation message will be printed on the serial monitor following each value entry.

Voltage Calibration

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

  1. Enter the command cal start into the serial monitor, this will activate the calibration mode. The message Calibration start should appear on the serial monitor.
  2. Connect a constant voltage source of approximately 750 mV between the B- terminal and the power supply ground (0 V) and measure its exact value using a digital multimeter. Note that 750 mV corresponds to 1.5 A flowing through the shunt resistors R8 and R9. Note: If your power supply is unable to deliver 1.5 A of current, then temporarily disconnect the shunt resistors R8 and R9.
  3. Enter the command cal v2 <value> into the serial monitor, where <value> is the value in mV of the voltage measured in the previous step (e.g. 854). The value of the calibration constant V_{2,cal} will be displayed upon the successful calibration of V_2. If the calibration fails, the message Out of range will appear in the serial monitor.
  4. Connect a constant voltage source of approximately 16800 mV (4200 mV per cell) between the B+ terminal and the power supply ground (0 V) and measure its exact voltage using a digital multimeter.
  5. Enter the command cal v1 <value> into the serial monitor, where <value> is the value in mV of the voltage measured in the previous step (e.g. 16450). The value of the calibration constant V_{1,cal} will be displayed upon the successful calibration of V_1. If the calibration fails, the message Out of range will appear in the serial monitor.
  6. Verify the voltage calibration by applying a known voltage to each of B+ and B- (relative to 0 V), 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.
  7. Repeat steps 2, 3, 4, 5 and 6 until the voltage readings are correct.
  8. Enter the command cal stop in order to exit the voltage calibration mode. The message Calibration stop should appear on the serial monitor.

Current Calibration

Please proceed with calibrating the reading of the current I by following the steps below:

  1. Connect a discharged Lithium-Ion battery to in series with a digital ampere meter (set to the 10 A range) to the terminals B+ and B-.
  2. The message Charging should appear in the serial monitor and the measured current value should start to gradually increase until it reaches a maximum of approximately 1.5 A.
  3. Enter the . (dot) command and check the displayed value for I which must match the measured current as closely as possible.
  4. If the output of the . command is higher than the amperemeter reading: increase the value of R_{shunt} by 10 mΩ by calling the rshunt command with the original shunt resistance value increased by 10 mΩ.
  5. If the output of the . command is lower than the amperemeter reading: decrease the value of R_{shunt} by 10 mΩ by calling the rshunt command with the original shunt resistance value decreased by 10 mΩ.
  6. Repeat steps 3, 4 and 5 until you get an accurate reading of I.

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:

  0: * 16760
  0: % 0
  0: v 7820
  0: T 135
  0: C 3263
  0: S 150
  0: I 1500
  2: v 13222
  2: i 1495
  4: v 13719
  4: i 1499
  6: v 13982
  6: i 1495
  8: v 14137
  8: i 1503
 10: v 14206
100: v 16767
100: i 638
102: v 16764
102: i 529
104: v 16761
104: i 381
106: v 16754
106: i 241
108: v 16759
108: i 231
110: v 16764
110: i 221
112: v 16761
112: i 150
113: F 1
113: t 113
113: c 2508
113: v 16767
113: i 139

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 allowed charge duration T_{max} in minutes
CMaximum allowed 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 mV
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 March 9, 2024

83 thoughts on “Lithium-Ion Battery Charger”

  1. Well done Karim, overall this is a great project however some hardware issues around safety need a closer look. I get the impression you are more on the firmware/software side of the equation. My main concern is around failure modes of the PWM output stage. Should either a software or hardware failure cause Q2 to turn on then the full supply voltage will be applied to B+ with possibly catastrophic consequences for the Li cells. How the BMS reacts to this OV condition and how quickly it reacts is unknown, but IMHO you need some protection in the B+ line to disconnect it from the battery under Q2 ON condition. Remember also that the load will see this voltage as well and even if the BMS protects the batteries the load may not like it at all. Entrusting the Li-ion charging algorithm to software is another concern. There are so many excellent dedicated hardware devices out there to do the job more reliably. Something like the MAX745 would do a very adequate job of looking after the charging side, while software would control it according to your needs. You do raise safety concerns in your article and warn of the dangers of abusing Li-ion cells but I would not entrust the total safety of this project to the BMS. I would not be comfortable to run this charger unattended for any length of time.

    1. Thanks, ATtiny85 features 4 10-bit ADC channels. For this project you only need two of them, so I believe the implementation could be easily ported to ATtiny85. Please feel free to fork the Github repository to perform the required code adaptations. Unfortunately, I have no bandwidth to support, however I think it’s perfectly feasible. Best Regards, Karim

  2. Hi Karim,

    Interesting project. Have you considered adding a low pass filter after Q1? I don’t believe the circuit as designed can truly perform CV charging and even in CC mode would potentially cause the pack to pulse with each PWM cycle above their absolute max voltage ratings. Maybe I’m missing something here and would like to hear your thoughts as I’m planning a similar project.

    1. Hi Eric,
      Thanks for your feedback. This is a simplified design using plain PWM. A commercial product would usually use a buck converter circuit consisting of an inductor, capacitor and a diode. The buck converter design is more power efficient and provides more constant voltage output. Buck converters are however more difficult to design implement and require a correct PCB layout, so using a prototyping board will not work. Regarding the voltage peaks you have mentioned, I have thought about it and came to the following hypothesis: The battery itself acts as a large capacitor, which results in a low pass filter when combined with the shunt resistor. At 31.250 kHz PWM frequency, the maximum pulse duration amounts to 32 microseconds. This duration is way too short to cause any significant voltage rise at the battery terminals, given that the battery voltage does not rise instantaneously but follows the battery charge curve instead.

  3. Karim,

    My apologies for the name mistake, I was trying to find your name on your write-up but I couldn’t find it. So I looked at other comments and thought I got it, but obviously not.

    And I’ve never worked with the pro mini before – I mainly use the Nano and lately, I’ve been tinkering with the Teensy 4.0 which is a really nice microcontroller.

    This project would be ideal for a custom-designed PCB using just the Mega 328 chip at which point you could easily incorporate the AREF pin.

    I’ve been wanting to build my own Li-Ion charger for a long time now to charge 18650 batteries individually. Not because doing so would be cost-effective, but because I’ve always wanted to learn about the process of charging LiIon and LiPo cells and because I want some features that you can’t find in retail chargers, such as the ability to run a process that determines how much life a battery has left in it, which from what I understand is determined by how long a battery can sustain a specific current draw. Also, being able to keep a recorded history of charges on specific batteries – could tell me about the overall quality of a battery so that I would know whether or not that battery was worth purchasing again.

    After reading your write-up, I’m wanting to really get on that project now.

    In my charger though, I wanted a way for the microcontroller to use an RGB LED and light it red if the battery is inserted backwards and I came with two different ways of detecting a reverse connection, though I’m not sure either of them would work because I’m not sure if the shunt resistor would interfere with current flow. Do you think either of these would work? In either case, if there was a voltage detected on pin A2, then the red LED would be lit.


    1. Hi Mike,
      No problem, thanks for elaborating on you project’s details. Having reviewed your schematics, both of them have the issue where inserting the Lithium cell with inverted polarity will apply a negative voltage on A2. This will cause a current to flow through the following path ( Cell+ -> R8/R9 -> 0V -> (ATmega internal clamping diode) -> A2 -> R11 -> D4 -> Cell-. For, a very high current will flow out of A2 due to the missing R11; this will very likely damage the ATmega.
      Regarding VREF, I think that the best option would be to use the internal 1.1V reference voltage as it happens to be the most stable voltage source. Using the output of the 5V regulator U1 would be a sub-optimal solution, the output voltage of the regulator being subject to load regulation and may vary depending on the overall current consumption. This might be problematic as it will result in inaccurate voltage readings at the A0 and A1 pins.

  4. Karim,

    I’m curious as to why you kept all of your analog input voltages at 1.1v or less. Since you are using the ATMega 328P chip, you could connect the output of the 5-volt regulator to pin AREF, then issue the command analogReference(EXTERNAL) and your reference voltage would be 5 volts. Wouldn’t accuracy increase with a wider range to read from on the analog input pins?


    1. Hi Mike, the external AREF pin is not available on the Arduino Pro Mini. You could still access it by soldering a jumper wire to the corresponding pin on the ATmega 328, however I have preferred to keep int simple :-). (BTW, i have edited your comment as my name is Karim :-))

  5. Hi Karim,

    Hands down, this is the absolute best Arduino LiIon charger project on the Internet. Your write up is thorough – lacking nothing, yet never providing unnecessary information. My hats off to you for doing this.

    One minor issue; the schematic shows B+ on A0 and B- on A1, yet in your write-up, you have them flipped (B+ on A1 and B- on A0) – just a little mental conflict while reading and following along is all… not a big deal but everything is so neat and tidy, I thought you might like to know about it just in case.

    Thanks again,


    1. Hi Mike, many thanks for your positive feedback and for pointing-out the typos. I have fixed the text accordingly.

  6. Hello Karim,

    Well noted about the custom bootloader. Regarding the charging voltage I was referring to lifepo4 cell type, which is 4 x 3.65v = 14.6v and looking at digikey, I could get a 15v meanwell brand. do you think a 15v source for 4S lifepo4 is enough, or should still get a little higher?

    i’m very grateful for the knowledge you’re sharing.

    1. Hi Edel,
      you need to take into account the voltage drop across Q1 and D2. Depending on the current, this can exceed 0.7V. So 15V will be a bit on the edge. You can try to replace D2 with a Schottky diode, but I still believe that 16V would be a better choice.

  7. Hi again karim,

    thank you very much for your time. i have follow up questions and i hope you do not mind.
    1. for 4-cell pack, what would be the recommended power supply voltage? in general how many volts as headroom per cell? i initially thought of 15V but decided to ask you first;
    2. is the custom bootloader strictly necessary?

    again, thank you.

    1. Hi Edel,
      you are welcome. Answering your questions:
      1. 4 x 4.2V = 16.8V . Thus, i would go for at least 18V.
      2. The custom bootloader enables faster startup time and prevents the Arduino from getting stuck upon watchdog timer (WDT) expiry. The li-charger implementation makes use of a 1 second WDT, so the custom bootloader is recommended in order to prevent the Arduino from eventually getting stuck due to a WDT expiry.
      Alternatively, you can comment-out the following lines within li-charger.ino to disable the WDT:

      357: wdt_enable (WDTO_1S);
      378: wdt_reset ();

      You will however lose the WDT safety feature, which is meant to reset the Arduino in case the firmware gets stuck (never occurred to me for this project).

  8. Hello,

    Interesting project and thank you for sharing!

    i have a question. this design would also work for LifePo4 (LFP) by adjusting some variables in the “Configuration Parameters” specific to LFP? would also be safe charging large packs, like 100AH?

    any advice would be greatly appreciated.

    1. Hi Edel,
      thanks for your feedback.
      Of course you can use my design to charge a LifePo4 cell. Since LifePo4 chemistry has a different voltage range, you need to modify the following values in near line 56 :
      #define V_SURGE 3650000
      #define V_MAX 3600000
      #define V_TRICKLE_START 3500000
      #define V_TRICKLE_MAX 3550000

      Please change the values as shown above before compiling the firmware.
      For 100AH you are going to need a higher charging current. For this you need to reduce the values of shunt resistors R8 and R9 to 0.2 Ohm, and adapt the following configuration parameters (assuming 10 A charging current):
      ichrg 10000
      rshunt 100

      You also need to put a large heatsink on Mosfet Q1 and replace Diode D2 with a part that can handle at least 12 A (BY255 is rated 3 A only).
      Note that i’m giving you the values from the back of my head so you still need to test them.
      Regards, Karim

  9. Hi Karim
    it is a well-explained project, congrats on you
    however I don’t get very well few points if you answer them would be appreciated

    1) for one cell, what value of power supply we should use whether 6V or 19.5V- which is directly connected to the MOSFET ? let’s say we use the 6V in order to deliver 1.5A to the battery plus terminal how to arrange our duty cycle on MOSFET? as you already know Id = k(Vgs-Vt)’2 to get 1.5A the Vgs plays a important role which controlled by turning on and off via PWM mode.

    2) can you provide me more information on the PWM mode with using Arduino to control duty cycle that we desire to get 1.5A current from drain-to-source in MOSFET?

    3) in the article you have said that for one cell connect D1 directly to the Vcc on Arduino does it make sense to power our controller board with 19.5V and why?

    Thank you in advance

    1. Hi Ciya,
      thanks for your feedback. Answering your questions:

      1) As shown in the table above, 5V to 6V should be used for charging 1 cell. This applies to both Arduino and MOSFET power supplies (19.5 V on the schematic). In this case, a logic-level P-Channel MOSFET (IRLML2244) shall be used. This MOSFET is directly driven by the Arduino’s PWM output pin, so there is no need for transistor Q2. Due to the missing transistor Q2, the PWM signal must be inverted by modifying the analogWrite() function calls in the firmware code as explained above. Once the circuit is properly calibrated, the PWM will be automatically adjusted by the microcontroller to ensure a constant current of 1.5A. Note that 1.5A is the average current value, whereas the peak current value is equal to (5V – Vds – Vdiode) / (R8 + R9).

      2) The following article explains how to use the PWM pins on the Arduino: The microcontroller measures the voltage across the shunt resistors R8/R9 over the analog pin A1, from which it calculates the actual charging current. If the current is higher than 1.5A, the microcontroller will adjust the PWM duty cycle as to reduce the MOSFET on duration. If the current is lower than 1.5A, the microcontroller will increase the MOSFET on duration. This process is referred-to as a Closed Feedback Loop.

      3) As mentioned above, 5V (or up to 6V) is used for powering the circuit in single cell mode, so there is no need for the linear voltage regulator U1 as Arduino can be safely powered by this voltage range.

      Best Regards, Karim

  10. Hello Karim,
    Thanks for the wonderful project. Apart from changing the shunt (R8&R9) values to determine the desire charging current, kindly indicate other parameters to change to charge at the rate of 15A.

    1. Hi Raslan,
      thanks for your feedback. For 15A you need to replace Rshunt (R8 & R9) with a 60 Milliohm / 15 Watt resistor (or 2x 120 Milliohm / 10 Watt in parallel). Also, you will need to attach the Mosfet to a proper heatsink with a cooling fan. Please follow the calibration procedure as described in this article. Please closely watch the Mosfet temperature.
      Please set the relevant parameters as follows: ichrg=15000; rshunt=60
      Best regards, Karim

  11. Hi,
    This looks like a perfect starting point for my project. I want to buld a charging control module for 36 volts Li-ion batteries for electic bicycles. The module should stop charging the batteries when they are charged at 80%. The module shall fit in the battery pack and also contain the following functions and related HW:
    -Real time clock function
    – Event recorder for charging/discharging event
    – Estimation of capacity in Ah and percentage of capacity compared to new battery.
    -GPS Module to keep track of location
    – GSM-module with simcard to send and receive SMS
    – Temerature sensor
    – Blutooth module
    – Wifi module (EESP8266 12E or similar).

    The idea is to build this into existing battery pack to prolong battery lofe , keep track of ‘mileage’ and handle theft attempts.

    1. Hi Lennart, looks like an ambitious project you are working on. You may certainly reuse the battery charging logic from this project. By the way, for your convenience i have implemented the same as a separate library in LiCharger.h/LiCharger.cpp that you may download from my Raspberry Pi UPS project source code:
      Best Regards, Karim

  12. Hi there Karim,
    Thank-you for this project, it seems to be hard to find comprehensive information on charging lithium batteries and slightly modified version of this is perfect for my current project. Anyhow, would be okay to swap the by255 diode for a 1N5408? They’re much cheaper in my area. From what I understand it would work but I am new to electronics and don’t want to take risk where lithium is involved.

    1. Hi Isaac,
      you are welcome, I’m glad that you find my documentation useful. Sure you can use any diode and MOSFET combinations as long as the voltage and current ratings are in line with your application. In fact, I believe that the ones that I have actually used might not be the optimal ones. I would rather go for a faster switching diode and a MOSFET with a lower drain-source resistance RDS(on). Best regards, Karim.

  13. Hi! Congratulations for your great project!
    Any ideia how can I can use this as a auto-switch power source to a 4A led-strip?
    When connected to the power source it charges the battery (if necessary) and powers up the led-strip.
    When not connected to the power source it uses the battery power to the led-strip.

    Kind Regards

    1. Thanks Juliano. There are many ways to achieve what you would like to do. In any case you would need a separate Buck converter (or LED driver circuit) to power the LEDs from the battery. The easiest way would be to keep the LEDs connected to the battery at all times, also when on external power. You can draw a load from the battery to power the LEDs while simultaneously charging it using the above charging circuit. For this approach you would need to disable the charging time and capacity limits in the Arduino source code. Another way would be to connect the LED driver to both battery and external power via powerful Schottky diodes, whereas the external power will always have precedence due to its higher voltage. You can also use MOSFETs instead of diodes as described in Best regards, Karim

  14. Hiya Karim,

    Thanks for the updated 3.1.1 case sensitive fix for us linux users – you missed a lower case h in the Trace.cpp #include “helper.h” – once that is changed to a capital H it compiles correctly.

    Great Project

    1. Hi Marcus, thanks a lot for your feedback. I have uploaded 3.1.2 with the fix. Hopefully the last one. Cheers.

  15. Hi Karim,

    I am trying to build this very interesting project of yours.
    I need some help with the li-charger.ino.
    It will not compile as it cannot find CLI.h
    Where must these files be located.

    1. Hi Paul, thank you very much for your feedback. This was a file name capitalization mistake, whereas the correct file name is Cli.h and it is present on the project’s GitHub repository. It appears that you have case sensitive file system while i have a case insensitive one, that is why this error went unnoticed on my side. Please download the fix that i have provided in version 3.1.1:

    1. Hi Aslam, yes any ATmega328 based Arduino should work, including the Uno. Best regards, Karim.

  16. 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).

    2. 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.

    3. 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.

  17. 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.

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

    3. 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

  18. 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?

    2. 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.

    3. 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).

    4. 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.

Leave a Reply

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