//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
// DIY Smart Multipurpose Battery Tester
// by Open Green Energy, INDIA ( www.opengreenenergy.com )
// Beta Version
// Last Updated on: 25.10.2024
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Pushbutton.h>
//#include <JC_Button.h>
// Define OLED display dimensions and reset pin
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
// Create an instance of the SSD1306 display object
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
// Define GPIO pins for buttons
//#define MODE_PIN D3
//#define UP_PIN D6
//#define DOWN_PIN D9
// Define IO pins for buttons
//#define MODE_PIN 4
//#define UP_PIN 2
//#define DOWN_PIN 3
Pushbutton MODE_PIN(4);
Pushbutton UP_PIN(2);
Pushbutton DOWN_PIN(3);
// Instantiate Button objects
//Button Mode_Button(MODE_PIN, 25, false, true); // Pin (D4) Arduino Nano
//Button UP_Button(UP_PIN, 25, false, true); // Pin (D2) Arduino Nano
//Button Down_Button(DOWN_PIN, 25, false, true); // Pin (D3) Arduino Nano
// Mode selection variables
int selectedMode = 0;
bool modeSelected = false;
bool inAnalyzeMode = false; // To track if analyze mode is running
float cutoffVoltage = 3.0; // Default cutoff voltage, to be selected by user
const float Min_BAT_level = 2.8; // Minimum threshold voltage for discharge
const float Max_BAT_level = 3.2; // Maximum threshold voltage for discharge
float FULL_BAT_level = 4.20; // Threshold voltage for stopping charge ( Typical Value is 4.2V )
const float DAMAGE_BAT_level = 2.5; // Define voltage for damged battery
const float NO_BAT_level = 0.3; // Define voltage for empty slot
const float RShunt = 0.050; // Résistance du shunt XL4015
int Current[] = {0, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000};
int PWM[] = {0, 1, 3, 7, 12, 17, 22, 27, 32, 38, 43, 48, 53, 59, 64, 70, 75, 80, 86, 91, 97, 102};
int Array_Size = sizeof(Current) / sizeof(Current[0]);
int Current_Value = 0;
int currentOffset = 25; // Default offset current
int PWM_Value = 0;
int PWM_Index = 0;
unsigned long Capacity = 0;
float Capacity_f = 0;
float Vref_Voltage = 1.244; // LM385-1.2V reference voltage ( adjust it for calibration )
//float Vcc = 5.025; // Voltage of Arduino 5V pin ( Measured during setup using Band Gap Reference )
float Vcc = 0;
float BAT_Voltage = 0;
float Resistance = 0;
float sample = 0;
bool calc = false, Done = false, Report_Info = true;
// Define global time variables
unsigned long previousMillis = 0;
const long interval = 50; // Interval to update the battery icon
unsigned long startTime = 0; // Store the start time for the entire process
unsigned long elapsedTime = 0; // Total elapsed time
// Declare Hour, Minute, and Second globally
int Hour = 0;
int Minute = 0;
int Second = 0;
// Control pins
//const byte PWM_Pin = D8; // GPIO 8 on XIAO ESP32C3 (D8)
//const byte Buzzer = D7; // GPIO 7 on XIAO ESP32C3 (D7)
//const int BAT_Pin = A0; // GPIO 0 on XIAO ESP32C3 (A0)
//const int Vref_Pin = A1; // GPIO 1 on XIAO ESP32C3 (A1)
//const byte Mosfet_Pin = D2; // GPIO 2 on XIAO ESP32C3 (D2)
// Control pins
const byte PWM_Pin = 10; // Pin (D10) Arduino Nano
const byte Buzzer = 9; // Pin (D9) Arduino Nano
const int BAT_Pin = A0; // Pin (A0) Arduino Nano
const int Vref_Pin = A1; // Pin (A1) Arduino Nano
const byte Chrg_Li = 8; // Pin (D8) Arduino Nano (CHRG_Li)
const byte Chrg_Ni = 11; // Pin (D11) Arduino Nano (CHRG_Ni)
// Battery level for icon
int batteryLevel = 0;
// Resistor values for the voltage divider
//const float R1 = 200000.0; // 200k ohms
//const float R2 = 100000.0; // 100k ohms
// ========================================= SETUP FUNCTION ========================================
void setup() {
//Serial.begin(9600);
pinMode(PWM_Pin, OUTPUT);
pinMode(Buzzer, OUTPUT);
pinMode(Chrg_Li, OUTPUT);
pinMode(Chrg_Ni, OUTPUT);
//pinMode(UP_PIN, INPUT_PULLUP);
//pinMode(DOWN_PIN, INPUT_PULLUP);
//pinMode(MODE_PIN, INPUT_PULLUP);
analogWrite(PWM_Pin, PWM_Value);
//UP_Button.begin();
//Down_Button.begin();
//Mode_Button.begin();
// Initialize the OLED display with I2C address 0x3C
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
for (;;); // Stop if OLED initialization fails
}
// Clear the buffer
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
// Display the Logo during startup
display.setTextSize(1);
display.setCursor(10, 25);
display.print(F("Open Green Energy"));
display.display();
delay(2000);
// Affichage des tensions de Références
mesTensions();
regCharg();
// Start mode selection
selectMode();
}
// ========================================= LOOP FUNCTION ========================================
void loop() {
if (modeSelected) {
if (selectedMode == 0) {
chargeModeLi();
} else if (selectedMode == 1) {
chargeModeNi();
} else if (selectedMode == 2) {
dischargeMode();
} else if (selectedMode == 3) {
analyzeMode();
} else if (selectedMode == 4) {
internalResistanceMode(); // IR test mode added
}
}
}
// ========================================= MODE SELECTION ========================================
void selectMode() {
modeSelected = false;
selectedMode = 0;
while (!modeSelected) {
// Mode_Button.read();
// UP_Button.read();
// Down_Button.read();
// Handle UP button press (move up in the menu)
//if (UP_Button.isPressed()) {
//if ((digitalRead(UP_PIN)== 0)) {
if (UP_PIN.getSingleDebouncedPress()){
selectedMode = (selectedMode == 0) ? 4 : selectedMode - 1; // If at the top, wrap around to the bottom
beep(100);
//delay(100); // Debounce delay to prevent multiple presses
}
// Handle DOWN button press (move down in the menu)
//if (Down_Button.isPressed()) {
//if ((digitalRead(DOWN_PIN)== 0)) {
if (DOWN_PIN.getSingleDebouncedPress()){
selectedMode = (selectedMode == 4) ? 0 : selectedMode + 1; // If at the bottom, wrap around to the top
beep(100);
//delay(100); // Debounce delay to prevent multiple presses
}
// Confirm selection with MODE button press
//if (Mode_Button.isPressed()) {
//if ((digitalRead(MODE_PIN)== 0)) {
if (MODE_PIN.getSingleDebouncedPress()){
beep(300);
modeSelected = true;
//delay(100); // Debounce delay to prevent multiple presses
}
// Display the menu options and highlight the selected one
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.print((selectedMode == 0) ? "> Charge Li-ion" : " Charge Li-ion");
display.setCursor(0, 12);
display.print((selectedMode == 1) ? "> Charge Ni-Mh" : " Charge Ni-Mh");
display.setCursor(0, 26);
display.print((selectedMode == 2) ? "> Decharge" : " Decharge");
display.setCursor(0, 40);
display.print((selectedMode == 3) ? "> Analyze" : " Analyze");
display.setCursor(0, 54);
display.print((selectedMode == 4) ? "> IR Test" : " IR Test");
display.display();
}
// Show the selected mode after selection
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0, 20);
if (selectedMode == 0) {
display.print(F("ChargeLi"));
} else if (selectedMode == 1) {
display.print(F("ChargeNi"));
} else if (selectedMode == 2) {
display.print(F("Dischrg.."));
} else if (selectedMode == 3) {
display.print(F("Analyze.."));
} else if (selectedMode == 4) {
display.print(F("IR Test.."));
}
display.display();
delay(500);
}
// ========================================= MEASURE BATTERY VOLTAGE ========================================
float measureVcc() {
float vrefSum = 0; // Sum of all the Vref readings
for (int i = 0; i < 100; i++) {
vrefSum += analogRead(Vref_Pin); // Read raw analog value from Vref pin 100 times
delay(2); // Small delay between each reading for stability
}
float averageVrefReading = vrefSum / 100.0; // Calculate the average Vref reading
//float vcc = (Vref_Voltage * 4096.0) / averageVrefReading; // Calculate Vcc using average Vref and 12-bit ADC (pour ESP32)
float vcc = (Vref_Voltage * 1024.0) / averageVrefReading; // Calculate Vcc using average Vref and 12-bit ADC (pour arduino nano)
return vcc;
}
float measureBatteryVoltage() {
Vcc = measureVcc(); // Measure the actual Vcc
float batterySum = 0; // Sum of all the battery readings
for (int i = 0; i < 100; i++) {
batterySum += analogRead(BAT_Pin); // Read raw analog value from the battery pin 100 times
delay(2); // Small delay between each reading for stability
}
float averageBatteryReading = batterySum / 100.0; // Calculate the average battery reading
//float voltageDividerRatio = (R1 + R2) / R2;
float voltageDividerRatio = 1; // Modification
//float batteryVoltage = (averageBatteryReading * Vcc / 4096.0) * voltageDividerRatio; // Convert ADC value to battery voltage (pour ESP32)
float batteryVoltage = (averageBatteryReading * Vcc / 1024.0) * voltageDividerRatio; // Convert ADC value to battery voltage (pour arduino nano)
batteryVoltage = batteryVoltage - (RShunt * (Current_Value/1000)); // On déduit la chute de tension aux bornes du shunt
return batteryVoltage;
}
// ========================================= UPDATE THE ELAPSED TIME =============================
void updateTiming() {
unsigned long currentMillis = millis();
elapsedTime = currentMillis - startTime; // Calculate total elapsed time since the process started
// Convert elapsed time to hours, minutes, and seconds
Second = (elapsedTime / 1000) % 60;
Minute = (elapsedTime / 60000) % 60;
Hour = (elapsedTime / 3600000);
}
// ========================================= CHARGE MODE Li-Ion et Lipo ========================================
void chargeModeLi() {
FULL_BAT_level = 4.20; // Threshold voltage for stopping charge ( Typical Value is 4.2V )
calc = true;
Done = false;
Capacity = 0; // Reset capacity for the test
unsigned long lastUpdateTime = millis(); // Start timer for the charging process
batteryLevel = 0; // Start from 0%
//regCharg();
// Measure battery voltage before starting the charge
BAT_Voltage = measureBatteryVoltage();
// Check if the battery voltage is below the minimum threshold
if (BAT_Voltage < NO_BAT_level) {
// If battery voltage is below NO_BAT_level, display "No Battery"
display.clearDisplay();
display.setTextSize(1);
display.setCursor(15, 25);
display.print(F("EMPTY BAT SLOT"));
display.display();
delay(3000); // Wait for 3 seconds
selectMode(); // Return to mode selection
return;
} else if (BAT_Voltage < DAMAGE_BAT_level) {
// If battery voltage is below DAMAGE_BAT_level but above NO_BAT_level, consider the battery damaged
display.clearDisplay();
display.setTextSize(1);
display.setCursor(25, 25);
display.print(F("BAT DAMAGED"));
display.display();
delay(3000); // Wait for 3 seconds
selectMode(); // Return to mode selection
return;
}
// If the battery voltage is between Min_BAT_level and FULL_BAT_level, proceed with charging
digitalWrite(Chrg_Ni, LOW); // Turn off Relais to start charging
digitalWrite(Chrg_Li, HIGH); // Turn on Relais to start charging
while (!Done) {
updateTiming();
BAT_Voltage = measureBatteryVoltage(); // Measure battery voltage
display.clearDisplay();
// Simulate battery charging progression
updateBatteryDisplay(true); // True indicates charging
display.setTextSize(1);
display.setCursor(25, 5);
display.print(F("Charging.."));
display.setCursor(40, 25);
display.print(Hour);
display.print(F(":"));
display.print(Minute);
display.print(F(":"));
display.print(Second);
display.setTextSize(2);
display.setCursor(15, 40);
display.print(F("V:"));
display.print(BAT_Voltage,2);
display.print(F("V"));
display.display();
// Check if battery voltage has reached the full battery level
if (BAT_Voltage >= FULL_BAT_level) {
Done = true;
digitalWrite(Chrg_Li, LOW); // Turn off Relais to stop charging
beep(300); // Beep to indicate charging is complete
displayFinalCapacity(Capacity_f, true); // Pass true for charging complete
}
delay(100);
}
selectMode(); // Return to mode selection after charging is complete
}
// ========================================= CHARGE MODE Ni-MH ========================================
void chargeModeNi() {
FULL_BAT_level = 4.50; // Threshold voltage for stopping charge ( Typical Value is 4.5V -> 3x 1.5V)
calc = true;
Done = false;
Capacity = 0; // Reset capacity for the test
unsigned long lastUpdateTime = millis(); // Start timer for the charging process
batteryLevel = 0; // Start from 0%
//regCharg();
// Measure battery voltage before starting the charge
BAT_Voltage = measureBatteryVoltage();
// Check if the battery voltage is below the minimum threshold
if (BAT_Voltage < NO_BAT_level) {
// If battery voltage is below NO_BAT_level, display "No Battery"
display.clearDisplay();
display.setTextSize(1);
display.setCursor(15, 25);
display.print(F("EMPTY BAT SLOT"));
display.display();
delay(3000); // Wait for 3 seconds
selectMode(); // Return to mode selection
return;
} else if (BAT_Voltage < DAMAGE_BAT_level) {
// If battery voltage is below DAMAGE_BAT_level but above NO_BAT_level, consider the battery damaged
display.clearDisplay();
display.setTextSize(1);
display.setCursor(25, 25);
display.print(F("BAT DAMAGED"));
display.display();
delay(3000); // Wait for 3 seconds
selectMode(); // Return to mode selection
return;
}
// If the battery voltage is between Min_BAT_level and FULL_BAT_level, proceed with charging
digitalWrite(Chrg_Li, LOW); // Turn off Relais to start charging
digitalWrite(Chrg_Ni, HIGH); // Turn on Relais to start charging
while (!Done) {
updateTiming();
BAT_Voltage = measureBatteryVoltage(); // Measure battery voltage
display.clearDisplay();
// Simulate battery charging progression
updateBatteryDisplay(true); // True indicates charging
display.setTextSize(1);
display.setCursor(25, 5);
display.print(F("Charging.."));
display.setCursor(40, 25);
display.print(Hour);
display.print(F(":"));
display.print(Minute);
display.print(F(":"));
display.print(Second);
display.setTextSize(2);
display.setCursor(15, 40);
display.print(F("V:"));
display.print(BAT_Voltage,2);
display.print(F("V"));
display.display();
// Check if battery voltage has reached the full battery level
if (BAT_Voltage >= FULL_BAT_level) {
Done = true;
digitalWrite(Chrg_Ni, LOW); // Turn off MOSFET to stop charging
beep(300); // Beep to indicate charging is complete
displayFinalCapacity(Capacity_f, true); // Pass true for charging complete
}
delay(100);
}
selectMode(); // Return to mode selection after charging is complete
}
// ========================================= DISCHARGE MODE Ni-MH========================================
void dischargeMode() {
bool cutoffSelected = selectCutoffVoltage();
bool currentSelected = selectDischargeCurrent();
// bool currentSelected = true;
// Current[PWM_Index] = 5;
Current_Value = Current[PWM_Index];
if (cutoffSelected && currentSelected) {
calc = true;
Done = false;
Capacity = 0; // Reset capacity for the test
unsigned long lastUpdateTime = millis(); // Start timer for the discharging process
digitalWrite(Chrg_Li, LOW); // Ensure the charging Relais is off
digitalWrite(Chrg_Ni, LOW); // Ensure the charging Relais is off
analogWrite(PWM_Pin, PWM_Value); // Start discharging by applying PWM to the load
while (!Done) {
updateTiming();
BAT_Voltage = measureBatteryVoltage();
// Calculate time elapsed since the last update
unsigned long currentTime = millis();
float elapsedTimeInHours = (currentTime - lastUpdateTime) / 3600000.0; // Convert ms to hours
// Update capacity using I * t (Current * elapsed time)
if (calc) {
Capacity_f += (Current[PWM_Index] + currentOffset) * elapsedTimeInHours; // Capacity in mAh
lastUpdateTime = currentTime; // Update last update time
}
display.clearDisplay();
updateBatteryDisplay(false); // Update battery icon (false for discharging)
display.setTextSize(1);
display.setCursor(15, 5);
display.print(F("Discharging.."));
display.setCursor(15, 20);
display.print(F("Time: "));
display.print(Hour);
display.print(F(":"));
display.print(Minute);
display.print(F(":"));
display.print(Second);
display.setCursor(15, 30);
display.print(F("Cap:"));
display.print(Capacity_f, 1);
display.print(F("mAh"));
display.setCursor(15, 40);
display.print(F("V: "));
display.print(BAT_Voltage, 2);
display.print(F("V"));
display.setCursor(15, 50);
display.print(F("I: "));
display.print(Current[PWM_Index]);
display.print(F("mA"));
display.display();
if (BAT_Voltage <= cutoffVoltage) {
Done = true;
analogWrite(PWM_Pin, 0); // Stop discharging by turning off the load (PWM)
beep(300); // Long beep to indicate discharging is complete
displayFinalCapacity(Capacity_f, false); // Pass false for discharging complete
}
delay(100);
}
}
selectMode(); // Return to mode selection after discharging is complete
}
// ========================================= ANALYZE MODE ========================================
void analyzeMode() {
inAnalyzeMode = true; // Set analyze mode flag to true
calc = true;
Done = false;
Capacity = 0; // Reset capacity for this test
unsigned long lastUpdateTime = millis(); // Start the timer for the entire analyze process
// Step 1: Check if the battery is damaged, missing, or already full
BAT_Voltage = measureBatteryVoltage();
// Check if the battery voltage is below the minimum threshold
if (BAT_Voltage < NO_BAT_level) {
// If battery voltage is below NO_BAT_level, display "No Battery"
display.clearDisplay();
display.setTextSize(1);
display.setCursor(15, 25);
display.print(F("EMPTY BAT SLOT"));
display.display();
delay(3000); // Wait for 3 seconds
selectMode(); // Return to mode selection
return;
} else if (BAT_Voltage < DAMAGE_BAT_level) {
// If battery voltage is below DAMAGE_BAT_level but above NO_BAT_level, consider the battery damaged
display.clearDisplay();
display.setTextSize(1);
display.setCursor(25, 25);
display.print(F("BAT DAMAGED"));
display.display();
delay(3000); // Wait for 3 seconds
selectMode(); // Return to mode selection
return;
}
// Step 2: Charge the battery until full
digitalWrite(Chrg_Ni, LOW); // Turn off Relais to start charging
digitalWrite(Chrg_Li, HIGH); // Turn on Relais to start charging
while (!Done) {
updateTiming();
BAT_Voltage = measureBatteryVoltage(); // Measure battery voltage
display.clearDisplay();
// Simulate battery charging progression
updateBatteryDisplay(true); // True indicates charging
display.setTextSize(1);
display.setCursor(10, 5);
display.print(F("Analyzing - C"));
display.setCursor(40, 25);
display.print(Hour);
display.print(F(":"));
display.print(Minute);
display.print(F(":"));
display.print(Second);
display.setTextSize(2);
display.setCursor(15, 40);
display.print(F("V:"));
display.print(BAT_Voltage,2);
display.print(F("V"));
display.display();
// Check if battery voltage has reached the full battery level
if (BAT_Voltage >= FULL_BAT_level) {
Done = true;
digitalWrite(Chrg_Li, LOW); // Turn off Relais to stop charging
}
delay(100);
}
// Step 3: Rest for 5 minutes to allow the battery to stabilize
display.clearDisplay();
display.setTextSize(2);
display.setCursor(5, 25);
display.print(F("Resting.."));
display.display();
delay(180000); // Wait for 3 minutes (180000 ms)
// Step 4: Discharge the battery at 500mA to calculate real capacity
cutoffVoltage = 3.0; // Set cutoff voltage for discharge
PWM_Index = 6; // Index for 500mA discharge current in the Current array
PWM_Value = PWM[PWM_Index]; // Set PWM value for 500mA discharge
Done = false;
Capacity = 0; // Reset capacity for this test
lastUpdateTime = millis(); // Reset the start time for discharge process
digitalWrite(Chrg_Li, LOW); // Ensure the charging Relais is off
digitalWrite(Chrg_Ni, LOW); // Ensure the charging Relais is off
analogWrite(PWM_Pin, PWM_Value); // Start discharging
while (!Done) {
updateTiming();
BAT_Voltage = measureBatteryVoltage();
// Calculate time elapsed since the last update
unsigned long currentTime = millis();
float elapsedTimeInHours = (currentTime - lastUpdateTime) / 3600000.0; // Convert ms to hours
// Update capacity using I * t (Current * elapsed time)
if (calc) {
Capacity_f += (Current[PWM_Index] + currentOffset) * elapsedTimeInHours; // Capacity in mAh
lastUpdateTime = currentTime; // Update last update time
}
display.clearDisplay();
updateBatteryDisplay(false); // Update battery icon (false for discharging)
display.setTextSize(1);
display.setCursor(10, 5);
display.print(F("Analyzing - D"));
display.setCursor(15, 20);
display.print(F("Time: "));
display.print(Hour);
display.print(F(":"));
display.print(Minute);
display.print(F(":"));
display.print(Second);
display.setCursor(15, 35);
display.print(F("Cap:"));
display.print(Capacity_f, 1);
display.print(F("mAh"));
display.setCursor(15, 50);
display.print(F("V: "));
display.print(BAT_Voltage, 2);
display.print(F("V"));
display.display();
if (BAT_Voltage <= cutoffVoltage) {
Done = true;
analogWrite(PWM_Pin, 0); // Stop discharging by turning off the load (PWM)
beep(300); // Long beep to indicate discharging is complete
displayFinalCapacity(Capacity_f, false); // Pass false for discharging complete
}
delay(100);
}
inAnalyzeMode = false; // Reset analyze mode flag
selectMode(); // Return to mode selection after analyzing
}
// ========================================= INTERNAL RESISTANCE MODE ========================================
void internalResistanceMode() {
float voltageNoLoad = 0;
float voltageLoad = 0;
float internalResistance = 0;
bool resistanceMeasured = false;
digitalWrite(Chrg_Li, LOW); // Ensure the charging Relais is off
digitalWrite(Chrg_Ni, LOW); // Ensure the charging Relais is off
// Step 1: Measure voltage without load (open circuit)
analogWrite(PWM_Pin, 0); // Ensure no current flows through the load
delay(500); // Let the voltage stabilize
voltageNoLoad = measureBatteryVoltage(); // Measure voltage with no load
// Step 2: Apply load using PWM
PWM_Index = 6; // Index corresponding to 1A current in the Current array
PWM_Value = PWM[PWM_Index]; // Set PWM value corresponding to 1A current
analogWrite(PWM_Pin, PWM_Value); // Apply PWM to control load
delay(500); // Let the voltage stabilize under load
// voltageLoad = measureBatteryVoltage(); // Measure the loaded voltage
voltageLoad = measureBatteryVoltage();
// Calculate the load current and internal resistance using Ohm's Law
float currentDrawn = Current[PWM_Index] / 1000.0; // Convert current in mA to Amps
// Step 4: Calculate internal resistance using Ohm's Law
if (currentDrawn > 0) {
internalResistance = (voltageNoLoad - voltageLoad) / currentDrawn; // R = (V_no_load - V_load) / I
} else {
internalResistance = 0; // Avoid division by zero
}
// Display the IR test results
displayIRTestIcon(voltageNoLoad, voltageLoad, internalResistance);
// Beep to indicate completion of the IR measurement
beep(300);
// Step 5: Turn off the load after measurement
analogWrite(PWM_Pin, 0); // Stop the current flow
delay(5000); // Wait for the user to read the display
resistanceMeasured = true;
// Return to the mode selection if measurement is complete
if (resistanceMeasured) {
selectMode();
}
}
// ========================================= FINAL CAPACITY DISPLAY ON OLED ========================================
void displayFinalCapacity(float capacity, bool chargingComplete) {
display.clearDisplay();
// Display "Complete" message and final capacity
display.setTextSize(1);
display.setCursor(15, 5);
display.print(F("Complete"));
display.setCursor(15, 20);
display.print(F("Time: "));
display.print(Hour);
display.print(F(":"));
display.print(Minute);
display.print(F(":"));
display.print(Second);
display.setCursor(15, 35);
display.print(F("Cap:"));
display.print(Capacity_f, 1);
display.print(F("mAh"));
display.setCursor(15, 50);
display.print(F("V: "));
display.print(BAT_Voltage,2);
display.print(F("V"));
// Display battery icon full (charging complete) or empty (discharging complete)
if (chargingComplete) {
drawBatteryOutline(); // Draw the battery outline
drawBatteryFill(100); // Battery full when charge is complete
} else {
drawBatteryOutline(); // Draw the battery outline
drawBatteryFill(0); // Battery empty when discharge is complete
}
display.display(); // Update the OLED display
// Keep the screen on until a button is pressed
bool buttonPressed = false;
while (!buttonPressed) {
// Mode_Button.read();
// UP_Button.read();
// Down_Button.read();
//if (((digitalRead(UP_PIN)== 0)) || ((digitalRead(DOWN_PIN)== 0)) || ((digitalRead(MODE_PIN)== 0))) {
if ((UP_PIN.getSingleDebouncedPress()) || (DOWN_PIN.getSingleDebouncedPress()) || (MODE_PIN.getSingleDebouncedPress())) {
buttonPressed = true;
}
//delay(100); // Polling delay to reduce the button read frequency
}
// Once a button is pressed, return to mode selection
selectMode();
}
// ========================================= SELECT CUTOFF VOLTAGE ========================================
bool selectCutoffVoltage() {
bool cutoffSelected = false;
while (!cutoffSelected) {
// UP_Button.read();
// Down_Button.read();
// Mode_Button.read();
// Increase cutoff voltage with UP button
//if (UP_Button.isPressed() && cutoffVoltage < Max_BAT_level) {
//if (((digitalRead(UP_PIN)== 0)) && (cutoffVoltage < Max_BAT_level)) {
if ((UP_PIN.getSingleDebouncedPress()) && (cutoffVoltage < Max_BAT_level)) {
cutoffVoltage += 0.1;
beep(100);
//delay(100);
}
// Decrease cutoff voltage with DOWN button
//if (Down_Button.isPressed() && cutoffVoltage > Min_BAT_level) {
//if (((digitalRead(DOWN_PIN)== 0)) && (cutoffVoltage > Min_BAT_level)) {
if ((DOWN_PIN.getSingleDebouncedPress()) && (cutoffVoltage > Min_BAT_level)){
cutoffVoltage -= 0.1;
beep(100);
//delay(100);
}
// Confirm cutoff voltage with MODE button
//if (Mode_Button.isPressed()) {
//if ((digitalRead(MODE_PIN)== 0)) {
if (MODE_PIN.getSingleDebouncedPress()) {
cutoffSelected = true;
beep(100);
//delay(100);
}
// Update OLED with selected cutoff voltage
display.clearDisplay();
display.setTextSize(1);
display.setCursor(2, 10);
display.print(F("Select Cutoff Volt:"));
display.setTextSize(2);
display.setCursor(20, 30);
display.print(F("V:"));
display.print(cutoffVoltage, 1);
display.print(F("V"));
display.display();
}
return cutoffSelected;
display.display();
}
// ========================================= SELECT DISCHARGE CURRENT ========================================
bool selectDischargeCurrent() {
bool currentSelected = false;
PWM_Index = 0;
PWM_Value = PWM[PWM_Index];
while (!currentSelected) {
// // UP_Button.read();
// // Down_Button.read();
// // Mode_Button.read();
// // Increase discharge current with UP button
// //if (UP_Button.isPressed() && PWM_Index < (Array_Size - 1)) {
// //if (((digitalRead(UP_PIN)== 0)) && (PWM_Index < (Array_Size - 1))) {
if ((UP_PIN.getSingleDebouncedPress()) && PWM_Index < (Array_Size - 1)) {
// PWM_Value = PWM[++PWM_Index];
PWM_Index += 1;
PWM_Value += 100;
beep(100);
//delay(100);
}
// // Decrease discharge current with DOWN button
// //if (Down_Button.isPressed() && PWM_Index > 0) {
// //if (((digitalRead(DOWN_PIN)== 0)) && (PWM_Index > 0)) {
if ((DOWN_PIN.getSingleDebouncedPress()) && (PWM_Index > 0)) {
//if (DOWN_PIN.getSingleDebouncedPress()) {
//PWM_Value = PWM[--PWM_Index];
PWM_Index -= 1;
PWM_Value -= 100;
beep(100);
//delay(300);
}
// Confirm current selection with MODE button
//if (Mode_Button.isPressed()) {
//if ((digitalRead(MODE_PIN)== 0)) {
if (MODE_PIN.getSingleDebouncedPress()) {
currentSelected = true;
beep(100);
//delay(100);
}
// Update the OLED display with the selected current
display.clearDisplay();
display.setTextSize(1);
display.setCursor(2, 10);
display.print(F("Select Dischrg Curr:"));
display.setTextSize(2);
display.setCursor(15, 30);
display.print(F("I:"));
display.print(Current[PWM_Index]);
display.print(F("mA"));
display.display();
}
//currentSelected = true;
return currentSelected;
}
// ========================================= Réglage Chargeur XL4015 ========================================
void regCharg() {
bool regl = false;
digitalWrite(Chrg_Ni, LOW); // Turn off Relais to start charging
digitalWrite(Chrg_Li, HIGH); // Turn on MOSFET to start charging
BAT_Voltage = measureBatteryVoltage();
while (!regl) {
// // Update OLED with selected cutoff voltage
display.clearDisplay();
display.setTextSize(1);
display.setCursor(2, 10);
display.print(F("Reglage Chargeur"));
display.setCursor(2, 25);
display.print(F("Tension et Courant"));
display.setCursor(2, 45);
display.print(F("V: "));
display.print(BAT_Voltage,2);
display.print(F("V"));
display.display();
// while (!reglage) {
if (MODE_PIN.getSingleDebouncedPress()) {
regl = true;
beep(100);
}
}
// delay(5000);
digitalWrite(Chrg_Li, LOW); // Turn off MOSFET to stop charging
}
void mesTensions() {
bool mesures = false;
// Measure battery voltage et Vcc
BAT_Voltage = measureBatteryVoltage();
//Vcc = measureVcc(); // Measure the actual Vcc
// Affiche les différentes Tensions
display.clearDisplay();
display.setTextSize(1);
display.setCursor(15, 5);
display.print(F("Tensions"));
display.setCursor(15, 20);
display.print(F("Vcc: "));
display.print(Vcc,2);
display.print(F("V"));
display.setCursor(15, 30);
display.print(F("VBat: "));
display.print(BAT_Voltage,2);
display.print(F("V"));
display.setCursor(15, 40);
display.print("Vref: ");
display.print(Vref_Voltage,3);
display.print(F("V"));
display.display();
while (!mesures) {
if (MODE_PIN.getSingleDebouncedPress()) {
mesures = true;
beep(100);
}
}
//delay(5000);
//mesures = false;
}
// ========================================= DRAW BATTERY ICON AND ANIMATE ========================================
void drawBatteryOutline() {
display.drawRect(100, 15, 12, 20, SSD1306_WHITE); // Adjusted Y-axis for the Battery box (12x20 size)
display.drawRect(102, 12, 8, 3, SSD1306_WHITE); // Adjusted Y-axis for the Battery head (8x3 size)
}
// Fill the battery based on the level (for both charging and discharging)
void drawBatteryFill(int level) {
int fillHeight = map(level, 0, 100, 0, 18); // Map level to height (0-18 pixels)
display.fillRect(102, 33 - fillHeight, 8, fillHeight, SSD1306_WHITE); // Adjusted Y-axis for bottom fill
}
// Update the battery display during charge/discharge
void updateBatteryDisplay(bool charging) {
unsigned long currentMillis = millis();
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
// Modify the battery level behavior based on charging or discharging
if (charging) {
batteryLevel += 4; // Faster fill for charge mode
if (batteryLevel > 100) batteryLevel = 0; // Reset to 0% when reaching 100%
} else {
batteryLevel -= 4; // Faster depletion for discharge mode
if (batteryLevel < 0) batteryLevel = 100; // Reset to 100% when reaching 0%
}
// Clear and draw the updated battery icon
drawBatteryOutline();
drawBatteryFill(batteryLevel);
}
}
// ========================================= DRAW ICON FOR IR TEST ========================================
void displayIRTestIcon(float voltageNoLoad, float voltageLoad, float internalResistance) {
display.clearDisplay();
// Drawing a resistor-like icon (simple lines and zig-zag), centered horizontally and shifted 5 pixels up
display.drawLine(34, 15, 54, 15, SSD1306_WHITE); // Straight line
display.drawLine(54, 15, 59, 20, SSD1306_WHITE); // Zigzag start
display.drawLine(59, 20, 64, 10, SSD1306_WHITE); // Zigzag mid
display.drawLine(64, 10, 69, 20, SSD1306_WHITE); // Zigzag mid
display.drawLine(69, 20, 74, 15, SSD1306_WHITE); // Zigzag end
display.drawLine(74, 15, 94, 15, SSD1306_WHITE); // Straight line
display.setTextSize(2);
display.setCursor(2, 35);
display.print(F("IR:"));
display.print(internalResistance * 1000, 0); // Display internal resistance in milliohms
display.print(F("mOhm"));
display.display(); // Update the OLED display
}
// ========================================= BUZZER BEEP ========================================
void beep(int duration) {
digitalWrite(Buzzer, HIGH);
delay(duration);
digitalWrite(Buzzer, LOW);
}