//bibliotecas para incluir =========================================
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
//macros de definicoes
#define time_refresh 50 //intervalo minimo de contagem de tempo em ms.
#define display_sview_refresh 1000 //intervalo de atualização do display, quando no modo sview.
#define aread_refresh 100 //intervalo entre leituras analogicas em ms
#define sview_refresh 10 //Intervalo de atualizaçao da supervisao em ms
// Prototipos de funcoes ========================================
void display_cfg(void); //configura o display lcd
void serial_cfg(void); //configura a comunicação serial
void aread(void); //realiza as leituras das portas analogicas.
void sview(void); //Imprime no serial os principais dados monitorados
void display_sview(void); //Imprime no display os principais valores monitorados.
void convert(void); //converte os valores binarios lidos para analogico.
void calc(void); //calcula todas variaveis que dependen de valor lido.
void time(void); //tempo de operacao.
void consumo(int a); //calcula o consumo da bateria em tempo real. a = 1 -> carga. a = 0 -> discarga
bool read_bus_bit(char pin); //realiza a leitura de um bit no BUS e returna seu status (0 ou 1)
void menu_display(void); //funcao que detecta as teclas pressionadas para navegar no menu.
void pin_values(void); //realiza uma varredura nos botoes pressionados.
void print_pin_values(void); //imprime na comunicacao serial os estados logicos das portas monitoradas.
void print_sts_values(void); //imprime na comunicação serial os status ativos.
void routine(void); //atualiza entradas, saidas e status;
void button_pressed(void); //detecta se um botão foi pressionado.
void menu_select(void); //Gerencia qual indice da variavel nav_menu, consequentemente, qual menu sera exibido.
void interruptcfg(void); //Funcao que configura todas as interrupcoes utilizadas no programa.
void iocfg(void); //Funcao que configura os pinos como entrada e saída.
void disp_vbat(void); //exibe a tensao atual da bateria no display
void IRAM_ATTR btn1(void); //rotina de interrucao do botao 1
void IRAM_ATTR btn2(void); //rotina de interrucao do botao 1
// Variaveis globais ============================================
float ibat, ibatd, vbat, temp, charge = 0, discharge = 0, consu = 0;
int time_charge = 0, time_discharge = 0, time_ms = 0, time_ss =0, time_mm =45,
time_hh =16, time_dd =10, time_mt =12, time_yr =1999,
aux0 = time_refresh, aux1 = aread_refresh,
aux2 = sview_refresh, aux3 = display_sview_refresh, lab_cons,
power =0, energy =0;
bool menu_monitor_on = true; //menu esta monitor esta ativo
const char prd_name[] = "Battery Tester";
const char prd_version[] = "Vr.003-I - Interrupt";
const char prd_creator[] = "Kevin Luiz @ 2023";
//variables to keep track of the timing of recent interrupts
int b_time = 0;
int b_ltime = 0;
int nav_menu = 0;
bool subnav_menu = 0, btn1_en = 1, btn2_en = 1;
bool active_menu = true;
LiquidCrystal_I2C lcd(0x27, 4, 20);
bool aux_button[4] = {0, 0, 0, 0}; /*
Quando alguem pressiona um botão do tipo ON/OFF, a sua logica é armazenada
nesse vetor. Quando pressiona o botão 2 (BT2), o primeiro valor desse vetor é alterado para 1.
*/
bool alarm_sts [10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};/*
Cada bit do vetor de alarme indica uma condicao de atencao.
Quando o bit for 1, significa que aquela condicao foi atingida.
bit ! Descricao ! Quando atua
MSB 0 ! Sobrecorrente Carga ! Ibat > 15A
1 ! Sobrecorrente Discarga ! Idbat > 15A
2 ! Sobretemperatura ! Temperatura > 60ºC
3 ! Subtemperatura ! Temperatura < 15ºC
4 ! Sobretensao ! Vbat > 14V
5 ! Subtensao ! Vbat < 9V
6 ! Livre ! A definir
7 ! Livre ! A definir
8 ! Livre ! A definir
9 ! Livre ! A definir
LSB 10 ! Livre ! A definir
*/
bool pinread [10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
char pinname [10] = {15, 2, 4, 16, 17, 5, 18, 19, 26, 25};
// Funcao de configuracoes =======================================
void setup() {
// put your setup code here, to run once:
millis();
iocfg(); //configura as portas como entradas e saidas
serial_cfg(); //configura a taxa de comunicacao serial
display_cfg(); //configura e habilita o display
lcd.clear(); //limpa os dados exibidos no display
menu_display(); //invoca a funcao que inicia as informacoes no display.
interruptcfg(); //configura e habilita as interrupcoes.
delay(500);
} //end setup function
// Funcao de loop
void loop() {
while(1){
delay(300); //remover esse deley no codigo final, ele é utilizado apenas para simulação fluir.
//pin_values(); //Realiza a leitura dos pinos de entrada
//button_pressed(); //Realiza a ação conforme o botão pressionado lido.
//routine(); //Executa uma série de tarefas como leituras analogicas e conversoes.
///vTaskDelay(500);
if(active_menu){
menu_display();
//active_menu = false;
}
aread();
sview();
/*
lcd.clear();
lcd.setCursor(0,0);
lcd.print("-------SVIEW-------");
lcd.setCursor(0,1);
lcd.print("Vb:00.0V ! ib:00.0A");
lcd.setCursor(0,2);
lcd.print("T:000.0C !E:000.0Wh");
lcd.setCursor(0,3);
lcd.print("Time: 00:00:00 ");
*/
}//end while
}//end void loop function
/***********************************************************************
DETALHAMENTO DAS FUNCOES AUXILIARES
************************************************************************/
void routine(void){
if(millis()>aux0){ //Executa a funcao time a um intervalo de tempo definido.
aux0 += time_refresh;
time();
}//end if
if(millis()>aux1){ //Realiza a leitura das portas analogicas a cada intervalo de tempo definido.
aux1 += aread_refresh;
aread();
}//end if
if(millis()>aux2){ //Atualiza as variaveis de saida no serial port a cada intervalo de tempo.
aux2 += sview_refresh;
sview();
}//end if
if(millis()>aux3 & subnav_menu == true){ //Atualiza as variaveis de saida no serial port a cada intervalo de tempo.
aux3 += display_sview_refresh;
display_sview();
}//end if
Serial.println("ROUTINE COMPLETE");
}//end routine function
void display_cfg(void){
lcd.begin(20,4);
lcd.init(); //initialize display
lcd.setBacklight(HIGH); //set backlights on
lcd.setCursor(0,0); //coloca o cursor na coluna 0, linha 0
Serial.println("Display configuration: OK");
}//end display_cfg function
void serial_cfg(void){
Serial.begin(115200); //initialize serial comunication
Wire.begin(); //starts i2C comunication
Serial.println(prd_name);
Serial.print(prd_creator);
Serial.println(prd_version);
Serial.println("===================================");
delay(1000);
Serial.print("Serial configuration: OK");
Serial.println("Baud rate: 115200");
}//end serial_cfg function
void aread(void){
ibat = analogRead(13); //faz a leitura do shunt de corrente de carga da bateria
vbat = analogRead(12); //faz a leitura do divisor de tensao que mede a bateria
temp = analogRead(14); //faz a leitura do sensor de temperatura Lm35
ibatd = analogRead(27); //faz a leitura do shunt de corrente de descarga da bateria.
convert();
}//end aread function
void sview(void){
Serial.print(ibat);
Serial.print("A ! ");
Serial.print(ibatd);
Serial.print("A ! ");
Serial.print(vbat);
Serial.print("V ! ");
Serial.print(charge);
Serial.print("W/h ! ");
Serial.print(discharge);
Serial.print("W/h ! ");
Serial.print(temp);
Serial.print("ºC !");
Serial.print(time_hh);
Serial.print("h");
Serial.print(time_mm);
Serial.print("m");
Serial.print(time_ss);
Serial.print("s");
Serial.print(time_ms);
Serial.print("ms ! ");
Serial.print("nav_menu:");
Serial.print(nav_menu);
Serial.print(" ! bt1_en:");
Serial.print(btn1_en);
Serial.print(" ! bt1_en:");
Serial.print(btn2_en);
Serial.print(" ! ");
print_sts_values();
}//end sview function
void display_sview(void){
lcd.setCursor(0,0);
lcd.print("-------SVIEW-------");
lcd.setCursor(0,1);
lcd.print("Vb:");
lcd.print(vbat);
lcd.print("V !ib:");
lcd.print(ibat);
lcd.print("A ");
lcd.setCursor(0,2);
lcd.print("T:");
lcd.print(temp);
lcd.print("C !E:");
lcd.print(discharge);
lcd.print("Wh ");
lcd.setCursor(0,3);
lcd.print("Time: ");
lcd.print(time_hh);
lcd.print("hh");
lcd.print(time_mm);
lcd.print("mm");
lcd.print(time_ss);
lcd.print("ss ");
}//end display_sview funciton
void convert(void){
ibat *= 33; //100mV/A
ibat /= 4095;
if(ibat>15){
alarm_sts[0] = 1;
}else{
alarm_sts[0] = 0;
}//end else
ibatd *= 33; //100mV/A
ibatd /= 4095;
if(ibatd>15){
alarm_sts[1] = 1;
}else{
alarm_sts[1] = 0;
}//end else
vbat *= 30; //Divisor de tensao de 10x, 3,3V = 33V
vbat /= 4095;
if(vbat>14){
alarm_sts[4] = 1;
}else{
alarm_sts[4] = 0;
}//end else
if(vbat<9){
alarm_sts[5] = 1;
}else{
alarm_sts[5] = 0;
}//end else
temp *= 330; //Sensor de temperatura Lm35, 1ºC = 10mV
temp /= 4095;
if(temp>60){
alarm_sts[2] = 1;
}else{
alarm_sts[2] = 0;
}//end else
if(temp<15){
alarm_sts[3] = 1;
}else{
alarm_sts[3] = 0;
}//end else
calc(); //calcula variaveis que dependem dos valores lidos.
}//end convert function
void calc(void){
if(ibat>0){
lab_cons = 1;
}
if(ibatd>0){
lab_cons = 0;
}
}//end calc function
void time(void){ //Favor terminar essa funcao!
time_ms += time_refresh;
if(time_ms == 1000) {time_ss++; time_ms = 0; consumo(lab_cons); }
if(time_ss == 59) {time_ss = 0; time_mm++;}
if(time_hh == 23 & time_mm == 59) {time_hh = 0; time_mm = 0; time_dd++;}
if(time_mm == 59) {time_mm = 0; time_hh++;}
if(time_dd == 31) {time_mt++; time_dd = 0;}
}//end time funcion
void consumo(int a){
float aux;
if(a){
time_charge++;
aux = vbat * ibat;
aux /= 3600;
charge += (aux * time_charge);
} else {
time_discharge++;
aux = vbat * ibatd;
aux /= 3600;
discharge += (aux * time_discharge);
}//end else
}//end consumo function
bool read_bus_bit(char pin){
return digitalRead(pin);
}//end read_bus_bit funciton.
void menu_display(void){
switch (nav_menu){
case 5:
nav_menu = 0;
active_menu = true;
case 0:
btn1_en = 1;
btn2_en = 1;
lcd.clear();
lcd.print(prd_name);
lcd.setCursor(0,1);
lcd.print(prd_version);
lcd.setCursor(0,2);
lcd.print(prd_creator);
lcd.setCursor(0,3);
lcd.print("-----------------");
break;
case 10: //imprime a tela monitor
if(1){
lcd.clear();
lcd.setCursor(0,0); //posiciona cursor na linha 1
lcd.print("Vbt:");
lcd.print(vbat);
lcd.setCursor(8,0);
lcd.print("V |Pwr:");
lcd.print(power);
lcd.setCursor(18,0);
lcd.print("kW");
lcd.setCursor(0,1);
lcd.print("Iin:");
lcd.print(ibat);
lcd.setCursor(8,1);
lcd.print("A |Iou:");
lcd.print(ibatd);
lcd.setCursor(19,1);
lcd.print("A");
lcd.setCursor(0,2);
lcd.print("Tmp:");
lcd.print(temp);
lcd.setCursor(7,2);
lcd.print("oC |Ene:");
lcd.print(energy);
lcd.setCursor(18,2);
lcd.print("Wh");
lcd.setCursor(0,3);
lcd.print("Time:");
lcd.setCursor(7,3);
lcd.print(time_hh);
lcd.print("h");
lcd.print(time_mm);
lcd.print("|Day: ");
lcd.print(time_dd);
lcd.setCursor(17,3);
lcd.print("/");
lcd.print(time_mt);
}//end if (se a tela nunca foi aberta, imprime tudo pela primeira vez)
break;
case 1:
if(active_menu){
lcd.clear();
lcd.setCursor(0,0);
lcd.print("->Monitor");
lcd.setCursor(0,1);
lcd.print(" Alarms");
lcd.setCursor(0,2);
lcd.print(" Options");
lcd.setCursor(0,3);
lcd.print(" Exit");
} else {
lcd.setCursor(0,0);
lcd.print("->");
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,3);
lcd.print(" ");
}
active_menu = false;
break;
case 2:
lcd.setCursor(0,0);
lcd.print(" ");
lcd.setCursor(0,1);
lcd.print("->");
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,3);
lcd.print(" ");
break;
case 3:
lcd.setCursor(0,0);
lcd.print(" ");
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,2);
lcd.print("->");
lcd.setCursor(0,3);
lcd.print(" ");
break;
case 4:
lcd.setCursor(0,0);
lcd.print(" ");
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,2);
lcd.print(" ");
lcd.setCursor(0,3);
lcd.print("->");
break;
case 16: //Esse caso só ocorre se o caso 1 + BT2 pressionado!
nav_menu = 10;
/*case 10: //Esse caso só ocorre se o caso 1 + BT2 pressionado!
lcd.setCursor(0,0);
lcd.print("Monitor-------------");
lcd.setCursor(0,1);
lcd.print("->Vbat ! Charge ");
lcd.setCursor(0,2);
lcd.print(" Ibat ! Dischg ");
lcd.setCursor(0,3);
lcd.print(" Temp ! Exit ");
break;*/
case 11: //Esse caso só ocorre se o caso 1 + BT2 pressionado!
lcd.setCursor(0,0);
lcd.print("Monitor-------------");
lcd.setCursor(0,1);
lcd.print(" Vbat ! Charge ");
lcd.setCursor(0,2);
lcd.print("->Ibat ! Dischg ");
lcd.setCursor(0,3);
lcd.print(" Temp ! Exit ");
break;
case 12: //Esse caso só ocorre se o caso 1 + BT2 pressionado!
lcd.setCursor(0,0);
lcd.print("Monitor-------------");
lcd.setCursor(0,1);
lcd.print(" Vbat ! Charge ");
lcd.setCursor(0,2);
lcd.print(" Ibat ! Dischg ");
lcd.setCursor(0,3);
lcd.print("->Temp ! Exit ");
break;
case 13: //Esse caso só ocorre se o caso 1 + BT2 pressionado!
lcd.setCursor(0,0);
lcd.print("Monitor-------------");
lcd.setCursor(0,1);
lcd.print(" Vbat !->Charge ");
lcd.setCursor(0,2);
lcd.print(" Ibat ! Dischg ");
lcd.setCursor(0,3);
lcd.print(" Temp ! Exit ");
break;
case 14: //Esse caso só ocorre se o caso 1 + BT2 pressionado!
lcd.setCursor(0,0);
lcd.print("Monitor-------------");
lcd.setCursor(0,1);
lcd.print(" Vbat ! Charge ");
lcd.setCursor(0,2);
lcd.print(" Ibat !->Dischg ");
lcd.setCursor(0,3);
lcd.print(" Temp ! Exit ");
break;
case 15: //Esse caso só ocorre se o caso 1 + BT2 pressionado!
lcd.setCursor(0,0);
lcd.print("Monitor-------------");
lcd.setCursor(0,1);
lcd.print(" Vbat ! Charge ");
lcd.setCursor(0,2);
lcd.print(" Ibat ! Dischg ");
lcd.setCursor(0,3);
lcd.print(" Temp !->Exit ");
break;
}//end switch case
}//end menu_display function
void pin_values(void){
short i;
for(i=0;i<10;i++){ //laco for que coleta os dados de todos os pinos monitorados.
pinread[i] = read_bus_bit(pinname[i]);
}
}//end pin_values function
void print_pin_values(void){
short i;
for(i=0;i<10;i++){ //laco for que imprime todos os dados coletados dos pinos monitorados.
Serial.print(pinread[i]);
}//end for
Serial.println(" "); //Quebra de linha
}//end print_pin_values function
void print_sts_values(void){
short i;
for(i=0;i<10;i++){ //laco for que imprime todos os dados coletados dos pinos monitorados.
Serial.print(alarm_sts[i]);
}//end for
Serial.println(" "); //Quebra de linha
}//end print_sts_values function
void button_pressed(void){
pin_values(); //atualiza os valores dos pinos pressionados.
if(pinread[0]==0){ //se o botao for pressionado executa o IF condicional
nav_menu++; // atualiza a variavel que armazena posicao do menu.
while(1){ //loop para evitar acionamentos consecutivos.
pin_values(); //atualiza os valores e observa se botao foi solto
if(pinread[0]==1){ //se o botao for solto, sai do loop
break;
}//end if
}//end while
menu_display();
}//end if
if(pinread[1]==0){ //se o botao for pressionado executa o IF condicional
menu_select();//Chama a funcao para detectar qual menu sera aberto.
while(1){ //loop para evitar acionamentos consecutivos.
pin_values(); //atualiza os valores e observa se botao foi solto
if(pinread[1]==1){ //se o botao for solto, sai do loop
break;
}//end if
}//end while
menu_display();
}//end ifnd if
}//end button_pressed function
void menu_select(void){
switch (nav_menu){
case 1: //Caso o menu 1 estiver aberto e o botao for pressionado, ele vai para o menu de monitor
aux_button[0] = 1;
nav_menu = 10; //Menu de monitor esta no intervalo de 10 a 20
break;
case 15: //Caso o botao seja pressionado em EXIT do menu Monitor, ele volta para o Menu inicial.
nav_menu = 1;
break;
}//end switch
}//end menu_select function
void interruptcfg(void){
/*INERRUPCOES NOS BOTOES ======================================================================*/
/* attachInterrupt(pin name, function ISR name, type of interrupt detection)
function ISR = Coloque o nome da funcao que sera chamada quando ocorrer a interrupcao
pin name = Coloque o numero do pino utilizado como interrupcao.
Type of interrupt = Configure o tipo de interrupcao:
pode ser dos tipos:
LOW Triggers the interrupt whenever the pin is LOW
HIGH Triggers the interrupt whenever the pin is HIGH
CHANGE Triggers the interrupt whenever the pin changes value, from HIGH to LOW or LOW to HIGH
FALLING Triggers the interrupt when the pin goes from HIGH to LOW
RISING Triggers the interrupt when the pin goes from LOW to HIGH
OBS: Para desativar a interrupcao em um pino, use a funcao abaixo:
detachInterrupt(GPIOPin);
*/
attachInterrupt(15, btn1, FALLING); //Configura o pino 15 com interrupcao.
attachInterrupt( 2, btn2, FALLING); //Configura o pino 15 com interrupcao.
Serial.println("interrupt config: OK");
}//end interruptcfg function
void iocfg(void){
/* ENTRADAS DIGITAIS ================================================================*/
pinMode(25, INPUT_PULLUP); //configura o pino 25 como entrada e com resistor de pullup.
pinMode(26, INPUT_PULLUP); //configura o pino 26 como entrada e com resistor de pullup.
pinMode(19, INPUT_PULLUP); //configura o pino 19 como entrada e com resistor de pullup.
pinMode(18, INPUT_PULLUP); //configura o pino 18 como entrada e com resistor de pullup.
pinMode(5, INPUT_PULLUP); //configura o pino 5 como entrada e com resistor de pullup.
pinMode(4, INPUT_PULLUP); //configura o pino 4 como entrada e com resistor de pullup.
pinMode(2, INPUT_PULLUP); //configura o pino 2 como entrada e com resistor de pullup.
pinMode(15, INPUT_PULLUP); //configura o pino 15 como entrada e com resistor de pullup.
pinMode(17, INPUT_PULLUP); //configura o pino 17 como entrada e com resistor de pullup.
pinMode(16, INPUT_PULLUP); //configura o pino 16 como entrada e com resistor de pullup.
/* ENTRADAS ANALOGICAS =============================================================*/
pinMode(27, INPUT_PULLUP); //configura o pino 27 como entrada e com resistor de pullup.
pinMode(14, INPUT_PULLUP); //configura o pino 14 como entrada e com resistor de pullup.
pinMode(12, INPUT_PULLUP); //configura o pino 12 como entrada e com resistor de pullup.
pinMode(13, INPUT_PULLUP); //configura o pino 13 como entrada e com resistor de pullup.
Serial.println("GPIO config: OK");
}//end iocfg functioN
void disp_vbat(void){
btn1_en = 0; //desabilita o botao select
lcd.setCursor(0,0);
lcd.print("Battery Voltage -----");
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,2);
lcd.print(" Vbat ! ");
lcd.print(vbat);
lcd.setCursor(0,3);
lcd.print(" [ X ]");
}//end disp_voltage
void IRAM_ATTR btn1(void){
b_time = millis();
if (b_time - b_ltime > 250 && btn1_en){ //verifica se não há bouncing (botao pressionado varias vezes em curto intervalo de tempo)
nav_menu++; //atualiza o indice no display lcd.
active_menu = true;
b_ltime = millis(); //atualiza o registro da ultima vez que foi pressionado o botao.
}//end if
}//end IRAM_ATTR btn1 ISR function
void IRAM_ATTR btn2(void){
b_time = millis();
if (b_time - b_ltime > 250 && btn2_en){ //verifica se não há bouncing (botao pressionado varias vezes em curto intervalo de tempo)
btn1_en = false;
nav_menu = nav_menu * 10; //atualiza o indice no display lcd.
if(nav_menu >= 100) {
lcd.clear();
nav_menu = nav_menu / 100;
btn1_en = true;
}//end if
active_menu = true;
menu_monitor_on = false; //desabilita o display
b_ltime = millis(); //atualiza o registro da ultima vez que foi pressionado o botao.
}//end if
}//end IRAM_ATTR btn1 ISR function