/*
* RELOJ_LCD V2.0
* Gabriel Tula - 11.10.21
*/
//#define _PROTEUS
//#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "RTClib.h"
LiquidCrystal_I2C lcd(0x27,16,2);
RTC_DS1307 rtc;
byte BTN_PIN = 6;
byte INT_PIN = 2;
byte SPK_PIN = 10;
byte BATT_PIN = A2;
byte SAMPLES = 1; // 30;
byte BATT_ICON = 0;
byte DCHGT_ICON = 7;
uint32_t DEBOUNCETIME = 20;
float VOLTS_PCT[21] = {3.31,3.68,3.69,3.71,3.74,3.76,3.77,3.78,
3.79,3.81,3.82,3.84,3.88,3.92,3.95,3.98,4.00,4.05,4.09,4.13,4.18};
float VREF = 5.0;
float K_FACTOR = VREF / 1024.0;
const byte BATT_CHRS[11][8] = {
{0b00110,0b01111,0b01001,0b01001,0b01001,0b01001,0b01001,0b01111},
{0b00110,0b01111,0b01001,0b01001,0b01001,0b01001,0b01011,0b01111},
{0b00110,0b01111,0b01001,0b01001,0b01001,0b01001,0b01111,0b01111},
{0b00110,0b01111,0b01001,0b01001,0b01001,0b01011,0b01111,0b01111},
{0b00110,0b01111,0b01001,0b01001,0b01001,0b01111,0b01111,0b01111},
{0b00110,0b01111,0b01001,0b01001,0b01011,0b01111,0b01111,0b01111},
{0b00110,0b01111,0b01001,0b01001,0b01111,0b01111,0b01111,0b01111},
{0b00110,0b01111,0b01001,0b01011,0b01111,0b01111,0b01111,0b01111},
{0b00110,0b01111,0b01001,0b01111,0b01111,0b01111,0b01111,0b01111},
{0b00110,0b01111,0b01011,0b01111,0b01111,0b01111,0b01111,0b01111},
{0b00110,0b01111,0b01111,0b01111,0b01111,0b01111,0b01111,0b01111}
};
byte DOWN_CHR[8] = {0b00000,0b00100,0b00100,0b00100,0b10101,0b01110,0b00100,0b00000};
// char daysOfWeek[7][4] = {"Dom","Lun","Mar","Mie","Jue","Vie","Sab"};
// char months[12][4] = {"Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Set","Oct","Nov","Dic"};
//char strBuff[17];
//String strBuff;
String strTime;
bool rfshDate = false;
bool rfshBatt = false;
bool shwVolts = false;
bool bkLight = true;
bool oldBtn = true;
bool debounce = false;
bool holdBtn = false;
bool beep = false;
bool getTemp = true;
bool ledSt = false;
bool battOK = true;
volatile bool alarm = false;
uint32_t newTime, oldTime, oldTimeA2;
uint32_t oldTimeD, oldTimeBtn, oldTimeLed;
uint32_t oldTimeBat, pressTime;
uint32_t ledDelay = 4500;
int auxTemp;
int battAcc = 0;
byte battCnt = 0;
byte battPct;
byte battLvl = 0;
byte oldBattLvl = 0;
float volts = 0.0;
bool get_button(){
static bool oldbtn = true;
uint32_t newtime = millis();
if(debounce){
if(newtime - oldTimeBtn >= DEBOUNCETIME) debounce = false;
}
if(!debounce) {
bool newbtn = !digitalRead(BTN_PIN);
if(newbtn != oldbtn){
debounce = true;
oldTimeBtn = newtime;
oldbtn = newbtn;
}
}
return oldbtn;
}
void format_data(String &buff, int d1, int d2, int d3, char sep){
buff = "";
if(d1 < 10) buff = "0";
buff += d1;
buff += sep ;
if(d2 < 10) buff += "0";
buff += d2;
buff += sep;
if(d3 < 10) buff += "0";
buff += d3;
}
void show_data(){
const char DEG_CHR = 0xDF; // simbolo Grados
//Serial.println(freeRam());
String strBuff;
DateTime actualTime = rtc.now();
// sprintf(strBuff,"%02d:%02d:%02d",actualTime.hour(),actualTime.minute(),actualTime.second());
format_data(strBuff,actualTime.hour(),actualTime.minute(),actualTime.second(),':');
lcd.setCursor(0,0);
lcd.print(strBuff);
//Serial.println(freeRam());
if(getTemp){
getTemp = false;
// auxTemp = (rtc.getTemperature() * 10 + 4) / 10;
auxTemp = 24;
// sprintf(strBuff,"%2d%cC",auxTemp,0xDF);
strBuff = "";
if(auxTemp < 10) strBuff = " ";
strBuff += auxTemp;
strBuff += DEG_CHR;
strBuff += "C";
// Serial.println(strBuff);
lcd.setCursor(12,0);
lcd.print(strBuff);
//Serial.println(freeRam());
}
if(rfshDate){
// sprintf(strBuff,"%02d.%02d.%02d",actualTime.day(),actualTime.month(),actualTime.year() % 100);
format_data(strBuff,actualTime.day(),actualTime.month(),actualTime.year() % 100,'.');
lcd.setCursor(0,1);
lcd.print(strBuff);
//Serial.println(freeRam());
rfshDate = false;
}
if(rfshBatt){
lcd.setCursor(10, 1);
if(shwVolts){
lcd.print(volts,2);
lcd.print('V');
}
else {
lcd.write(32);
if(!battOK){
lcd.write(32);
lcd.write(DCHGT_ICON);
lcd.write(DCHGT_ICON);
lcd.write(DCHGT_ICON);
}
else {
// sprintf(strBuff,"%3d%%",battPct);
strBuff = "";
if(battPct < 100) strBuff += ' ';
if(battPct < 10) strBuff += ' ';
strBuff += battPct;
strBuff += '%';
lcd.print(strBuff);
//Serial.println(freeRam());
}
}
lcd.write(BATT_ICON);
rfshBatt = false;
}
}
bool chk_serial(){
String strBuff;
char inChar;
bool isValid = false;
while(Serial.available() > 0){
inChar = Serial.read();
if(inChar == '\n') break; // si es LF sale
if(isDigit(inChar)) strTime += inChar;
if(inChar == 'H'){
DateTime actualTime = rtc.now();
// sprintf(strBuff,"%02d:%02d:%02d - ",actualTime.hour(),actualTime.minute(),actualTime.second());
format_data(strBuff,actualTime.hour(),actualTime.minute(),actualTime.second(),':');
strBuff += " - ";
Serial.print(strBuff);
// sprintf(strBuff,"%02d.%02d.%02d\r\n",actualTime.day(),actualTime.month(),actualTime.year() % 100);
format_data(strBuff,actualTime.day(),actualTime.month(),actualTime.year() % 100,'.');
strBuff += "\r\n";
Serial.print(strBuff);
Serial.flush();
}
}
isValid = (strTime.length() == 12);
if(!isValid && (inChar == '\n')) strTime = "";
return isValid;
}
byte to_decimal(char dd, char uu){
return (dd & 0x0F) * 10 + (uu & 0x0F);
}
void adjust_time(){
int hh, mm, ss, dd, mo, yy;
hh = to_decimal(strTime[0], strTime[1]);
mm = to_decimal(strTime[2], strTime[3]);
ss = to_decimal(strTime[4], strTime[5]);
dd = to_decimal(strTime[6], strTime[7]);
mo = to_decimal(strTime[8], strTime[9]);
yy = to_decimal(strTime[10], strTime[11]) + 2000;
rtc.adjust(DateTime(yy, mo, dd, hh, mm, ss));
}
void display_test(){
for(byte j = 0; j < 2; j++){
for(byte i = 0; i < 16; i++){
byte chr = 255 - j;
lcd.setCursor(i, 0);
lcd.write(chr);
lcd.setCursor(i, 1);
lcd.write(chr);
delay(100);
}
}
}
void calc_volts(){
battOK = false;
battPct = 0;
float vBatt = (battAcc / float(SAMPLES)) * K_FACTOR;
for(int8_t i = 20; i >= 0; i--){
if(vBatt >= VOLTS_PCT[i]){
battPct = i * 5;
battOK = true;
break;
}
}
battLvl = battPct / 10;
if(battLvl != oldBattLvl){
oldBattLvl = battLvl;
byte tmpChr[8];
memcpy(tmpChr,BATT_CHRS[battLvl],sizeof(BATT_CHRS[battLvl]));
lcd.createChar(BATT_ICON,tmpChr);
}
volts = vBatt * 2.0;
}
void onAlarm(){ // ** ISR para alarma **
alarm = true;
}
void chime_alarm(){
// ** Chime **
if(alarm){
// if(rtc.alarmFired(2)){
// rtc.clearAlarm(2);
// beep = true;
// rfshDate = true;
// }
// tone(SPK_PIN,8100,120);
// oldTimeA2 = newTime;
alarm = false;
}
if(beep){
if(newTime - oldTimeA2 >= 180UL){
tone(SPK_PIN,8100,120);
beep = false;
}
}
}
void read_batt_init(){ // ** Lectura inicial de nivel de batería **
for(byte i = 0; i < SAMPLES; i++){
battAcc += analogRead(BATT_PIN);
delay(1);
}
calc_volts();
battAcc = 0;
}
void read_batt() {
// ** Leer batería cada segundo **
if(newTime - oldTimeBat >= 1000UL){
oldTimeBat = newTime;
battAcc += analogRead(BATT_PIN);
battCnt++;
// ** actualizar nivel de bateria cada x samples
if(battCnt == SAMPLES){
calc_volts();
battCnt = 0;
battAcc = 0;
rfshBatt = true;
}
//Serial.println(freeRam());
}
}
void show_batt_low(){
if(!battOK){
if(newTime - oldTimeLed >= ledDelay){
oldTimeLed = newTime;
ledSt = !ledSt;
digitalWrite(LED_BUILTIN,ledSt);
ledDelay = ledSt ? 500 : 4500;
}
}
else {
if(ledSt){
ledSt = false;
digitalWrite(LED_BUILTIN,LOW);
}
}
}
void refresh_display(){
// ** Actualizar fecha y temperatura cada minuto **
if(newTime - oldTimeD >= 60000UL){
oldTimeD = newTime;
rfshDate = true;
getTemp = true;
}
// ** Actualizar hora cada 200 ms **
if(newTime - oldTime >= 200UL){
oldTime = newTime;
show_data();
}
}
void time_adjust(){
adjust_time();
tone(SPK_PIN,1000,50);
strTime = "";
rfshDate = true;
show_data();
}
void display_init(){
lcd.init();
// lcd.createChar(BATT_ICON,BATT_CHRS[battLvl]);
byte tmpChr[8];
memcpy(tmpChr,BATT_CHRS[battLvl],sizeof(BATT_CHRS[battLvl]));
lcd.createChar(BATT_ICON,tmpChr);
memcpy(tmpChr,DOWN_CHR,sizeof(DOWN_CHR));
lcd.createChar(DCHGT_ICON,tmpChr);
// lcd.createChar(DCHGT_ICON,DOWN_CHR);
lcd.clear();
delay(150);
lcd.backlight();
delay(150);
display_test();
delay(150);
lcd.clear();
}
void rtc_init(){
if (!rtc.begin()){
lcd.setCursor(5,0);
lcd.print(F("RELOJ"));
lcd.setCursor(1,1);
lcd.print(F("NO DISPONIBLE!"));
abort();
}
// rtc.writeSqwPinMode(DS3231_OFF);
// rtc.disable32K();
// rtc.clearAlarm(1);
// rtc.clearAlarm(2);
// rtc.disableAlarm(1);
// rtc.disableAlarm(2);
// attachInterrupt(digitalPinToInterrupt(INT_PIN), onAlarm, FALLING);
// rtc.setAlarm2(DateTime(2022,1,1,0,0,0),DS3231_A2_Minute); // alarma cada hora
}
void button_actions(){
bool newBtn = get_button();
if(newBtn){
pressTime = millis();
while(get_button()){
if(millis() - pressTime >= 500UL){
if(bkLight) holdBtn = true; // indica pulsación larga solo si el display encendido
break;
}
}
}
if(newBtn != oldBtn){
oldBtn = newBtn;
if(newBtn){
tone(SPK_PIN,880,50);
if(holdBtn){
shwVolts = !shwVolts;//true;
oldTimeBat = millis();
battCnt = 0;
battAcc = 0;
rfshBatt = true;
}
else {
bkLight = !bkLight;
if(bkLight){
rfshDate = true;
show_data();
lcd.display();
delay(100);
lcd.backlight();
}
else{
lcd.noDisplay();
lcd.noBacklight();
}
}
}
else {
holdBtn = false;
}
}
}
//int freeRam() {
// extern int __heap_start, *__brkval;
// int v;
// return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
//}
void setup(){
pinMode(BTN_PIN,INPUT_PULLUP);
pinMode(INT_PIN,INPUT_PULLUP);
pinMode(SPK_PIN,OUTPUT);
pinMode(LED_BUILTIN,OUTPUT);
digitalWrite(LED_BUILTIN,LOW);
strTime.reserve(15);
#ifdef _PROTEUS
Serial.begin(57600); // *** Proteus
#else
Serial.begin(115200);
#endif
// Wire.setClock(400000);
display_init(); // iniciar display y presentación
rtc_init(); // iniciar y verificar reloj
delay(1000);
show_data();
delay(300);
rfshDate = true;
read_batt_init(); // primera lectura de batería
rfshBatt = true;
oldTime = millis();
oldTimeD = oldTime;
oldTimeBat = oldTime;
Serial.println(F("LISTO!")); // mensaje para soft de PC
Serial.flush();
}
void loop (){
newTime = millis();
chime_alarm(); // ** Chime **
if(!rfshBatt){
read_batt(); // ** Leer batería cada segundo **
}
show_batt_low(); // ** Chequear si batería baja y señalizarlo **
if(bkLight){
refresh_display(); // ** Refrescar display **
}
if(chk_serial()){ // ** Chequear puerto serie *
time_adjust(); // ** Ajustar fecha y hora **
}
button_actions(); // ** Acciones desencadenadas por el botón
delay(1); // wokwi
}