Charge controller for 4S4P Li-ion batteryWichit Sirichote, wichit.sirichote@gmail.com
Build your own charge controller for Li-ion battery. Available now DIY kit for Charge Controller
This charge controller has been designed for charging the Li-ion battery pack, 4S4P made with 18650 cell. The controller is MEGA328 based MCU. The firmware was developed with Arduino IDE. The power source is 24Voc, PV panel. Analog display shows battery charging voltage. Optional load is 14V LED street lamp.
Figure 1: Charge controller
Figure 2: Sample 4S4P, 18650 Li-ion battery pack The PWM charge controller connects PV panel to the battery directly. On stage 1, the charging current will depend on the insolation. PWM will be used for stage 2 voltage regulation.
Figure 3: Basic circuit of PWM charge controller The MCU detects PV voltage, turns on Q1, connects the PV to Li-ion battery. PWM signal varies from 0-100% for current regulation. On stage 1, it will be 100%. On stage 2 it will be 0-100% to retain constant voltage. Q2 is optional for controlling LED street lamp. All control functions are running by MCU firmware.
Figure 4: Block diagram.
Hardware
The MCU is MEGA328, 32-pin TQFP flash microcontroller. The oscillator is 16MHz. ADC channel 0 detects battery voltage, ADC channel 1 detects PV voltage. PWM signal controls Q1, to turn on P-channel FET, IRF4905. The optional D12 is for controlling the LED street lamp. U1 provides +5V for MCU chip.The analog display with D2-D7 is for battery charging voltage indicator. LED D9 is for tick signal.
Figure 5: Schematic (click to enlarge). Firmware
The firmware was c++ code, developed by Arduino IDE. Every 2s, the MCU reads PV and battery voltages. PI control is for voltage regulation for stage 2.
Figure 6: state diagram
The source code is shown below.
// Solar Charger for Li-ion battery 4s +16.8V // created Mar, 2018 // by Wichit Sirichote // // 14 May 2018 add AUX lamp and switching code from head lamp to aux lamp // 18 May 2018 add serial command to reset sample number to zero#include <TimerOne.h> #include <avr/wdt.h>#define ledPin 13 #define load 7 #define TEST 2 // TEST KEY #define LED7 4#define LED8 5#define LED1 8 #define LED2 9 #define LED3 10 #define LED4 11 #define LED5 12#define PWM 6 #define AUX 3 // aux lamp for failed safe lightingunsigned int n,p,I,t, vbatt, pv; unsigned int temp;char command; char f0=0; long sample=0; char load_status=0; char state=0; // charge state // 0 no charge // 1 bulk charge // 2 saturation charge // 3 fully chargedchar night_flag=0, fail_flag=0, aux_flag=0;unsigned char Pout=50;int error; int PB=20; // test with 20% proportional band float pTerm, Kp; int j; int duty_cycle; unsigned int saturation_time=0;void bulk_charging() { if((pv > 170) && (state==0)) { state = 1; // digitalWrite(charge,1); // connect pv to battery duty_cycle=255; analogWrite(PWM,255); digitalWrite(LED7,1); // connect pv to battery fail_flag =0; // clear fail_flag} }void state_change() { if((vbatt >=168) && (state==1) ) { state = 2; // enter to saturation charge //digitalWrite(charge,0); // disconnect pv to battery // digitalWrite(LED7,0); // disconnect pv to battery // digitalWrite(LED8, 1); // indicates fully charged}}// detect charging current insted // regulate at 168 Vvoid saturation_charge() {if(state==2) {error = 168 - vbatt; Kp=100/PB; pTerm=Kp*error; j=(int) pTerm;if(j<0) j=0; if(j>100) j=100;if(j>=0 && j <=100) { duty_cycle=j; // for status display j= (j*255)/100; // convert to 0-255 for 0-100% analogWrite(PWM,j); }if( saturation_time++ > 3000) // test 30 ticks or one minute actual is 3000 ticks or 1.6Hrs { saturation_time=0; state=3; // digitalWrite(charge,0); // disconnect pv to battery terminate charging // digitalWrite(LED7,0); // disconnect pv to battery analogWrite(PWM,0); // disconnect PV digitalWrite(LED8, 1); // indicates fully charged }} }void enter_night_mode() { if(night_flag==0 && fail_flag==0) {if(pv <90) { digitalWrite(load,1); // turn load on digitalWrite(AUX,0); // turn aux load off night_flag=1; state =0; load_status=1; digitalWrite(LED8, 0); // turn off fully charged LED// digitalWrite(charge,0); // disconnect pv to battery // analogWrite(PWM,0); digitalWrite(LED7,0); // disconnect pv to battery}} }void morning_off() { if(pv>100 && night_flag==1) { night_flag=0; load_status=0; digitalWrite(load,0); // turn load off digitalWrite(AUX,0); // turn aux lamp off }}// low voltage disconnect is 2.8V/cellvoid low_voltage_disconnect() {// if(vbatt<112 && night_flag==1) if(vbatt<120 && night_flag==1) //12.0-11.2 { digitalWrite(load,0); // turn load off load_status=0; fail_flag=1; night_flag=0; // turn off meter to save power digitalWrite(LED1, 0); digitalWrite(LED2, 0); digitalWrite(LED3, 0); digitalWrite(LED4, 0); digitalWrite(LED5, 0);}}//????????????????????????????????????????????void very_low_voltage_disconnect() { if(vbatt<112 && aux_flag==1) { night_flag=0; fail_flag=0; aux_flag=0; digitalWrite(AUX,0); // turn aux lamp off} }// X Y //165 6 //155 5 //145 4 //135 3 //125 2 //115 1 // Y = 0.1X - 10.5 float g; int x;void batt_volt_meter() { // display volt only if fail_flag == 0if(fail_flag==0) {g = vbatt; g = g/10 - 10.5; x = (int) g; if (x<1) {// digitalWrite(LED8, 0); digitalWrite(LED1, 0); digitalWrite(LED2, 0); digitalWrite(LED3, 0); digitalWrite(LED4, 0); digitalWrite(LED5, 0); }if (x== 1) { // digitalWrite(LED8, 0); digitalWrite(LED1, 0); digitalWrite(LED2, 0); digitalWrite(LED3, 0); digitalWrite(LED4, 0); digitalWrite(LED5, 1); } if(x==2) {// digitalWrite(LED8, 0); digitalWrite(LED1, 0); digitalWrite(LED2, 0); digitalWrite(LED3, 0); digitalWrite(LED4, 1); digitalWrite(LED5, 1); } if(x==3) { // digitalWrite(LED8, 0); digitalWrite(LED1, 0); digitalWrite(LED2, 0); digitalWrite(LED3, 1); digitalWrite(LED4, 1); digitalWrite(LED5, 1); } if(x==4) { // digitalWrite(LED8, 0); digitalWrite(LED1, 0); digitalWrite(LED2, 1); digitalWrite(LED3, 1); digitalWrite(LED4, 1); digitalWrite(LED5, 1); } if(x==5) { // digitalWrite(LED8, 0); digitalWrite(LED1, 1); digitalWrite(LED2, 1); digitalWrite(LED3, 1); digitalWrite(LED4, 1); digitalWrite(LED5, 1); } if(x>5) { // digitalWrite(LED8, 0); digitalWrite(LED1, 1); digitalWrite(LED2, 1); digitalWrite(LED3, 1); digitalWrite(LED4, 1); digitalWrite(LED5, 1); }}}// enter interrupt service every 2svoid isrCallback() // callback function when interrupt is asserted { sample++; // disconnect PV before read Vbatt // digitalWrite(charge,0); analogWrite(PWM,0); delay(10); n = analogRead(A0); temp = n; vbatt= (n*168)/305; // battery voltage x0.1 // vbatt =n; // connect PC before read PV in bulk charging or saturation charge if(state==1) { // digitalWrite(charge,1); analogWrite(PWM,255); delay(10); }p = analogRead(A1); // pv voltage x0.1 pv= (p*168)/305; // pv = p; t = analogRead(A2); // lm35 temperature sensor I = analogRead(A3); // hall sensorbulk_charging(); state_change(); saturation_charge(); enter_night_mode(); morning_off(); low_voltage_disconnect(); // turn head lamp off. aux lamp on // very_low_voltage_disconnect(); // turn aux lamp offbatt_volt_meter();if(digitalRead(TEST)==0) { load_status^=1; digitalWrite(load, load_status); // manually turn on/off for both lamps digitalWrite(AUX,load_status);}if(f0) {Serial.print(sample); Serial.write(","); Serial.print(vbatt); Serial.write(","); // Serial.print(temp); // Serial.write(","); Serial.print(pv); Serial.write(","); Serial.print(t); Serial.write(","); Serial.print(I); // enable print data on serial Serial.write(","); Serial.print(load_status,DEC); Serial.write(","); Serial.print(state,DEC); // charging state Serial.write(","); Serial.print(duty_cycle,DEC); // duty cycle Serial.print("\r\n");}digitalWrite(ledPin, HIGH); delay(10); digitalWrite(ledPin, LOW);if(state==2) { digitalWrite(LED7,1); // saturation mode delay(20); digitalWrite(LED7,0);} }void wdtSetup() { cli(); MCUSR = 0; // WDTCSR |= B00101000; WDTCSR = B00101000; // 4 seconds timeout sei(); }void setup() { // put your setup code here, to run once: Serial.begin(9600); // prints title with ending line break Serial.println("Li-ion Solar Charger v1.0");pinMode(load, OUTPUT); // load connect pin digitalWrite(load, 0); // power on disconnect first pinMode(AUX, OUTPUT); digitalWrite(AUX, 0); // power on disconnect first pinMode(TEST, INPUT_PULLUP); // test button for manually turn on/off loadpinMode(13, OUTPUT); // tick LED pinMode(12, OUTPUT); pinMode(11, OUTPUT); pinMode(10, OUTPUT); pinMode(9, OUTPUT); pinMode(8, OUTPUT); pinMode(4, OUTPUT); pinMode(5, OUTPUT); pinMode(PWM, OUTPUT);// analogWrite(PWM, (255*Pout)/100); Timer1.initialize(2000000); // initialize timer1, and set a 2 second period Timer1.attachInterrupt(isrCallback);}void loop() { while (Serial.available() > 0) {command= Serial.read(); switch(command) {case 0x0d : Serial.println("Li-ion Solar Charger v1.0"); break; case 'b' : Serial.println(vbatt); break; case 'l' : Serial.println("light ON"); load_status=1; digitalWrite(load, load_status); break; case 'o' : Serial.println("light OFF"); load_status=0;digitalWrite(load, load_status); break; case 'p' : Serial.println(analogRead(A3)); break; case ' ' : f0^=1; break; // case 'c' : Serial.println("test charge"); digitalWrite(charge,1); digitalWrite(LED7,1); break; case 'r' : sample = 0; break; default : Serial.write("What?");} } }Figure 7: The source code. Testing result was charging profile, by recording the PV, battery and LOAD voltage.
Figure 8: Charging profile.
Figure 9: Prototype testing with 20W LED street lamp.
PARTS LIST
SemiconductorsD1 1N4742, +12V zener diode
D2,D3,D4,D5,D6,D7,D8,D9 LED
D10 1N5227
D12,D11 IRF4905/TO, P-channel FET
D13 MBR1645
D14,D16,D17 P6KE36CA
D15 LOAD LED
IC1 ATmega328TQ32, Atmel Flash MCU
J1 CON6AP
J4 CON6
Q1,Q2 BC337, NPN transistor
U1 NCP1117DT50RKG, +5V voltage regulator
Resistors (all resistors are 1/4W +/-5%)
R1 100/1W
R2,R4,R6,R7,R8 10k
R5,R10,R11,R12 1k
R9 4k7
R13,R14 680Capacitors
C1,C3,C4,C5 100nF
C2 10uF
C6 22uF
C7,C8 20pF
Additional parts
SW1,SW2 SW TACT-SPST/SM
Y1 16MHzMore information please contact wichit.sirichote@gmail.com
Download Schematic, Firmware HEX file and Source code, presentation file , Technical documentation
20 July 2019
4S Li-ion Charge Controller Kit
Wichit Sirichote, wichit.sirichote@gmail.com
DIY kit for learning how to charge Li-ion battery with solar panel
![]()
Complete assembled Charge Controller
![]()
DIY kit Charge Controller for 4S Li-ion battery. The MCU is presolderd and preloaded firmware ready for testing.
![]()
Using USBasp for firmware programming. The source code is compatible with Arduino IDE.
Sample of 4S2P Li-ion battery made from 8-cell 18650 Panasonic. The battery has XT60 power connector.
More information please contact Wichit Sirichote, wichit.sirichote@gmail.com
Download: Source code, Hardware schematic, Assembly Manual, Experiment Manual, Technical paper
14 September, 2020