#include <Wire.h>
#include <U8g2lib.h>
#include <EEPROM.h>
#include <Adafruit_NeoPixel.h>
#define ENCODER_CLK 3
#define ENCODER_DT 4
#define ENCODER_SW 5
#define TRIGGER_PIN 2
#define BUZZER_PIN 6
#define OUTPUT1 8
#define OUTPUT2 9
#define NEOPIXEL_PIN 7
#define NUMPIXELS 1
U8G2_ST7565_EA_DOGM128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);
Adafruit_NeoPixel pixels(NUMPIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);
volatile int setTime = 30;
volatile int currentTime = 30;
volatile bool inMenu = false;
volatile int menuPage = 0; // 0 for set time, 1 for sound option, 2 for contrast, 3 for volume, 4 for NeoPixel
volatile int contrast = 100;
volatile int volume = 50; // Default volume level
volatile int red = 255, green = 255, blue = 255; // RGB values for NeoPixel
unsigned long lastInteractionTime = 0;
volatile bool encoderButtonPressed = false;
volatile bool soundOn = true;
volatile int encoderPos = 0;
volatile int lastEncoderPos = 0;
volatile bool encoderDirection = false; // True for clockwise, false for counterclockwise
volatile bool timerRunning = false;
volatile bool lastTriggerState = HIGH;
void setup() {
Serial.begin(9600); // Start serial communication for debugging
pinMode(ENCODER_CLK, INPUT_PULLUP);
pinMode(ENCODER_DT, INPUT_PULLUP);
pinMode(ENCODER_SW, INPUT_PULLUP);
pinMode(TRIGGER_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(OUTPUT1, OUTPUT);
pinMode(OUTPUT2, OUTPUT);
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), readEncoder, CHANGE);
attachInterrupt(digitalPinToInterrupt(ENCODER_DT), readEncoder, CHANGE);
// Read settings from EEPROM
EEPROM.get(0, setTime);
EEPROM.get(4, contrast);
EEPROM.get(8, volume);
EEPROM.get(12, red);
EEPROM.get(16, green);
EEPROM.get(20, blue);
if (setTime < 0 || setTime > 300) {
setTime = 30;
}
if (contrast < 10 || contrast > 100) {
contrast = 50; // Default contrast if out of bounds
}
if (volume < 0 || volume > 100) {
volume = 50; // Default volume if out of bounds
}
if (red < 0 || red > 255) {
red = 255; // Default red if out of bounds
}
if (green < 0 || green > 255) {
green = 255; // Default green if out of bounds
}
if (blue < 0 || blue > 255) {
blue = 255; // Default blue if out of bounds
}
currentTime = setTime;
u8g2.begin();
u8g2.setContrast(contrast);
u8g2.setFont(u8g2_font_ncenB08_tr);
pixels.begin();
pixels.setPixelColor(0, pixels.Color(red, green, blue));
pixels.show();
// Display splash screen with the current contrast setting
displaySplashScreen();
delay(3000); // Show splash screen for 3 seconds
}
void loop() {
bool triggerState = digitalRead(TRIGGER_PIN);
if (triggerState == LOW && !timerRunning && lastTriggerState == HIGH) {
startTimer();
}
if (triggerState == HIGH && timerRunning) {
resetTimer();
}
if (triggerState == HIGH && lastTriggerState == LOW && !timerRunning) {
u8g2.begin();
u8g2.setContrast(contrast);
updateDisplay();
}
if (triggerState == HIGH && !timerRunning && !inMenu) {
updateDisplay();
}
if (encoderButtonPressed) {
encoderButtonPressed = false;
lastInteractionTime = millis();
if (!inMenu) {
inMenu = true;
menuPage = 0;
Serial.println("Entering menu");
} else {
menuPage = (menuPage + 1) % 5; // Toggle between menu pages
if (menuPage == 0) { // If it cycles back to the first page, exit menu
inMenu = false;
EEPROM.put(0, setTime); // Save set time to EEPROM
EEPROM.put(4, contrast); // Save contrast to EEPROM
EEPROM.put(8, volume); // Save volume to EEPROM
EEPROM.put(12, red); // Save red to EEPROM
EEPROM.put(16, green); // Save green to EEPROM
EEPROM.put(20, blue); // Save blue to EEPROM
Serial.println("Exiting menu and saving settings");
updateDisplay(); // Update display with the new set time
}
}
delay(200); // Debounce delay
}
if (inMenu && (millis() - lastInteractionTime > 5000)) {
inMenu = false;
EEPROM.put(0, setTime); // Save set time to EEPROM
EEPROM.put(4, contrast); // Save contrast to EEPROM
EEPROM.put(8, volume); // Save volume to EEPROM
EEPROM.put(12, red); // Save red to EEPROM
EEPROM.put(16, green); // Save green to EEPROM
EEPROM.put(20, blue); // Save blue to EEPROM
Serial.println("Exiting menu due to timeout");
updateDisplay(); // Update display with the new set time
}
if (inMenu) {
enterMenu();
}
if (digitalRead(ENCODER_SW) == LOW) {
delay(50); // Debounce delay
if (digitalRead(ENCODER_SW) == LOW) {
while (digitalRead(ENCODER_SW) == LOW); // Wait for button release
encoderButtonPressed = true;
}
}
if (encoderPos != lastEncoderPos) {
if (abs(encoderPos - lastEncoderPos) >= 2) { // Adjust threshold as needed
lastInteractionTime = millis();
if (inMenu) {
if (menuPage == 0) {
setTime += encoderDirection ? 1 : -1;
setTime = constrain(setTime, 0, 300);
tone(BUZZER_PIN, 2500, 100); // Beep sound when changing set time
Serial.print("Set time: ");
Serial.println(setTime);
currentTime = setTime;
updateDisplay(); // Update display with the new set time
} else if (menuPage == 1) {
soundOn = encoderDirection;
tone(BUZZER_PIN, 1000, 100); // Beep sound when toggling sound
Serial.print("Sound: ");
Serial.println(soundOn ? "ON" : "OFF");
} else if (menuPage == 2) {
contrast += encoderDirection ? 1 : -1;
contrast = constrain(contrast, 10, 100); // Adjust contrast range
u8g2.setContrast(contrast);
tone(BUZZER_PIN, 2000, 100); // Beep sound when changing contrast
Serial.print("Contrast: ");
Serial.println(contrast);
} else if (menuPage == 3) {
volume += encoderDirection ? 1 : -1;
volume = constrain(volume, 0, 100); // Adjust volume range
tone(BUZZER_PIN, 1000 + volume * 10, 100); // Change pitch based on volume
Serial.print("Volume: ");
Serial.println(volume);
} else if (menuPage == 4) {
if (encoderDirection) {
if (encoderPos % 3 == 0) red = constrain(red + 1, 0, 255);
if (encoderPos % 3 == 1) green = constrain(green + 1, 0, 255);
if (encoderPos % 3 == 2) blue = constrain(blue + 1, 0, 255);
} else {
if (encoderPos % 3 == 0) red = constrain(red - 1, 0, 255);
if (encoderPos % 3 == 1) green = constrain(green - 1, 0, 255);
if (encoderPos % 3 == 2) blue = constrain(blue - 1, 0, 255);
}
pixels.setPixelColor(0, pixels.Color(red, green, blue));
pixels.show();
tone(BUZZER_PIN, 3000, 100); // Beep sound when changing color
Serial.print("Red: ");
Serial.print(red);
Serial.print(", Green: ");
Serial.print(green);
Serial.print(", Blue: ");
Serial.println(blue);
}
} else {
currentTime = setTime;
updateDisplay(); // Update display with the new set time
}
lastEncoderPos = encoderPos;
}
}
lastTriggerState = triggerState;
}
void readEncoder() {
static unsigned long lastInterruptTime = 0;
unsigned long interruptTime = millis();
if (interruptTime - lastInterruptTime > 50) { // Increased debounce time
bool clkState = digitalRead(ENCODER_CLK);
bool dtState = digitalRead(ENCODER_DT);
encoderDirection = clkState != dtState;
encoderPos += encoderDirection ? 1 : -1;
lastInterruptTime = interruptTime;
}
}
void startTimer() {
timerRunning = true;
currentTime = setTime;
digitalWrite(OUTPUT1, HIGH);
digitalWrite(OUTPUT2, HIGH);
while (timerRunning && currentTime > 0) {
if (digitalRead(TRIGGER_PIN) == HIGH) {
resetTimer();
return;
}
delay(1000);
currentTime--;
updateDisplay();
if (soundOn) {
tone(BUZZER_PIN, 2500, 100);
delay(100);
noTone(BUZZER_PIN);
}
}
if (currentTime == 0) {
if (soundOn) {
for (int i = 5000; i >= 1000; i -= 333) { // From 5kHz to 1kHz in 12 steps
tone(BUZZER_PIN, i, 100);
delay(100);
}
noTone(BUZZER_PIN);
}
digitalWrite(OUTPUT1, LOW);
digitalWrite(OUTPUT2, LOW);
}
timerRunning = false;
updateDisplay(); // Ensure the display is updated when the timer stops
while (digitalRead(TRIGGER_PIN) == LOW) {
delay(100); // Wait for trigger pin to go high
}
while (digitalRead(TRIGGER_PIN) == HIGH) {
delay(100); // Wait for trigger pin to return to low
}
updateDisplay(); // Ensure the display is updated when the timer stops
}
void resetTimer() {
timerRunning = false;
currentTime = setTime;
digitalWrite(OUTPUT1, LOW);
digitalWrite(OUTPUT2, LOW);
noTone(BUZZER_PIN);
u8g2.begin(); // Reinitialize the display
u8g2.setContrast(contrast); // Apply the contrast setting to the display
updateDisplay(); // Ensure the display is updated when the timer is reset
Serial.println("Timer reset");
}
void enterMenu() {
u8g2.clearBuffer();
if (menuPage == 0) {
u8g2.setCursor(4, 10); // Adjusted cursor position
u8g2.print("Set Time: ");
u8g2.print(setTime);
u8g2.print(" s");
drawBarGraph(4, 20, setTime, 300); // Adjusted position
} else if (menuPage == 1) {
u8g2.setCursor(4, 10); // Adjusted cursor position
u8g2.print("Sound: ");
u8g2.print(soundOn ? "ON" : "OFF");
} else if (menuPage == 2) {
u8g2.setCursor(4, 10); // Adjusted cursor position
u8g2.print("Contrast: ");
u8g2.print(contrast);
drawBarGraph(4, 20, contrast, 100); // Adjusted position
} else if (menuPage == 3) {
u8g2.setCursor(4, 10); // Adjusted cursor position
u8g2.print("Volume: ");
u8g2.print(volume);
drawBarGraph(4, 20, volume, 100); // Adjusted position
} else if (menuPage == 4) {
u8g2.setCursor(4, 10); // Adjusted cursor position
u8g2.print("NeoPixel RGB: ");
u8g2.print("R: ");
u8g2.print(red);
u8g2.print(" G: ");
u8g2.print(green);
u8g2.print(" B: ");
u8g2.print(blue);
drawBarGraph(4, 20, encoderPos % 3 == 0 ? red : (encoderPos % 3 == 1 ? green : blue), 255); // Adjusted position
}
u8g2.sendBuffer();
}
void updateDisplay() {
u8g2.clearBuffer();
u8g2.setCursor(4, 10); // Adjusted cursor position
if (timerRunning) {
u8g2.print("SHUT DOWN: ");
} else {
u8g2.print("ENGINE OFF: ");
}
u8g2.print(currentTime);
u8g2.print(" s");
drawBarGraph(4, 20, currentTime, setTime); // Draw the current time bar graph with scaling
u8g2.sendBuffer();
}
void drawBarGraph(int x, int y, int value, int maxValue) {
int barWidth = map(value, 0, maxValue, 0, 120); // Map value to width of the display, max width adjusted to 120
u8g2.drawFrame(x, y, 120, 20); // Draw frame for bar graph, width adjusted to 120, height increased to 20
u8g2.drawBox(x, y, barWidth, 20); // Draw filled bar, height increased to 20
// Invert colors for text inside the bar
u8g2.setDrawColor(0);
u8g2.setCursor(x + barWidth - 20, y + 15); // Set cursor for text inside the bar, adjusted to be at the right end
u8g2.print(value);
u8g2.setDrawColor(1); // Restore normal color for text outside the bar
}
void displaySplashScreen() {
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB08_tr); // Reset to normal font size
u8g2.setContrast(contrast); // Apply the contrast setting to the display
u8g2.drawStr(10, 32, "RE1M Innovation"); // Centered text
u8g2.sendBuffer();
// Generate splash sound from 1 kHz to 5 kHz over 2 seconds
for (int i = 1000; i <= 5000; i += 100) {
tone(BUZZER_PIN, i, 40);
delay(40);
noTone(BUZZER_PIN);
}
}