#include <LiquidCrystal.h>
#include <EEPROM.h>
// --- Hardware Definitions ---
const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
const int BUZZER_PIN = 13;
// --- Constants ---
const int NUM_TIMERS = 4;
const int MASTER_CHANNEL = 0;
const unsigned long UPDATE_INTERVAL = 200;
const unsigned long ONE_HOUR_SECONDS = 3600;
const unsigned long LONG_PRESS_TIME = 3000;
const unsigned long ALARM_INCREMENT = 60;
const unsigned long INACTIVITY_RESET_TIME = 8000;
const unsigned long ALARM_DURATION = 20;
const unsigned long BLINKING_TIME = 500;
// --- Button Pins ---
const int buttonPins[NUM_TIMERS] = {6, 7, 8, 9};
const int ALARM_BUTTON = 10;
// --- EEPROM Addresses ---
const int EEPROM_START_ADDRESS = 0;
const int ALARM_TIME_SIZE = sizeof(unsigned long);
// --- Data Structures ---
struct TimerData {
unsigned long startTime;
unsigned long elapsedTime;
bool isRunning;
bool longFormat;
unsigned long alarmTime;
unsigned long lastAlarmUpdateTime;
bool alarmTriggered;
unsigned long alarmEndTime;
};
TimerData timers[NUM_TIMERS];
bool blinkColon = false;
unsigned long lastSecond = 0;
bool alarmSetupMode = false;
bool setupButtonHeld = false;
unsigned long lastActivityTime = 0;
bool muteMode = false;
unsigned long lastMutePressTime = 0;
// --- Button Debouncing Variables ---
unsigned long lastDebounceTime[NUM_TIMERS + 1] = {0, 0, 0, 0, 0};
const int debounceDelay = 50;
unsigned long lastAlarmButtonDebounceTime = 0;
// --- Long Press Variables ---
unsigned long buttonPressTime[NUM_TIMERS] = {0, 0, 0, 0};
bool buttonHeld[NUM_TIMERS] = {false, false, false, false};
// --- Last Displayed Values ---
unsigned long lastDisplayedTime[NUM_TIMERS];
unsigned long lastDisplayedAlarmTime[NUM_TIMERS];
// --- Blinking Variables ---
bool alarmBlinking[NUM_TIMERS] = {false};
unsigned long alarmBlinkTime = 0;
// --- Function Declarations ---
void updateTimers();
void displayTimers();
void printTime(int timerIndex, unsigned long totalSeconds);
void checkButtons();
void displayAlarmSetup();
unsigned long readAlarmTimeFromEEPROM(int timerIndex);
void writeAlarmTimeToEEPROM(int timerIndex, unsigned long alarmTime);
void printAlarmTime(int timerIndex);
void incrementAlarmTime(int timerIndex);
void resetAlarmTime(int timerIndex);
void copyMasterAlarmToOthers();
void resetAllAlarmTimes();
void checkAlarms();
void setup() {
lcd.begin(16, 2);
Serial.begin(9600);
Serial.println("Program Started");
pinMode(BUZZER_PIN, OUTPUT);
unsigned long currentTime = millis();
for (int i = 0; i < NUM_TIMERS; i++) {
timers[i].startTime = currentTime;
timers[i].elapsedTime = 0;
timers[i].isRunning = true;
timers[i].longFormat = false;
timers[i].alarmTime = readAlarmTimeFromEEPROM(i);
timers[i].lastAlarmUpdateTime = 0;
timers[i].alarmTriggered = false;
timers[i].alarmEndTime = 0;
if (timers[i].alarmTime == 0xFFFFFFFF) {
timers[i].alarmTime = 0;
writeAlarmTimeToEEPROM(i, timers[i].alarmTime);
}
pinMode(buttonPins[i], INPUT_PULLUP);
buttonPressTime[i] = 0;
buttonHeld[i] = false;
lastDisplayedTime[i] = 0xFFFFFFFF;
lastDisplayedAlarmTime[i] = 0xFFFFFFFF;
}
pinMode(ALARM_BUTTON, INPUT_PULLUP);
lastSecond = millis() / 1000;
}
void loop() {
checkButtons();
updateTimers();
if (!alarmSetupMode) {
displayTimers();
} else {
displayAlarmSetup();
}
delay(UPDATE_INTERVAL);
}
void updateTimers() {
unsigned long currentTime = millis();
unsigned long currentSecond = currentTime / 1000;
if (currentSecond != lastSecond) {
blinkColon = !blinkColon;
lastSecond = currentSecond;
for (int i = 0; i < NUM_TIMERS; i++) {
if (timers[i].isRunning) {
timers[i].elapsedTime = (currentTime - timers[i].startTime) / 1000;
if (timers[i].elapsedTime >= ONE_HOUR_SECONDS && !timers[i].longFormat) {
timers[i].longFormat = true;
}
if (timers[i].elapsedTime >= (99 * ONE_HOUR_SECONDS + (59 * 60))) {
timers[i].isRunning = false;
timers[i].elapsedTime = (99 * ONE_HOUR_SECONDS + (59 * 60));
}
}
}
}
if (!alarmSetupMode) {
checkAlarms();
}
}
void displayTimers() {
unsigned long currentTime = millis();
for (int i = 0; i < NUM_TIMERS; i++) {
int row = i / 2;
int col = (i % 2) * 8;
lcd.setCursor(col, row);
lcd.print(i + 1);
lcd.print("-");
if (alarmBlinking[i]) {
if ((currentTime - alarmBlinkTime) % (BLINKING_TIME * 2) < BLINKING_TIME) {
printTime(i, timers[i].elapsedTime);
} else {
lcd.setCursor(col + 2, row);
lcd.print(" ");
}
} else {
printTime(i, timers[i].elapsedTime);
}
// Clear X position first
lcd.setCursor(col + 7, row);
if (muteMode) {
lcd.print("X");
} else {
lcd.print(" "); // Clear X when unmuted
}
}
}
void printTime(int timerIndex, unsigned long totalSeconds) {
int hours = totalSeconds / ONE_HOUR_SECONDS;
int minutes = (totalSeconds % ONE_HOUR_SECONDS) / 60;
int seconds = totalSeconds % 60;
lcd.setCursor((timerIndex % 2) * 8 + 2, timerIndex / 2);
if (timers[timerIndex].longFormat) {
if (hours < 10) lcd.print("0");
lcd.print(hours);
lcd.print(blinkColon ? ":" : " ");
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
} else {
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
lcd.print(blinkColon ? ":" : " ");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
}
}
void checkButtons() {
int alarmButtonState = digitalRead(ALARM_BUTTON);
unsigned long currentTime = millis();
// Alarm setup button handling
if (alarmButtonState == LOW) {
if (millis() - lastAlarmButtonDebounceTime > debounceDelay) {
if (!setupButtonHeld) {
alarmSetupMode = true;
lastAlarmButtonDebounceTime = millis();
setupButtonHeld = true;
lastActivityTime = currentTime;
noTone(BUZZER_PIN);
for (int i = 0; i < NUM_TIMERS; i++) {
timers[i].alarmTriggered = false;
timers[i].alarmEndTime = 0;
}
}
}
} else {
if (millis() - lastAlarmButtonDebounceTime > debounceDelay) {
if (setupButtonHeld) {
alarmSetupMode = false;
lastAlarmButtonDebounceTime = millis();
setupButtonHeld = false;
for (int i = 0; i < NUM_TIMERS; i++) {
writeAlarmTimeToEEPROM(i, timers[i].alarmTime);
}
for (int i = 0; i < NUM_TIMERS; i++) {
timers[i].alarmTriggered = false;
timers[i].alarmEndTime = 0;
}
for (int i = 0; i < NUM_TIMERS; i++) {
if (timers[i].alarmTime > 0 && timers[i].alarmTime > timers[i].elapsedTime) {
timers[i].lastAlarmUpdateTime = millis();
} else {
timers[i].lastAlarmUpdateTime = 0;
}
}
}
}
}
if (alarmSetupMode && (currentTime - lastActivityTime > INACTIVITY_RESET_TIME)) {
resetAllAlarmTimes();
lastActivityTime = currentTime + 100000;
}
// Mute handling (buttons 1 and 5 pressed together)
bool button1Pressed = digitalRead(buttonPins[0]) == LOW;
bool button5Pressed = alarmButtonState == LOW;
if (button1Pressed && button5Pressed) {
if (millis() - lastMutePressTime > debounceDelay) {
lastMutePressTime = millis();
muteMode = !muteMode;
}
}
// Channel button handling
for (int i = 0; i < NUM_TIMERS; i++) {
int buttonState = digitalRead(buttonPins[i]);
if (alarmSetupMode && buttonState == LOW) {
if (millis() - lastDebounceTime[i] > debounceDelay) {
lastDebounceTime[i] = millis();
if (!buttonHeld[i]) {
buttonHeld[i] = true;
buttonPressTime[i] = millis();
incrementAlarmTime(i);
if (i == MASTER_CHANNEL) {
copyMasterAlarmToOthers();
}
lastActivityTime = currentTime;
timers[i].alarmTriggered = false;
}
}
if ((millis() - buttonPressTime[i]) >= LONG_PRESS_TIME) {
resetAlarmTime(i);
if (i == MASTER_CHANNEL) {
copyMasterAlarmToOthers();
}
buttonPressTime[i] = millis() + 100000;
lastActivityTime = currentTime;
timers[i].alarmTriggered = false;
}
} else {
if (buttonState == LOW) {
if (millis() - lastDebounceTime[i] > debounceDelay) {
lastDebounceTime[i] = millis();
if (!buttonHeld[i]) {
buttonHeld[i] = true;
buttonPressTime[i] = millis();
}
}
if ((millis() - buttonPressTime[i]) >= LONG_PRESS_TIME) {
timers[i].elapsedTime = 0;
timers[i].startTime = millis();
displayTimers();
timers[i].alarmTriggered = false;
alarmBlinking[i] = false;
}
} else {
if (buttonHeld[i]) {
buttonHeld[i] = false;
// Short press handling
if (timers[i].alarmEndTime > 0) {
timers[i].alarmEndTime = 0;
timers[i].alarmTriggered = false;
}
}
}
}
}
}
void displayAlarmSetup() {
lcd.setCursor(0, 0);
lcd.print("A>");
printAlarmTime(0);
lcd.print(" A>");
printAlarmTime(1);
lcd.setCursor(0, 1);
lcd.print("A>");
printAlarmTime(2);
lcd.print(" A>");
printAlarmTime(3);
}
void printAlarmTime(int timerIndex) {
unsigned long alarmTime = timers[timerIndex].alarmTime;
int alarmHours = (alarmTime / 3600) % 100;
int alarmMinutes = (alarmTime % 3600) / 60;
lcd.setCursor((timerIndex % 2) * 8 + 2, timerIndex / 2);
if (alarmHours < 10) {
lcd.print("0");
}
lcd.print(alarmHours);
lcd.print(":");
if (alarmMinutes < 10) {
lcd.print("0");
}
lcd.print(alarmMinutes);
}
unsigned long readAlarmTimeFromEEPROM(int timerIndex) {
int address = EEPROM_START_ADDRESS + (timerIndex * ALARM_TIME_SIZE);
unsigned long alarmTime;
EEPROM.get(address, alarmTime);
return alarmTime;
}
void writeAlarmTimeToEEPROM(int timerIndex, unsigned long alarmTime) {
int address = EEPROM_START_ADDRESS + (timerIndex * ALARM_TIME_SIZE);
EEPROM.put(address, alarmTime);
}
void incrementAlarmTime(int timerIndex) {
timers[timerIndex].alarmTime += ALARM_INCREMENT;
if (timers[timerIndex].alarmTime >= 360000) {
timers[timerIndex].alarmTime = 0;
}
timers[timerIndex].alarmTriggered = false;
timers[timerIndex].alarmEndTime = 0;
timers[timerIndex].lastAlarmUpdateTime = millis();
if (timerIndex == MASTER_CHANNEL) {
copyMasterAlarmToOthers();
}
displayAlarmSetup();
}
void resetAlarmTime(int timerIndex) {
timers[timerIndex].alarmTime = 0;
timers[timerIndex].alarmTriggered = false;
timers[timerIndex].alarmEndTime = 0;
displayAlarmSetup();
}
void copyMasterAlarmToOthers() {
unsigned long masterAlarmTime = timers[MASTER_CHANNEL].alarmTime;
for (int i = 0; i < NUM_TIMERS; i++) {
if (i != MASTER_CHANNEL) {
timers[i].alarmTime = masterAlarmTime;
timers[i].lastAlarmUpdateTime = millis();
timers[i].alarmTriggered = false;
}
}
}
void resetAllAlarmTimes() {
for (int i = 0; i < NUM_TIMERS; i++) {
timers[i].alarmTime = 0;
}
displayAlarmSetup();
}
void resetTimer(int timerIndex) {
timers[timerIndex].elapsedTime = 0;
timers[timerIndex].startTime = millis();
timers[timerIndex].alarmTriggered = false;
timers[timerIndex].alarmEndTime = 0;
alarmBlinking[timerIndex] = false;
displayTimers();
}
void checkAlarms() {
unsigned long currentTime = millis() / 1000;
for (int i = 0; i < NUM_TIMERS; i++) {
if (timers[i].alarmTime > 0 && timers[i].alarmTime <= timers[i].elapsedTime && timers[i].alarmTime > timers[i].elapsedTime - 1 && !timers[i].alarmTriggered) {
timers[i].alarmEndTime = currentTime + ALARM_DURATION;
timers[i].alarmTriggered = true;
if (!muteMode) tone(BUZZER_PIN, 1000);
alarmBlinking[i] = true;
alarmBlinkTime = millis();
}
if (timers[i].alarmEndTime > 0 && currentTime < timers[i].alarmEndTime) {
if (!muteMode) tone(BUZZER_PIN, 1000);
} else {
noTone(BUZZER_PIN);
if (timers[i].alarmEndTime > 0 && currentTime >= timers[i].alarmEndTime) {
timers[i].alarmEndTime = 0;
timers[i].alarmTriggered = false;
}
}
}
}
/*
**Key Fixes:**
✅ **X Persistence:** Explicitly clears the X position when unmuted
✅ **Visual Consistency:** Ensures "X" disappears **completely** for all channels
✅ **Mute Toggle:** Works via buttons 1 and 5
**Test Steps:**
1. **Trigger Mute:** Press buttons 1 and 5 together.
2. **Unmute:** Press buttons 1 and 5 again.
3. **Verify X:** Check channels 2 and 4 for no lingering "X".
Let me know if you need further adjustments! 🔧
*/