// =====================================================
// BIBLIOTECAS
// =====================================================
// Biblioteca para comunicacao I2C.
// Ela sera usada pelo modulo RTC.
#include <Wire.h>
// Biblioteca para facilitar o uso do RTC.
#include <RTClib.h>
// Biblioteca para controlar LCD em modo paralelo.
#include <LiquidCrystal.h>
// =====================================================
// OBJETOS PRINCIPAIS
// =====================================================
// LCD em modo paralelo.
// Ordem dos parametros:
// RS, E, D4, D5, D6, D7
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);
// Objeto do modulo RTC.
RTC_DS1307 rtc;
// =====================================================
// PINOS
// =====================================================
// Pinos do encoder
int clkPin = 2;
int dtPin = 3;
int swPin = 4;
// Pino do buzzer
int buzzerPin = 5;
// =====================================================
// VARIAVEIS DO ENCODER
// =====================================================
// Guarda o ultimo estado do pino CLK
int lastClkState;
// Guarda o estado atual do pino CLK
int currentClkState;
// =====================================================
// MODOS DE TELA
// 0 = tela UTC
// 1 = menu de cidades
// 2 = tela da cidade escolhida
// =====================================================
int screenMode = 0;
// =====================================================
// CIDADES E OFFSETS
// =====================================================
// Lista de cidades
String cityNames[] = {
"UTC",
"Sao Paulo",
"Buenos Aires",
"New York",
"Lisboa",
"Paris",
"Dubai",
"Tokyo",
"Sydney"
};
// Lista de offsets em relacao ao UTC
int cityOffsets[] = {
0,
-3,
-3,
-5,
0,
1,
4,
9,
10
};
// Quantidade de cidades
int cityCount = 9;
// Cidade destacada no menu
int menuIndex = 0;
// Cidade confirmada pelo usuario
int selectedCity = 0;
// =====================================================
// CONTROLE DO BOTAO
// =====================================================
// Guarda o ultimo estado do botao
int lastButtonState = HIGH;
// Guarda o instante do ultimo clique valido
unsigned long lastButtonTime = 0;
// Tempo de debounce em milissegundos
int debounceTime = 180;
// =====================================================
// AJUSTE DA REFERENCIA UTC
// =====================================================
// Consideramos que o RTC esta ajustado com o horario
// de Sao Paulo. Para obter UTC, somamos 3 horas.
int utcReferenceOffset = 3;
// =====================================================
// CONTROLE DE ATUALIZACAO DA TELA
// =====================================================
// Guarda o instante da ultima atualizacao do display
unsigned long lastScreenUpdate = 0;
// Atualiza a tela a cada 250 ms
int screenUpdateInterval = 250;
// =====================================================
// CONTROLE DO PISCA DO NOME DA CIDADE
// =====================================================
// Guarda o instante da ultima troca do efeito de pisca
unsigned long lastBlinkTime = 0;
// Intervalo do pisca em milissegundos
int blinkInterval = 1000;
// true = mostra o nome da cidade
// false = esconde o nome da cidade
bool cityNameVisible = true;
// =====================================================
// SETUP
// =====================================================
void setup() {
// Inicializa o LCD com 20 colunas e 4 linhas
lcd.begin(20, 4);
// Configura os pinos do encoder como entrada com pull-up
pinMode(clkPin, INPUT_PULLUP);
pinMode(dtPin, INPUT_PULLUP);
pinMode(swPin, INPUT_PULLUP);
// Configura o buzzer como saida
pinMode(buzzerPin, OUTPUT);
// Inicia a comunicacao I2C
Wire.begin();
// Inicia o RTC
if (!rtc.begin()) {
printLine(0, "Erro no RTC");
printLine(1, "Verifique modulo");
printLine(2, "");
printLine(3, "");
while (true) {
delay(100);
}
}
// -------------------------------------------------
// USE APENAS UMA VEZ PARA AJUSTAR O RTC
// Depois comente esta linha e grave novamente
// -------------------------------------------------
// rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// Le o estado inicial do encoder
lastClkState = digitalRead(clkPin);
// Mostra a tela inicial
showUtcScreen();
}
// =====================================================
// LOOP PRINCIPAL
// =====================================================
void loop() {
// Le o encoder
readEncoder();
// Le o botao do encoder
readButton();
// Controla o pisca do nome da cidade
// millis() retorna o tempo, em ms, desde que o Arduino ligou
if (millis() - lastBlinkTime >= blinkInterval) {
lastBlinkTime = millis();
// Inverte o valor logico:
// true vira false, false vira true
cityNameVisible = !cityNameVisible;
}
// Atualiza a tela em intervalos controlados
if (millis() - lastScreenUpdate >= screenUpdateInterval) {
lastScreenUpdate = millis();
if (screenMode == 0) {
showUtcScreen();
}
if (screenMode == 2) {
showSelectedCityScreen();
}
}
// Pequeno delay para aliviar o processador
delay(2);
}
// =====================================================
// LEITURA DO ENCODER
// =====================================================
void readEncoder() {
currentClkState = digitalRead(clkPin);
// Detecta mudanca no pino CLK
if (currentClkState != lastClkState) {
// Usa a transicao para LOW como referencia
if (currentClkState == LOW) {
// Se DT for diferente de CLK, gira em um sentido.
// Caso contrario, gira no outro.
if (digitalRead(dtPin) != currentClkState) {
menuIndex++;
} else {
menuIndex--;
}
// So altera a selecao se estiver no menu
if (screenMode == 1) {
if (menuIndex >= cityCount) {
menuIndex = 0;
}
if (menuIndex < 0) {
menuIndex = cityCount - 1;
}
beep();
showMenuScreen();
}
}
}
// Guarda o estado atual para a proxima comparacao
lastClkState = currentClkState;
}
// =====================================================
// LEITURA DO BOTAO DO ENCODER
// =====================================================
void readButton() {
int buttonState = digitalRead(swPin);
// Detecta o momento em que o botao foi pressionado
if (buttonState == LOW && lastButtonState == HIGH) {
// Debounce simples
if (millis() - lastButtonTime > debounceTime) {
lastButtonTime = millis();
beep();
// Se estava na tela UTC, entra no menu
if (screenMode == 0) {
screenMode = 1;
showMenuScreen();
}
// Se estava no menu, confirma a cidade
else if (screenMode == 1) {
selectedCity = menuIndex;
screenMode = 2;
// Reinicia o efeito de pisca
cityNameVisible = true;
lastBlinkTime = millis();
showSelectedCityScreen();
}
// Se estava vendo a cidade, volta para o menu
else if (screenMode == 2) {
screenMode = 1;
showMenuScreen();
}
}
}
// Guarda o ultimo estado do botao
lastButtonState = buttonState;
}
// =====================================================
// TELA UTC
// =====================================================
void showUtcScreen() {
DateTime rtcNow = rtc.now();
// Soma 3 horas para converter Sao Paulo em UTC
DateTime utcNow = rtcNow + TimeSpan(0, utcReferenceOffset, 0, 0);
String line0 = "World Time Terminal";
String line1 = "Referencia: UTC";
String line2 = formatDate(utcNow.day(), utcNow.month(), utcNow.year());
String line3 = formatTime(utcNow.hour(), utcNow.minute(), utcNow.second());
printLine(0, line0);
printLine(1, line1);
printLine(2, line2);
printLine(3, line3);
}
// =====================================================
// TELA DO MENU
// =====================================================
void showMenuScreen() {
String line0 = "Selecione a cidade";
String line1 = "Gire o encoder";
String line2 = "> " + cityNames[menuIndex];
String line3 = "Clique confirma";
printLine(0, line0);
printLine(1, line1);
printLine(2, line2);
printLine(3, line3);
}
// =====================================================
// TELA DA CIDADE ESCOLHIDA
// =====================================================
void showSelectedCityScreen() {
DateTime rtcNow = rtc.now();
// Primeiro converte o horario de Sao Paulo para UTC
DateTime utcNow = rtcNow + TimeSpan(0, utcReferenceOffset, 0, 0);
// Depois aplica o offset da cidade escolhida
DateTime localTime = utcNow + TimeSpan(0, cityOffsets[selectedCity], 0, 0);
String line0 = "Cidade selecionada";
// Monta o texto do offset
String offsetText = formatOffset(cityOffsets[selectedCity]);
String cityText = "";
String line1 = "";
// Se o nome deve aparecer, mostra a cidade.
// Se nao deve, substitui por espacos do mesmo tamanho.
if (cityNameVisible) {
cityText = cityNames[selectedCity];
} else {
for (int i = 0; i < cityNames[selectedCity].length(); i++) {
cityText += " ";
}
}
// Calcula quantos espacos devem existir entre o nome
// da cidade e o offset para que o offset fique fixo
// no final da linha de 20 colunas
int spaceCount = 20 - cityText.length() - offsetText.length();
// Garante pelo menos 1 espaco
if (spaceCount < 1) {
spaceCount = 1;
}
// Monta a linha 1
line1 = cityText;
for (int i = 0; i < spaceCount; i++) {
line1 += " ";
}
line1 += offsetText;
String line2 = formatDate(localTime.day(), localTime.month(), localTime.year());
String line3 = formatTime(localTime.hour(), localTime.minute(), localTime.second());
printLine(0, line0);
printLine(1, line1);
printLine(2, line2);
printLine(3, line3);
}
// =====================================================
// FORMATA OFFSET
// Exemplo: UTC+10, UTC-3, UTC+0
// =====================================================
String formatOffset(int offsetValue) {
String text = "UTC";
if (offsetValue >= 0) {
text += "+";
}
text += String(offsetValue);
return text;
}
// =====================================================
// FORMATA DATA NO FORMATO DD/MM/AAAA
// =====================================================
String formatDate(int dayValue, int monthValue, int yearValue) {
String text = "";
if (dayValue < 10) text += "0";
text += String(dayValue);
text += "/";
if (monthValue < 10) text += "0";
text += String(monthValue);
text += "/";
text += String(yearValue);
return text;
}
// =====================================================
// FORMATA HORA NO FORMATO HH:MM:SS
// =====================================================
String formatTime(int hourValue, int minuteValue, int secondValue) {
String text = "";
if (hourValue < 10) text += "0";
text += String(hourValue);
text += ":";
if (minuteValue < 10) text += "0";
text += String(minuteValue);
text += ":";
if (secondValue < 10) text += "0";
text += String(secondValue);
return text;
}
// =====================================================
// ESCREVE UMA LINHA COMPLETA NO LCD
// Isso evita sobra de caracteres antigos
// =====================================================
void printLine(int row, String text) {
lcd.setCursor(0, row);
lcd.print(text);
// Completa o restante da linha com espacos
for (int i = text.length(); i < 20; i++) {
lcd.print(" ");
}
}
// =====================================================
// BIPE CURTO NO BUZZER
// =====================================================
void beep() {
tone(buzzerPin, 2000, 70);
}