#include <Keypad.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <util/delay.h> // Memastikan fungsi _delay_ms() terdefinisi
LiquidCrystal_I2C lcd(0x27, 16, 2);
RTC_DS3231 rtc;
#define set_high(port, pin) port |= (1 << pin)
#define set_low(port, pin) port &= ~(1 << pin)
#define BUZZER PB1 // D9
#define LAMPU PB2 // D10
enum Mode { HOME, SET };
enum SetState { SET_IDLE, SET_MINUTE, SET_SECOND };
SetState setState = SET_IDLE;
Mode mode = HOME;
const byte ROWS = 4;
const byte COLS = 3;
char hexaKeys[ROWS][COLS] = {
{ '1', '2', '3' },
{ '4', '5', '6' },
{ '7', '8', '9' },
{ '*', '0', '#' }
};
byte rowPins[ROWS] = { 2, 3, 4, 5 };
byte colPins[COLS] = { 6, 7, 8 };
Keypad customKeypad = Keypad(makeKeymap(hexaKeys), rowPins, colPins, ROWS, COLS);
char buf[17];
char baris1[17];
char baris2[17];
const char *hari[] = { "Min", "Sen", "Sel", "Rab", "Kam", "Jum", "Sab" };
const char *bulan[] = { "Jan", "Feb", "Mar", "Apr", "Mei", "Jun", "Jul", "Agu", "Sep", "Okt", "Nov", "Des" };
char key;
uint8_t set_menit = 0, set_detik = 0;
DateTime now;
unsigned long lastRTC = 0;
unsigned long alarmStart = 0;
bool alarmActive = false;
bool alarmSet = false; // BARU: Penanda apakah alarm sedang aktif terpasang
int lastSecond = -1;
int lastCountdown = -1;
// ikon bel
byte bellChar[8] = { B00100, B01110, B01110, B01110, B11111, B00000, B00100, B00000 };
// Deklarasi fungsi prototipe
void tampil_home();
void tampil_set(char key);
void beep();
void keypadEvent(KeypadEvent eKey);
int main() {
init();
lcd.init();
lcd.backlight();
lcd.createChar(0, bellChar);
if (!rtc.begin()) {
lcd.setCursor(0, 0);
lcd.print("RTC Error");
while (1);
}
// --- SET WAKTU RTC SEKALI SESUAI JAM LAPTOP ---
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
lcd.clear();
DDRB |= (1 << BUZZER) | (1 << LAMPU);
customKeypad.addEventListener(keypadEvent);
while (1) {
key = customKeypad.getKey();
if (key) beep();
if (millis() - lastRTC >= 100) {
lastRTC = millis();
now = rtc.now();
}
// trigger alarm (hanya berbunyi jika alarmSet bernilai true)
if (alarmSet && !alarmActive && now.minute() == set_menit && now.second() == set_detik) {
alarmActive = true;
alarmStart = millis();
lastCountdown = -1;
}
// ===== ALARM AKTIF =====
if (alarmActive) {
int sisa = 10 - (millis() - alarmStart) / 1000;
if (sisa < 0) sisa = 0;
if (sisa != lastCountdown) {
lastCountdown = sisa;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("!!! ALARM !!!");
lcd.setCursor(0, 1);
lcd.print("Sisa: ");
lcd.print(sisa);
lcd.print(" detik ");
}
// buzzer kedip
if ((millis() / 200) % 2 == 0) set_high(PORTB, BUZZER);
else set_low(PORTB, BUZZER);
// lampu nyala terus
set_high(PORTB, LAMPU);
// stop setelah 10 detik secara otomatis
if (millis() - alarmStart >= 10000) {
alarmActive = false;
alarmSet = false; // Matikan indikator bel setelah durasi alarm habis
set_low(PORTB, BUZZER);
set_low(PORTB, LAMPU);
lcd.clear();
}
continue;
}
// MODE
if (mode == HOME) tampil_home();
else tampil_set(key);
}
return 0;
}
void keypadEvent(KeypadEvent eKey) {
switch (customKeypad.getState()) {
case PRESSED:
break;
case HOLD:
// Tahan tombol '*' untuk masuk menu setting
if (eKey == '*') {
mode = SET;
setState = SET_MINUTE;
set_menit = 0;
set_detik = 0;
alarmSet = false; // Reset status alarm saat mulai setting baru
lcd.clear();
}
// TAHAN tombol '#' untuk mematikan alarm yang sedang berbunyi
if (alarmActive && eKey == '#') {
alarmActive = false;
alarmSet = false; // BARU: Menghilangkan ikon bel saat alarm dimatikan paksa dengan pagar
set_low(PORTB, BUZZER);
set_low(PORTB, LAMPU);
lcd.clear();
}
break;
}
}
void tampil_home() {
if (now.second() == lastSecond) return;
lastSecond = now.second();
snprintf(baris1, sizeof(baris1), "%s, %02d-%s-%04d",
hari[now.dayOfTheWeek()],
now.day(),
bulan[now.month() - 1],
now.year());
snprintf(baris2, sizeof(baris2), "%02d:%02d:%02d",
now.hour(),
now.minute(),
now.second());
lcd.setCursor(0, 0);
lcd.print(baris1);
// jam presisi di bawah tanggal (kolom 4)
lcd.setCursor(4, 1);
lcd.print(baris2);
// Logo bel di sebelah kanan detik (kolom 13)
// Bel HANYA tampil jika alarmSet bernilai true (sudah disetting & belum dimatikan)
lcd.setCursor(13, 1);
if (alarmSet) {
lcd.write(byte(0));
} else {
lcd.print(" ");
}
}
void tampil_set(char key) {
lcd.setCursor(0, 0);
lcd.print("SETTING ALARM ");
if (setState == SET_MINUTE) {
if (key >= '0' && key <= '9') set_menit = (set_menit * 10 + (key - '0')) % 60;
if (key == '#') set_menit = 0;
if (key == '*') setState = SET_SECOND;
snprintf(buf, sizeof(buf), "Jam:__:%02d:__", set_menit);
}
else if (setState == SET_SECOND) {
if (key >= '0' && key <= '9') set_detik = (set_detik * 10 + (key - '0')) % 60;
if (key == '#') set_detik = 0;
if (key == '*') {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("SAVING...");
for (byte i = 0; i < 16; i++) {
lcd.setCursor(i, 1);
lcd.print(">");
_delay_ms(10);
}
mode = HOME;
setState = SET_IDLE;
alarmSet = true; // BARU: Menandakan alarm selesai diset (Ikon bel akan muncul)
lcd.clear();
return;
}
snprintf(buf, sizeof(buf), "Jam:__:%02d:%02d", set_menit, set_detik);
}
lcd.setCursor(0, 1);
lcd.print(buf);
}
void beep() {
set_high(PORTB, BUZZER);
_delay_ms(10);
set_low(PORTB, BUZZER);
}