#include <Wire.h>
#include <RTClib.h>
#include <LiquidCrystal.h>
// ======================================================
// OBJETOS DAS BIBLIOTECAS
// ======================================================
// RTC do tipo DS1307
RTC_DS1307 rtc;
// LCD 20x4 em modo paralelo
// Ordem: RS, E, D4, D5, D6, D7
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
// ======================================================
// PINOS DO PROJETO
// ======================================================
const int pinEncoderClk = 2;
const int pinEncoderDt = 3;
const int pinEncoderSw = 4;
const int pinBuzzer = 5;
// ======================================================
// DADOS DAS CIDADES
// ======================================================
struct Cidade {
const char* nomeCidade;
int utcCidade;
};
Cidade listaCidades[] = {
{"Sao Paulo", -3},
{"Buenos Aires", -3},
{"New York", -5},
{"Lisboa", 0},
{"Paris", 1},
{"Dubai", 4},
{"Tokyo", 9},
{"Sydney", 10}
};
const int totalCidades = sizeof(listaCidades) / sizeof(listaCidades[0]);
// ======================================================
// VARIAVEIS DE CONTROLE
// ======================================================
int indiceCidade = 0;
bool estaNoMenu = true;
int ultimoEstadoClk;
bool ultimoEstadoBotao = HIGH;
unsigned long ultimoTempoBotao = 0;
unsigned long ultimoTempoAtualizacao = 0;
// ======================================================
// FUNCOES AUXILIARES
// ======================================================
// Emite um bip curto no buzzer
void tocarBuzzer() {
tone(pinBuzzer, 2000, 60);
}
// Verifica se um ano é bissexto
bool anoBissexto(int ano) {
return ((ano % 4 == 0 && ano % 100 != 0) || (ano % 400 == 0));
}
// Retorna o total de dias de um mês
int obterDiasDoMes(int mes, int ano) {
if (mes == 1) return 31;
if (mes == 2) return anoBissexto(ano) ? 29 : 28;
if (mes == 3) return 31;
if (mes == 4) return 30;
if (mes == 5) return 31;
if (mes == 6) return 30;
if (mes == 7) return 31;
if (mes == 8) return 31;
if (mes == 9) return 30;
if (mes == 10) return 31;
if (mes == 11) return 30;
return 31;
}
// Aplica o UTC da cidade sobre a hora lida do RTC
void aplicarUtcNaDataHora(int &dia, int &mes, int &ano, int &hora, int utcCidade) {
hora = hora + utcCidade;
while (hora >= 24) {
hora = hora - 24;
dia++;
if (dia > obterDiasDoMes(mes, ano)) {
dia = 1;
mes++;
if (mes > 12) {
mes = 1;
ano++;
}
}
}
while (hora < 0) {
hora = hora + 24;
dia--;
if (dia < 1) {
mes--;
if (mes < 1) {
mes = 12;
ano--;
}
dia = obterDiasDoMes(mes, ano);
}
}
}
// ======================================================
// FUNCOES DE EXIBICAO
// ======================================================
// Mostra o menu de selecao
void mostrarMenu() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Escolha a cidade:");
lcd.setCursor(0, 1);
lcd.print("> ");
lcd.print(listaCidades[indiceCidade].nomeCidade);
lcd.setCursor(0, 2);
lcd.print("Gire para mudar");
lcd.setCursor(0, 3);
lcd.print("Botao: selecionar");
}
// Mostra data, hora e UTC da cidade
void mostrarHorarioDaCidade() {
DateTime agoraUtc = rtc.now();
int diaAtual = agoraUtc.day();
int mesAtual = agoraUtc.month();
int anoAtual = agoraUtc.year();
int horaAtual = agoraUtc.hour();
int minutoAtual = agoraUtc.minute();
int segundoAtual = agoraUtc.second();
aplicarUtcNaDataHora(
diaAtual,
mesAtual,
anoAtual,
horaAtual,
listaCidades[indiceCidade].utcCidade
);
char textoLinha[21];
lcd.setCursor(0, 0);
snprintf(textoLinha, sizeof(textoLinha), "%-20s", listaCidades[indiceCidade].nomeCidade);
lcd.print(textoLinha);
lcd.setCursor(0, 1);
snprintf(textoLinha, sizeof(textoLinha), "Data: %02d/%02d/%04d", diaAtual, mesAtual, anoAtual);
lcd.print(textoLinha);
lcd.setCursor(0, 2);
snprintf(textoLinha, sizeof(textoLinha), "Hora: %02d:%02d:%02d", horaAtual, minutoAtual, segundoAtual);
lcd.print(textoLinha);
lcd.setCursor(0, 3);
snprintf(textoLinha, sizeof(textoLinha), "UTC: %+d", listaCidades[indiceCidade].utcCidade);
lcd.print(textoLinha);
}
// ======================================================
// LEITURA DO ENCODER
// ======================================================
// Le o giro do encoder
void lerEncoder() {
int estadoAtualClk = digitalRead(pinEncoderClk);
// Detecta mudança no CLK do encoder
if (estadoAtualClk != ultimoEstadoClk) {
// Quando o CLK vai para LOW, lemos o DT para descobrir o sentido.
// Essa é uma forma comum e didática de leitura do KY-040. [web:27]
if (estadoAtualClk == LOW) {
int estadoDt = digitalRead(pinEncoderDt);
if (estadoDt == HIGH) {
indiceCidade++;
if (indiceCidade >= totalCidades) {
indiceCidade = 0;
}
} else {
indiceCidade--;
if (indiceCidade < 0) {
indiceCidade = totalCidades - 1;
}
}
tocarBuzzer();
if (estaNoMenu) {
mostrarMenu();
} else {
lcd.clear();
mostrarHorarioDaCidade();
}
}
}
ultimoEstadoClk = estadoAtualClk;
}
// Le o botao do encoder
void lerBotaoEncoder() {
bool estadoAtualBotao = digitalRead(pinEncoderSw);
// O botão usa INPUT_PULLUP, então pressionado = LOW,
// como indicado no comportamento do KY-040 no Wokwi. [web:27]
if (estadoAtualBotao == LOW && ultimoEstadoBotao == HIGH) {
if (millis() - ultimoTempoBotao > 200) {
estaNoMenu = !estaNoMenu;
ultimoTempoBotao = millis();
tocarBuzzer();
lcd.clear();
if (estaNoMenu) {
mostrarMenu();
} else {
mostrarHorarioDaCidade();
}
}
}
ultimoEstadoBotao = estadoAtualBotao;
}
// ======================================================
// SETUP
// ======================================================
void setup() {
pinMode(pinEncoderClk, INPUT);
pinMode(pinEncoderDt, INPUT);
pinMode(pinEncoderSw, INPUT_PULLUP);
pinMode(pinBuzzer, OUTPUT);
// Inicializa o LCD
lcd.begin(20, 4);
lcd.setCursor(0, 0);
lcd.print("Iniciando RTC...");
// Inicializa o RTC DS1307
if (!rtc.begin()) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("RTC nao encontrado");
while (true) {
}
}
// Se o RTC não estiver rodando, ajusta com a data/hora da compilação.
// Esse fluxo é o usado nos exemplos da RTClib para DS1307. [web:64]
if (!rtc.isrunning()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
ultimoEstadoClk = digitalRead(pinEncoderClk);
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Relogio Mundial");
lcd.setCursor(0, 1);
lcd.print("LCD 20x4 + DS1307");
lcd.setCursor(0, 2);
lcd.print("Wokwi");
tocarBuzzer();
delay(1200);
mostrarMenu();
}
// ======================================================
// LOOP PRINCIPAL
// ======================================================
void loop() {
lerEncoder();
lerBotaoEncoder();
// Atualiza o relógio somente quando estiver fora do menu
if (!estaNoMenu) {
if (millis() - ultimoTempoAtualizacao >= 300) {
ultimoTempoAtualizacao = millis();
mostrarHorarioDaCidade();
}
}
}