/**
* Arduino Digital Alarm Clock
* Copyright (C) 2020, Uri Shaked. MIT License.
*
* Descrição rápida:
* - Display 4 dígitos/7 segmentos via SevSeg
* - 3 botões: hora, minuto, alarme (com auto-repeat)
* - Máquina de estados para UI (relógio, status alarme, hora alarme, tocando, soneca)
*/
#include <SevSeg.h>
#include "Button.h"
#include "AlarmTone.h"
#include "Clock.h"
#include "config.h"
// GPIOs
const int COLON_PIN = 13; // Dois-pontos do display (nível invertido: LOW = aceso)
const int SPEAKER_PIN = A3; // Buzzer/alto-falante (usado como digital)
// Botões (entradas analógicas usadas como digitais)
Button hourButton(A0);
Button minuteButton(A1);
Button alarmButton(A2);
// Serviços
AlarmTone alarmTone;
Clock clock;
SevSeg sevseg;
// Estados de UI
enum DisplayState {
DisplayClock, // Exibe HHMM, permite ajustar hora/minuto
DisplayAlarmStatus, // Mostra " on " / " off " após toggle do alarme
DisplayAlarmTime, // Exibe/edita hora do alarme (HHMM)
DisplayAlarmActive, // Alarme tocando; curto = soneca, longo = parar
DisplaySnooze, // Tela "****" por alguns ms após ativar soneca
};
DisplayState displayState = DisplayClock;
long lastStateChange = 0;
// Helpers de estado/tempo
void changeDisplayState(DisplayState newValue) {
displayState = newValue;
lastStateChange = millis();
}
long millisSinceStateChange() {
return millis() - lastStateChange;
}
// Controle do "colon" (dois-pontos) do display (LOW = aceso)
void setColon(bool value) {
digitalWrite(COLON_PIN, value ? LOW : HIGH);
}
// Exibe hora atual no display e pisca os dois-pontos a cada segundo
void displayTime() {
DateTime now = clock.now();
bool blinkState = (now.second() % 2) == 0;
sevseg.setNumber(now.hour() * 100 + now.minute()); // Formato HHMM
setColon(blinkState);
}
// Estado principal: relógio
void clockState() {
displayTime();
// Se botão ALARME foi solto e o alarme está ativo, entrar no estado "tocando"
if (alarmButton.read() == Button::RELEASED && clock.alarmActive()) {
(void)alarmButton.has_changed(); // limpa a borda na lib Button
changeDisplayState(DisplayAlarmActive);
return;
}
// Ajustes hora/minuto com auto-repeat
if (hourButton.pressed()) {
clock.incrementHour();
}
if (minuteButton.pressed()) {
clock.incrementMinute();
}
// Toggle do alarme e mostra status por alguns ms
if (alarmButton.pressed()) {
clock.toggleAlarm();
changeDisplayState(DisplayAlarmStatus);
}
}
// Mostra " on " / " off " por ALARM_STATUS_DISPLAY_TIME
void alarmStatusState() {
setColon(false);
sevseg.setChars(clock.alarmEnabled() ? " on" : " off");
if (millisSinceStateChange() > ALARM_STATUS_DISPLAY_TIME) {
changeDisplayState(clock.alarmEnabled() ? DisplayAlarmTime : DisplayClock);
return;
}
}
// Exibe/edita a hora do alarme (HHMM) por ALARM_HOUR_DISPLAY_TIME
void alarmTimeState() {
DateTime alarm = clock.alarmTime();
sevseg.setNumber(alarm.hour() * 100 + alarm.minute(), -1 /* sem ponto */);
// Sai por timeout ou botão ALARME
if (millisSinceStateChange() > ALARM_HOUR_DISPLAY_TIME || alarmButton.pressed()) {
changeDisplayState(DisplayClock);
return;
}
// Edição com auto-repeat; ao editar, renova o timeout
if (hourButton.pressed()) {
clock.incrementAlarmHour();
lastStateChange = millis();
}
if (minuteButton.pressed()) {
clock.incrementAlarmMinute();
lastStateChange = millis();
}
if (alarmButton.pressed()) {
changeDisplayState(DisplayClock);
}
}
// Alarme tocando: gerencia som, soneca (curto) e parar (longo)
void alarmState() {
displayTime(); // Continua mostrando a hora
// Enquanto o botão está sendo "lido como solto", manter tocando
if (alarmButton.read() == Button::RELEASED) {
alarmTone.play();
}
// Se pressionado, silencia (enquanto pressiona)
if (alarmButton.pressed()) {
alarmTone.stop();
}
// Na borda de soltura, decide soneca (curto) ou parar (longo)
if (alarmButton.released()) {
alarmTone.stop();
bool longPress = alarmButton.repeat_count() > 0; // >=1 s conforme set_repeat
if (longPress) {
clock.stopAlarm(); // para o alarme
changeDisplayState(DisplayClock);
} else {
clock.snooze(); // agenda soneca
changeDisplayState(DisplaySnooze);
}
}
}
// Tela de soneca por alguns ms
void snoozeState() {
sevseg.setChars("****");
if (millisSinceStateChange() > SNOOZE_DISPLAY_TIME) {
changeDisplayState(DisplayClock);
return;
}
}
void setup() {
Serial.begin(115200);
clock.begin(); // inicia RTC/relógio interno e controle de alarme
// Configura botões com auto-repeat
hourButton.begin();
hourButton.set_repeat(500, 200); // 0,5 s para iniciar; repete a cada 200 ms
minuteButton.begin();
minuteButton.set_repeat(500, 200);
alarmButton.begin();
alarmButton.set_repeat(1000, -1); // define limite de "pressão longa" em ~1 s
alarmTone.begin(SPEAKER_PIN); // inicializa saída para buzzer
pinMode(COLON_PIN, OUTPUT);
// Configuração do display 7 segmentos (4 dígitos)
byte digits = 4;
byte digitPins[] = {2, 3, 4, 5};
byte segmentPins[] = {6, 7, 8, 9, 10, 11, 12};
bool resistorsOnSegments = false; // resistores não estão nos segmentos
bool updateWithDelays = false; // multiplex sem delays bloqueantes
bool leadingZeros = true; // mostra zeros à esquerda (ex.: 09:03)
bool disableDecPoint = true; // ponto decimal desativado
sevseg.begin(DISPLAY_TYPE, digits, digitPins, segmentPins,
resistorsOnSegments, updateWithDelays, leadingZeros, disableDecPoint);
sevseg.setBrightness(90); // 0–100
}
void loop() {
// Necessário para o multiplex do display (chamar sempre e com frequência)
sevseg.refreshDisplay();
// Máquina de estados da interface
switch (displayState) {
case DisplayClock: clockState(); break;
case DisplayAlarmStatus: alarmStatusState();break;
case DisplayAlarmTime: alarmTimeState(); break;
case DisplayAlarmActive: alarmState(); break;
case DisplaySnooze: snoozeState(); break;
}
}