Arduino-Controlled Lithium Battery Charger | |||||||||||
Lithium Battery Charger - Software
Programming the ATmega328The 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. 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. }
|