#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <RTClib.h>
#include <EEPROM.h>
LiquidCrystal_I2C lcd(0x27, 16, 2); // Address 0x27 for a 16x2 LCD
RTC_DS3231 rtc; // Create an instance of the DS3231 object
const int buttonUpPin = 7;
const int buttonDownPin = 6;
const int buttonSelectPin = 5;
const int buzzerPin = 4; // Define the buzzer pin
const int buttonOffPin = 2; // Define the push button pin
int currentMode = 0; // 0: Time Mode, 1: Date Mode, 2: Alarm Mode, 3: View Alarm, 4: Clear Alarm, 5: Date and Time Change
void handleStopAlarmButton();
int buttonOffState = HIGH;
struct Alarm {
int hour;
int minute;
int tuneIndex; // Index of the selected tune
};
int snoozeDuration = 5; // Default snooze duration in minutes // Snooze duration in minutes
unsigned long snoozeEndTime = 0; // Time when snooze will end
bool snoozeActive = false; // Flag to indicate if snooze is active
const int snoozeButtonPin = 3; // Define the snooze button pin
int maxMode = 8; // Updated total number of modes to include Set Snooze Time mode // Updated total number of modes to include Stopwatch mode
// Total number of modes
const int numTunes = 3;
const int tuneFrequencies[numTunes][3] = {
{1000, 1200, 1500}, // Frequencies for Tune 1
{800, 1000, 1200}, // Frequencies for Tune 2
{600, 800, 1000} // Frequencies for Tune 3
};
const int tuneDurations[numTunes][3] = {
{500, 500, 500}, // Durations for Tune 1 (in milliseconds)
{300, 300, 300}, // Durations for Tune 2
{400, 400, 400} // Durations for Tune 3
};
bool stopAlarm = false;
const int maxAlarms = 5; // Maximum number of alarms
Alarm alarms[maxAlarms]; // Array to store alarm settings for each slot
bool buzzerActive = false; // Flag to indicate whether the buzzer is active
unsigned long lastButtonPressTime = 0; // Variable to store the time of the last button press
const unsigned long backlightOffDelay = 10000; // Delay in milliseconds before turning off backlight (10 seconds)
void setup() {
// Initialize pins and LCD
pinMode(buttonUpPin, INPUT_PULLUP);
pinMode(buttonDownPin, INPUT_PULLUP);
pinMode(buttonSelectPin, INPUT_PULLUP);
pinMode(buttonOffPin, INPUT_PULLUP);
pinMode(snoozeButtonPin, INPUT_PULLUP);
pinMode(buzzerPin, OUTPUT);
lcd.init();
lcd.backlight();
Wire.begin();
rtc.begin(); // Initialize the DS3231
int address = 0;
EEPROM.get(address, alarms);
// Ensure that tuneIndex is initialized
for (int i = 0; i < maxAlarms; i++) {
if (alarms[i].tuneIndex < 0 || alarms[i].tuneIndex >= numTunes) {
alarms[i].tuneIndex = 0; // Default to first tune
}
}
showTime(); // Display current time
}
void loop() {
handleStopAlarmButton(); // Check for the stop alarm button
// Check for alarm
if (!buzzerActive) { // Only handle button presses when the buzzer is not active
if (digitalRead(buttonUpPin) == LOW) {
currentMode = (currentMode + 1) % (maxMode + 1); // Wrap around to first mode
displayMode();
delay(200); // Button debounce
}
if (digitalRead(buttonDownPin) == LOW) {
currentMode = (currentMode - 1 + maxMode + 1) % (maxMode + 1); // Wrap around to last mode
displayMode();
delay(200); // Button debounce
}
if (digitalRead(buttonSelectPin) == LOW) {
selectMode();
delay(200); // Button debounce
}
}
// Check for alarm
if (currentMode != 2 && !buzzerActive) { // Check alarm only if not in alarm mode and buzzer is not active
checkAlarm();
}
// Display current time in Time Mode
if (currentMode == 0) {
showTime();
}
}
void checkAlarm() {
static unsigned long lastCheckTime = 0;
unsigned long currentMillis = millis();
if (currentMillis - lastCheckTime >= 1000) { // Check every second
lastCheckTime = currentMillis;
DateTime now = rtc.now(); // Get current time
for (int i = 0; i < maxAlarms; i++) {
if (alarms[i].hour == now.hour() && alarms[i].minute == now.minute() && !buzzerActive && !snoozeActive) {
buzzerActive = true;
triggerAlarm(i);
}
}
// Check if snooze time is over
if (snoozeActive && currentMillis >= snoozeEndTime) {
snoozeActive = false;
triggerAlarm(-1); // Use -1 to indicate that this is a snooze alarm
}
}
}
void triggerAlarm(int alarmIndex) {
int tune = (alarmIndex >= 0) ? alarms[alarmIndex].tuneIndex : 0; // Default to the first tune for snooze
unsigned long alarmStartMillis = millis();
bool backlightOn = true;
while (millis() - alarmStartMillis < 10000) { // Blink and sound for 10 seconds
for (int j = 0; j < 3; j++) {
tone(buzzerPin, tuneFrequencies[tune][j]);
delay(tuneDurations[tune][j]);
noTone(buzzerPin);
// Check for the stop alarm button
if (digitalRead(buttonOffPin) == LOW) {
buzzerActive = false; // Set buzzerActive to false
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Alarm stopped");
delay(2000); // Display "Alarm stopped" message for 2 seconds
showTime(); // Return to showing time
return; // Exit the function
}
// Check for the snooze button
if (digitalRead(snoozeButtonPin) == LOW) {
handleSnooze();
return; // Exit the function
}
}
// Toggle backlight state
if (backlightOn) {
lcd.noBacklight();
} else {
lcd.backlight();
}
backlightOn = !backlightOn;
delay(500); // Delay between blinks
}
// Ensure backlight is turned on after the alarm
lcd.backlight();
buzzerActive = false;
}
void handleSnooze() {
buzzerActive = false;
snoozeActive = true;
snoozeEndTime = millis() + snoozeDuration * 60000; // Set snooze end time
unsigned long snoozeEndTimeInSeconds = snoozeDuration * 60; // Convert snooze duration to seconds
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Snoozing...");
for (unsigned long remainingTime = snoozeEndTimeInSeconds; remainingTime > 0; remainingTime--) {
lcd.setCursor(0, 1);
lcd.print("Time left: ");
lcd.print(remainingTime / 60); // Display minutes
lcd.print(":");
if (remainingTime % 60 < 10) {
lcd.print("0"); // Add leading zero for single-digit seconds
}
lcd.print(remainingTime % 60); // Display seconds
delay(1000); // Update every second
}
snoozeActive = false; // Snooze period is over
triggerAlarm(-1); // Use -1 to indicate that this is a snooze alarm
}
void showTime() {
DateTime now = rtc.now(); // Get the current time
lcd.setCursor(0, 0);
lcd.print("Time: ");
if (now.hour() < 12) {
lcd.print(now.hour() == 0 ? 12 : now.hour()); // Convert 0 to 12 for AM
lcd.print(":");
} else {
lcd.print(now.hour() == 12 ? 12 : now.hour() - 12); // Convert 12-hour format to 1-12 for PM
lcd.print(":");
}
if (now.minute() < 10) lcd.print("0");
lcd.print(now.minute());
lcd.print(":");
if (now.second() < 10) lcd.print("0");
lcd.print(now.second());
lcd.print(" ");
lcd.print(now.hour() < 12 ? "AM" : "PM");
// Display the alarm time below the current time
lcd.setCursor(0, 1);
lcd.print("Alarm: ");
lcd.print(alarms[0].hour < 10 ? "0" : "");
lcd.print(alarms[0].hour);
lcd.print(":");
lcd.print(alarms[0].minute < 10 ? "0" : "");
lcd.print(alarms[0].minute);
}
void displayMode() {
lcd.clear();
lcd.setCursor(0, 0);
switch (currentMode) {
case 0:
lcd.print("> Time Mode");
break;
case 1:
lcd.print("> Date Mode");
break;
case 2:
lcd.print("> Alarm Mode");
break;
case 3:
lcd.print("> View Alarm");
break;
case 4:
lcd.print("> Clear Alarm");
break;
case 5:
lcd.print("> Date & Time Change");
break;
case 6:
lcd.print("> Set Tune");
break;
case 7:
lcd.print("> Stopwatch"); // New mode for Stopwatch
break;
case 8:
lcd.print("> Set Snooze Time");
break;
}
}
void selectMode() {
switch (currentMode) {
case 0:
showTime();
break;
case 1:
showDate();
break;
case 2:
setAlarm();
break;
case 3:
viewAlarm();
break;
case 4:
clearAlarm();
break;
case 5:
changeDateTime();
break;
case 6:
setTune();
break;
case 7:
stopwatch(); // Function to handle the Stopwatch mode
break;
case 8:
setSnoozeTime();
break;
}
}
void setSnoozeTime() {
int cursorPosition = 0; // 0: hours, 1: minutes, 2: confirm
int snoozeHours = 0; // Initialize snooze hours
int snoozeMinutes = 0; // Initialize snooze minutes
bool buttonUpPressed = false;
bool buttonDownPressed = false;
bool buttonSelectPressed = false;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set Snooze Time");
while (true) {
lcd.setCursor(0, 1);
lcd.print(" "); // Clear the line
// Display the current setting based on cursor position
lcd.setCursor(0, 1);
if (cursorPosition == 0) {
lcd.print("Hours: ");
lcd.print(snoozeHours < 10 ? "0" : ""); // Add leading zero if necessary
lcd.print(snoozeHours);
} else if (cursorPosition == 1) {
lcd.print("Minutes: ");
lcd.print(snoozeMinutes < 10 ? "0" : ""); // Add leading zero if necessary
lcd.print(snoozeMinutes);
} else {
lcd.print("Confirm");
}
// Handle button presses with debouncing
if (digitalRead(buttonUpPin) == LOW && !buttonUpPressed) {
buttonUpPressed = true;
if (cursorPosition == 0) {
snoozeHours = (snoozeHours + 1) % 24; // Increment snooze hours (wrap around at 24)
} else if (cursorPosition == 1) {
snoozeMinutes = (snoozeMinutes + 1) % 60; // Increment snooze minutes (wrap around at 60)
}
} else if (digitalRead(buttonUpPin) == HIGH) {
buttonUpPressed = false;
}
if (digitalRead(buttonDownPin) == LOW && !buttonDownPressed) {
buttonDownPressed = true;
if (cursorPosition == 0) {
snoozeHours = (snoozeHours - 1 + 24) % 24; // Decrement snooze hours (wrap around at 0)
} else if (cursorPosition == 1) {
snoozeMinutes = (snoozeMinutes - 1 + 60) % 60; // Decrement snooze minutes (wrap around at 0)
}
} else if (digitalRead(buttonDownPin) == HIGH) {
buttonDownPressed = false;
}
if (digitalRead(buttonSelectPin) == LOW && !buttonSelectPressed) {
buttonSelectPressed = true;
if (cursorPosition == 2) {
// Confirm snooze duration and return to Time Mode
snoozeDuration = snoozeHours * 60 + snoozeMinutes; // Combine hours and minutes into snooze duration in minutes
currentMode = 0;
showTime();
return;
} else {
cursorPosition = (cursorPosition + 1) % 3; // Move to next position
}
} else if (digitalRead(buttonSelectPin) == HIGH) {
buttonSelectPressed = false;
}
// Additional delay to allow button release
delay(100);
}
}
void stopwatch() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Stopwatch Mode");
delay(1000); // Display "Stopwatch Mode" message for 1 second
unsigned long startTime = 0;
unsigned long lapStartTime = 0; // Variable to store start time of each lap
unsigned long elapsedTime = 0;
unsigned long lapElapsedTime = 0; // Variable to store elapsed time for each lap
bool running = false;
while (true) {
if (digitalRead(buttonSelectPin) == LOW) { // Start/Stop button
if (!running) {
startTime = millis() - elapsedTime; // Resume from where it was stopped
lapStartTime = millis() - lapElapsedTime; // Set lap start time
running = true;
} else {
elapsedTime = millis() - startTime; // Pause the stopwatch
lapElapsedTime = millis() - lapStartTime; // Pause the lap timer
running = false;
}
delay(200); // Button debounce
}
if (digitalRead(buttonOffPin) == LOW) { // Reset button
running = false;
elapsedTime = 0;
lapElapsedTime = 0;
lcd.clear();
lcd.print("Stopwatch Reset");
delay(1000);
lcd.clear();
lcd.print("Stopwatch Mode");
delay(1000);
}
if (running) {
elapsedTime = millis() - startTime; // Calculate elapsed time
lapElapsedTime = millis() - lapStartTime; // Calculate lap elapsed time
}
// Calculate hours, minutes, seconds, and milliseconds
unsigned long hours = (elapsedTime / (1000 * 60 * 60)) % 24;
unsigned long minutes = elapsedTime / 60000; // Calculate minutes dire
unsigned long seconds = (elapsedTime / 1000) % 60;
unsigned long milliseconds = elapsedTime % 1000;
// Display the elapsed time
lcd.setCursor(0, 1);
if (hours < 10) lcd.print("0");
lcd.print(hours);
lcd.print(":");
if (minutes < 10) lcd.print("0");
lcd.print(minutes);
lcd.print(":");
if (seconds < 10) lcd.print("0");
lcd.print(seconds);
lcd.print(".");
if (milliseconds < 100) lcd.print("0");
if (milliseconds < 10) lcd.print("0");
lcd.print(milliseconds / 10);
delay(100); // Update display every 100ms
}
}
void setTune() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Select Tune");
delay(1000);
for (int i = 0; i < maxAlarms; i++) {
int currentTune = alarms[i].tuneIndex;
while (true) {
lcd.clear();
lcd.setCursor(0, 1);
lcd.print("Alarm ");
lcd.print(i + 1);
lcd.print(" Tune: ");
lcd.print(currentTune + 1); // Display tune number (1-based index)
if (digitalRead(buttonUpPin) == LOW) {
currentTune = (currentTune + 1) % numTunes;
delay(200);
}
if (digitalRead(buttonDownPin) == LOW) {
currentTune = (currentTune - 1 + numTunes) % numTunes;
delay(200);
}
if (digitalRead(buttonSelectPin) == LOW) {
alarms[i].tuneIndex = currentTune;
int address = i * sizeof(Alarm);
EEPROM.put(address, alarms[i]);
lcd.clear();
lcd.print("Tune set!");
delay(1000);
// Return to Time Mode
currentMode = 0;
showTime();
return; // Exit the function to prevent further execution
}
}
}
}
void handleStopAlarmButton() {
// Check if the stop alarm button is pressed and take appropriate action
if (buttonOffState == HIGH && digitalRead(buttonOffPin) == LOW) {
buzzerActive = false; // Set buzzerActive to false
digitalWrite(buzzerPin, LOW); // Turn off the buzzer
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Alarm stopped");
delay(60000); // Display "Alarm stopped" message for 2 seconds
showTime(); // Return to showing time
}
}
void showDate() {
DateTime now = rtc.now(); // Get the current date and time from the RTC
// Display the date on the LCD screen, for example:
lcd.setCursor(0, 0);
lcd.print("Date: ");
lcd.print(now.year(), DEC);
lcd.print('/');
lcd.print(now.month(), DEC);
lcd.print('/');
lcd.print(now.day(), DEC);
}
void changeDateTime() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Change Date & Time");
delay(1000);
DateTime now = rtc.now();
int currentField = 0; // 0: Year, 1: Month, 2: Day, 3: Hour, 4: Minute
while (true) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set ");
switch (currentField) {
case 0:
lcd.print("Year:");
lcd.print(now.year());
break;
case 1:
lcd.print("Month:");
lcd.print(now.month());
break;
case 2:
lcd.print("Day:");
lcd.print(now.day());
break;
case 3:
lcd.print("Hour:");
lcd.print(now.hour());
break;
case 4:
lcd.print("Minute:");
lcd.print(now.minute());
break;
}
if (currentField <= 2) {
lcd.print("/");
} else {
lcd.print(":");
}
// Handle button presses
if (digitalRead(buttonUpPin) == LOW) {
if (currentField == 0) {
now = now + TimeSpan(365 * 24 * 3600); // Add a year
} else if (currentField == 1) {
now = now + TimeSpan(31 * 24 * 3600); // Add a month
} else if (currentField == 2) {
now = now + TimeSpan(24 * 3600); // Add a day
} else if (currentField == 3) {
now = now + TimeSpan(3600); // Add an hour
} else if (currentField == 4) {
now = now + TimeSpan(60); // Add a minute
}
delay(200); // Button debounce
}
if (digitalRead(buttonDownPin) == LOW) {
if (currentField == 0) {
now = now - TimeSpan(365 * 24 * 3600); // Subtract a year
} else if (currentField == 1) {
now = now - TimeSpan(31 * 24 * 3600); // Subtract a month
} else if (currentField == 2) {
now = now - TimeSpan(24 * 3600); // Subtract a day
} else if (currentField == 3) {
now = now - TimeSpan(3600); // Subtract an hour
} else if (currentField == 4) {
now = now - TimeSpan(60); // Subtract a minute
}
delay(200); // Button debounce
}
if (digitalRead(buttonSelectPin) == LOW) {
currentField++;
if (currentField > 4) {
currentField = 0; // Wrap around to the first field
rtc.adjust(now); // Write the updated date and time back to the RTC
lcd.clear();
lcd.print("DateTime changed!");
delay(1000);
break;
}
delay(200); // Button debounce
}
delay(100); // Small delay to allow for smooth display
}
}
void clearAlarm() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Clear Alarm");
delay(1000); // Display "Clear Alarm" message for 1 second
for (int i = 0; i < sizeof(alarms) / sizeof(alarms[0]); i++) {
Alarm emptyAlarm;
emptyAlarm.hour = 0;
emptyAlarm.minute = 0;
int address = i * sizeof(Alarm);
EEPROM.put(address, emptyAlarm);
alarms[i] = emptyAlarm;
}
lcd.clear();
lcd.print("Alarms cleared!");
delay(1000);
}
void viewAlarm() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Zz");
lcd.setCursor(3, 0);
lcd.print(snoozeDuration / 60); // Display hours
lcd.print(":");
lcd.print(snoozeDuration % 60 < 10 ? "0" : ""); // Add leading zero if necessary
lcd.print(snoozeDuration % 60); // Display minutes
// Display alarm 1
lcd.setCursor(0, 1);
lcd.print("Alarm 1: ");
if (alarms[0].hour < 10) {
lcd.print("0");
}
lcd.print(alarms[0].hour);
lcd.print(":");
if (alarms[0].minute < 10) {
lcd.print("0");
}
lcd.print(alarms[0].minute);
// Delay to prevent accidental mode switching
delay(500);
while (true) {
// Handle button presses
if (digitalRead(buttonSelectPin) == LOW) {
// Exit viewAlarm mode and display the time
currentMode = 0;
showTime();
return;
}
delay(100); // Small delay to allow for smooth display
}
}
void setAlarm() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Set Alarm");
delay(1000); // Display "Set Alarm" message for 1 second
Alarm newAlarm;
newAlarm.hour = 0;
newAlarm.minute = 0;
newAlarm.tuneIndex = 0; // Initialize tuneIndex
bool pmSelected = false; // Flag to track whether PM is selected
int cursorPosition = 0; // 0: Hour, 1: Minute, 2: AM/PM
while (true) {
// Display alarm time
lcd.setCursor(0, 1);
lcd.print("Alarm: ");
if (cursorPosition == 0) {
lcd.print(newAlarm.hour < 10 ? "0" : "");
lcd.print(newAlarm.hour);
lcd.print(":");
} else {
lcd.print(" "); // Print spaces for hour if not selected
}
if (cursorPosition == 1) {
lcd.print(newAlarm.minute < 10 ? "0" : "");
lcd.print(newAlarm.minute);
lcd.print(" ");
} else {
lcd.print(" "); // Print spaces for minute if not selected
}
if (cursorPosition == 2) {
lcd.print(pmSelected ? "PM" : "AM"); // Display AM/PM selection
} else {
lcd.print(" "); // Print spaces for AM/PM if not selected
}
// Handle button presses
if (digitalRead(buttonUpPin) == LOW) {
if (cursorPosition == 0) {
newAlarm.hour = (newAlarm.hour + 1) % 24;
} else if (cursorPosition == 1) {
newAlarm.minute = (newAlarm.minute + 1) % 60;
} else if (cursorPosition == 2) {
pmSelected = !pmSelected; // Toggle between AM and PM
}
delay(200); // Button debounce
}
if (digitalRead(buttonDownPin) == LOW) {
if (cursorPosition == 0) {
newAlarm.hour = (newAlarm.hour == 0) ? 23 : newAlarm.hour - 1;
} else if (cursorPosition == 1) {
newAlarm.minute = (newAlarm.minute == 0) ? 59 : newAlarm.minute - 1;
} else if (cursorPosition == 2) {
pmSelected = !pmSelected; // Toggle between AM and PM
}
delay(200); // Button debounce
}
if (digitalRead(buttonSelectPin) == LOW) {
if (cursorPosition < 2) {
cursorPosition++;
} else {
// Save the alarm
if (pmSelected && newAlarm.hour < 12) {
newAlarm.hour += 12; // Convert to 24-hour format if PM is selected and hour is less than 12
} else if (!pmSelected && newAlarm.hour == 12) {
newAlarm.hour = 0; // Convert 12-hour format midnight (12:00 AM) to 24-hour format (00:00)
}
// Save the alarm to the first slot in EEPROM
int address = 0; // Address in EEPROM
alarms[0] = newAlarm; // Store the new alarm in the first slot
EEPROM.put(address, alarms[0]);
lcd.clear();
lcd.print("Alarm set!");
delay(1000);
// Return to Time Mode
currentMode = 0;
showTime();
return; // Exit the function
}
delay(200); // Button debounce
}
}
}