#include <Arduino.h>
#include <Wire.h>
#include <U8g2lib.h>
#define OLED_RESET -1
// Initialize four OLED displays
U8G2_SSD1306_128X32_UNIVISION_1_HW_I2C display1(U8G2_R0, U8X8_PIN_NONE);
U8G2_SSD1306_128X32_UNIVISION_1_HW_I2C display2(U8G2_R0, U8X8_PIN_NONE);
U8G2_SSD1306_128X32_UNIVISION_1_HW_I2C display3(U8G2_R0, U8X8_PIN_NONE);
U8G2_SSD1306_128X32_UNIVISION_1_HW_I2C display4(U8G2_R0, U8X8_PIN_NONE);
// Define button and LED pins
const int buttonUp1 = A2;
const int buttonDown1 = A1;
const int buttonUp2 = 6;
const int buttonDown2 = 5;
const int buttonUp3 = A0;
const int buttonDown3 = 13;
const int buttonUp4 = 4;
const int buttonDown4 = A3;
const int ledPin1 = 9;
const int ledPin2 = 7;
const int ledPin3 = 10;
const int ledPin4 = 8;
const int buzzerPin = 11;
#define incrementBy 10000 // Time to inrement the alarm
// Timer structure to manage individual timers
struct Timer
{
unsigned long start = 0;
unsigned long alarmTime = incrementBy; // Default alarm time
unsigned long alarmTargetTime = 0;
unsigned long offsetTime = 0;
unsigned long refreshTime = 0;
unsigned long bothPressStart = 0;
unsigned long lastButtonPress = 0;
unsigned long disableFish = 0;
bool alarmSet = false;
bool alarmActive = false;
bool settingMode = false;
bool wasSettingMode = false;
bool refreshOLED = false;
int fishCounter = 0;
};
// Create four timer instances
Timer timer1, timer2, timer3, timer4;
// Function prototypes (not needed for Arduino IDE)
void checkButtons(Timer &timer, int buttonUp, int buttonDown, bool main);
void updateAlarm(Timer &timer, int ledPin, int frequency);
void displayTimer(Timer &timer, U8G2_SSD1306_128X32_UNIVISION_1_HW_I2C &display);
void checkSetAlarm(Timer &timer);
void setup()
{
// Configure buttons as input with pull-up resistors
pinMode(buttonUp1, INPUT_PULLUP);
pinMode(buttonDown1, INPUT_PULLUP);
pinMode(buttonUp2, INPUT_PULLUP);
pinMode(buttonDown2, INPUT_PULLUP);
pinMode(buttonUp3, INPUT_PULLUP);
pinMode(buttonDown3, INPUT_PULLUP);
pinMode(buttonUp4, INPUT_PULLUP);
pinMode(buttonDown4, INPUT_PULLUP);
// Configure LEDs and buzzer as outputs
pinMode(ledPin1, OUTPUT);
pinMode(ledPin2, OUTPUT);
pinMode(ledPin3, OUTPUT);
pinMode(ledPin4, OUTPUT);
pinMode(buzzerPin, OUTPUT);
Wire.begin(); // Start I2C communication
// Assign unique I2C addresses to each display
display1.setI2CAddress(0x3A * 2);
display2.setI2CAddress(0x3C * 2);
display3.setI2CAddress(0x3B * 2);
display4.setI2CAddress(0x3D * 2);
// Initialize all displays
display1.begin();
display2.begin();
display3.begin();
display4.begin();
// Set font for each display
display1.setFont(u8x8_font_chroma48medium8_r);
display2.setFont(u8x8_font_chroma48medium8_r);
display3.setFont(u8x8_font_chroma48medium8_r);
display4.setFont(u8x8_font_chroma48medium8_r);
// Start all timers
timer1.start = millis();
timer2.start = millis();
timer3.start = millis();
timer4.start = millis();
}
void loop()
{
// Check button inputs for each timer
checkButtons(timer1, buttonUp1, buttonDown1, 1);
checkButtons(timer2, buttonUp2, buttonDown2, 0);
checkButtons(timer3, buttonUp3, buttonDown3, 0);
checkButtons(timer4, buttonUp4, buttonDown4, 0);
// Check if alarm should be set or updated
checkSetAlarm(timer1);
checkSetAlarm(timer2);
checkSetAlarm(timer3);
checkSetAlarm(timer4);
// Update alarms and LED indicators
updateAlarm(timer1, ledPin1, 400);
updateAlarm(timer2, ledPin2, 600);
updateAlarm(timer3, ledPin3, 800);
updateAlarm(timer4, ledPin4, 100);
// Refresh display timers
displayTimer(timer1, display1);
displayTimer(timer2, display2);
displayTimer(timer3, display3);
displayTimer(timer4, display4);
}
// Function to set end millis timer after exiting the alarm setup menu
void checkSetAlarm(Timer &timer)
{
// Detect if exiting setting mode and an alarm is set
if (timer.wasSettingMode && !timer.settingMode && timer.alarmSet)
{
timer.alarmTargetTime = millis() + timer.alarmTime; // Set alarm countdown start time
timer.alarmActive = false; // Ensure alarm is not active yet
}
timer.wasSettingMode = timer.settingMode; // Store the previous setting mode state
// If alarm time is zero, disable alarm and update display
if(timer.alarmTime == 0) {
timer.alarmActive = false;
timer.alarmSet = false;
timer.refreshOLED = true; // Trigger display update
}
}
void checkButtons(Timer &timer, int buttonUp, int buttonDown, bool main)
{
// Read button states (inverted due to INPUT_PULLUP)
bool upPressed = !digitalRead(buttonUp);
bool downPressed = !digitalRead(buttonDown);
// Debounce: Ignore rapid presses within 200ms
if (millis() - timer.lastButtonPress < 200)
return;
// Handle simultaneous button presses
if (upPressed && downPressed)
{
Serial.println("Double pressed");
if (timer.bothPressStart == 0)
timer.bothPressStart = millis();
if (millis() - timer.bothPressStart >= 1600)
{
timer.settingMode = !timer.settingMode; // Toggle setting mode
Serial.println("Setting mode");
timer.disableFish = millis(); // Disable fish counter temporarily when exiting setting mode
timer.bothPressStart = 0;
}
}
else
{
timer.bothPressStart = 0;
}
// Handle button presses in setting mode
if (timer.settingMode)
{
if (upPressed && !downPressed)
{
delay(50); // Short delay to check for second button press
if (!digitalRead(buttonDown))
return; // Skip if down button was pressed too
timer.alarmTime += incrementBy; // Increase alarm time by 5 minutes
timer.alarmSet = true;
timer.lastButtonPress = millis();
timer.refreshOLED = true;
}
if (downPressed && timer.alarmTime >= incrementBy && !upPressed)
{
delay(50); // Short delay to check for second button press
if (!digitalRead(buttonUp))
return; // Skip if up button was pressed too
timer.alarmTime -= incrementBy; // Decrease alarm time by 5 minutes
timer.lastButtonPress = millis();
timer.refreshOLED = true;
}
}
else // Handle button presses outside setting mode
{
if (upPressed && !downPressed && millis() - timer.disableFish > 2000)
{
unsigned long holdingTime = millis();
delay(50); // Check if down button is pressed shortly after
if (!digitalRead(buttonDown))
return;
while (!digitalRead(buttonUp)) // Wait for button release
;
if (millis() - holdingTime > 3000 && main) // If main button is held for more than 3s, reset timers
{
unsigned long offsetT = millis();
timer1.offsetTime = offsetT;
timer2.offsetTime = offsetT;
timer3.offsetTime = offsetT;
timer4.offsetTime = offsetT;
}
else if (millis() - holdingTime > 1000)
{
timer.fishCounter = 0; // Reset fish counter
}
else
timer.fishCounter++; // Increment fish counter
timer.refreshOLED = true;
timer.lastButtonPress = millis();
}
// Stop active alarm when the down button is pressed
if (downPressed && !upPressed && timer.alarmActive && timer.alarmSet)
{
timer.alarmActive = false;
timer.alarmSet = false;
timer.refreshOLED = true;
timer.lastButtonPress = millis();
}
// Handle long-pressing the down button
if (downPressed && !upPressed)
{
unsigned long holdingTime = millis();
while (!digitalRead(buttonDown)) // Wait for button release
;
if (millis() - holdingTime > 3000 && main) // If main button is held for more than 3s, reset alarms
{
timer1.alarmActive = false;
timer1.alarmSet = false;
timer2.alarmActive = false;
timer2.alarmSet = false;
timer3.alarmActive = false;
timer3.alarmSet = false;
timer4.alarmActive = false;
timer4.alarmSet = false;
}
else if (millis() - holdingTime > 1000)
{
timer.offsetTime = millis(); // Reset offset time
}
timer.refreshOLED = true;
}
}
}
void updateAlarm(Timer &timer, int ledPin, int frequency)
{
// If in setting mode, do nothing
if (timer.settingMode)
return;
// If alarm time has been reached and alarm is not already active, activate it
if (timer.alarmSet && millis() >= timer.alarmTargetTime && !timer.alarmActive)
{
timer.alarmActive = true;
}
// If alarm is active, blink LED and sound buzzer at specified frequency
if (timer.alarmActive)
{
digitalWrite(ledPin, millis() % 500 < 250 ? HIGH : LOW); // Blink LED every 500ms
if (millis() % 500 < 250){
tone(buzzerPin, frequency); // Sound buzzer
}
else // If alarm is not active, turn off LED and buzzer
{
noTone(buzzerPin); // Stop buzzer
}
}
else
{
digitalWrite(ledPin, LOW);
noTone(buzzerPin);
}
}
void displayTimer(Timer &timer, U8G2_SSD1306_128X32_UNIVISION_1_HW_I2C &display) {
// Refresh display only if enough time has passed or an update is needed
if (millis() - timer.refreshTime < 1000 && !timer.refreshOLED)
return;
timer.refreshTime = millis(); // Update last refresh time
timer.refreshOLED = false; // Reset update flag
display.firstPage(); // Start drawing display
do {
display.setFont(u8g2_font_profont11_tf ); // Use a small, readable font
display.setCursor(0, 10);
display.print("Timer: ");
// Calculate and display elapsed time
if (millis() >= timer.offsetTime) {
unsigned long elapsed = millis() - timer.offsetTime;
display.print(elapsed / 60000);
display.print(" min ");
display.print((elapsed / 1000) % 60);
display.print(" sec");
} else {
display.print("0 min 0 sec");
}
// Display alarm countdown if alarm is set and not in setting mode
if (timer.alarmSet && !timer.settingMode) {
display.setCursor(0, 20);
display.print("Alarm in: ");
unsigned long timeLeft = (timer.alarmTargetTime > millis()) ? (timer.alarmTargetTime - millis()) : 0;
unsigned long alarmSeconds = timeLeft / 1000;
display.print(alarmSeconds / 60);
display.print(":");
if (alarmSeconds % 60 < 10) display.print("0");
display.print(alarmSeconds % 60);
}
// Display alarm time if alarm is set or in setting mode
else if (timer.alarmSet || timer.settingMode) {
display.setCursor(0, 20);
display.print("Alarm: ");
unsigned long alarmSeconds = timer.alarmTime / 1000;
display.print(alarmSeconds / 60);
display.print(":");
if (alarmSeconds % 60 < 10) display.print("0");
display.print(alarmSeconds % 60);
}
// Indicate if user is in alarm setting mode
if (timer.settingMode) {
display.setCursor(0, 30);
display.print("Setting Alarm...");
}
// Display fish counter if greater than 0 and not in setting mode
if (timer.fishCounter > 0 && !timer.settingMode) {
display.setCursor(0, 30);
display.print("Fish: ");
display.print(timer.fishCounter);
}
} while (display.nextPage()); // Update display pages
}