// ============================================================================
// CREATED AND DESIGNED BY: MARVIN A. QUIZZAGAN
// DATE: MARCH 14, 2026 – STABILITY / ANTI-JITTER VERSION
// Arduino Nano SWITCHING – Alert / Reverse Beeper Controller
//
// INPUTS
// D9 → Mirror to A0 (external pull-up)
// D8 → Mirror to A1 + HOLD command + D6 trigger (external pull-up)
// D7 → Internal pull-up → inverted output to A2
// D4 → HOLD input + D6 trigger (external pull-up)
// D2 → HOLD input (external pull-up)
// D12 → Enable reverse function (LOW = allow D13)
// D13 → Reverse trigger input (internal pull-up)
//
// OUTPUTS
// A0 → Mirrors D9
// A1 → Mirrors D8
// A2 → Inverted D7
// D3 → Alert / reverse beeper
// D6 → Default HIGH, becomes LOW only when D8 + D4 are LOW
// but stays HIGH if D2 is also LOW
//
// CONTROL
// Hold D8 + D4 + D2 LOW:
// OFF → hold 5 sec → ALERT ON
// ON → hold 1.5 sec → ALERT OFF
//
// EXTRA OUTPUT CONTROL
// D8 + D4 LOW for 30 ms, while D2 is HIGH:
// D6 = LOW
// If D8 or D4 becomes HIGH, or D2 becomes LOW:
// D6 = HIGH immediately
//
// ALERT MODE
// beep-beep-beep (fast) → pause 3 sec → repeat
//
// REVERSE MODE (Highest Priority)
// Active when: D12 LOW + D13 LOW
// BEEEP … pause … BEEEP … pause … continuously
//
// NOTE
// External pull-ups required: D9, D8, D4, D2
// Internal pull-ups used: D7, D12, D13
// ============================================================================
const byte IN1 = 9;
const byte OUT1 = A0;
const byte IN2 = 8;
const byte OUT2 = A1;
const byte IN3 = 7;
const byte OUT3 = A2;
const byte HOLD1 = 4;
const byte HOLD2 = 2;
const byte D13_ENABLE = 12; // D12 = enables D13 function only when LOW
const byte REV_IN = 13; // D13 = reverse trigger, INPUT_PULLUP
const byte LATCH_OUT = 3; // D3 = beeping output
const byte OUT_D6 = 6; // D6 = HIGH by default
// ---------------- Hold Timing ----------------
const unsigned long LONG_HOLD_ON_MS = 5000;
const unsigned long LONG_HOLD_OFF_MS = 1500;
// ---------------- Debounce / Input Filtering ----------------
// 35 ms is more resistant to vehicle/electrical noise than 25 ms
const unsigned long INPUT_FILTER_MS = 35;
// ---------------- D6 Trigger Timing ----------------
const unsigned long D6_TRIGGER_MS = 30;
// ---------------- Alert Pattern ----------------
const unsigned long ALERT_BEEP_HIGH_MS = 80;
const unsigned long ALERT_BEEP_LOW_MS = 80;
const unsigned long ALERT_BEEP_PAUSE_MS = 3000;
// ---------------- Reverse Pattern ----------------
const unsigned long REV_BEEP_HIGH_MS = 320;
const unsigned long REV_BEEP_LOW_MS = 680;
// ---------------- Mode Values ----------------
#define MODE_OFF 0
#define MODE_ALERT 1
#define MODE_REVERSE 2
// ---------------- Input Filter Object ----------------
struct DebouncedInput {
byte pin;
bool rawState;
bool stableState;
unsigned long lastChangeMs;
void begin(byte p, uint8_t mode) {
pin = p;
pinMode(pin, mode);
bool r = digitalRead(pin);
rawState = r;
stableState = r;
lastChangeMs = millis();
}
void update(unsigned long now) {
bool r = digitalRead(pin);
if (r != rawState) {
rawState = r;
lastChangeMs = now;
}
if ((now - lastChangeMs) >= INPUT_FILTER_MS) {
stableState = rawState;
}
}
bool read() const {
return stableState;
}
};
// ---------------- Inputs ----------------
DebouncedInput inD9;
DebouncedInput inD8;
DebouncedInput inD7;
DebouncedInput inD4;
DebouncedInput inD2;
DebouncedInput inD12;
DebouncedInput inD13;
// ---------------- Main States ----------------
bool latchState = false;
// Hold arming / release lock
bool holdLock = false;
unsigned long longHoldStartMs = 0;
// D6 state
bool d6Active = false;
bool d6Timing = false;
unsigned long d6StartMs = 0;
// Beeper state
byte currentMode = MODE_OFF;
unsigned long beepT0 = 0;
byte beepCount = 0;
byte alertState = 0;
byte reverseState = 0;
// Output shadow states to avoid unnecessary rewrites
bool out1Last = LOW;
bool out2Last = LOW;
bool out3Last = LOW;
bool out6Last = HIGH;
bool out3BeepLast = LOW;
// ---------------- Safe Output Writer ----------------
void writeIfChanged(byte pin, bool &shadow, bool value) {
if (shadow != value) {
shadow = value;
digitalWrite(pin, value ? HIGH : LOW);
}
}
// ---------------- Reset Beeper FSM ----------------
void resetBeeperFSM() {
alertState = 0;
reverseState = 0;
beepT0 = 0;
beepCount = 0;
writeIfChanged(LATCH_OUT, out3BeepLast, LOW);
}
// ---------------- Beeper Engine ----------------
void serviceD3Beep(byte requestedMode, unsigned long now) {
if (requestedMode != currentMode) {
currentMode = requestedMode;
resetBeeperFSM();
}
if (currentMode == MODE_OFF) {
writeIfChanged(LATCH_OUT, out3BeepLast, LOW);
return;
}
if (currentMode == MODE_ALERT) {
switch (alertState) {
case 0:
writeIfChanged(LATCH_OUT, out3BeepLast, HIGH);
alertState = 1;
beepT0 = now;
beepCount = 0;
break;
case 1:
if (now - beepT0 >= ALERT_BEEP_HIGH_MS) {
writeIfChanged(LATCH_OUT, out3BeepLast, LOW);
alertState = 2;
beepT0 = now;
}
break;
case 2:
if (now - beepT0 >= ALERT_BEEP_LOW_MS) {
beepCount++;
if (beepCount >= 3) {
writeIfChanged(LATCH_OUT, out3BeepLast, LOW);
alertState = 3;
beepT0 = now;
beepCount = 0;
} else {
writeIfChanged(LATCH_OUT, out3BeepLast, HIGH);
alertState = 1;
beepT0 = now;
}
}
break;
case 3:
if (now - beepT0 >= ALERT_BEEP_PAUSE_MS) {
writeIfChanged(LATCH_OUT, out3BeepLast, HIGH);
alertState = 1;
beepT0 = now;
}
break;
}
return;
}
if (currentMode == MODE_REVERSE) {
switch (reverseState) {
case 0:
writeIfChanged(LATCH_OUT, out3BeepLast, HIGH);
reverseState = 1;
beepT0 = now;
break;
case 1:
if (now - beepT0 >= REV_BEEP_HIGH_MS) {
writeIfChanged(LATCH_OUT, out3BeepLast, LOW);
reverseState = 2;
beepT0 = now;
}
break;
case 2:
if (now - beepT0 >= REV_BEEP_LOW_MS) {
writeIfChanged(LATCH_OUT, out3BeepLast, HIGH);
reverseState = 1;
beepT0 = now;
}
break;
}
}
}
// ---------------- Setup ----------------
void setup() {
// Inputs
inD9.begin(IN1, INPUT); // external pull-up
inD8.begin(IN2, INPUT); // external pull-up
inD7.begin(IN3, INPUT_PULLUP); // internal pull-up
inD4.begin(HOLD1, INPUT); // external pull-up
inD2.begin(HOLD2, INPUT); // external pull-up
inD12.begin(D13_ENABLE, INPUT_PULLUP);
inD13.begin(REV_IN, INPUT_PULLUP);
// Outputs
pinMode(OUT1, OUTPUT);
pinMode(OUT2, OUTPUT);
pinMode(OUT3, OUTPUT);
pinMode(LATCH_OUT, OUTPUT);
pinMode(OUT_D6, OUTPUT);
latchState = false;
holdLock = false;
longHoldStartMs = 0;
d6Active = false;
d6Timing = false;
d6StartMs = 0;
currentMode = MODE_OFF;
resetBeeperFSM();
// Safe startup states
digitalWrite(OUT1, LOW);
digitalWrite(OUT2, LOW);
digitalWrite(OUT3, LOW);
digitalWrite(LATCH_OUT, LOW);
digitalWrite(OUT_D6, HIGH);
out1Last = LOW;
out2Last = LOW;
out3Last = LOW;
out3BeepLast = LOW;
out6Last = HIGH;
}
// ---------------- Main Loop ----------------
void loop() {
unsigned long now = millis();
// -------- Update all filtered inputs first --------
inD9.update(now);
inD8.update(now);
inD7.update(now);
inD4.update(now);
inD2.update(now);
inD12.update(now);
inD13.update(now);
// -------- Sample stable states once --------
const bool in1State = inD9.read(); // D9
const bool in2State = inD8.read(); // D8
const bool in3State = inD7.read(); // D7
const bool hold1State = inD4.read(); // D4
const bool hold2State = inD2.read(); // D2
const bool d12State = inD12.read(); // D12
const bool revState = inD13.read(); // D13
// -------- Mirror outputs --------
writeIfChanged(OUT1, out1Last, in1State);
writeIfChanged(OUT2, out2Last, in2State);
writeIfChanged(OUT3, out3Last, !in3State);
// -------- D6 Logic --------
// LOW only when D8 LOW + D4 LOW + D2 HIGH for at least D6_TRIGGER_MS
const bool d6Condition =
(in2State == LOW) &&
(hold1State == LOW) &&
(hold2State == HIGH);
if (!d6Condition) {
d6Timing = false;
d6Active = false;
} else {
if (!d6Timing) {
d6Timing = true;
d6StartMs = now;
} else if ((now - d6StartMs) >= D6_TRIGGER_MS) {
d6Active = true;
}
}
writeIfChanged(OUT_D6, out6Last, d6Active ? LOW : HIGH);
// -------- Reverse Mode --------
const bool d13Allowed = (d12State == LOW);
const bool reverseActive = d13Allowed && (revState == LOW);
// -------- Hold Detection --------
const bool holdComboActive =
(in2State == LOW) &&
(hold1State == LOW) &&
(hold2State == LOW);
if (holdLock) {
// Must fully release combo before next toggle
if (!holdComboActive) {
holdLock = false;
longHoldStartMs = 0;
}
} else {
if (holdComboActive) {
if (longHoldStartMs == 0) {
longHoldStartMs = now;
} else {
const unsigned long requiredMs =
latchState ? LONG_HOLD_OFF_MS : LONG_HOLD_ON_MS;
if ((now - longHoldStartMs) >= requiredMs) {
latchState = !latchState;
holdLock = true;
longHoldStartMs = 0;
}
}
} else {
longHoldStartMs = 0;
}
}
// -------- Priority for D3 --------
byte requestedMode = MODE_OFF;
if (reverseActive) {
requestedMode = MODE_REVERSE; // highest priority
} else if (latchState) {
requestedMode = MODE_ALERT;
}
// -------- Service beeper --------
serviceD3Beep(requestedMode, now);
}