/***************************************************************
HNGR13 Bomb Timer - ILI9341 TFT (Querformat) + TM1637 + NeoPixel
- Adafruit_ILI9341 & Adafruit_GFX verwendet
- Hauptschalter D12: "Bitte einschalten" wird gezeigt bis ON
- Nach Einschalten: "HNGR13 BOMB - bitte START drücken" bis START
- Danach: Konfigurationsmodus (UP/DOWN LEDs einstellen)
- START (in Config): erster Timerstart -> sofort (3x Piep non-blocking)
- Später: 5s halten toggelt Start/Stop mit Hold-Animation (Ring2)
***************************************************************/
#include <Arduino.h>
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <TM1637Display.h>
#include <Adafruit_NeoPixel.h>
// ----------------- PIN-Definition -----------------
#define BTN_START 6
#define BTN_UP 3
#define BTN_DOWN 4
#define DISP_CLK A4
#define DISP_DIO A5
#define RING1_PIN 2
#define RING2_PIN 5
#define RELAY1 A0
#define RELAY2 A1
#define BUZZER 7
#define LED_BTNS A2
#define MAIN_SW A3 // Hauptschalter
// TFT pins (CS, DC, RST) -> SPI: MOSI=D11, MISO=D12, SCK=D13
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 8
// ----------------- Hardware-Objekte -----------------
TM1637Display tmDisplay(DISP_CLK, DISP_DIO);
Adafruit_NeoPixel ring1(16, RING1_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel ring2(16, RING2_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
// ----------------- Konfiguration -----------------
const unsigned long HOLD_MS = 5000UL;
const int R2_LEDS = 16;
const int STEP_TONE_MS = 100;
const unsigned long CONFIRM_BEEP_MS = 500UL;
const int SEGMENT_HUNDREDTHS = 6000; // 60.00s per segment
const int FREQ_LOW = 400;
const int FREQ_HIGH = 2000;
const int FREQ_STEP = (FREQ_HIGH - FREQ_LOW) / (R2_LEDS - 1);
const unsigned long TFT_UPDATE_MS = 500; // Weniger häufige Updates für bessere Performance
const unsigned long BTN_DEBOUNCE_MS = 200;
// ----------------- State -----------------
unsigned long previousMillis = 0;
unsigned long displayBlinkMillis = 0;
unsigned long ringBlinkMillis = 0;
unsigned long lastTftUpdate = 0;
bool dispBlinkState = true;
bool colonOn = true;
int totalLeds = 10;
int ledsRemaining = totalLeds;
int timeValue = SEGMENT_HUNDREDTHS;
bool running = false;
bool hasStarted = false;
unsigned long holdStart = 0;
bool actionExecutedWhileHeld = false;
bool wasRunningAtHoldStart = false;
bool pausedForHold = false;
int prevRing2Lit = -1;
int lastSecondBeepSec = -1;
bool tftPresent = true;
char tftStatus[40] = "Bereit";
int lastLedsRemaining = -1;
int lastTotalLeds = -1;
bool systemEnabled = false; // MAIN_SW
// Simulator-Optimierungen
bool forceRedraw = false;
bool lastSystemEnabled = false;
// Debounce für UP/DOWN
unsigned long lastUpPress = 0;
unsigned long lastDownPress = 0;
// Mode flow
bool greetingAck = false;
// ----------------- Hilfsfunktionen -----------------
void beepBlocking(int freq, int dur) {
tone(BUZZER, freq, dur);
delay(dur);
noTone(BUZZER);
}
void shortBeepNonBlock(int freq, int dur) {
tone(BUZZER, freq, dur);
}
void fillRing1(int count, int blinkIndex = -1, bool blinkOn = true) {
ring1.clear();
for (int i = 0; i < count && i < ring1.numPixels(); ++i)
ring1.setPixelColor(i, ring1.Color(255, 0, 255));
if (blinkIndex >= 0 && blinkIndex < ring1.numPixels() && blinkOn)
ring1.setPixelColor(blinkIndex, ring1.Color(255, 131, 250));
ring1.show();
}
void pulseRelay(int pin, int durationMs) {
digitalWrite(pin, HIGH);
delay(durationMs);
digitalWrite(pin, LOW);
}
void showTimeValueOnSeg(int tv, bool show = true, bool colon = true) {
if (!show) { tmDisplay.clear(); return; }
uint8_t mask = colon ? 0b01000000 : 0x00;
tmDisplay.showNumberDecEx(tv, mask, true);
}
void clearRing2() {
ring2.clear();
ring2.show();
}
void updateRing2DuringHold(float progress, bool wasRunningWhenHoldStarted) {
if (progress < 0.0f) progress = 0.0f;
if (progress > 1.0f) progress = 1.0f;
int lit;
if (!wasRunningWhenHoldStarted) lit = (int)floor(progress * R2_LEDS + 0.0001f);
else { lit = (int)ceil((1.0f - progress) * R2_LEDS - 0.0001f); if (lit < 0) lit = 0; }
if (lit > R2_LEDS) lit = R2_LEDS;
ring2.clear();
for (int i = 0; i < lit && i < ring2.numPixels(); ++i)
ring2.setPixelColor(i, ring2.Color(255,215,0));
ring2.show();
if (lit != prevRing2Lit) {
int toneIndex = -1;
if (!wasRunningWhenHoldStarted) {
if (lit > prevRing2Lit) toneIndex = lit - 1;
} else {
if (lit < prevRing2Lit) toneIndex = lit;
}
if (toneIndex >= 0) {
int freq = FREQ_LOW + toneIndex * FREQ_STEP;
shortBeepNonBlock(freq, STEP_TONE_MS);
}
prevRing2Lit = lit;
}
}
// TFT Status setzen (ersetzt oledSetStatus)
void tftSetStatus(const char* status) {
strncpy(tftStatus, status, sizeof(tftStatus) - 1);
tftStatus[sizeof(tftStatus) - 1] = '\0';
}
// ----------------- TFT Anzeige (optimiert für Simulator) -----------------
void tftDraw() {
if (!tftPresent) return;
// Nur bei größeren Änderungen komplett neu zeichnen
if (forceRedraw) {
tft.fillScreen(ILI9341_BLACK);
forceRedraw = false;
}
if (!systemEnabled) {
tft.fillScreen(ILI9341_BLACK); // Nur einmal löschen
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(3);
tft.setCursor(40, 90);
tft.print("Bitte");
tft.setCursor(20, 130);
tft.print("einschalten!");
return;
}
if (!greetingAck) {
if (forceRedraw || lastSystemEnabled != systemEnabled) {
tft.fillScreen(ILI9341_BLACK);
}
tft.setTextColor(ILI9341_YELLOW);
tft.setTextSize(2);
tft.setCursor(20, 40);
tft.print("HNGR13 BOMB");
tft.setTextSize(1);
tft.setCursor(18, 80);
tft.print("bitte START druecken");
return;
}
// Nur Status-Bereich aktualisieren statt ganzes Display
tft.fillRect(8, 6, 300, 10, ILI9341_BLACK); // Nur Status-Zeile löschen
tft.setTextColor(ILI9341_WHITE);
tft.setTextSize(1);
tft.setCursor(8, 6);
tft.print(tftStatus);
// LED-Balken (nur bei Änderung)
static int lastBarW = -1;
int barW = 0;
if (totalLeds > 0) barW = (int)((304UL * (unsigned long)ledsRemaining) / (unsigned long)totalLeds);
if (barW < 0) barW = 0; if (barW > 304) barW = 304;
if (barW != lastBarW) {
tft.fillRect(9, 23, 304, 16, ILI9341_BLACK); // Balken-Bereich löschen
tft.drawRect(8, 22, 304, 18, ILI9341_WHITE);
tft.fillRect(9, 23, barW, 16, ILI9341_GREEN);
lastBarW = barW;
}
// Sekunden-Balken (nur bei Änderung)
static int lastSecW = -1;
int secondsRemain = timeValue / 100;
if (secondsRemain < 0) secondsRemain = 0;
if (secondsRemain > 60) secondsRemain = 60;
int secW = (int)((304UL * (unsigned long)secondsRemain) / 60UL);
if (secW != lastSecW) {
tft.fillRect(9, 47, 304, 16, ILI9341_BLACK); // Balken-Bereich löschen
tft.drawRect(8, 46, 304, 18, ILI9341_WHITE);
tft.fillRect(9, 47, secW, 16, ILI9341_RED);
lastSecW = secW;
}
// Text unten (nur bei Änderung)
static char lastBuf[64] = "";
char buf[64];
snprintf(buf, sizeof(buf), "LED: %02d / %02d Sec: %02d", ledsRemaining, totalLeds, secondsRemain);
if (strcmp(buf, lastBuf) != 0) {
tft.fillRect(8, 74, 300, 10, ILI9341_BLACK); // Text-Bereich löschen
tft.setTextColor(ILI9341_CYAN);
tft.setTextSize(1);
tft.setCursor(8, 74);
tft.print(buf);
strcpy(lastBuf, buf);
}
}
// ----------------- Setup -----------------
void setup() {
Serial.begin(115200);
pinMode(BTN_START, INPUT_PULLUP);
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
pinMode(RELAY1, OUTPUT);
pinMode(RELAY2, OUTPUT);
pinMode(BUZZER, OUTPUT);
pinMode(LED_BTNS, OUTPUT);
pinMode(MAIN_SW, INPUT_PULLUP);
digitalWrite(RELAY1, LOW);
digitalWrite(RELAY2, LOW);
digitalWrite(LED_BTNS, LOW);
tmDisplay.setBrightness(7);
showTimeValueOnSeg(timeValue, false, true);
ring1.begin(); ring1.show();
ring2.begin(); ring2.show();
fillRing1(ledsRemaining, ledsRemaining - 1, true);
tft.begin();
tft.setRotation(1);
tft.fillScreen(ILI9341_BLACK);
tftDraw();
lastTftUpdate = millis();
Serial.println("HNGR13 ready - waiting for MAIN_SW");
}
// ----------------- Loop -----------------
void loop() {
unsigned long now = millis();
// Hauptschalter
bool currentSystemEnabled = (digitalRead(MAIN_SW) == LOW);
if (currentSystemEnabled != systemEnabled) {
forceRedraw = true; // Bei Systemzustand-Änderung neu zeichnen
}
systemEnabled = currentSystemEnabled;
lastSystemEnabled = systemEnabled;
if (!systemEnabled) {
tmDisplay.clear();
ring1.clear(); ring1.show();
ring2.clear(); ring2.show();
digitalWrite(LED_BTNS, LOW);
running = false;
hasStarted = false;
greetingAck = false;
if (tftPresent && now - lastTftUpdate >= 1000) { // Sehr langsame Updates für Simulator
tftDraw();
lastTftUpdate = now;
}
delay(100); // Längere Pause für bessere Simulator-Performance
return;
}
// Begrüßung
if (systemEnabled && !greetingAck) {
if (digitalRead(BTN_START) == LOW) {
greetingAck = true;
forceRedraw = true; // Vollständiges Neuzeichnen nach Greeting
tftSetStatus("Konfig: LEDs einstellen");
tftDraw();
lastTftUpdate = millis();
return;
}
if (tftPresent && now - lastTftUpdate >= 1000) { // Sehr langsame Updates für Simulator
tftDraw();
lastTftUpdate = now;
}
return;
}
// --- Restlicher Timer-Code ---
int startRaw = digitalRead(BTN_START);
if (!hasStarted) digitalWrite(LED_BTNS, HIGH);
else digitalWrite(LED_BTNS, LOW);
// --- Hold / Start-Stop logic ---
if (startRaw == LOW) {
if (holdStart == 0) {
holdStart = now;
actionExecutedWhileHeld = false;
wasRunningAtHoldStart = running;
prevRing2Lit = wasRunningAtHoldStart ? R2_LEDS : 0;
if (!hasStarted) {
running = true; hasStarted = true; digitalWrite(RELAY2, HIGH);
previousMillis = now; lastSecondBeepSec = timeValue / 100;
beepBlocking(1500,200); delay(70); beepBlocking(1500,200); delay(70); beepBlocking(1500,200); delay(260);
tftSetStatus("Erster Start");
holdStart = 1; actionExecutedWhileHeld = true; clearRing2();
return;
}
if (wasRunningAtHoldStart) { pausedForHold = true; running = false; tftSetStatus("Pause (5s halten)"); }
else { pausedForHold = false; tftSetStatus("starten (5s halten)"); }
ring2.clear();
if (wasRunningAtHoldStart) for (int i=0;i<R2_LEDS;i++) ring2.setPixelColor(i, ring2.Color(150,0,0));
ring2.show();
}
if (!actionExecutedWhileHeld) {
unsigned long held = now - holdStart;
float progress = (float)held / (float)HOLD_MS;
if (progress > 1.0f) progress = 1.0f;
updateRing2DuringHold(progress, wasRunningAtHoldStart);
if (progress >= 1.0f) {
if (!wasRunningAtHoldStart) {
running = true; hasStarted = true; digitalWrite(RELAY2, HIGH);
previousMillis = now; lastSecondBeepSec = timeValue / 100; tftSetStatus("Timer gestartet");
} else {
running = false; digitalWrite(RELAY2, LOW); tftSetStatus("Timer gestoppt");
}
beepBlocking(1000, CONFIRM_BEEP_MS);
pulseRelay(RELAY1, 1000);
actionExecutedWhileHeld = true; clearRing2(); pausedForHold = false;
}
}
} else {
if (holdStart != 0) {
if (!actionExecutedWhileHeld && pausedForHold) {
running = true; previousMillis = now; lastSecondBeepSec = timeValue / 100; pausedForHold = false;
tftSetStatus("Resume");
}
holdStart = 0; actionExecutedWhileHeld = false; prevRing2Lit = -1; clearRing2();
}
}
// --- UP / DOWN (nur vor erstem Start, mit Entprellung) ---
if (!hasStarted) {
if (digitalRead(BTN_UP) == LOW && now - lastUpPress >= BTN_DEBOUNCE_MS) {
if (totalLeds < 16) totalLeds++;
ledsRemaining = totalLeds;
shortBeepNonBlock(800,80);
fillRing1(ledsRemaining, ledsRemaining - 1, true);
tftSetStatus(("Setze LEDs: " + String(totalLeds)).c_str());
lastUpPress = now;
}
if (digitalRead(BTN_DOWN) == LOW && now - lastDownPress >= BTN_DEBOUNCE_MS) {
if (totalLeds > 1) totalLeds--;
ledsRemaining = totalLeds;
shortBeepNonBlock(600,80);
fillRing1(ledsRemaining, ledsRemaining - 1, true);
tftSetStatus(("Setze LEDs: " + String(totalLeds)).c_str());
lastDownPress = now;
}
}
// --- Timer in 10ms Quanten ---
if (running) {
unsigned long now2 = millis();
if (previousMillis == 0) { previousMillis = now2; lastSecondBeepSec = timeValue / 100; }
unsigned long elapsed = now2 - previousMillis;
if (elapsed >= 10) {
unsigned long steps = elapsed / 10;
int dec = (int)steps;
timeValue -= dec;
previousMillis += steps * 10UL;
while (timeValue <= 0) {
timeValue += SEGMENT_HUNDREDTHS;
ledsRemaining--;
if (ledsRemaining < 0) ledsRemaining = 0;
fillRing1(ledsRemaining, ledsRemaining - 1, true);
if (ledsRemaining <= 0) {
running = false; digitalWrite(RELAY2, LOW);
tftSetStatus("BOOM!Bombe explodiert");
beepBlocking(400,2000); pulseRelay(RELAY1,7000);
timeValue = SEGMENT_HUNDREDTHS;
showTimeValueOnSeg(timeValue, true, true);
break;
}
}
showTimeValueOnSeg(timeValue, true, colonOn);
} else showTimeValueOnSeg(timeValue, true, colonOn);
int currentSec = timeValue / 100;
if (currentSec != lastSecondBeepSec) {
lastSecondBeepSec = currentSec;
shortBeepNonBlock(2000, 80);
colonOn = !colonOn;
}
} else {
if (now - displayBlinkMillis >= 500) {
displayBlinkMillis = now; dispBlinkState = !dispBlinkState;
}
if (dispBlinkState) {
if (!hasStarted) tmDisplay.showNumberDecEx(SEGMENT_HUNDREDTHS, 0b01000000, true);
else showTimeValueOnSeg(timeValue, true, true);
} else tmDisplay.clear();
}
// --- Ring1 blink (2Hz next LED) ---
if (running) {
if (now - ringBlinkMillis >= 500) {
ringBlinkMillis = now;
static bool ringBlinkState = true; ringBlinkState = !ringBlinkState;
fillRing1(ledsRemaining, ledsRemaining - 1, ringBlinkState);
}
} else fillRing1(ledsRemaining, ledsRemaining - 1, true);
// --- TFT: redraw ---
bool needRedraw = false;
if (now - lastTftUpdate >= TFT_UPDATE_MS) needRedraw = true;
if (ledsRemaining != lastLedsRemaining || totalLeds != lastTotalLeds) needRedraw = true;
if (needRedraw && tftPresent) {
tftDraw();
lastTftUpdate = now;
lastLedsRemaining = ledsRemaining;
lastTotalLeds = totalLeds;
}
}
1000 µF Kondensator
1000 µF Kondensator
Beleuchtung up/down
Hauptschalter