/*############################################################################
# Codigo criado por: sasiskas
# Dispovivel em: https://www.instructables.com/DIY-Li-On-Capacity-Tester-and-More/
# Data: 27/12/2023
# Detalhes do projeto: v2.3.1
# Modificado e traduzido por: Mario Parente
############################################################################*/
//-----------------------------------------------------Testador de capacidade da bateria e mais V2.3.0-----------------------------------------------------------
#include <SPI.h>
#include <stdlib.h>
#include <Adafruit_SSD1306.h>
// Variaveis para utilização no Registro de tempo
float Segundo = 0;
int T_Hora = 0;
int T_Minuto = 0;
int T_Segundo = 0;
char buf[21];
unsigned long previousMillis = 0;
unsigned long millisPassed = 0;
unsigned long BatFull = 0;
float BatMin = 3.4;
float BatVoltQuiescent = 0; // Tensão da bateria quando não há carga na bateria
float Capacity = 0.0; // Capacidade calculada da bateria, medida durante a descarga
float mA = 0;
float SetI = 0; // Ponto de ajuste para a corrente de descarga. Lembre-se que você pode ajustar a corrente de descarga usando o trimpot 500K.
float ChargeI = 0;
float CurTP4056 = 0;
float ResTP4056 = 0;
float ratioRI = 0;
float BatLevel = 0; // Porcentagem de carga/nível da baterial
float BatVoltCorrected = 0; // Tensão da bateria, medida com base na referência interna do arduino
float A2VoltCorrected = 0;
float VoltTP4056Pin2 = 0;
float Rprog = 0;
float BatRes = 0; // Resistência interna da bateria
const float RES = 3.3; // <-- // Valor do seu resistor. O valor sugerido é 1 Ohm. Altere-o, se você usar um valor de resistor diferente
#define CLK 2
#define DT 3
#define SW 4
#define MOSFET_Discharge 9 // É um N-MOSFET, então HIGH está ON e LOW está OFF
#define MOSFET_Charge 8 // É um P-MOSFET, então HIGH está OFF e LOW está ON
#define LED 13
#define PinBuz 6
char screen = 0;
char arrowpos = 0;
char BatVoltQuiescentValue = 0; // Se existe um valor para "tensão da bateria sem carga" ou não. 1 para sim e 0 para não.
const unsigned char PS_128 = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
volatile boolean TurnDetected = false;
volatile boolean up = false;
volatile boolean button = false;
#define SCREEN_WIDTH 128 // Largura da tela OLED, em pixels
#define SCREEN_HEIGHT 64 // Altura da tela OLED, em pixels
#define OLED_RESET -1 // Pino de reset # (ou -1 se compartilhar o pino de reset do Arduino)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
static const unsigned char PROGMEM Bateria[] = {
B11111111, B11111110,
B10000000, B00000010,
B10000000, B00000011,
B10000000, B00000011,
B10000000, B00000011,
B10000000, B00000010,
B11111111, B11111110,
};
static const unsigned char PROGMEM Status_Bateria_Carregar[] = {
B00000000,B00000000,
B00001000,B00001000,
B00011100,B00010000,
B00111110,B00100000,
B01111111,B01111100,
B00011100,B00001000,
B00011100,B00010000,
B00011100,B00100000,
};
static const unsigned char PROGMEM Status_Bateria_Descarregar[] = {
B00000000,B00000000,
B00011100,B00001000,
B00011100,B00010000,
B00011100,B00100000,
B01111111,B01111100,
B00111110,B00001000,
B00011100,B00010000,
B00001000,B00100000,
};
static const unsigned char PROGMEM cedinha[] = {
B00011100,
B00001000,
B00011000,
};
ISR(PCINT2_vect) {
if (digitalRead(SW) == LOW) {
button = true;
tone(PinBuz, 1300, 40);
}
}
void isr0 () {
TurnDetected = true;
up = (digitalRead(CLK) == digitalRead(DT));
}
void Config_Ini(){
arrowpos = 1;
display.setCursor(0,0);
display.print(F("Iniciando config. do testador."));
while (arrowpos == 1) {
delay(150);
if (TurnDetected) {
if (up && BatMin > 2.1) {
BatMin +=0.1;
}
else{
BatMin -=0.1;
BatMin = max(BatMin, 2.2);
}
TurnDetected = false;
}
Modo_Configuracao();
display.display();
if (digitalRead(SW) == LOW) {
arrowpos = 0;
button = false;
}
}
display.display();
delay(50);
}
void setup() {
pinMode(LED, OUTPUT);
digitalWrite(LED, HIGH);
pinMode(MOSFET_Discharge, OUTPUT);
pinMode(MOSFET_Charge, OUTPUT);
digitalWrite(MOSFET_Discharge, LOW);
digitalWrite(MOSFET_Charge, HIGH);
pinMode(PinBuz, OUTPUT);
pinMode(SW, INPUT_PULLUP);
pinMode(CLK, INPUT);
pinMode(DT, INPUT);
PCICR |= 0b00000100;
PCMSK2 |= 0b00010000; // Desliga PCINT20(D4)
attachInterrupt(0, isr0, RISING);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); //Inicializa OLED com endereço I2C 0x3C (para 128x64)
display.clearDisplay();
display.setTextColor(WHITE,BLACK); //configura o display para ativar 'branco' no pixel
display.setTextSize(1); //tamanho do texto
Config_Ini();
display.clearDisplay();
Menu(); //Limpeza e Menu Inicial
display.setCursor(0,0);
display.print(F("Testador de"));
display.setCursor(0,8);
display.print(F("Capacidade de Bateria"));
display.setCursor(2, 19);
display.write(26);
display.display();
/* TESTE
display.contrast(100);
display.ssd1306_command(SSD1306_SETCONTRAST);
display.ssd1306_command(contrast);
display.dim(true); */
}
void loop() {
int in0 = analogRead(A0); //lê o valor do pino A0
int sensorValue1 = analogRead(A1); //lê o valor do pino A1
float val0 = in0 * 5.0 / 1024.0;
float supply = readVcc() / 1000.0;
SetI = sensorValue1 * (5.00 / 1024.00) / RES * 1000; // Calcular a taxa de descarga da bateria
if (SetI <= 1200) {
SetI = SetI;
}
else{
SetI = 1200;
}
BatVoltCorrected = supply / 5 * val0 * 4.35; //Caso você use divisores de tensão diferentes, você precisa alterar o número 4.3 de acordo.
millisPassed = millis() - previousMillis;
previousMillis = millis();
//------------------------------------------------------------------- calcula a corrente de carga ------------------------------------------------------------------------
int in2 = analogRead(A2); //leia o valor do pino A2
int in3 = analogRead(A3); //leia o valor do pino A3
float val2 = in2 * 5.0 / 1024.0;
float val3 = in3 * 5.0 / 1024.0;
A2VoltCorrected = supply / 5 * val2; //calcula a leitura do pino A2 com base na referência de tensão interna
VoltTP4056Pin2 = supply / 5 * val3; //calcula a leitura do pino A3 com base na referência de tensão interna
Rprog = 1220 + (A2VoltCorrected * 1220 / (VoltTP4056Pin2 - A2VoltCorrected)); //calcula o Rprog do módulo TP4056
//calcula a corrente de carregamento com base nos números da folha de dados TP4056
if (Rprog >= 10000) {
CurTP4056 =130 ;
ResTP4056 =10000 ;
ratioRI = 0.107 ;
}
else if (Rprog < 10000 && Rprog >= 5000) {
CurTP4056 = 250 ;
ResTP4056 = 5000 ;
ratioRI = 0.024 ;
}
else if (Rprog < 5000 && Rprog >= 4000) {
CurTP4056 = 300 ;
ResTP4056 = 4000 ;
ratioRI = 0.050 ;
}
else if (Rprog < 4000 && Rprog >= 3000) {
CurTP4056 = 400 ;
ResTP4056 = 3000 ;
ratioRI = 0.100 ;
}
else if (Rprog < 3000 && Rprog >= 2000) {
CurTP4056 = 580 ;
ResTP4056 = 2000 ;
ratioRI = 0.180 ;
}
else if (Rprog < 2000 && Rprog >= 1660) {
CurTP4056 = 690 ;
ResTP4056 = 1660 ;
ratioRI = 0.324 ;
}
else if (Rprog < 1660 && Rprog >= 1500) {
CurTP4056 = 780 ;
ResTP4056 = 1500 ;
ratioRI = 0.563 ;
}
else if (Rprog < 1500 && Rprog >= 1330) {
CurTP4056 = 900 ;
ResTP4056 = 1330 ;
ratioRI = 0.706 ;
}
else if (Rprog >= 1220) {
CurTP4056 = 995 ;
ResTP4056 = 1220 ;
ratioRI = 0.864 ;
}
if (Rprog < 1220) {
ChargeI = 1000 ;
}
else {
ChargeI = (CurTP4056 - ((Rprog - ResTP4056) * ratioRI));
}
if (ChargeI > 0) {
ChargeI = ChargeI;
}
else{
ChargeI = 0;
}
//-------------------------------------------------------------- porcentagem da bateria - cálculo linear baseado na tensão (linear NÃO representa a porcentagem REAL)
if (BatVoltCorrected < BatMin) {
BatLevel = 0;
}
else if (BatVoltCorrected > 4.2) {
BatLevel = 100;
}
else {
BatLevel = map(int(BatVoltCorrected*100), int(BatMin*100), int(420), 0, 100);
}
//---------------------------------------------------------- Controle Menu Inicial --------------------------------------------------------------------------------------------
if (screen == 0) {
BatVoltQuiescent = BatVoltCorrected;
BatVoltQuiescentValue = 1;
BatFull = 0;
if (TurnDetected) {
delay(200);
switch (arrowpos) {
case 0:
if (!up) {
display.setCursor(2, 31);
arrowpos = 1;
}
else {
display.setCursor(2, 55);
arrowpos = 3;
}
break;
case 1:
if (up) {
display.setCursor(2, 19);
arrowpos = 0;
}
else {
display.setCursor(2, 43);
arrowpos = 2;
}
break;
case 2:
if (up) {
display.setCursor(2, 31);
arrowpos = 1;
}
else {
display.setCursor(2, 55);
arrowpos = 3;
}
break;
case 3:
if (up) {
display.setCursor(2, 43);
arrowpos = 2;
}
else {
display.setCursor(2, 19);
arrowpos = 0;
}
break;
}
display.fillRect(0,16,10,63,0);
display.write(26);
display.display();
tone(PinBuz, 1109, 40); // mudança de menu
TurnDetected = false;
}
}
//------------------------------------------------------------------------------Modo de teste de capacidade-------------------------------------------------------
if (screen == 1) {
Logo_bat();
Modo_Teste();
Tempo();
if (BatVoltCorrected <= BatMin) {
Capacity = Capacity;
BatVoltQuiescentValue = 0;
digitalWrite(MOSFET_Discharge, LOW);
tone(PinBuz, 831, 1000); // Final do Teste de Carga
Advertencia (18,20,"TESTE RESULTADO");
}
else if (BatVoltCorrected > BatMin && BatVoltQuiescentValue == 0 ) {
BatRes = (BatVoltQuiescent - BatVoltCorrected) / (BatVoltCorrected / SetI); //Calcula a resistência interna da bateria em mOhms
Capacity = Capacity + (SetI * (millisPassed / 3600000.0));
BatVoltQuiescent = BatVoltCorrected;
BatVoltQuiescentValue = 1;
digitalWrite(MOSFET_Discharge, HIGH);
Segundo += (millisPassed / 1000.0);
display.drawBitmap(85,0,Status_Bateria_Descarregar,16,8,1);
Mensagem (0,20,"TESTANDO BATERIA");
}
else if (BatVoltCorrected > BatMin && BatVoltQuiescentValue == 1 ) {
BatRes = (BatVoltQuiescent - BatVoltCorrected) / (BatVoltCorrected / SetI); //Calcula a resistência interna da bateria em mOhms
Capacity = Capacity + (SetI * (millisPassed / 3600000.0));
digitalWrite(MOSFET_Discharge, HIGH);
Segundo += (millisPassed / 1000.0);
display.drawBitmap(85,0,Status_Bateria_Descarregar,16,8,1);
Mensagem (0,20,"TESTANDO BATERIA");
}
display.display();
delay(100);
}
//------------------------------------------------------------------------------Modo de carga-------------------------------------------------------
else if (screen == 2) {
Logo_bat();
Modo_Carregar();
if (BatVoltCorrected >= 4.15) {
digitalWrite(MOSFET_Charge, HIGH); //OFF //É um P-MOSFET, então HIGH está OFF e LOW está ON
Advertencia(12,30,"Bateria Carregada");
BatFull = BatFull + 1;
}
else if (BatVoltCorrected <= 2 ) {
BatFull = 0;
digitalWrite(MOSFET_Charge, HIGH); //É um P-MOSFET, então HIGH está OFF e LOW está ON
Advertencia(30,30,"Sem Bateria");
}
else if (BatVoltCorrected < 4.15 && BatFull < 10 && BatVoltCorrected > 0 ) {
digitalWrite(MOSFET_Charge, LOW); //É um P-MOSFET, então HIGH está OFF e LOW está ON
display.drawBitmap(85,0,Status_Bateria_Carregar,16,8,1);
Mensagem(0,30,"Carregando ");
display.print(BatLevel,0);
display.print(F("%"));
}
else if (BatVoltCorrected < 4.15 && BatFull >= 10) {
digitalWrite(MOSFET_Charge, HIGH); //É um P-MOSFET, então HIGH está OFF e LOW está ON
Advertencia(12,30,"Bateria Carregada!");
}
display.display();
delay(500);
}
//------------------------------------------------------------------------------Modo de armazenamento-----------------------------------------------------------------------------------------
//Você precisa ajustar suas tensões de acordo com suas baterias, para baterias de 3400mAh a tensão de armazenamento (50% da capacidade) é de 3,6-3,7V, para baterias abaixo de 2600mAh deve ser maior.
// Para mais informações, visite: https://lygte-info.dk/info/BatteryChargePercent%20UK.html
else if (screen == 3) {
delay(100);
Logo_bat();
Modo_Armazenamento();
if (BatVoltCorrected < 1.7 ) {// 01 Não tem bateria
Advertencia(30,30,"Sem Bateria");
digitalWrite(MOSFET_Discharge, LOW); //DESLIGADO É um N-MOSFET, então HIGH está ON e LOW está OFF
digitalWrite(MOSFET_Charge, HIGH); //DESLIGADO É um P-MOSFET, então HIGH está OFF e LOW está ON
}
else if ( BatVoltCorrected > 3.65 && BatVoltCorrected < 3.75 ) {// 02
Advertencia(1,30,"Pronto para Armazenar");
digitalWrite(MOSFET_Discharge, LOW); //DESLIGADO É um N-MOSFET, então HIGH está ON e LOW está OFF
digitalWrite(MOSFET_Charge, HIGH); //DESLIGADO É um P-MOSFET, então HIGH está OFF e LOW está ON
}
else if (BatVoltCorrected >= 1.7 && BatVoltCorrected < 3.75) {// 03 Bateria baixa > Carregando por 1 segundo e para a carga para verificar
display.drawBitmap(85,0,Status_Bateria_Carregar,16,8,1);
Mensagem(0,30,"Carregando Aguarde!");
digitalWrite(MOSFET_Discharge, LOW); //DESLIGADO É um N-MOSFET, então HIGH está ON e LOW está OFF
digitalWrite(MOSFET_Charge, LOW); //LIGADO É um P-MOSFET, então HIGH está OFF e LOW está ON
}
else if (BatVoltCorrected > 3.76) {// 04 Bateria Alta> Descarrega por 1 segundo e para a carga para verificar
display.drawBitmap(85,0,Status_Bateria_Descarregar,16,8,1);
Mensagem(0,30,"Descarregando Aguarde");
digitalWrite(MOSFET_Charge, HIGH); //DESLIGADO É um P-MOSFET, então HIGH está OFF e LOW está ON
digitalWrite(MOSFET_Discharge, HIGH); //LIGADO É um N-MOSFET, então HIGH está ON e LOW está OFF
}
else { // 05
Advertencia(30,30,"Error?");
digitalWrite(MOSFET_Discharge, LOW); //DESLIGADO É um N-MOSFET, então HIGH está ON e LOW está OFF
digitalWrite(MOSFET_Charge, HIGH); //DESLIGADO É um P-MOSFET, então HIGH está OFF e LOW está ON
}
display.display();
delay(100);
}
//------------------------------------------------------------------------------Modo de Configuração-----------------------------------------------------------------------------------------
else if (screen == 4) {
delay(150);
if (TurnDetected) {
if (up && BatMin > 2.1) {
BatMin +=0.1;
}
else{
BatMin -=0.1;
BatMin = max(BatMin, 2.2);
}
TurnDetected = false;
}
Modo_Configuracao();
display.display();
}
//--------------------------------------------------------------------------------------------BOTÃO PRESSIONADO--------------------------------------------------------------------------------------------
if (button) {
delay(200);
switch (screen) {
case 0:
if (arrowpos == 0) {
screen = 1;
screen_clean();
}
else if (arrowpos == 1) {
screen = 2;
screen_clean();
}
else if (arrowpos == 2) {
screen = 3;
screen_clean();
}
else {
screen = 4;
screen_clean();
}
break;
case 1: // Sai do modo teste
sair();
Segundo = 0;
Capacity = 0;
digitalWrite(MOSFET_Discharge, LOW);
digitalWrite(MOSFET_Charge, HIGH);
break;
case 2: // Sai do modo Carregamento
sair();
digitalWrite(MOSFET_Discharge, LOW);
digitalWrite(MOSFET_Charge, HIGH);
break;
case 3: // Sai do modo Armazenamento
sair();
digitalWrite(MOSFET_Discharge, LOW);
digitalWrite(MOSFET_Charge, HIGH);
break;
case 4: // Sai do modo Configuração
sair();
break;
}
arrowpos = 0;
button = false;
}
}
void sair() {
screen = 0;
Menu();
display.setCursor(2, 19);
display.write(26);
display.display();
}
//-----------------------------------------------------------------------------------------------------TELAS-------------------------------------------------------------------------------------
void Menu() {
digitalWrite(MOSFET_Discharge, LOW);
digitalWrite(MOSFET_Charge, HIGH);
display.fillRect(0,16,127,63,0);
display.setCursor(10,19);
display.print(F("Testar Bateria"));
display.setCursor(10,31);
display.print(F("Carregar Bateria"));
display.setCursor(10,43);
display.print(F("Armazenar Bateria"));
display.setCursor(10,55);
display.print(F("Configuracao"));
display.setCursor(70,54);
display.setTextColor(WHITE);
display.write(0x7e);
display.setTextColor(WHITE, BLACK);
display.drawBitmap(62,61,cedinha,8,3,1);
}
void screen_clean() {
display.fillRect(0,17,127,47,0); //Limpa a tela
}
void Logo_bat() {
display.fillRect(84,0,32,8,0); //Limpa a logo_bateria
display.drawBitmap(100,0,Bateria,16,7,1);
int intbat = BatVoltCorrected * 10;
intbat = min(intbat,41);
intbat = max(intbat,BatMin*10);
intbat = map(intbat, BatMin*10, 41, 0, 13);
display.fillRect(101,1, intbat, 5, 1);
}
void Modo_Teste() {
display.setCursor(0,30);
display.print(F("V:"));
display.print(BatVoltCorrected);
display.print(F("V "));
display.setCursor(70,30);
display.print(F("I:"));
display.print(SetI,0);
display.print(F("mA "));
display.setCursor(0,41);
display.print(F("C:"));
display.print(Capacity,0);
display.print(F("mAh "));
display.setCursor(70,41);
display.print(F("R:"));
display.print(BatRes,0);
display.print(F("m"));
display.write(233);
display.print(F(" "));
}
void Tempo() {
T_Segundo = int (Segundo) % 60;
T_Minuto = Segundo / 60;
T_Hora = T_Minuto / 60;
display.setCursor(0,52);
sprintf(buf,"Tempo:%02d:%02d:%02d ",T_Hora,T_Minuto,T_Segundo);
display.print(buf);
}
void Modo_Carregar() {
display.setCursor(0,20);
display.print(F("MODO: CARREGAMENTO"));
display.setCursor(0,41);
display.print(F("Tensao bateria: "));
display.print(BatVoltCorrected);
display.print(F("V "));
display.setCursor(0,52);
display.print(F("Velocidade: "));
display.print(ChargeI,0);
display.print(F("mA"));
display.setCursor(24,40);
display.setTextColor(WHITE);
display.write(0x7e);
display.setTextColor(WHITE, BLACK);
}
void Modo_Armazenamento() {
display.setCursor(0,20);
display.print(F("Modo: ARMAZENAMENTO"));
display.setCursor(0,41);
display.print(F("Tensao bateria: "));
display.print(BatVoltCorrected);
display.print(F("V "));
display.setCursor(0,52);
display.print(F("Velocidade: "));
display.print(ChargeI,0);
display.print(F("mA "));
display.setCursor(24,40);
display.setTextColor(WHITE);
display.write(0x7e);
display.setTextColor(WHITE, BLACK);
}
void Modo_Configuracao() {
display.setCursor(0,20);
display.print(F("Modo: CONFIGURA"));
display.write(128);
display.print(F("AO"));
display.setCursor(0,30);
display.print(F("Valor Minimo da"));
display.setCursor(0,40);
display.print(F("Bateria: "));
display.print(BatMin,2);
display.print(F("V"));
display.setTextColor(WHITE);
display.setCursor(96,17);
display.write(126);
display.setTextColor(WHITE, BLACK);
}
void Advertencia (int N,int M ,char *T) {
display.fillRect(0,(M-1),127,9,1);
display.setTextColor(2);
display.setCursor(N,M);
display.print(T);
display.setTextColor(WHITE, BLACK);
}
void Mensagem (int N,int M ,char *T) {
display.fillRect(0,(M-1),127,9,0);
display.setCursor(N,M);
display.print(T);
}
//-------------------------------------------------------------------tensão com base na referência interna-------------------------------------------------------------
long readVcc() {
long result;
// Leia a referência de 1,1 V contra AVcc
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
delay(2); // Aguarde Vref resolver
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA, ADSC));
result = ADCL;
result |= ADCH << 8;
result = 1126400L / result; // Calcular Vcc (em mV); 1126400 = 1,1*1024*1000
return result;
}