Jam Seven segment.
jam digital dengan fitur menampilkan Suhu, Kelembapan, Alarm.
hardware: Seven segment COM ANODA, DHT 11, RTC, ESP32, BUZZER.
/*
HARDWARE: ESP32, RTC, DHT 11 , TOMBOL .
seven segment COM ANODA.
TR PNP 9012 X4 untuk trigger multiplex
TR NPN 2N2222 X2 untuk buzzer dan dot led.
*/
#include <Wire.h>
#include <RTClib.h>
#include <DHT.h>
#include <EEPROM.h>
#define SER_PIN 23
#define SRCLK_PIN 19
#define RCLK_PIN 18
#define DOT_PIN 2
#define BUZZER 33
#define MENU 26
#define SET 27
#define DHT_PIN 25
#define DHTTYPE DHT11
DHT dht(DHT_PIN, DHTTYPE);
RTC_DS3231 rtc;
#define NOTE_C4 523
#define NOTE_E4 659
#define NOTE_G4 784
#define NOTE_C5 1047
#define NOTE_E5 1319
#define NOTE_G5 1568
int melody[] = {
NOTE_E5, NOTE_E5, 0,
NOTE_E5, 0, NOTE_C5, NOTE_E5,
NOTE_G5, 0, NOTE_G4
};
int noteDurations[] = {
8, 8, 4,
8, 4, 8, 4,
4, 4, 4
};
uint8_t angkaAktif[4] = {8, 8, 8, 8};
unsigned long lastSwitch = 0;
uint8_t step = 0;
uint8_t digitAktif = 0;
unsigned long lastMux = 0;
// Mode tampilan
enum Mode { MODE_JAM, MODE_SUHU, MODE_KELEMBAPAN };
Mode mode = MODE_JAM;
unsigned long lastModeSwitch = 0;
bool inSettingMode = false;
uint8_t setStep = 0; // 0: jam, 1: menit, 2: selesai
uint8_t setJam = 0, setMenit = 0;
unsigned long menuPressTime = 0;
bool menuHoldDetected = false;
bool menuReleasedAfterHold = false;
unsigned long lastAlarmCheck = 0;
int lastAlarmMinute = -1; // mendeteksi perubahan menit
bool inAlarmSettingMode = false;
uint8_t alarmSetStep = 0;
bool setHoldDetected = false;
bool setReleasedAfterHold = false;
unsigned long setPressTime = 0;
int jamAlarmAwal = 5; // default 05.00
int jamAlarmAkhir = 21; // default 21.00
int alarmTiap30Menit = 1; // default aktif
void setup() {
Serial.begin(115200);
Wire.begin();
rtc.begin();
dht.begin();
EEPROM.begin(16);
pinMode(SER_PIN, OUTPUT);
pinMode(SRCLK_PIN, OUTPUT);
pinMode(RCLK_PIN, OUTPUT);
pinMode(DOT_PIN, OUTPUT);
pinMode(MENU, INPUT_PULLUP);
pinMode(SET, INPUT_PULLUP);
pinMode(BUZZER, OUTPUT);
digitalWrite(DOT_PIN, LOW); // Dot mati. PAKAI TRANSISTOR NPN
if (rtc.lostPower()) {
rtc.adjust(DateTime(2025, 1, 1, 0, 0, 0)); // Set default waktu jika RTC belum diset
tone(BUZZER, 1000, 3000); //Bunyikan buzzer peringatan RTC Jam bermasalah.
for(int i = 0; i < 10; i++ ){
tone(BUZZER, 200 + (i * 100), 200);
delay(80);
}
for(int i = 0; i < 10; i++ ){
tone(BUZZER, 200 + (i * 100), 200);
delay(50);
}
noTone(BUZZER);
}
Serial.println("digital clock ready");
// Tes tampilan angka dari 0000, 1111, ..., 9999
for (int d = 0; d <= 9; d++) {
angkaAktif[0] = d;
angkaAktif[1] = d;
angkaAktif[2] = d;
angkaAktif[3] = d;
unsigned long start = millis();
while (millis() - start < 400) { // tampilkan selama 200 ms per angka
tampilDigit(digitAktif, angkaAktif[digitAktif]);
digitAktif = (digitAktif + 1) % 4;
}
}
tone(BUZZER, 1000, 100);
noTone(BUZZER);
jamAlarmAwal = EEPROM.read(0);
if (jamAlarmAwal > 23) jamAlarmAwal = 5;
jamAlarmAkhir = EEPROM.read(1);
if (jamAlarmAkhir > 23) jamAlarmAkhir = 21;
alarmTiap30Menit = EEPROM.read(2);
if (alarmTiap30Menit > 1) alarmTiap30Menit = 1;
Serial.print("Alarm Awal: "); Serial.println(jamAlarmAwal);
Serial.print("Alarm Akhir: "); Serial.println(jamAlarmAkhir);
Serial.print("30 Menit: "); Serial.println(alarmTiap30Menit);
}
void loop() {
unsigned long now = millis();
// Deteksi tahan SET untuk masuk mode setting alarm
if (!inSettingMode && !inAlarmSettingMode) {
if (digitalRead(SET) == LOW) {
if (setPressTime == 0) setPressTime = now;
if (now - setPressTime >= 5000 && !setHoldDetected) {
tone(BUZZER, 1000, 200);
inAlarmSettingMode = true;
alarmSetStep = 0;
setHoldDetected = true;
setReleasedAfterHold = false;
Serial.println("Masuk mode setting alarm");
}
} else {
setPressTime = 0;
}
}
if (inAlarmSettingMode) {
// Tampilkan data alarm yang sedang disetel
angkaAktif[0] = 10;
angkaAktif[1] = 10;
if (alarmSetStep == 0) {
angkaAktif[2] = jamAlarmAwal / 10;
angkaAktif[3] = jamAlarmAwal % 10;
} else if (alarmSetStep == 1) {
angkaAktif[2] = jamAlarmAkhir / 10;
angkaAktif[3] = jamAlarmAkhir % 10;
} else if (alarmSetStep == 2) {
angkaAktif[2] = 0;
angkaAktif[3] = alarmTiap30Menit;
}
if (!setReleasedAfterHold) {
if (digitalRead(SET) == HIGH) {
setReleasedAfterHold = true;
}
} else {
if (digitalRead(SET) == LOW) {
tone(BUZZER, 600, 100);
delay(200);
while (digitalRead(SET) == LOW);
if (alarmSetStep == 0) jamAlarmAwal = (jamAlarmAwal + 1) % 24;
else if (alarmSetStep == 1) jamAlarmAkhir = (jamAlarmAkhir + 1) % 24;
else if (alarmSetStep == 2) alarmTiap30Menit = (alarmTiap30Menit + 1) % 2;
}
if (digitalRead(MENU) == LOW) {
tone(BUZZER, 1200, 100);
delay(200);
while (digitalRead(MENU) == LOW);
alarmSetStep++;
if (alarmSetStep >= 3) {
inAlarmSettingMode = false;
setHoldDetected = false;
// Simpan ke EEPROM
EEPROM.write(0, jamAlarmAwal);
EEPROM.write(1, jamAlarmAkhir);
EEPROM.write(2, alarmTiap30Menit);
EEPROM.commit();
Serial.println("Selesai setting alarm");
tone(BUZZER, 1100, 300);
delay(200);
tone(BUZZER, 1500, 500);
}
}
}
}
// Jalankan alarm hanya jika tidak sedang masuk menu setting
if (!inSettingMode && !inAlarmSettingMode && now - lastAlarmCheck > 1000) {
lastAlarmCheck = now;
DateTime t = rtc.now();
int jam = t.hour();
int menit = t.minute();
if (jam >= jamAlarmAwal && jam <= jamAlarmAkhir) {
// Cegah pemutaran ganda di menit yang sama
if (lastAlarmMinute != menit) {
lastAlarmMinute = menit;
if (menit == 0) {
nadaCampina();
} else if (menit == 30 && alarmTiap30Menit == 1) {
tone(BUZZER, 1000);
delay(2000);
noTone(BUZZER);
mainkanMelodiMario();
}
}
}
}
// Deteksi tahan tombol MENU untuk masuk mode set waktu
// === MODE SETTING WAKTU ===
if (!inSettingMode && !inAlarmSettingMode) {
if (digitalRead(MENU) == LOW) {
if (menuPressTime == 0) menuPressTime = now;
if (now - menuPressTime >= 5000 && !menuHoldDetected) {
tone(BUZZER, 1500, 300);
inSettingMode = true; //masuk ke menu setting jam mode.
setStep = 0;
menuHoldDetected = true;
menuReleasedAfterHold = false;
DateTime nowTime = rtc.now();
setJam = nowTime.hour();
setMenit = nowTime.minute();
Serial.println("Masuk mode set waktu");
}
} else {
menuPressTime = 0;
}
}
if (inSettingMode) {
// Tampilkan nilai jam/menit yang sedang disetel
angkaAktif[0] = 10;
angkaAktif[1] = 10;
if (setStep == 0) {
angkaAktif[2] = setJam / 10;
angkaAktif[3] = setJam % 10;
} else if (setStep == 1) {
angkaAktif[2] = setMenit / 10;
angkaAktif[3] = setMenit % 10;
}
if (!menuReleasedAfterHold) {
if (digitalRead(MENU) == HIGH) {
menuReleasedAfterHold = true;
}
} else {
if (digitalRead(MENU) == LOW) {
tone(BUZZER, 1500, 300);
delay(100);
while (digitalRead(MENU) == LOW);
setStep++;
if (setStep == 2) {
rtc.adjust(DateTime(2025, 1, 1, setJam, setMenit, 0));
inSettingMode = false;
menuHoldDetected = false;
tone(BUZZER, 1100, 300);
delay(200);
tone(BUZZER, 1500, 500);
Serial.println("Selesai set waktu");
}
}
if (digitalRead(SET) == LOW) {
tone(BUZZER, 500, 100);
delay(100);
while (digitalRead(SET) == LOW);
if (setStep == 0) setJam = (setJam + 1) % 24;
else if (setStep == 1) setMenit = (setMenit + 1) % 60;
}
}
}
// Ganti mode tampilan setiap 8 + 2 + 2 detik (total 12 detik siklus)
if (!inSettingMode && !inAlarmSettingMode) {
// MODE NORMAL: logika rotasi tampilan
if (now - lastModeSwitch > 12000) {
lastModeSwitch = now;
mode = MODE_JAM;
} else if (now - lastModeSwitch > 10000) {
mode = MODE_KELEMBAPAN;
} else if (now - lastModeSwitch > 8000) {
mode = MODE_SUHU;
} else {
mode = MODE_JAM;
}
// Update angkaAktif[] berdasarkan mode
if (mode == MODE_JAM) {
DateTime nowTime = rtc.now();
int jam = nowTime.hour();
int menit = nowTime.minute();
angkaAktif[0] = jam / 10;
angkaAktif[1] = jam % 10;
angkaAktif[2] = menit / 10;
angkaAktif[3] = menit % 10;
} else if (mode == MODE_SUHU) {
float suhu = dht.readTemperature();
int val = suhu * 100;
angkaAktif[0] = (val / 1000) % 10;
angkaAktif[1] = (val / 100) % 10;
angkaAktif[2] = (val / 10) % 10;
angkaAktif[3] = 10;
} else if (mode == MODE_KELEMBAPAN) {
float hum = dht.readHumidity();
int val = hum * 100;
angkaAktif[0] = (val / 1000) % 10;
angkaAktif[1] = (val / 100) % 10;
angkaAktif[2] = 10;
angkaAktif[3] = 10;
}
}
// Multiplex tiap 2ms
if (now - lastMux >= 2) {
lastMux = now;
tampilDigit(digitAktif, angkaAktif[digitAktif]);
digitAktif = (digitAktif + 1) % 4;
}
}
// Fungsi menampilkan satu digit
void tampilDigit(uint8_t pos, uint8_t angka) {
if (angka > 9) {
angka = 0x0F; // semua segmen mati
}
uint8_t bcd = angka & 0x0F;
uint8_t select = ~(1 << (4 + pos));
uint8_t data = (select & 0xF0) | (bcd & 0x0F);
digitalWrite(RCLK_PIN, LOW);
shiftOutStable(SER_PIN, SRCLK_PIN, MSBFIRST, data);
digitalWrite(RCLK_PIN, HIGH);
// Logika pengaturan DOT
if (mode == MODE_JAM && (pos == 1 || pos == 2)) {
digitalWrite(DOT_PIN, HIGH); // titik jam antara HH:MM
} else if (mode == MODE_SUHU && pos == 1) {
digitalWrite(DOT_PIN, HIGH); // titik suhu di digit ke-2 sebagai koma
} else {
digitalWrite(DOT_PIN, LOW); // mati untuk kelembapan & lainnya
}
}
void shiftOutStable(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val) {
for (uint8_t i = 0; i < 8; i++) {
uint8_t bitVal = (bitOrder == LSBFIRST) ? (val >> i) & 1 : (val >> (7 - i)) & 1;
digitalWrite(dataPin, bitVal);
delayMicroseconds(2);
digitalWrite(clockPin, HIGH);
delayMicroseconds(2);
digitalWrite(clockPin, LOW);
}
}
void mainkanMelodiMario() {
int panjang = sizeof(melody) / sizeof(melody[0]);
for (int i = 0; i < panjang; i++) {
int noteDuration = 1000 / noteDurations[i];
if (melody[i] > 0)
tone(BUZZER, melody[i], noteDuration);
delay(noteDuration * 1.30); // ada jeda antar nada
noTone(BUZZER);
}
}
void nadaCampina() { //CAMPINA
int nada[] = {1568, 1568, 1568, 1760, 1760, 1760, 1568, 1760, 1396, 1568, 2094, 2094, 2094, 1976, 1760, 1568, 1568, 2348, 2094, 1320, 1396, 1396, 1320, 1176, 2348, 2094, 2094}
;
int durasi[] = {80, 80, 80, 200, 80, 80, 150, 80, 80, 300, 80, 80, 80, 80, 80, 200, 80, 80, 200, 80, 200, 80, 80, 200, 80, 80, 400 };
int jumlahNada = sizeof(nada) / sizeof(nada[0]);
// Mainkan nada
for (int i = 0; i < jumlahNada; i++) {
tone(BUZZER, nada[i]);
delay(durasi[i]);
noTone(BUZZER);
delay(150);
}
}