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

85 thoughts on “Lithium-Ion Battery Charger”

  1. Thanks a lot. Got a lot of inspiration. However, I would like to make a correction. The cell balancer on these BMSs only work when one of the cell is about to overcharge. It doesn’t care whether the charge is distributed equally or not

    1. Hi Sheikh Mishar,
      you are welcome. As far as I know, the passive cell balancer ensures that all of the cells have equal voltage. It connects a small resistive load across higher voltage cells. This results in additional resistive losses that reduce the overall charging efficiency. This however doesn’t really matter since the current design already dissipates excess energy as thermal loss within the shunt resistors and MOSFET. Thus, it acts similar to a linear voltage regulator. Best regards, Karim

  2. Hi again Karim,
    I was wondering how feasible it is to convert the circuit for LiFePo4 batteries charging?
    Best regards,

    1. Hi Charis,
      Glad to hear from you. Of course it is possible to adapt the charger for LiFePo4. For this you have to set the following values within li-charger.ino:
      V_SURGE 3650000
      V_MAX 3600000
      V_MIN 2200000
      V_SAFE 2500000
      V_TRICKLE_START 3500000
      V_TRICKLE_MAX 3550000
      Best regards, Karim

  3. Your loader is phantastic! I have designed a PCB and have it made through a chinese PCB service (paid less than 5€ for 5 pieces). Soldered, put an arduino nano clone on it, calibrated and works like a charm. If anybody is interessted in the PCB I’ll provide all files for free. Just write me a mail to arnold@ude(hyphen)consult(dot)de

    1. Hi Arnold,
      glad to hear from you and thanks a lot for your contribution. If you don’t mind we could upload your PCB design files to Github and share a download link on this page. Please let me know. Best regards, Karim

    2. Hi Karim, I designed the PCB with an additional 12 pin connector so that the I2C bus and some I/Os for additional user feedback can be connected to it. I have put together a working prototype of an expanded software displaying some globals vars on a optional 4-line LED display like state, C, I and V as well as the time. I will provide everything as soon as my software patch is stable. At the moment I struggle with the stability of the system. I provided a static buffer of one line (20 chars) and tried to let sprintf format the display messages into it. This works perfectly up to the moment that the charger detects “I_FULL”. The next update of the LCD display (and using my sprintfs) freezes the system. The LED on D13 is flashing erronously and I’m not able to reset the system by the reset button ! Never seen this. Probably something with the watchdog? I have to cut of the power to the Arduino to be able to reset the system. I have tracked it down to the sprintf command but I’m sure my buffer is large enough…

      Let me walk this through and then I’ll provide everything…

    3. Hi Arnold,
      sounds like an interesting challenge you have. Please consider forking the Github repository in order to apply your code adaptations. Best regards, Karim

  4. Hi again Karim,
    I just finished the project and works very well, I was wondering if there is memory and resources to add an oled or other display that will show voltage, current and maybe the passed time in near real time. Wouldn’t that be useful?

    1. Hi Charis,
      thanks for your feedback. I believe that there should be enough memory and I/O pins left to drive an OLED display. Please feel free to clone the source code in GitHub and perform the corresponding updates. I would be happy to see the final result. Best regards, Karim.

  5. Hi Karim,
    This is interesting project, I have build the PCB, but I found some problem here. while I calibration process I follow every instruction you provide, but when I calibrating V2 it always said ‘out of range’, I connecting 750mv of my bench psu while measured with multimeter connecting to P- (psu-) and B- (psu+) right? than voltage will drop significantly because there is connected to current resistor, psu trying to stabilize and my multimeter read ~400mv, then I put ‘cal V2 432’ (432mv read by multimeter), then on cli show ‘out of range’. But when I put 750mv at A1 pin directly there is no ‘out of range’ message.
    When I not connected B+ to psu, sometimes voltage read V = 4294964mv, but sometimes just 0mv, then I connected B+ to psu with voltage 13,65V, at the voltage status it read almost correct, but it seems decraesing 0 at the end, it just read 1363mv, state cannot change to charging while I connecting battery (V read while battery connected 1202mv), maybe because v_max = 12600mv and Vmin = 2500mv/cell or 7500mv/3cell.
    What I’m doing wrong? here is the status:
    state = Calibration
    T = 00:00:00
    C = 0mAh
    V = 1363mV
    I = 4mA
    T_max = 0min
    C_max = 0mAh
    V_max = 12600mV
    I_max = 0mA
    PWM = 0
    V1 = 1365mV
    V2 = 2mV
    V1_raw = 255
    V2_raw = 2

    Best regards,

    1. Hi Dani,
      thanks for your feedback. The valid range for the V2 calibration constant V2_cal is 800..1200, where V2_cal = [calibration voltage]*1000/V2_Raw (see li-charger.ino line 202). If you connect your PSU betwen B- and 0V across the shunt resistors (R8 and R9), the resulting current will be 1.5 A at 750 mV. It seems that your PSU is not able to deliver 1.5 A, so I recommend temporarily disconnecting R8 and R9 and performing the V2 calibration without the shunt resistors in order to avoid the voltage drop.
      Further, it seems that your ADC reading for V2 is not correct. The value of the ADC reading V2_raw must be roughly equal to the V2 calibration voltage in mV. This may have a few reasons, such as incorrect ADC reference voltage (should be internal 1.1 V source). Another reason may be an incorrect circuit implementation of the A1 input circuitry (R5, C5). The incorrect reference voltage can be ruled out unless you are using a modified firmware. The A1 input voltage can be checked by measuring the V1 voltage of 750 mV directly at the A1 pin (bypassing R5), in this case, V2_raw must be close to 750.
      Similar troubleshooting can be applied to V1 calibration, where the A0 input voltage is equal to [calibration voltage]*R7/(R4+R7). I hope this helps.
      Best regards, Karim

    2. ok, I found the problem, it’s seems I wrong at voltage divider resistor for input, maybe it just to far from it should, so the arduino A0 just get ~230mv at full 12600mv voltage, after I change divider resistor for V1 arduino get ~800mv and it works for calibration process. and for V2 cal the 750mv I feed it directly to pin A1, and addjust rshunt constant to get as close as possible to current flowing. Now it can charging with current that I sett. But I felt strange about the voltage that appeared during calibration and when charging, during calibration with a voltage of 12600mv the voltage that appeared was almost the same as the measurement on the multimeter, but when charging with the battery voltage around 12100mv (on the multimeter) the voltage read by the system only around 10000mv
      Best regards

    3. Hi Dani, glad to hear that it worked. You might be looking at the battery voltage V as shown by the “status” command, whereas V = V1 – V2. On the other hand, the voltage measured by the multimeter might be V1 relative to 0V. Once the current flows, a significant voltage drop appears across the shunt resistor, which is used for calculating the current value. This voltage drop might be causing the voltage reading discrepancy you have observed. Please try to connect the multimeter right across the battery terminals between V1 and V2. Best regards, Karim

  6. in the security section, at last paragraph
    “Overvoltage is detected whenever the battery pack voltage momentarily exceeds V_{surge} = \SI{2.25}{V/cell}. Exceeding this value would raise an “overvolt error”. ”
    you probably mean 4.25V/Cell?

    1. Hi Charis,
      thanks for the comment. Yes, you are right, this was a typo, it should be 4.25V/Cell. I have just fixed it. Best Regards, Karim

  7. Hi Karim
    thanks for fast reply.
    All working (happy days) thanks for a great project and all your hard work.
    21700 batteries sitting at 16720 9o% cap on charge completion
    Regards John

  8. Hi Karim
    bit confused re
    “Improved calibration procedure: the exact reference voltage value is now passed as an argument, so there is no more need to use a precision power supply”
    do i still need to use cal V1 and v2 and what values do need to pass if any ?.
    setup is 4s 5ooomAh
    Regards John

    1. Hi John,
      you still need to follow the calibration procedure as described in Whereas this calibration procedure has changed over time. In the initial firmware versions, the cal command did not take any voltage value as an argument, so one had to set the power supply to an exact predefined voltage. In later versions, the measured calibration voltage needs to be passed to the cal command. Hence, V1 need not to be exactly 750 mV and an approximate value such as 713 mV would be good enough. Consequently, the measured voltage value needs to be passed to the cal command such as “cal v1 713”. This will enable the firmware to derive the voltage calibration coefficient and store it within EEPROM. I hope this answers your question.
      Best Regards, Karim

  9. Hello Karim,
    thank you very much for your very quick reply.
    Just an additional understanding question: What I meant asking about measuring B- is that P- does not necessarily equal B-. But, if I am right you assumed that the difference between P- and B- is negligible, am I right?
    Best regards

    1. Hi Tomasz,
      you are right, when the BMS is active, P- and B- are connected via a MOSFET pair and are at the same potential. When the BMS disconnects, it creates an open circuit between B- and P-. In this case, the charger will see an open circuit and go into an error state.

  10. Dear Karim,
    thank you very much for the publishing and extraordinary good documentation of your very interesting and really useful project.
    I have decided to build your charger myself, perhaps in a slightly modified form.
    I’m more of a beginner with a bit of electronics/linon battery experience, and as such I have a few questions about the details of your project:
    According to your circuit diagram and also the description, it seems to me that your charger uses the B+/B- terminals for charging the batteries, at the same time the charger measures the voltages of the same B+/B- terminals (?).
    My question is whether this is really the case, or whether you do not mean B+/B-, but rather the P+/P- terminals of the BMS intended for loading/unloading?
    Also, looking more closely at your photos, it seems to me that the charger is actually connected to the B+(=P+)/P- terminals, and these are the only connections connecting it to the battery pack – if so, how is the voltage measured at B- under these circumstances?
    Best regards

    1. Hi Tomasz,
      thanks for your feedback. You are right, B+/B- must be connected to P+/P- of the BMS. This is required for the BMS to protect the battery from accidental overcharging. The voltage at B- (P- / Arduino A1) is proportional to the current flowing through the battery. It is used for calculating the charging current. If the BMS disconnects the battery, which it does by disconnecting its negative side, the current flowing through the battery will be 0. Thus, the voltage at B- will show 0 as well. I hope this helps.
      Regards, Karim.

  11. Hi and thank you for doing this charger, I was really excited as it is just what I need, but now I find I cannot use it 🙁 I am not as clever as you, I find that I get unknown exit status and rialise I have not got the libraries, then I find you use something called “GIT submodules”? I have no idea what they are I am not clever enough to use git, couldn’t you just supply them as libraries so we can all enjoy it please?
    Kind regards. Bob

    1. Hi Bob,
      thank you for your interest in my project. You may simply download a .zip file with the latest firmware release from:
      Here is the direct link to the latest release:
      It contains the main Arduino (.ino) file, as well as all of the required submodules. Simply unzip it and double click on the .ino file, then it will open in the Arduino IDE and you can compile it and upload it form there. I strongly recommend that you get familiar with the basics of Arduino IDE by visiting
      Best Regards, Karim

  12. Hi Karim,
    Your new battery pack’s voltage is 16.8v. My understanding is that you have to charge your pack for this voltage (16.8v), but you are using a 19.5v supply. How does the two fit together. How do you check for 16.8v when your battery is in CV mode. Won’t the 19.5v have an impact towards that. Hope you understand my question.

    1. Hi Stefan,
      the power supply voltage must be higher than the maximum battery voltage. The charger regulates the voltage via pulse width modulation (PWM), so the voltage seen by the battery is the resulting average voltage. During the MOSFET on phase, the full power supply voltage is connected to the battery for a very brief period of less than 32 microseconds (given the PWM frequency of 31.25 kHz). Whereas the power supply voltage is divided between the battery, MOSFET, diode, the battery’s internal resistance and the shunt resistor. Thus, the power resulting for the excess voltage is actually dissipated as heat within the resistive components in series. This makes for a simple, but not very efficient design.

      The power efficiency is calculated as follows:

      Efficiency = (power fed into the battery) / (power consumed by the charger)
      = (V * I) / (V_supply * I) * 100 = V / V_supply * 100

      V is the battery voltage between B+ and B- = V1 – V2
      V_supply is the power supply voltage.
      I is the current flowing through the battery.

      For increased efficiency, commercial battery chargers usually implement some sort of a buck or boost power converter.

      Best Regards, Karim

  13. Hi,

    I am planning to use your circuit to design a solar powered charger for my 10S E-bike battery, which has a peak voltage of 42 V and working voltage of 36 V for the whole battery pack. I wanted to ask a few things to see that I have interpreted the circuit correctly before I try to build the circuit on a breadboard.

    1. The values for R4/R7 needs to be adjusted in order to keep the voltage at A0 at slightly bellow 1.1 V, correct? In my case, this would mean that R4≈400 kΩ if the voltage drop across D2 is 0.7 V and VCC is 42.5 V, to set the reference at A0 to ~1 V.

    2. Does the value of R2 also need to be adjusted? I am aware that I will need to choose a different MOS-transistor in order to handle the higher voltage.

    3. Are there any other hardware adjustments that you would recommend for charging 10 cells?

    4. Are there any risks that you know of when using a switched power supply which may have different amounts of available current? I am planning to use a switched power supply with a solar panel, which will stabilise the voltage. The current however, will of course vary. At peak power the panel will output ~100 W which I think should be enough. What I really want to know, is if the charger will adapt the charging rate/current depending on how much current is supplied through VCC or if this may cause trouble.

    Thanks in advance and thank you for making this project publicly available!
    If you have any other tips or thoughts on my solar-power charger project, please let me know.

    1. Hi Mattias,
      thanks for the message. Following are my answers:

      1. That is correct, you want to make sure that the voltage at A0 is around 1 V at maximum battery voltage. However, it should never reach the 1.1 V threshold.

      2. The purpose of the R1/R2 voltage divider is to reduce the Vgs gate-source voltage to an acceptable value. It depends on your MOSFET’s maximum allowed Vgs.

      3. Beware that the 7805 voltage regulator U1 has a maximum input voltage of about 30-40 Volts. Hence, it is likely to fail at a higher VCC. With this regard, I recommend replacing it with a buck converter module such as those found on eBay under “buck converter 48v 5v”.

      4. I don’t see any major issue. The charger’s CC-CV regulation loop will try to achieve the preset constant current value by increasing the MOSFET’s duty cycle. If the CC current value cannot be reached, it will turn on the MOSFET continuously and charge at the solar controller’s maximum current. Once the CV voltage value has been reached, the charger will gradually reduce the MOSFET’s duty cycle until the current drops below the battery full threshold.

      Best regards, Karim

  14. This is awesome as I too have an abundance on the lg he4 cells (I bought 2500 of them for less than $1/ea from a reliable source and verified that they are genuine). If you would be so kind as to let me know what I would need to change in order to implement your circuit to charge a 16s2p pack I would be forever grateful.

  15. Hej Karim,

    I am glad that I found your project, because it’s *not* using a self contained BMS chip like Frank suggested some posts earlier, but in essences a bunch of lines of code and two simple components (i.e. FET, shunt) to make an Arduino based battery charger. Simplicity and tangibility are valid approaches to improve a system’s security! Anyway, this looks like a perfect baseline to start from building my ship’s multi-battery, multi-power-source, priority-driven charger and electrical system monitor 🙂

    Even though the project’s documentation is extremly good, on the website and in the code, there’s one detail I don’t get, and that’s why to “Run the regulation routine at the preset interval”:

    439 : if (G.adcTick – updateTs > G.tUpdate) {
    440 : updateTs = G.adcTick;
    441 : // Regulate voltage and current with the CC-CV algorithm
    : …
    452 : }

    First of all I believe that variable names and/or usage are a bit confusing here: G.tUpdate is commented as “…interval in ms”, and updateTs also suggests something in ms (like all the other *Ts variables), but apparently both are not, rather than being “number of ADC conversions” like G.adcTick, DELAY_UPDATE_UP and DELAY_UPDATE_DN … otherwise we’d be comparing apples and oranges here.

    Beyond that I don’t understand what sense it makes to use every 20th ADC conversion (434 : G.tUpdate = (uint32_t)DELAY_UPDATE_UP; ) instead of every one all the time, like in cases when “to mitigate voltage or current surge conditions” ( 431: G.tUpdate = (uint32_t)DELAY_UPDATE_DN; )

    Please be so kind (as always) to comment on these issues and share with us what you had in mind here.

    I am planning to replace the Arduino’s built-in ADC system for measuring v and i in my project with two INA3221 communicaing over I2C because I have a couple more values to sample. Therefore I’d like to understand the problem you solved just to understand if I am going to face the same or a similar problem.

    Btw. there’s a value-typo in the sentence on this page “Overvoltage is detected whenever the battery pack voltage momentarily exceeds Vsurge = 2.25 V/cell.”

    Thanks a lot for your patience!

    Best regards,

  16. Dear Karim,

    Thank you so much to have published your Li-Charger. I own an e-bike, and want to charge it’s 36V battery in the car with the cigarette lighter socket. It seems to be impossible to find a 12V Charger to charge 36V e-bike batteries on the market. The cigarette lighter socket is also limited to 10A.
    So I changed some components in your diagram to power your charger with a 12V to 42V DC-DC converter and charge a 10S battery with the limited 10A 12 V car socket.
    Apart of the hardware and after having succeed to compile your li-charger.ino and sent it to an Arduino Nano, the message received from the nano is like this :

    + + + L I C H A R G E R + + +

    V 3.1.3

    . : System status
    r : EEPROM status
    t : Dump trace
    ncells : Set N_cells
    cfull : Set C_full (arg: )
    ichrg : Set I_chrg (arg: )
    ifull : Set I_full (arg: )
    lut : Set LUT (arg: )
    rshunt : Set R_shunt (arg: )
    cal : Calibrate (arg: [mV])
    h : Help

    CRC = ff281da0


    Do you have an idea of from where comes the CRC error? Could it be something wrong with the bootloader I couldn’t burn correctly? Could it be because I used a Nano in place of a arduino Pro Mini?
    In addition how could I make the modification in your program to charge a 10S or more battery?

    Thank you so much in advance, best regards


    1. Salut Thierry,
      glad to hear that you have made it so far. CRC error is normal upon initial startup, CRC refers to the EEPROM integrity check. The EEPROM contains some garbage data (zeros) and needs to be initialized as described in the “INITIAL CONFIGURATION” section. The CRC error will disappear once you set all the parameters through the serial terminal. For safety reasons, the device will not charge if the EEPROM contents have been corrupted. Please feel free to follow-up via email (address found within the source code comments).
      Best Regards, Karim

  17. Dear Mr. Hraibi,
    Thank you for this useful project. I want to set up this circuit to be used for both 2S and 3S cells. But I have a few questions. According to the 15v upper limit for 2S and 14v lower limit for 3s in the power supply circuit, it will be appropriate to feed the circuit with 15v. The resistors R2 and R4 will be adjusted by a switch. But there is a difficulty as the “ncells” variable that changes with calibration. Could the ncells variable be set according to whether a port of the arduino gets low or high logic level? If this happens, I think it will be possible to use the circuit for 2s and 3s.

    1. Dear Recep,
      Thank you for your feedback. You can still charge the 2s battery using a 3s setup, as long as you set ncells=2 in the firmware configuration. No need to dynamically adjust R2 and R4, just set them to the values for 3s and calibrate the firmware accordingly. The only disadvantages for 2s would be a lower ADC precision and higher resistive losses during charging. Indeed you can implement an easy way to quickly switch ncells between 2 and 3 cells, without having to connect over the serial terminal, by attaching a switch to one of the digital input pins and use it for selecting the number of cells. Please feel free to adapt the firmware accordingly and consider it to be a small exercise in Arduino programming. Please make sure that each of your battery packs is equipped with its own BMS!
      Best regards, Karim

  18. Hi Karim
    This is an excellent and interesting software project as an exercise in controlling and logging a Li-ion charger. However I have major concerns about safety in this design. You mention safety concerns and you caution against abusing Li-ion batteries and I want to extend that theme to the whole approach of this design.
    Top of the list, I would simply not entrust the charging algorithm for Li-ion batteries to software. There are so many hardware solutions and devices out there that would do a far better and more reliable job of charging than doing it in software. Many devices will allow you to connect to them through hardware interfaces and those could be used in software to calculate any number of parameters. Of course reading variables such as voltages and currents is always available. Software should only be used for monitoring, reporting, communicating and as another safety net by taking precautionary actions when critical parameters are outside limits.
    In the hardware as it stands, my other safety concern is the failure modes of the PWM output stage. For example if Q2 fails ON due to hardware or software malfunction then the whole supply voltage is applied to the batteries and to the load. If the BMS is doing its job then the batteries may survive but the load may not. If the BMS hiccups then thats it for the batteries.
    Bottom line, I would not be comfortable running this charger unattended for any length of time.

    1. Hi Frank,

      many thanks for your detailed feedback. Of course as the disclaimer says, everyone shall implement this project at his/her own risk. Nevertheless, here is my feedback to the points you have mentioned:
      – There are definitely good off the shelf SOCs, specifically designed for the task of charging a Lithium-Ion battery, however implementing the charging algorithm in SW may have the following benefits:
      1. Cost: If your project already features an microcontroller (such as, you may spare yourself the additional PCB area and cost required for a dedicated charger IC.
      2. Flexibility: With a SW implementation enables full control over the voltage thresholds (e.g. in the pi-ups project, I prefer to charge the battery up to only 4.00 V). You also have the ability to implement features such as advanced logging.
      3. Last but not least, educational value: Implementing a Lithium-Ion charging algorithm on a microcontroller as a perfect topic for a university project.
      – If we are to fully trust a dedicated charger IC, why shouldn’t we trust the BMS IC, whose very purpose is to protect the battery from over- and under-voltage conditions? Of course the redundancy of simultaneous use of two commercial devices for both charger and BMS would be the optimal solution.
      – Assuming that all software is due to malfunction is a very generalized statement. Many mission-critical applications such as car and airplane systems fully rely on software. The SW development process in such applications must comply to very strict coding standards such as the MISRA in automotive.

      Best regards,

Leave a Reply

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