#include <Bounce2.h>
#include <TM1637Display.h>
#include <EEPROM.h>
// Pin Definitions
const int startButtonPin = 2; // Start button
const int stopButtonPin = 3; // Stop button
const int menuButtonPin = 6; // Menu button
const int motorFwPin = 4; // Motor forward
const int motorRevPin = 5; // Motor reverse
const int CLK_PIN = 11; // TM1637 clock pin
const int DIO_PIN = 12; // TM1637 data pin
// Display object
TM1637Display display(CLK_PIN, DIO_PIN);
// Button Debouncers
Bounce startButton = Bounce();
Bounce stopButton = Bounce();
Bounce menuButton = Bounce();
// State Machine
enum MotorState {
FORWARD_ON,
OFF_AFTER_FORWARD,
REVERSE_ON,
OFF_AFTER_REVERSE
};
// Menu States
enum MenuState {
MAIN_DISPLAY,
MENU_FORWARD_TIME,
MENU_REVERSE_TIME,
MENU_OFF_TIME,
MENU_MAIN_TIMER
};
// Configuration structure
struct Config {
unsigned long forwardTime;
unsigned long reverseTime;
unsigned long offTime;
unsigned long mainDuration;
};
// Program Variables
MotorState currentMotorState;
MenuState currentMenuState = MAIN_DISPLAY;
unsigned long startTime;
unsigned long motorStepStartTime;
unsigned long currentStepDuration;
bool mainTimerActive = false;
// Configuration with default values
Config config = {
.forwardTime = 3000, // 3 seconds
.reverseTime = 3000, // 3 seconds
.offTime = 1000, // 1 second
.mainDuration = 600000 // 10 minutes (600,000 ms)
};
// Menu navigation variables
bool inMenu = false;
unsigned long menuEnterTime = 0;
const unsigned long menuTimeout = 10000; // 10 seconds menu timeout
unsigned long lastButtonPress = 0;
const unsigned long buttonDebounceTime = 200;
const int EEPROM_ADDR = 0; // Starting address in EEPROM
// Custom character definitions for menu indicators
const uint8_t CHAR_F[] = {SEG_A | SEG_E | SEG_F | SEG_G}; // F
const uint8_t CHAR_R[] = {SEG_E | SEG_G}; // r
const uint8_t CHAR_S[] = {SEG_A | SEG_F | SEG_G | SEG_C | SEG_D}; // S
const uint8_t CHAR_T[] = {SEG_D | SEG_E | SEG_F | SEG_G}; // t
const uint8_t CHAR_BLANK[] = {0}; // Blank
void setup() {
// Initialize Serial for debugging
Serial.begin(9600);
// Load configuration from EEPROM
loadConfig();
// Pin Setup
pinMode(startButtonPin, INPUT_PULLUP);
pinMode(stopButtonPin, INPUT_PULLUP);
pinMode(menuButtonPin, INPUT_PULLUP);
pinMode(motorFwPin, OUTPUT);
pinMode(motorRevPin, OUTPUT);
// Initialize Motor in OFF state
digitalWrite(motorFwPin, LOW);
digitalWrite(motorRevPin, LOW);
// Button Debouncers
startButton.attach(startButtonPin);
startButton.interval(25);
stopButton.attach(stopButtonPin);
stopButton.interval(25);
menuButton.attach(menuButtonPin);
menuButton.interval(25);
// Display setup
display.setBrightness(7); // Set brightness (0-7)
display.showNumberDec(0); // Show 0 at startup
}
void loadConfig() {
EEPROM.get(EEPROM_ADDR, config);
// Check if EEPROM is empty or contains invalid data
if (config.forwardTime < 1000 || config.forwardTime > 30000 ||
config.reverseTime < 1000 || config.reverseTime > 30000 ||
config.offTime < 500 || config.offTime > 10000 ||
config.mainDuration < 60000 || config.mainDuration > 1800000) {
// Load defaults
config.forwardTime = 3000;
config.reverseTime = 3000;
config.offTime = 1000;
config.mainDuration = 600000;
saveConfig();
Serial.println("Loaded default config");
} else {
Serial.println("Loaded config from EEPROM");
}
}
void saveConfig() {
EEPROM.put(EEPROM_ADDR, config);
Serial.println("Config saved to EEPROM");
}
void setMotorForward() {
digitalWrite(motorFwPin, HIGH);
digitalWrite(motorRevPin, LOW);
}
void setMotorReverse() {
digitalWrite(motorFwPin, LOW);
digitalWrite(motorRevPin, HIGH);
}
void setMotorOff() {
digitalWrite(motorFwPin, LOW);
digitalWrite(motorRevPin, LOW);
}
void updateDisplay(unsigned long remainingTime) {
if (inMenu) {
// Show menu with parameter indicator
uint8_t segments[4];
switch (currentMenuState) {
case MENU_FORWARD_TIME:
display.setSegments(CHAR_F, 1, 0); // Show 'F' at first position
display.showNumberDecEx(config.forwardTime / 1000, 0b01000000, false, 2, 1);
break;
case MENU_REVERSE_TIME:
display.setSegments(CHAR_R, 1, 0); // Show 'r' at first position
display.showNumberDecEx(config.reverseTime / 1000, 0b01000000, false, 2, 1);
break;
case MENU_OFF_TIME:
display.setSegments(CHAR_S, 1, 0); // Show 'S' at first position
display.showNumberDecEx(config.offTime / 1000, 0b01000000, false, 2, 1);
break;
case MENU_MAIN_TIMER:
display.setSegments(CHAR_T, 1, 0); // Show 't' at first position
display.showNumberDecEx(config.mainDuration / 60000, 0b01000000, false, 2, 1);
break;
}
} else {
if (mainTimerActive) {
// Show countdown timer in MM:SS format
unsigned long remainingSeconds = remainingTime / 1000;
int minutes = remainingSeconds / 60;
int seconds = remainingSeconds % 60;
int displayValue = minutes * 100 + seconds;
display.showNumberDecEx(displayValue, 0b01000000, true);
} else {
display.showNumberDec(0);
}
}
}
void handleMenuNavigation() {
menuButton.update();
startButton.update();
stopButton.update();
// Menu button pressed - enter/exit menu or navigate
if (menuButton.fell() && !mainTimerActive && millis() - lastButtonPress > buttonDebounceTime) {
lastButtonPress = millis();
if (!inMenu) {
// Enter menu mode
inMenu = true;
currentMenuState = MENU_FORWARD_TIME;
menuEnterTime = millis();
updateDisplay(0);
} else {
// Cycle through menu items
switch (currentMenuState) {
case MENU_FORWARD_TIME:
currentMenuState = MENU_REVERSE_TIME;
break;
case MENU_REVERSE_TIME:
currentMenuState = MENU_OFF_TIME;
break;
case MENU_OFF_TIME:
currentMenuState = MENU_MAIN_TIMER;
break;
case MENU_MAIN_TIMER:
// Save config and exit menu
saveConfig();
inMenu = false;
display.showNumberDec(0);
return;
}
updateDisplay(0);
}
}
// Only allow value adjustment when in menu and washer not running
if (inMenu && !mainTimerActive) {
// Check for menu timeout
if (millis() - menuEnterTime > menuTimeout) {
saveConfig();
inMenu = false;
display.showNumberDec(0);
return;
}
// Start button (UP) pressed - increase value
if (startButton.fell() && millis() - lastButtonPress > buttonDebounceTime) {
lastButtonPress = millis();
menuEnterTime = millis();
switch (currentMenuState) {
case MENU_FORWARD_TIME:
config.forwardTime = min(config.forwardTime + 1000, 30000);
break;
case MENU_REVERSE_TIME:
config.reverseTime = min(config.reverseTime + 1000, 30000);
break;
case MENU_OFF_TIME:
config.offTime = min(config.offTime + 1000, 10000);
break;
case MENU_MAIN_TIMER:
config.mainDuration = min(config.mainDuration + 60000, 1800000);
break;
}
updateDisplay(0);
}
// Stop button (DOWN) pressed - decrease value
if (stopButton.fell() && millis() - lastButtonPress > buttonDebounceTime) {
lastButtonPress = millis();
menuEnterTime = millis();
switch (currentMenuState) {
case MENU_FORWARD_TIME:
config.forwardTime = max(config.forwardTime - 1000, 1000);
break;
case MENU_REVERSE_TIME:
config.reverseTime = max(config.reverseTime - 1000, 1000);
break;
case MENU_OFF_TIME:
config.offTime = max(config.offTime - 1000, 500);
break;
case MENU_MAIN_TIMER:
config.mainDuration = max(config.mainDuration - 60000, 60000);
break;
}
updateDisplay(0);
}
} else if (inMenu && mainTimerActive) {
saveConfig();
inMenu = false;
display.showNumberDec(0);
}
}
void loop() {
handleMenuNavigation();
// Start Button Pressed (normal operation)
if (startButton.fell() && !inMenu) {
if (!mainTimerActive) {
mainTimerActive = true;
startTime = millis();
currentMotorState = FORWARD_ON;
motorStepStartTime = startTime;
currentStepDuration = config.forwardTime;
setMotorForward();
}
}
// Stop Button Pressed (normal operation)
if (stopButton.fell()) {
mainTimerActive = false;
setMotorOff();
if (!inMenu) {
display.showNumberDec(0);
}
}
if (mainTimerActive) {
unsigned long currentMillis = millis();
unsigned long elapsedTime = currentMillis - startTime;
unsigned long remainingTime = config.mainDuration - elapsedTime;
updateDisplay(remainingTime);
if (elapsedTime >= config.mainDuration) {
mainTimerActive = false;
setMotorOff();
display.showNumberDec(0);
}
else {
if (currentMillis - motorStepStartTime >= currentStepDuration) {
switch (currentMotorState) {
case FORWARD_ON:
setMotorOff();
currentMotorState = OFF_AFTER_FORWARD;
motorStepStartTime = currentMillis;
currentStepDuration = config.offTime;
break;
case OFF_AFTER_FORWARD:
setMotorReverse();
currentMotorState = REVERSE_ON;
motorStepStartTime = currentMillis;
currentStepDuration = config.reverseTime;
break;
case REVERSE_ON:
setMotorOff();
currentMotorState = OFF_AFTER_REVERSE;
motorStepStartTime = currentMillis;
currentStepDuration = config.offTime;
break;
case OFF_AFTER_REVERSE:
setMotorForward();
currentMotorState = FORWARD_ON;
motorStepStartTime = currentMillis;
currentStepDuration = config.forwardTime;
break;
}
}
}
}
}