Arduino-Controlled Lithium Battery Charger

 

Lithium Battery Charger - Software

The opening screen when first applying DC power.
Press & release the Mode button to display the 'Set Volts' screen. Use the Set button to select 3.6v or 3.7v
Press & release the Mode button again to display the 'Set Max mA' screen. Use the Set button to cycle through 0mA, 250mA, 500mA, 750mA and 1000mA.
Press & release the Mode button again to start charging. This example shows a partially-charged battery so the current is less than the set mA.
The charging will normally end when the current has reduced to 10% of the 'Set Max mA' figure but the charging can be terminated early by pressing and holding the Mode button . Press the Mode button again to return to the 'Set Volts' screen.

Programming the ATmega328

The PCB for the charger is designed to use an external RS232-TTL programming lead. There's no RESET button on the PCB so the programming lead should have the 'auto-reset' function.

There are several Arduino libraries available for the Nokia 5110 display and for the INA219 current sensor. The ones I've used in the Sketch below are both from Adafruit.

Adafruit Nokia LCD libraries

Adafruit INA219 library

Copy the following program and paste it into the Arduino IDE and hit the Upload button on the IDE.

// Lithium Charger based on LT1510 (c) vwlowen.co.uk

// Last updated: 23 Sept 2013

#include <Adafruit_GFX.h>
#include <Adafruit_PCD8544.h>

#include <Wire.h>
#include <Adafruit_INA219.h>

#include <EEPROM.h>

// Tweakable constants and values

const float polyFuseOhms = 0.29;             // Used to calculate volt drop across polyfuse...
                                             // ...Tweak value here to get correct batt volts display
// const int fanThreshold = 500;             // Current (mA) at which fan starts running in simpler  
                                             // fan control scheme.
                                             
const float endChargePerCent = 0.1;          // Percentage of max charge at which to end charge.

const int LED_ON = LOW;                      // LCD Backlight. LOW is LED ON with my Nokia LCD

//Define the pins you are connecting the LCD to. 
//Adafruit_PCD8544(SCLK, DIN/MOSI, D/C, SCE/CS, RST); 
Adafruit_PCD8544 lcd = Adafruit_PCD8544(9, 10, 11, 13, 12); 

Adafruit_INA219 ina219;                      // Create instance for INA219 sensor library

float this_mA = 0;                           // Variables for INA219 readings
float busVoltage = 0;
float shuntVoltage = 0;
float loadVoltage = 0;

float targetVolts = 4.1;                     // Initial taget volts setting for LT1510 IC.
float displayVolts = 3.6;
float previous_mA = 0;

unsigned long  startMillisec;                 // Variables for charge timer.
unsigned long  endMillisec; 
int hours, mins, secs;

int contrast = 45;                            // Variables for contrast adjustment
int contrastDirection = 1;

// define button inputs
int mode_Button = 5;                          // Select 'Set volts', 'Set mA' or 'Charge'
int setting_Button = 6;                       // Sets target volts and mA charge rate


// define variables to hold the number of button presses...
int mode = -1;                                // .. for mode button..
int mA_setting = 0;                           // .. and setting button

// Define available mA settings & PWM values required to obtain them.
// LT1510 Rprog is chosen for 1170mA  max to allow for component tolerances,
// so 1000mA is less than PWM 255.  Actual mA for each setting can be tweaked
// here with the corresponding PWM value.
float milliAmps[] = {100, 250, 500, 750, 1000};
int  PWM[] = {24, 56, 110, 155, 217};        // Initial PWM values. Intentionally slightly high..
                                             // ..software will adjust mA to correct starting value later.

#define NUM_SETTINGS (sizeof(PWM)/sizeof(int))   //array size. Used to set various loop limits  

float chargeStart = 0;                       // Set safe start and end mA limits
float chargeEnd = 9999; 
int endFlag = 0;
int this_PWM = 0;                            // Variable to tweak PWM value at start of charge

// Define output pins
int mA_PWM = 3;                              // PWM Output for mA control
int cutOff = 7;                              // Vc safety cutoff
int endLED = 2;                              // End of charge LED indicator
int fan = 4;                                 // Cooling fan, simple on/off control

int targetVoltsOutput = 8;                   // Output to select 4.1 or 4.2 target voltage.

int Backlight = A0;                          // LCD LED output

float tC;


void setup() {
  TCCR2B = TCCR2B & 0b11111000 | 0x02;        // Set D3, D11 PWM @ 3.906 kHz for LT1510
  
  pinMode(cutOff, OUTPUT);                    // Pull LT1510 Pin Vc to GND to...
  digitalWrite(cutOff, HIGH);                 // .. disable charging.  
  
  pinMode(mA_PWM, OUTPUT);
  analogWrite(mA_PWM, 0);                     // PWM (charge current) to OFF

  pinMode(targetVoltsOutput, OUTPUT);
  digitalWrite(targetVoltsOutput, LOW);       // 4.1v default target volts setting  
  
  pinMode(Backlight, OUTPUT);
  digitalWrite(Backlight, LED_ON);            // LCD backlight to ON
 
  pinMode(endLED, OUTPUT);
  digitalWrite(endLED, LOW);                  // Charge end LED to OFF
  
  pinMode(fan, OUTPUT);
  digitalWrite(fan, LOW);                     // Cooling fan not running
  
  // Confirm which IO are inputs and enable internal pullup resistors.
  pinMode(mode_Button, INPUT);                // Mode Switch. 0= set V, 1 = set mA, 3 = run
  digitalWrite(mode_Button, HIGH);            // Enable internal pullup.
  
  pinMode(setting_Button, INPUT);             // 
  digitalWrite(setting_Button, HIGH);         // Enable internal pullup.  
  
  contrast = EEPROM.read(0);                  // Retrieve contrast from EEPROM
  if ((contrast < 35) || (contrast > 65)) {
     contrast = 50;                           // Set to default if sensible value was not saved
     EEPROM.write(0, contrast);               // Save default value for next time.
  }  

  lcd.begin(contrast);                        // Initialize lcd (default contrast)
  
 // Hold both buttons during power-on to set display contrast  
  if ((digitalRead(mode_Button) == LOW) && (digitalRead(setting_Button) == LOW)) {
    delay(200);
    while ((digitalRead(mode_Button) == LOW) && (digitalRead(setting_Button) == LOW)) {
      if (contrast > 65) contrastDirection = -1;
      if (contrast < 35) contrastDirection = +1;
      contrast = contrast + contrastDirection;
      lcd.clearDisplay();                          // Clear display and
      lcd.setTextSize(1);   
      lcd.setCursor(10, 0);                        // display 'Setting' header
      lcd.print("Set Contrast");
      lcd.drawLine(0,10, 84, 10, BLACK);
      lcd.setContrast(contrast);
      lcd.setTextSize(2);
      lcd.setCursor(12, 16);
      lcd.print(contrast);
      lcd.display();
      delay(250);     
    }
    EEPROM.write(0, contrast);                     // Save new contrast value to EEPROM
                                                   // Make sure both buttons are released.
    while ((digitalRead(mode_Button) == LOW) || (digitalRead(setting_Button) == LOW));
    delay(900);
  }  
  
  lcd.clearDisplay();
  lcd.setTextSize(2);
  lcd.setCursor(0, 5);
  
  lcd.print("Lithium");                            // Display opening screen
  lcd.setCursor(0, 25);
  lcd.print("Charger");
  lcd.setTextSize(1);
  lcd.display();
  ina219.begin();                                  // Initialise INA219 current sensor.
}


void loop() { 
  
 if ((mode == 0) || (mode == 1)) {                  // Not charging so set outputs to safe.
   digitalWrite(endLED, LOW); 
   digitalWrite(cutOff, HIGH);                      // .. disable charging.
   analogWrite(mA_PWM, 0);
 }
   
 // Set 'mode'. 0, 1, 2 = set volts, set mA, charge
 if (digitalRead(mode_Button) == LOW) {             // If mode switch is pressed,
   delay(100);
   mode++;                                          // increment mode. 0, 1, 2 = set v, set mA, run
   if (mode > 2)  mode = 0;   
   if (endFlag > 0) {                               // Reset mode and  endFlag
     mode = 0;
     endFlag = 0;                                   // endFlag > 0 means stop charging.
   }
   while (digitalRead(mode_Button) == LOW);         // Don't leave loop while button is pressed.
   delay(200);
 } 
 
 // Mode 0. Set target volts. 4.1v or 4.2v.
 if ((mode == 0) && (endFlag == 0)) {
   lcd.clearDisplay();                              // Clear display and
   lcd.setCursor(10, 0);                            // display 'Setting' header
   lcd.print("Set Volts");
   lcd.drawLine(0,10, 84, 10, BLACK);
   lcd.setCursor(12, 16);
   lcd.setTextSize(2);
   lcd.print(displayVolts, 1);                      // Display required battery volts
   lcd.print( " v");
   lcd.setTextSize(1);
   lcd.setCursor(5, 40);
   lcd.print("Target: ");
   lcd.print(targetVolts, 1);                       // Display required charging volts
   lcd.print("v");
   lcd.display();   
   if (digitalRead(setting_Button) == LOW) {        // If Setting switch is pressed...
     targetVolts == 4.1 ? targetVolts = 4.2 : targetVolts = 4.1;   // Set target volts
     while (digitalRead(setting_Button) == LOW);    // Don't leave loop while sw is pressed.
   } 
   targetVolts == 4.1 ? displayVolts = 3.6 : displayVolts = 3.7;
 }   
 
 // Mode 1. Set desired charge current in variable mA_setting.
 if ((mode == 1) && (endFlag == 0)){  
   lcd.clearDisplay();                              // Clear display and
   lcd.setCursor(10, 0);                            // display 'Setting' header
   lcd.print("Set Max mA");
   lcd.drawLine(0,10, 84, 10, BLACK);
   mA_setting == NUM_SETTINGS - 1 ? lcd.setCursor(0, 16) : lcd.setCursor(12, 16);
   lcd.setTextSize(2);
   lcd.print(milliAmps[mA_setting], 0);
   lcd.print(" mA");
   lcd.setTextSize(1);
   lcd.display();
   if (digitalRead(setting_Button) == LOW) {                 // If Setting switch is pressed...
     mA_setting++;                                           // ...increment setting. 0,1,2,3, 4 =
     if (mA_setting > NUM_SETTINGS - 1) mA_setting = 0;      // 100, 250, 500, 750, 1000mA
     while (digitalRead(setting_Button) == LOW);             // Don't leave loop while sw is pressed.
   } 
 }
 
 //Mode 2.  Initiate charging.  Main charging loop
 if ((mode == 2) and (endFlag == 0)){

   chargeStart = milliAmps[mA_setting];  
   chargeEnd =   chargeStart * endChargePerCent;    // Set end target current (10%)
     
   if (targetVolts == 4.2) {                        // Select appropriate R1/R2 potential divider.
       digitalWrite(targetVoltsOutput, HIGH);       // R1/R2a
   } else {
       digitalWrite(targetVoltsOutput, LOW);        // R1/(R2a+R2b)
   }  
     
   this_PWM = PWM[mA_setting];                      // Get PWM initial setting
   analogWrite(mA_PWM, this_PWM);                   // Set prog current for LT1510 
   digitalWrite(cutOff, LOW);                       // Start charging
   delay(250);                                      // Allow charge current to stabilise 
     
     
   // Reduce charging current to expected value. The 1% PWM_Allowance allows for low resultion of the 
   // PWM value at the 100mA setting (so 1 PWM step reduction doesn't reduce the current too much)
   // but steps are 'fine' enough at other mA settings so adjustments don't under-shoot as much.
   
   float PWM_Allowance;
   chargeStart == 100 ? PWM_Allowance = 0.01 : PWM_Allowance = 0.005;
   
   while(ina219.getCurrent_mA() > (chargeStart + (chargeStart * PWM_Allowance))) {
     this_PWM--;
     analogWrite(mA_PWM, this_PWM);
     delay(50);
   }
   
   startMillisec = millis();                        // get millisec time at start
   endMillisec = 0;    
   endFlag = 0;                                     // endFlag # will be reason for ending charge
   
   
   while(endFlag == 0) {                              // Loop while no endFlag
     this_mA = ina219.getCurrent_mA();                // Get battery current...
                                                      // Save value as previous value to... 
                                                      //... later check for unexpected rise.
     previous_mA == 0 ?  previous_mA = chargeStart : previous_mA = this_mA; 

     shuntVoltage = ina219.getShuntVoltage_mV();
     busVoltage = ina219.getBusVoltage_V();
     loadVoltage = busVoltage + (shuntVoltage /1000);   // Get battery voltage

//     if (this_mA > fanThreshold) {                // Original simple fan control
//       digitalWrite(fan, HIGH);                   // based on charge current.
//     } else {                                     // Replaced with control using a TMP36
//       digitalWrite(fan, LOW);                    // temperature sensor.
//     }

     lcd.clearDisplay();
     lcd.setCursor(0, 0);
     
     if (this_mA > chargeEnd)  {                  // Still charging, current above end target.
       lcd.setTextColor(BLACK);
       lcd.print("Charge@ ");                     // Display 'Charging' header...

       lcd.print(milliAmps[mA_setting], 0);       // ...and initial setting reminder.
       lcd.print("mA");

       showTime(millis() - startMillisec);        // Get time elapsed & display with volts and mA.    
     }  else {      
       analogWrite(mA_PWM, 0);                    // Charge current = 0
       digitalWrite(cutOff, HIGH);                // Disable charging
       digitalWrite(fan, LOW);
       endFlag = 1;                               // Normal shutdown so endFlag = 1
     }       

     if  (digitalRead(mode_Button) == LOW) {      // If mode switch is pressed... 
       delay(1000);                               // ... for more than 1 second...
       if (digitalRead(mode_Button) == LOW) {
         mode = 0;                                // Set mode back to setting mode
         analogWrite(mA_PWM, 0);                  // Charge current = 0
         digitalWrite(cutOff, HIGH);              // Disable charging
         digitalWrite(fan, LOW);
         endFlag = 2;                             // User terminated. endFlag = 2
         showStatus(endFlag);
       }
       while (digitalRead(mode_Button) == LOW);    // Ensure button is released before continuing.
     }
       
     if  (hours > 5) {                             // Timeout error
       mode = 0;                                  // Set mode back to setting mode
       analogWrite(mA_PWM, 0);                    // Charge current = 0
       digitalWrite(cutOff, HIGH);
       digitalWrite(fan, LOW);
       endFlag = 3;                               // Charging for 6 hours so terminate
     }
     if (loadVoltage > (targetVolts + (targetVolts * 0.1))) {              // High Volts error
       mode = 0;  
       analogWrite(mA_PWM, 0);                     // Charge current = 0
       digitalWrite(cutOff, HIGH);
       digitalWrite(fan, LOW);
       endFlag = 4;                                // Battery volts exceeds set target.
     } 

     if (this_mA > (previous_mA + (previous_mA * 0.5)))  {   // Unexpected higher mA.  
       mode = 0;  
       analogWrite(mA_PWM, 0);                     // Charge current = 0
       digitalWrite(cutOff, HIGH);
       digitalWrite(fan, LOW);
       endFlag = 5;                                // High Current.
     }     
    }   
   }   
   if (endFlag > 0) {
     showStatus(endFlag);                        // Charge ended so show battery status
   }         
}      // end main loop


// Show reason for charge ending
void showStatus(int flag) {  

     this_mA = ina219.getCurrent_mA();            // Get battery current...
     shuntVoltage + ina219.getShuntVoltage_mV();
     busVoltage = ina219.getBusVoltage_V();
     loadVoltage = busVoltage + (shuntVoltage /1000);  

     analogWrite(mA_PWM, 0);                      // Charge current = 0
     digitalWrite(cutOff, HIGH);                  // Safety cutoff
     digitalWrite(fan, LOW);
  
     lcd.clearDisplay();
     lcd.setCursor(0, 0);
     lcd.setTextColor(WHITE, BLACK);              // (highlighted text)
     if (endFlag == 1) {
       lcd.print(" Charge end ");
       digitalWrite(endLED, HIGH);                // Normal end. Steady LED
     }
     if (endFlag == 2) {
       lcd.print(" Terminated  ");                // Display 'Terminated' 
       flashEndLED(200);
     }
     if (endFlag == 3) {
       lcd.print(" Timeout ");
       flashEndLED(150);
     }
     if (endFlag == 4) {
       lcd.print(" High Volts ");
       flashEndLED(100);
     }
     if (endFlag == 5) {
       lcd.print(" High Amps ");
       flashEndLED(50);
     }     
     
     lcd.setTextColor(BLACK);
     if (endMillisec == 0) endMillisec = millis();
     showTime(endMillisec - startMillisec);         // Display end time, volts and mA.
     
     
  }           

// Flash LED at different rates to indicate end status.
void flashEndLED(int dly) {
  digitalWrite(endLED, HIGH);
  delay(dly);
  digitalWrite(endLED, LOW);
  delay(dly);
}

// Convert milliseconds to hrs, mins, secs & display on LCD
// Get battery volts and mA charge and display.
void showTime(unsigned long milli) {
  char buffer[20];
  unsigned long nTime;

  nTime  = milli / 1000;
  nTime  = nTime % (24*3600);
  hours  = nTime / 3600;
  nTime  = nTime % 3600;
  mins   = nTime / 60;
  secs  = nTime % 60;
  
  lcd.drawLine(0,10, 84, 10, BLACK);  
  lcd.setCursor(0, 12);
  lcd.print("Time   ");
  
  lcd.print(hours);                          // Display hr, min, sec since charge began.
  lcd.print(":"); 
  if (mins < 10) lcd.print("0");             // Add leading zero if mins less than 10
  lcd.print(mins);                           // Print mins
  lcd.print(":");
  if (secs < 10) lcd.print("0"); 
  lcd.print(secs);  
  
  lcd.setCursor(0, 24);                      // Get battery volts and display
  lcd.print("Batt:  ");                      // Compensate for voltage drop across polyfuse
  if (this_mA > 0) {
    lcd.print(loadVoltage - ((this_mA/1000) * polyFuseOhms), 2);
  } else {
    lcd.print(loadVoltage - ((this_mA/1000) * polyFuseOhms) - (shuntVoltage/1000) , 2);
  }
  lcd.print(" V"); 
 
  lcd.setCursor(30, 36);                      // Get battery mA and display
  if (this_mA < 100) lcd.print(" ");
  lcd.print(this_mA, 1);  
  lcd.print(" mA"); 

  getTemp();

  lcd.setCursor(0, 36);
  lcd.print(tC, 0);
  lcd.print("C");
  
  lcd.display(); 
  
  delay(500); 
}


// Read the TMP36 analogue value, convert to degrees C and control fan
void getTemp() {
  int r = 0;
  for (int i = 1; i < 11; i++){                    // Take 10 readings from
    r = r + analogRead(1);                         // ATmega328 A1..
  }
  r = r / 10;                                      // ..and average.

  float v = r * 3.3;                               // Convert to degrees C
  v /= 1024.0;
  tC = (v - 0.5) * 100;  
  
  if (tC > 30.0) digitalWrite(fan, HIGH);          // Switch fan on if above 30
  if (tC < 28.0) digitalWrite(fan, LOW);           // Switch fan off if below 28.
}


 

Back to Index | Page 1 | Page 2 | Page 3

This site and its contents are © Copyright 2005 - All Rights Reserved.