#include <SevSeg.h>
SevSeg sevseg;
// Pin Definitions
const int button1Pin = 2; // Increase time / Next company-unit
const int button2Pin = 3; // Decrease time / Previous company-unit
const int button3Pin = 5; // Start/Stop / Confirm / Exit mode
const int button4Pin = 4; // Toggle speed / Enter-exit mode
const int latchPin = 8; // 74HC595 latch
const int clockPin = 7; // 74HC595 clock
const int dataPin = 9; // 74HC595 data
const int relaySpeedPin = A5; // Speed relay (off=H, on=L)
const int relayMotorPin = 6; // Motor relay
// Common Anode Display Patterns (inverted for active-low)
const byte letterL = ~0b00001110; // L
const byte letterH = ~0b00110111; // H
const byte letterU = ~0b00111110; // U
const byte digit1 = ~0b00110000; // 1
const byte digit2 = ~0b01101101; // 2
const byte digit3 = ~0b01111001; // 3
const byte digit5 = ~0b01011011; // 5
const byte blankPattern = ~0b00000000; // Blank
// Category Selection Settings
#define NUM_COMPANIES 4
#define NUM_UNITS 4
const char* companyCodes[NUM_COMPANIES] = {"CL", "SP", "OY", "CS"}; // Sina Lux, Sepehr, Yottaly, Cina Silver
const byte unitCodes[NUM_UNITS] = {digit1, digit2, digit3, digit5};
const int units[NUM_UNITS] = {1, 2, 3, 5};
// Settings: [company][unit] = {time, isLowSpeed}
const byte settings[NUM_COMPANIES][NUM_UNITS][2] = {
// Sina Lux (CL)
{{20, 0}, {25, 0}, {30, 0}, {40, 1}}, // 1: 20s H, 2: 25s H, 3: 30s H, 5: 40s L
// Sepehr (SP)
{{15, 0}, {20, 0}, {25, 1}, {35, 1}}, // 1: 15s H, 2: 20s H, 3: 25s L, 5: 35s L
// Yottaly (OY)
{{10, 0}, {15, 0}, {20, 1}, {30, 1}}, // 1: 10s H, 2: 15s H, 3: 20s L, 5: 30s L
// Cina Silver (CS)
{{25, 0}, {30, 0}, {35, 0}, {45, 1}} // 1: 25s H, 2: 30s H, 3: 35s H, 5: 45s L
};
// Variables
int currentNumber = 0; // Current time setting (0-99)
int entryNumber = 0; // Time at mode entry
bool countingDown = false; // Mixing state
bool isLowSpeed = false; // false: H (high), true: L (low)
bool entrySpeed = false; // Speed at mode entry
bool categoryMode = false; // Category selection mode
bool showSC = false; // Show flashing "SC"
bool showUnit = false; // Show flashing "U" or unit
bool unitConfirmed = false; // Unit confirmed, allow adjust
bool optionsLocked = false; // Company and unit locked display
bool unitNavigationStarted = false; // Unit navigation started
bool firstUnitPress = true; // Track first Button 1 press
int selectedCompany = 0; // Current company index (0-3)
int selectedUnit = 0; // Current unit index (0-3)
unsigned long lastButtonPressTime = 0;
const unsigned long debounceDelay = 200; // Debounce for all buttons
unsigned long previousMillis = 0; // For countdown timing
int lastSetTime = 0; // Original time set at cycle start
bool lastSpeedWasLow = false; // Original speed set at cycle start
static int lastButton3State = HIGH; // Last state of Button 3
static int lastButton4State = HIGH; // Last state of Button 4
static unsigned long button3PressStart = 0; // Time when Button 3 is pressed
static unsigned long button4PressStart = 0; // Time when Button 4 is pressed
static bool button3HeldForExit = false; // Flag for 3s hold
static bool button4HeldForMode = false; // Flag for 3s hold
static unsigned long lastFlashTime = 0; // For "SC"/"U"/unit flashing
const unsigned long flashInterval = 500; // Flash every 500ms
static bool flashOn = false; // Flash state
void setup() {
// Initialize seven-segment displays
byte numDigits = 2;
byte digitPins[] = {A3, A4};
byte segmentPins[] = {10, 11, 12, 13, A0, A1, A2};
sevseg.begin(COMMON_CATHODE, numDigits, digitPins, segmentPins);
sevseg.setBrightness(90); // Adjust if needed
// Initialize pins
pinMode(button1Pin, INPUT_PULLUP);
pinMode(button2Pin, INPUT_PULLUP);
pinMode(button3Pin, INPUT_PULLUP);
pinMode(button4Pin, INPUT_PULLUP);
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
pinMode(relayMotorPin, OUTPUT);
pinMode(relaySpeedPin, OUTPUT);
// Initial relay state (active-low: LOW = on, HIGH = off)
digitalWrite(relayMotorPin, HIGH); // Motor off
digitalWrite(relaySpeedPin, LOW); // Speed high (H, relay on)
// Initial display
char displayStr[3];
sprintf(displayStr, "%02d", currentNumber);
sevseg.setChars(displayStr);
displayPattern(letterH);
}
void loop() {
unsigned long currentMillis = millis();
// Handle Button 4: Detect 3s hold for mode or short press for speed
int button4State = digitalRead(button4Pin);
if (button4State == LOW && lastButton4State == HIGH) {
button4PressStart = currentMillis;
button4HeldForMode = false;
} else if (button4State == LOW && currentMillis - button4PressStart >= 3000 && !button4HeldForMode) {
// 3s hold detected
if (!countingDown) {
categoryMode = !categoryMode;
button4HeldForMode = true;
lastButtonPressTime = currentMillis;
if (categoryMode) {
// Enter mode: Save settings, blank displays for 500ms
entryNumber = currentNumber;
entrySpeed = isLowSpeed;
sevseg.blank();
displayPattern(blankPattern);
delay(500);
showSC = true;
showUnit = false;
unitConfirmed = false;
optionsLocked = false;
unitNavigationStarted = false;
firstUnitPress = true;
selectedCompany = 0;
selectedUnit = 0;
flashOn = true;
} else {
// Exit mode: Restore settings
showSC = false;
showUnit = false;
unitConfirmed = false;
optionsLocked = false;
unitNavigationStarted = false;
firstUnitPress = true;
currentNumber = entryNumber;
isLowSpeed = entrySpeed;
char displayStr[3];
sprintf(displayStr, "%02d", currentNumber);
sevseg.setChars(displayStr);
displayPattern(isLowSpeed ? letterL : letterH);
digitalWrite(relaySpeedPin, isLowSpeed ? HIGH : LOW); // H: on, L: off
}
}
} else if (button4State == HIGH && lastButton4State == LOW && !button4HeldForMode && currentMillis - lastButtonPressTime > debounceDelay && !countingDown) {
// Short press: Toggle speed in normal mode or category mode after unit confirm, before lock
isLowSpeed = !isLowSpeed;
displayPattern(isLowSpeed ? letterL : letterH);
digitalWrite(relaySpeedPin, isLowSpeed ? HIGH : LOW); // H: on, L: off
lastButtonPressTime = currentMillis;
}
lastButton4State = button4State;
// Handle Button 3: Short press for confirm, 3s hold to exit, or start/stop
int button3State = digitalRead(button3Pin);
if (button3State == LOW && lastButton3State == HIGH) {
button3PressStart = currentMillis;
button3HeldForExit = false;
} else if (button3State == LOW && currentMillis - button3PressStart >= 3000 && !button3HeldForExit && categoryMode) {
// 3s hold: Exit category mode
categoryMode = false;
showSC = false;
showUnit = false;
unitConfirmed = false;
optionsLocked = false;
unitNavigationStarted = false;
firstUnitPress = true;
currentNumber = entryNumber;
isLowSpeed = entrySpeed;
char displayStr[3];
sprintf(displayStr, "%02d", currentNumber);
sevseg.setChars(displayStr);
displayPattern(isLowSpeed ? letterL : letterH);
digitalWrite(relaySpeedPin, isLowSpeed ? HIGH : LOW); // H: on, L: off
button3HeldForExit = true;
lastButtonPressTime = currentMillis;
} else if (button3State == HIGH && lastButton3State == LOW && !button3HeldForExit && currentMillis - lastButtonPressTime > debounceDelay && categoryMode) {
// Short press: Confirm
if (!showSC && !showUnit) {
// Confirm company
showUnit = true;
lastFlashTime = currentMillis;
flashOn = true;
selectedUnit = 0;
unitNavigationStarted = false;
firstUnitPress = true;
} else if (showUnit && !unitConfirmed) {
// Confirm unit, lock company and unit display
unitConfirmed = true;
optionsLocked = true;
unitNavigationStarted = false;
firstUnitPress = true;
currentNumber = settings[selectedCompany][selectedUnit][0];
isLowSpeed = settings[selectedCompany][selectedUnit][1];
sevseg.setChars(companyCodes[selectedCompany]);
displayPattern(unitCodes[selectedUnit]);
} else if (unitConfirmed && optionsLocked) {
// Second confirm: Show time and speed
optionsLocked = false;
char displayStr[3];
sprintf(displayStr, "%02d", currentNumber);
sevseg.setChars(displayStr);
displayPattern(isLowSpeed ? letterL : letterH);
digitalWrite(relaySpeedPin, isLowSpeed ? HIGH : LOW); // H: on, L: off
} else if (unitConfirmed && !optionsLocked) {
// Final confirm: Start countdown
categoryMode = false;
showSC = false;
showUnit = false;
unitConfirmed = false;
optionsLocked = false;
unitNavigationStarted = false;
firstUnitPress = true;
countingDown = true;
if (lastSetTime == 0) {
lastSetTime = currentNumber; // Store initial time
lastSpeedWasLow = isLowSpeed; // Store initial speed
}
previousMillis = currentMillis;
digitalWrite(relayMotorPin, LOW); // Start motor
digitalWrite(relaySpeedPin, isLowSpeed ? HIGH : LOW); // H: on, L: off
}
lastButtonPressTime = currentMillis;
} else if (button3State == HIGH && lastButton3State == LOW && !button3HeldForExit && currentMillis - lastButtonPressTime > debounceDelay && !categoryMode) {
// Short press: Start/Stop countdown
if (!countingDown && currentNumber > 0) {
countingDown = true;
if (lastSetTime == 0) {
lastSetTime = currentNumber; // Store initial time
lastSpeedWasLow = isLowSpeed; // Store initial speed
}
previousMillis = currentMillis;
digitalWrite(relayMotorPin, LOW); // Start motor
digitalWrite(relaySpeedPin, isLowSpeed ? HIGH : LOW); // H: on, L: off
} else if (countingDown) {
countingDown = false;
digitalWrite(relayMotorPin, HIGH); // Stop motor
digitalWrite(relaySpeedPin, isLowSpeed ? HIGH : LOW); // Maintain current speed
char displayStr[3];
sprintf(displayStr, "%02d", currentNumber);
sevseg.setChars(displayStr);
displayPattern(isLowSpeed ? letterL : letterH);
}
lastButtonPressTime = currentMillis;
}
lastButton3State = button3State;
// Handle category selection mode
if (categoryMode) {
// Flashing display
if (currentMillis - lastFlashTime >= flashInterval && (!unitConfirmed || !optionsLocked)) {
lastFlashTime = currentMillis;
flashOn = !flashOn;
}
char displayStr[3];
if (showSC) {
// Flash "SC"
if (flashOn) {
sevseg.setChars("SC");
} else {
sevseg.blank();
}
displayPattern(blankPattern);
} else if (showUnit && !unitConfirmed) {
// Flash "U" or unit
if (flashOn) {
displayPattern(unitNavigationStarted ? unitCodes[selectedUnit] : letterU);
} else {
displayPattern(blankPattern);
}
sevseg.setChars(companyCodes[selectedCompany]);
} else if (unitConfirmed && optionsLocked) {
// Show locked company and unit
sevseg.setChars(companyCodes[selectedCompany]);
displayPattern(unitCodes[selectedUnit]);
} else if (unitConfirmed && !optionsLocked) {
// Show time and speed
sprintf(displayStr, "%02d", currentNumber);
sevseg.setChars(displayStr);
displayPattern(isLowSpeed ? letterL : letterH);
} else {
// Show company
sevseg.setChars(companyCodes[selectedCompany]);
displayPattern(blankPattern);
}
// Navigation and adjustments
if (digitalRead(button1Pin) == LOW && currentMillis - lastButtonPressTime > debounceDelay) {
if (showSC) {
showSC = false;
selectedCompany = 0;
} else if (!showUnit && selectedCompany < NUM_COMPANIES - 1) {
selectedCompany++;
} else if (showUnit && !unitConfirmed) {
unitNavigationStarted = true;
if (!firstUnitPress && selectedUnit < NUM_UNITS - 1) {
selectedUnit++;
}
firstUnitPress = false;
} else if (unitConfirmed && !optionsLocked) {
// Adjust time
currentNumber = min(99, currentNumber + 1);
sprintf(displayStr, "%02d", currentNumber);
sevseg.setChars(displayStr);
}
lastButtonPressTime = currentMillis;
}
if (digitalRead(button2Pin) == LOW && currentMillis - lastButtonPressTime > debounceDelay) {
if (showSC) {
showSC = false;
selectedCompany = 0;
} else if (!showUnit && selectedCompany > 0) {
selectedCompany--;
} else if (showUnit && !unitConfirmed) {
unitNavigationStarted = true;
if (selectedUnit > 0) {
selectedUnit--;
firstUnitPress = false;
}
} else if (unitConfirmed && !optionsLocked) {
// Adjust time
currentNumber = max(0, currentNumber - 1);
sprintf(displayStr, "%02d", currentNumber);
sevseg.setChars(displayStr);
}
lastButtonPressTime = currentMillis;
}
} else {
// Normal mode
if (!countingDown) {
// Adjust time in normal mode or when stopped
if (digitalRead(button1Pin) == LOW && currentMillis - lastButtonPressTime > debounceDelay) {
currentNumber = min(99, currentNumber + 1);
char displayStr[3];
sprintf(displayStr, "%02d", currentNumber);
sevseg.setChars(displayStr);
lastButtonPressTime = currentMillis;
}
if (digitalRead(button2Pin) == LOW && currentMillis - lastButtonPressTime > debounceDelay) {
currentNumber = max(0, currentNumber - 1);
char displayStr[3];
sprintf(displayStr, "%02d", currentNumber);
sevseg.setChars(displayStr);
lastButtonPressTime = currentMillis;
}
}
// Update countdown
if (countingDown) {
if (currentMillis - previousMillis >= 1000) {
previousMillis = currentMillis;
if (currentNumber > 0) {
currentNumber--;
char displayStr[3];
sprintf(displayStr, "%02d", currentNumber);
sevseg.setChars(displayStr);
} else {
countingDown = false;
digitalWrite(relayMotorPin, HIGH); // Stop motor
digitalWrite(relaySpeedPin, isLowSpeed ? HIGH : LOW); // Restore speed
currentNumber = lastSetTime; // Restore initial time
isLowSpeed = lastSpeedWasLow; // Restore initial speed
char displayStr[3];
sprintf(displayStr, "%02d", currentNumber);
sevseg.setChars(displayStr);
displayPattern(isLowSpeed ? letterL : letterH);
digitalWrite(relaySpeedPin, isLowSpeed ? HIGH : LOW); // H: on, L: off
lastSetTime = 0; // Clear for next cycle
}
}
} else {
// Update display in normal mode
char displayStr[3];
sprintf(displayStr, "%02d", currentNumber);
sevseg.setChars(displayStr);
displayPattern(isLowSpeed ? letterL : letterH);
digitalWrite(relayMotorPin, HIGH); // Ensure motor off
}
}
sevseg.refreshDisplay();
}
void displayPattern(byte pattern) {
digitalWrite(latchPin, LOW);
shiftOut(dataPin, clockPin, LSBFIRST, pattern);
digitalWrite(latchPin, HIGH);
}