#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <TM1637Display.h>
#include <Adafruit_NeoPixel.h>
// =============================================================================
// PIN-DEFINITIONEN
// =============================================================================
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST 4
#define BUTTON_PIN 13
#define BUTTON_UP 32
#define BUTTON_DOWN 33
#define TM1637_CLK 21
#define TM1637_DIO 22
#define BUZZER_PIN 25
#define LED_RING_PIN 26
#define LED_RING_2_PIN 27
#define BUTTON_LED_PIN 14 // NEU: Pin für Button-Beleuchtung
#define LED_COUNT 35
// =============================================================================
// HARDWARE-OBJEKTE
// =============================================================================
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);
TM1637Display display(TM1637_CLK, TM1637_DIO);
Adafruit_NeoPixel ledRing(LED_COUNT, LED_RING_PIN, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel ledRing2(LED_COUNT, LED_RING_2_PIN, NEO_GRB + NEO_KHZ800);
// =============================================================================
// FARB-DEFINITIONEN
// =============================================================================
#define COLOR_BG 0x0000
#define COLOR_PANEL 0x2945
#define COLOR_ACCENT 0xFD20
#define COLOR_SUCCESS 0x07E0
#define COLOR_WARNING 0xFFE0
#define COLOR_DANGER 0xF800
#define COLOR_INFO 0x07FF
#define COLOR_TEXT 0xFFFF
#define COLOR_TEXT_DIM 0x8410
#define COLOR_CREDIT 0x87F0 // Hellblau für Credit-Box
// =============================================================================
// TIMING-KONSTANTEN
// =============================================================================
const unsigned long TIMER_DURATION = 60000;
const long SEGMENT_UPDATE_INTERVAL = 10;
const long TFT_UPDATE_INTERVAL = 100;
const long BLINK_INTERVAL = 500;
const long BEEP_DURATION = 100;
const long DEBOUNCE_DELAY = 50;
const long INITIAL_REPEAT_DELAY = 500;
const long REPEAT_INTERVAL = 100;
const long HOLD_DURATION = 3000; // 3 Sekunden Haltezeit
// =============================================================================
// LED-KONSTANTEN
// =============================================================================
const uint8_t LED_BRIGHTNESS = 100;
const uint8_t MIN_LEDS = 1;
const uint8_t MAX_LEDS = LED_COUNT;
// =============================================================================
// BUZZER-FREQUENZEN
// =============================================================================
const int BEEP_FREQ_NORMAL = 1000;
const int BEEP_FREQ_START = 1500;
const int BEEP_FREQ_PAUSE = 1200;
const int BEEP_FREQ_LED_DOWN = 1200;
const int BEEP_FREQ_FINAL = 2000;
const int BEEP_FREQ_UP = 800;
const int BEEP_FREQ_DOWN = 600;
const int BEEP_FREQ_HOLD_PROGRESS = 600; // Für Fortschritts-Töne
// =============================================================================
// SYSTEM-VARIABLEN
// =============================================================================
bool currentScreen = 0;
bool timerRunning = false;
bool timerStarted = false;
unsigned long timerStartTime = 0;
unsigned long timerPauseTime = 0;
unsigned long timerDuration = TIMER_DURATION;
// Update-Zeitstempel
unsigned long lastSegmentUpdate = 0;
unsigned long lastTftUpdate = 0;
unsigned long lastBlinkTime = 0;
unsigned long lastButtonBlinkTime = 0; // Für Button-Blinking
// Buzzer Variablen
unsigned long lastSecondBeeped = 99;
// LED Ring Variablen
int activeLeds = 10;
int initialLeds = 10;
bool blinkState = false;
bool buttonBlinkState = false; // Für Button-Blinking
int currentBlinkingLed = 0;
bool finalRound = false;
// Timer Status Variablen
int currentRound = 1;
unsigned long currentRemainingTime = TIMER_DURATION;
// Button Variablen
bool lastButtonState = HIGH;
bool lastButtonUpState = HIGH;
bool lastButtonDownState = HIGH;
bool buttonPressed = false;
bool buttonUpPressed = false;
bool buttonDownPressed = false;
unsigned long lastDebounceTime = 0;
unsigned long lastDebounceTimeUp = 0;
unsigned long lastDebounceTimeDown = 0;
unsigned long buttonUpPressTime = 0;
unsigned long buttonDownPressTime = 0;
// Hauptbutton Halte-Funktion
unsigned long mainButtonPressTime = 0;
bool mainButtonHeld = false;
bool firstStart = true; // Für den allerersten Start
bool holdProgressBeeped1 = false; // Für 1-Sekunden-Ton
bool holdProgressBeeped2 = false; // Für 2-Sekunden-Ton
// Zweiter Ring Animation
bool ring2AnimationActive = false;
unsigned long ring2AnimationStartTime = 0;
bool ring2Filling = true; // true = füllend, false = leerend
// Status
char statusText[20] = "Bereit";
bool forceRedraw = true;
// =============================================================================
// HILFSFUNKTIONEN - FARBEN & GRAFIK
// =============================================================================
inline uint16_t getTimeColor(int seconds) {
if (seconds > 40) return COLOR_SUCCESS;
if (seconds > 20) return COLOR_WARNING;
return COLOR_DANGER;
}
inline uint16_t getLEDColor(int current, int max) {
float ratio = (float)current / max;
if (ratio > 0.66f) return COLOR_SUCCESS;
if (ratio > 0.33f) return COLOR_WARNING;
return COLOR_DANGER;
}
inline void drawRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t color) {
tft.drawRoundRect(x, y, w, h, r, color);
}
inline void fillRoundRect(int16_t x, int16_t y, int16_t w, int16_t h, int16_t r, uint16_t color) {
tft.fillRoundRect(x, y, w, h, r, color);
}
void setStatus(const char* status) {
strncpy(statusText, status, sizeof(statusText) - 1);
statusText[sizeof(statusText) - 1] = '\0';
drawStatusBox();
}
inline void shortBeep(int freq, int duration) {
tone(BUZZER_PIN, freq, duration);
}
void drawHoldProgress(int progress) {
// Fortschrittsbalken für Haltefunktion zeichnen
const int barWidth = 200;
const int barHeight = 10;
const int barX = (320 - barWidth) / 2;
const int barY = 240;
// Hintergrund
fillRoundRect(barX, barY, barWidth, barHeight, 3, COLOR_PANEL);
drawRoundRect(barX, barY, barWidth, barHeight, 3, COLOR_TEXT_DIM);
// Füllstand
if (progress > 0) {
int16_t fillW = (barWidth * progress) / 100;
fillW = constrain(fillW, 0, barWidth - 2);
if (fillW > 0) {
fillRoundRect(barX + 1, barY + 1, fillW, barHeight - 2, 2, COLOR_ACCENT);
}
}
}
// =============================================================================
// BUTTON-BELEUCHTUNG
// =============================================================================
void updateButtonLED() {
// LEDs nur einschalten wenn Up/Down Buttons bedienbar sind
if (!timerStarted && currentScreen == 1) {
digitalWrite(BUTTON_LED_PIN, HIGH); // LEDs EIN
} else {
digitalWrite(BUTTON_LED_PIN, LOW); // LEDs AUS
}
}
// =============================================================================
// FORTSCHRITTSBALKEN
// =============================================================================
void drawProgressBar(int16_t x, int16_t y, int16_t w, int16_t h,
int progress, int maxVal, uint16_t color, const char* label) {
// Hintergrund
fillRoundRect(x, y, w, h, 3, COLOR_PANEL);
drawRoundRect(x, y, w, h, 3, COLOR_TEXT_DIM);
// Füllstand
if (maxVal > 0 && progress > 0) {
int16_t fillW = (w * progress) / maxVal;
fillW = constrain(fillW, 0, w - 2);
if (fillW > 0) {
fillRoundRect(x + 1, y + 1, fillW, h - 2, 2, color);
}
}
// Beschriftung - Bereich löschen
tft.fillRect(x, y - 10, w, 10, COLOR_PANEL);
// Label
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(x, y - 10);
tft.print(label);
// Wert - Bereich löschen
tft.fillRect(x + w - 30, y - 10, 30, 10, COLOR_PANEL);
// Wert anzeigen
char valueText[10];
snprintf(valueText, sizeof(valueText), "%d/%d", progress, maxVal);
tft.setTextColor(color);
tft.setCursor(x + w - 30, y - 10);
tft.print(valueText);
}
void drawTimeProgressBar(int16_t x, int16_t y, int16_t w, int16_t h,
int seconds, const char* label) {
// Hintergrund
fillRoundRect(x, y, w, h, 3, COLOR_PANEL);
drawRoundRect(x, y, w, h, 3, COLOR_TEXT_DIM);
// Füllstand mit Farbverlauf
if (seconds > 0) {
int16_t fillW = (w * seconds) / 60;
fillW = constrain(fillW, 0, w - 2);
if (fillW > 0) {
uint16_t timeColor = getTimeColor(seconds);
fillRoundRect(x + 1, y + 1, fillW, h - 2, 2, timeColor);
}
}
// Beschriftung - Bereich löschen
tft.fillRect(x, y - 10, w, 10, COLOR_PANEL);
// Label
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(x, y - 10);
tft.print(label);
// Wert - Bereich löschen
tft.fillRect(x + w - 30, y - 10, 30, 10, COLOR_PANEL);
// Wert anzeigen
char valueText[10];
snprintf(valueText, sizeof(valueText), "%02ds", seconds);
uint16_t timeColor = getTimeColor(seconds);
tft.setTextColor(timeColor);
tft.setCursor(x + w - 30, y - 10);
tft.print(valueText);
}
// =============================================================================
// SCREEN-DRAWING FUNKTIONEN
// =============================================================================
void drawStartButton() {
const int buttonX = 100;
const int buttonY = 190;
const int buttonWidth = 120;
const int buttonHeight = 35;
if (buttonBlinkState) {
// Button sichtbar - zeichnen
fillRoundRect(buttonX, buttonY, buttonWidth, buttonHeight, 5, COLOR_SUCCESS);
drawRoundRect(buttonX, buttonY, buttonWidth, buttonHeight, 5, COLOR_TEXT);
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(2);
tft.setCursor(buttonX + 25, buttonY + 8);
tft.print(F("START"));
} else {
// Button unsichtbar - mit Hintergrundfarbe füllen
fillRoundRect(buttonX, buttonY, buttonWidth, buttonHeight, 5, COLOR_BG);
drawRoundRect(buttonX, buttonY, buttonWidth, buttonHeight, 5, COLOR_BG);
}
}
void drawStartScreen() {
tft.fillScreen(COLOR_BG);
// Header - Obere Box
fillRoundRect(15, 15, 290, 45, 8, COLOR_PANEL);
drawRoundRect(15, 15, 290, 45, 8, COLOR_ACCENT);
tft.setTextColor(COLOR_ACCENT);
tft.setTextSize(2);
tft.setCursor(50, 28);
tft.print(F("HNGR13 BOMB TIMER"));
// Credit-Box - Mittlere Box
fillRoundRect(15, 75, 290, 35, 6, COLOR_PANEL);
drawRoundRect(15, 75, 290, 35, 6, COLOR_CREDIT);
tft.setTextColor(COLOR_CREDIT);
tft.setTextSize(1);
tft.setCursor(70, 85);
tft.print(F("Written by"));
tft.setTextSize(1);
tft.setCursor(85, 97);
tft.print(F("HNGR13 Hausteam"));
// Hauptbereich - Untere Box
fillRoundRect(15, 125, 290, 50, 6, COLOR_PANEL);
drawRoundRect(15, 125, 290, 50, 6, COLOR_INFO);
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(1);
tft.setCursor(25, 135);
tft.print(F("Bereit zum Start"));
tft.setTextColor(COLOR_INFO);
tft.setCursor(25, 150);
tft.print(F(">> START druecken zum Fortfahren"));
// Start Button - Mit Blink-Funktion
drawStartButton();
}
void drawStatusBox() {
// Status Box
fillRoundRect(10, 10, 300, 30, 5, COLOR_PANEL);
drawRoundRect(10, 10, 300, 30, 5, COLOR_INFO);
// Status Text
tft.setTextColor(COLOR_INFO);
tft.setTextSize(1);
tft.setCursor(15, 15);
tft.print(F("Status:"));
tft.setTextColor(COLOR_TEXT);
tft.setCursor(70, 15);
tft.print(statusText);
}
void drawLEDSetting() {
// LED Einstellung
fillRoundRect(10, 50, 300, 40, 5, COLOR_PANEL);
drawRoundRect(10, 50, 300, 40, 5, COLOR_WARNING);
// Titel
tft.setTextColor(COLOR_WARNING);
tft.setTextSize(1);
tft.setCursor(15, 57);
tft.print(F("LED EINSTELLUNG"));
if (!timerStarted) {
// UP/DOWN Buttons
fillRoundRect(20, 70, 25, 15, 3, COLOR_DANGER);
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(1);
tft.setCursor(27, 73);
tft.print(F("-"));
fillRoundRect(265, 70, 25, 15, 3, COLOR_SUCCESS);
tft.setCursor(272, 73);
tft.print(F("+"));
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(2);
tft.setCursor(120, 70);
tft.print(initialLeds);
tft.print(F("/35"));
} else {
// Gesperrt
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(100, 70);
tft.print(F("LEDs: "));
tft.print(activeLeds);
tft.print(F("/35 (gesperrt)"));
}
}
void drawTimerDisplay() {
// Timer Anzeige
fillRoundRect(10, 100, 300, 80, 5, COLOR_PANEL);
drawRoundRect(10, 100, 300, 80, 5, COLOR_INFO);
// Titel
tft.setTextColor(COLOR_INFO);
tft.setTextSize(1);
tft.setCursor(15, 107);
tft.print(F("TIMER ANZEIGE"));
// LED Fortschrittsbalken
uint16_t ledColor = getLEDColor(activeLeds, initialLeds);
drawProgressBar(15, 125, 270, 12, activeLeds, initialLeds, ledColor, "Verbleibende LEDs");
// Zeit Fortschrittsbalken
int secondsRemaining = currentRemainingTime / 1000;
drawTimeProgressBar(15, 150, 270, 12, secondsRemaining, "Verbleibende Zeit");
// Runden-Anzeige - KORRIGIERTE FARBE
tft.setTextColor(COLOR_TEXT_DIM); // Geändert von COLOR_INFO zu COLOR_TEXT_DIM
tft.setTextSize(1);
tft.setCursor(15, 170);
tft.print(F("Aktuelle Runde: "));
tft.setTextColor(COLOR_INFO); // Farbe für die Zahlen
tft.print(currentRound);
tft.setTextColor(COLOR_TEXT_DIM); // Zurück zur Standardfarbe
tft.print(F("/"));
tft.setTextColor(COLOR_INFO); // Farbe für die Zahlen
tft.print(initialLeds);
}
void updateTimerDisplay() {
int secondsRemaining = currentRemainingTime / 1000;
// LED-Balken
uint16_t ledColor = getLEDColor(activeLeds, initialLeds);
drawProgressBar(15, 125, 270, 12, activeLeds, initialLeds, ledColor, "Verbleibende LEDs");
// Zeit-Balken
drawTimeProgressBar(15, 150, 270, 12, secondsRemaining, "Verbleibende Zeit");
// Runden-Anzeige - KORRIGIERTE FARBE
tft.fillRect(15, 170, 150, 8, COLOR_PANEL);
tft.setTextColor(COLOR_TEXT_DIM); // Geändert von COLOR_INFO zu COLOR_TEXT_DIM
tft.setTextSize(1);
tft.setCursor(15, 170);
tft.print(F("Aktuelle Runde: "));
tft.setTextColor(COLOR_INFO); // Farbe für die Zahlen
tft.print(currentRound);
tft.setTextColor(COLOR_TEXT_DIM); // Zurück zur Standardfarbe
tft.print(F("/"));
tft.setTextColor(COLOR_INFO); // Farbe für die Zahlen
tft.print(initialLeds);
}
void drawControlButtons() {
const int buttonWidth = 120;
const int buttonX = (320 - buttonWidth) / 2;
if (!timerStarted) {
// Start Button
fillRoundRect(buttonX, 190, buttonWidth, 35, 5, COLOR_SUCCESS);
drawRoundRect(buttonX, 190, buttonWidth, 35, 5, COLOR_TEXT);
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(2);
tft.setCursor(buttonX + 25, 198);
tft.print(F("START"));
// Halte-Hinweis löschen
tft.fillRect(buttonX, 225, buttonWidth, 20, COLOR_BG); // Y-Position angepasst
} else if (timerRunning) {
// Pause Button
fillRoundRect(buttonX, 190, buttonWidth, 35, 5, COLOR_WARNING);
drawRoundRect(buttonX, 190, buttonWidth, 35, 5, COLOR_TEXT);
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(2);
tft.setCursor(buttonX + 30, 198);
tft.print(F("PAUSE"));
// Halte-Hinweis - KORRIGIERTE POSITION
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(buttonX - 10, 225); // Y-Position von 230 auf 225 geändert
tft.print(F("3 Sekunden halten"));
} else {
// Weiter Button
fillRoundRect(buttonX, 190, buttonWidth, 35, 5, COLOR_SUCCESS);
drawRoundRect(buttonX, 190, buttonWidth, 35, 5, COLOR_TEXT);
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(2);
tft.setCursor(buttonX + 20, 198);
tft.print(F("WEITER"));
// Halte-Hinweis - KORRIGIERTE POSITION
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(buttonX - 10, 225); // Y-Position von 230 auf 225 geändert
tft.print(F("3 Sekunden halten"));
}
}
void drawConfigScreen() {
tft.fillScreen(COLOR_BG);
forceRedraw = true;
drawStatusBox();
drawLEDSetting();
drawTimerDisplay();
drawControlButtons();
// Peripherie einschalten
display.setBrightness(0x0f);
ledRing.setBrightness(LED_BRIGHTNESS);
ledRing2.setBrightness(LED_BRIGHTNESS);
updateLedRing();
// Zweiter Ring wird nur bei Animation angezeigt
ledRing2.clear();
ledRing2.show();
// SEGMENTANZEIGE KORRIGIERT - Nur bei erstem Start oder Reset auf 6000 setzen
if (!timerStarted || firstStart) {
display.showNumberDecEx(6000, 0b01000000, true);
} else {
// Ansonsten aktuellen Timerstand anzeigen
unsigned long seconds = currentRemainingTime / 1000;
unsigned long hundredths = (currentRemainingTime % 1000) / 10;
int displayValue = (seconds * 100) + hundredths;
display.showNumberDecEx(displayValue, 0b01000000, true);
}
// Button-Beleuchtung aktualisieren
updateButtonLED();
}
// =============================================================================
// LED RING FUNKTIONEN - ERSTER RING
// =============================================================================
void updateLedRing() {
ledRing.clear();
// Farbbestimmung basierend auf verbleibenden LEDs
uint32_t baseColor;
if (activeLeds > LED_COUNT * 2 / 3) {
baseColor = ledRing.Color(0, 200, 0); // Grün
} else if (activeLeds > LED_COUNT / 3) {
baseColor = ledRing.Color(200, 200, 0); // Gelb
} else {
baseColor = ledRing.Color(200, 0, 0); // Rot
}
// Alle aktiven LEDs setzen
for (int i = 0; i < activeLeds; i++) {
ledRing.setPixelColor(i, baseColor);
}
// Blinkende LED (aktuelle Runde)
if (currentBlinkingLed >= 0 && currentBlinkingLed < LED_COUNT && blinkState) {
ledRing.setPixelColor(currentBlinkingLed, ledRing.Color(255, 255, 255));
}
ledRing.show();
}
// =============================================================================
// LED RING FUNKTIONEN - ZWEITER RING (ANIMATION)
// =============================================================================
void updateLedRing2() {
ledRing2.clear();
if (ring2AnimationActive) {
// Animation während des Haltens
unsigned long currentMillis = millis();
unsigned long elapsed = currentMillis - ring2AnimationStartTime;
float progress = (float)elapsed / HOLD_DURATION;
progress = constrain(progress, 0.0f, 1.0f);
int ledsToLight;
if (ring2Filling) {
// Füll-Animation: von 0 auf 35 LEDs
ledsToLight = (int)(progress * LED_COUNT);
} else {
// Leer-Animation: von 35 auf 0 LEDs
ledsToLight = LED_COUNT - (int)(progress * LED_COUNT);
}
ledsToLight = constrain(ledsToLight, 0, LED_COUNT);
// Farbe basierend auf Richtung der Animation
uint32_t animColor;
if (ring2Filling) {
// Blau für Füll-Animation (Pause -> Weiter)
animColor = ledRing2.Color(0, 0, 255);
} else {
// Orange für Leer-Animation (Weiter -> Pause)
animColor = ledRing2.Color(255, 100, 0);
}
// LEDs setzen
for (int i = 0; i < ledsToLight; i++) {
ledRing2.setPixelColor(i, animColor);
}
}
// Keine normale Anzeige mehr - nur Animation
ledRing2.show();
}
void startRing2Animation(bool filling) {
ring2AnimationActive = true;
ring2AnimationStartTime = millis();
ring2Filling = filling;
}
void stopRing2Animation() {
ring2AnimationActive = false;
}
void updateBlinkingLed() {
unsigned long currentMillis = millis();
if (currentMillis - lastBlinkTime >= BLINK_INTERVAL) {
lastBlinkTime = currentMillis;
blinkState = !blinkState;
updateLedRing();
// Zweiter Ring wird nicht mehr normal aktualisiert
}
}
void updateButtonBlink() {
unsigned long currentMillis = millis();
if (currentMillis - lastButtonBlinkTime >= BLINK_INTERVAL) {
lastButtonBlinkTime = currentMillis;
buttonBlinkState = !buttonBlinkState;
// Nur auf Startbildschirm den Button blinken lassen
if (currentScreen == 0) {
drawStartButton();
}
}
}
// =============================================================================
// TIMER FUNKTIONEN
// =============================================================================
void startTimer() {
timerRunning = true;
timerStarted = true;
timerStartTime = millis();
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
currentRound = 1;
finalRound = false;
currentRemainingTime = timerDuration;
lastSecondBeeped = 99;
firstStart = false; // Erststart ist vorbei
display.showNumberDecEx(6000, 0b01000000, true);
setStatus("Timer laeuft");
shortBeep(BEEP_FREQ_START, 200);
drawConfigScreen();
// Button-Beleuchtung ausschalten
updateButtonLED();
}
void pauseTimer() {
timerRunning = false;
timerPauseTime = millis();
lastSecondBeeped = 99;
setStatus("Pausiert");
shortBeep(BEEP_FREQ_PAUSE, 200);
drawConfigScreen();
}
void resumeTimer() {
timerRunning = true;
unsigned long pauseDuration = millis() - timerPauseTime;
timerStartTime += pauseDuration;
setStatus("Timer laeuft");
shortBeep(BEEP_FREQ_START, 200);
drawConfigScreen();
}
void updateSegmentDisplay() {
if (!timerRunning) return;
unsigned long currentMillis = millis();
unsigned long elapsed = currentMillis - timerStartTime;
unsigned long remaining = (timerDuration > elapsed) ? (timerDuration - elapsed) : 0;
currentRemainingTime = remaining;
unsigned long seconds = remaining / 1000;
unsigned long hundredths = (remaining % 1000) / 10;
int displayValue = (seconds * 100) + hundredths;
display.showNumberDecEx(displayValue, 0b01000000, true);
// Sekundenpiep - direkt hier wo die Zeit berechnet wird
if (remaining > 0 && seconds != lastSecondBeeped) {
lastSecondBeeped = seconds;
tone(BUZZER_PIN, BEEP_FREQ_NORMAL, 50);
}
// Timer abgelaufen
if (remaining == 0) {
if (activeLeds > 0) {
// LED abziehen
activeLeds--;
currentRound++;
shortBeep(BEEP_FREQ_LED_DOWN, 200);
updateLedRing();
// Zweiter Ring wird nicht mehr normal aktualisiert
drawConfigScreen();
if (activeLeds == 0) {
finalRound = true;
currentBlinkingLed = -1;
} else {
currentBlinkingLed = activeLeds - 1;
}
// Timer zurücksetzen
timerStartTime = currentMillis;
currentRemainingTime = timerDuration;
lastSecondBeeped = 99;
} else if (finalRound) {
// Finaler Alarm
timerRunning = false;
timerStarted = false;
finalRound = false;
for (int i = 0; i < 5; i++) {
display.showNumberDec(8888, true);
tone(BUZZER_PIN, BEEP_FREQ_FINAL, 200);
delay(200);
display.clear();
noTone(BUZZER_PIN);
delay(200);
}
// Reset
display.showNumberDecEx(6000, 0b01000000, true);
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
currentRound = 1;
currentRemainingTime = timerDuration;
setStatus("Bereit");
firstStart = true; // Beim nächsten Mal ist es wieder der Erststart
// Button-Beleuchtung wieder einschalten
updateButtonLED();
drawConfigScreen();
}
}
}
// =============================================================================
// BUTTON HANDLER
// =============================================================================
void handleMainButton() {
unsigned long currentMillis = millis();
bool reading = digitalRead(BUTTON_PIN);
// Fallende Flanke (Button gedrückt)
if (reading == LOW && lastButtonState == HIGH) {
lastDebounceTime = currentMillis;
mainButtonPressTime = currentMillis;
mainButtonHeld = false;
holdProgressBeeped1 = false;
holdProgressBeeped2 = false;
buttonPressed = true;
// Animation für zweiten Ring starten mit KORRIGIERTER Richtung
if (timerStarted) {
if (timerRunning) {
// Weiter -> Pause: LEER-Animation (false)
startRing2Animation(false);
} else {
// Pause -> Weiter: FÜLL-Animation (true)
startRing2Animation(true);
}
}
// Beim allerersten Start sofort reagieren
if (firstStart && currentScreen == 0) {
currentScreen = 1;
drawConfigScreen();
buttonPressed = false;
}
else if (firstStart && !timerStarted) {
startTimer();
buttonPressed = false;
}
}
// Button wird gehalten
if (reading == LOW && buttonPressed) {
unsigned long holdTime = currentMillis - mainButtonPressTime;
// Fortschrittsbalken anzeigen nach 1 Sekunde
if (holdTime > 1000 && !mainButtonHeld) {
int progress = (holdTime * 100) / HOLD_DURATION;
progress = constrain(progress, 0, 100);
drawHoldProgress(progress);
// Akustisches Feedback bei 1 Sekunde
if (holdTime >= 1000 && holdTime < 1100 && !holdProgressBeeped1) {
shortBeep(BEEP_FREQ_HOLD_PROGRESS, 100);
holdProgressBeeped1 = true;
}
// Akustisches Feedback bei 2 Sekunden
if (holdTime >= 2000 && holdTime < 2100 && !holdProgressBeeped2) {
shortBeep(BEEP_FREQ_HOLD_PROGRESS, 100);
holdProgressBeeped2 = true;
}
}
// Animation aktualisieren
if (ring2AnimationActive) {
updateLedRing2();
}
// 3 Sekunden erreicht
if (holdTime >= HOLD_DURATION && !mainButtonHeld) {
mainButtonHeld = true;
if (currentScreen == 0) {
currentScreen = 1;
drawConfigScreen();
} else {
if (!timerStarted) {
startTimer();
} else {
if (timerRunning) {
pauseTimer();
} else {
resumeTimer();
}
}
}
// Animation beenden
stopRing2Animation();
updateLedRing2();
// Fortschrittsbalken löschen
tft.fillRect(0, 235, 320, 20, COLOR_BG);
}
}
// Steigende Flanke (Button losgelassen)
if (reading == HIGH && lastButtonState == LOW) {
// Fortschrittsbalken löschen
tft.fillRect(0, 235, 320, 20, COLOR_BG);
// Wenn nicht lang genug gehalten und nicht der allererste Start
if (!mainButtonHeld && !firstStart && buttonPressed) {
// Kurzes Feedback, dass länger gehalten werden muss
shortBeep(BEEP_FREQ_HOLD_PROGRESS, 100);
}
// Animation beenden (falls aktiv)
if (ring2AnimationActive) {
stopRing2Animation();
updateLedRing2();
}
buttonPressed = false;
mainButtonHeld = false;
holdProgressBeeped1 = false;
holdProgressBeeped2 = false;
}
lastButtonState = reading;
}
void handleUpButton() {
if (timerStarted) return;
unsigned long currentMillis = millis();
bool reading = digitalRead(BUTTON_UP);
if (reading != lastButtonUpState) {
lastDebounceTimeUp = currentMillis;
}
if ((currentMillis - lastDebounceTimeUp) > DEBOUNCE_DELAY) {
if (reading == LOW && !buttonUpPressed) {
buttonUpPressed = true;
buttonUpPressTime = currentMillis;
if (initialLeds < MAX_LEDS) {
initialLeds++;
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
updateLedRing();
shortBeep(BEEP_FREQ_UP, 80);
drawConfigScreen();
}
} else if (reading == LOW && buttonUpPressed) {
// Kontinuierliches Erhöhen
if (currentMillis - buttonUpPressTime > INITIAL_REPEAT_DELAY) {
if ((currentMillis - lastDebounceTimeUp) > REPEAT_INTERVAL) {
if (initialLeds < MAX_LEDS) {
initialLeds++;
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
updateLedRing();
shortBeep(BEEP_FREQ_UP, 80);
drawConfigScreen();
lastDebounceTimeUp = currentMillis;
}
}
}
} else if (reading == HIGH) {
buttonUpPressed = false;
}
}
lastButtonUpState = reading;
}
void handleDownButton() {
if (timerStarted) return;
unsigned long currentMillis = millis();
bool reading = digitalRead(BUTTON_DOWN);
if (reading != lastButtonDownState) {
lastDebounceTimeDown = currentMillis;
}
if ((currentMillis - lastDebounceTimeDown) > DEBOUNCE_DELAY) {
if (reading == LOW && !buttonDownPressed) {
buttonDownPressed = true;
buttonDownPressTime = currentMillis;
if (initialLeds > MIN_LEDS) {
initialLeds--;
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
updateLedRing();
shortBeep(BEEP_FREQ_DOWN, 80);
drawConfigScreen();
}
} else if (reading == LOW && buttonDownPressed) {
// Kontinuierliches Verringern
if (currentMillis - buttonDownPressTime > INITIAL_REPEAT_DELAY) {
if ((currentMillis - lastDebounceTimeDown) > REPEAT_INTERVAL) {
if (initialLeds > MIN_LEDS) {
initialLeds--;
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
updateLedRing();
shortBeep(BEEP_FREQ_DOWN, 80);
drawConfigScreen();
lastDebounceTimeDown = currentMillis;
}
}
}
} else if (reading == HIGH) {
buttonDownPressed = false;
}
}
lastButtonDownState = reading;
}
// =============================================================================
// SETUP & LOOP
// =============================================================================
void setup() {
Serial.begin(115200);
// Pin-Modi
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUTTON_UP, INPUT_PULLUP);
pinMode(BUTTON_DOWN, INPUT_PULLUP);
pinMode(BUTTON_LED_PIN, OUTPUT); // NEU: Button-Beleuchtung
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(BUTTON_LED_PIN, LOW); // Initial aus
// Display Reset
pinMode(TFT_RST, OUTPUT);
digitalWrite(TFT_RST, HIGH);
delay(100);
digitalWrite(TFT_RST, LOW);
delay(100);
digitalWrite(TFT_RST, HIGH);
delay(100);
// TFT initialisieren
tft.begin();
tft.setRotation(1);
tft.fillScreen(COLOR_BG);
// TM1637 initialisieren
display.setBrightness(0x0f);
display.clear();
// LED Ring initialisieren
ledRing.begin();
ledRing.setBrightness(0);
ledRing.clear();
ledRing.show();
// Zweiten LED Ring initialisieren
ledRing2.begin();
ledRing2.setBrightness(0);
ledRing2.clear();
ledRing2.show();
Serial.println(F("HNGR13 Bomb Timer - System gestartet"));
drawStartScreen();
}
void loop() {
unsigned long currentMillis = millis();
// Timer Updates
if (currentScreen == 1 && timerRunning) {
if (currentMillis - lastSegmentUpdate >= SEGMENT_UPDATE_INTERVAL) {
lastSegmentUpdate = currentMillis;
updateSegmentDisplay();
}
if (currentMillis - lastTftUpdate >= TFT_UPDATE_INTERVAL) {
lastTftUpdate = currentMillis;
updateTimerDisplay();
}
}
// LED Ring blinken
if (currentScreen == 1) {
updateBlinkingLed();
}
// Button Blinken (nur auf Startbildschirm)
updateButtonBlink();
// Button Handling
handleMainButton();
handleUpButton();
handleDownButton();
// Animation für zweiten Ring aktualisieren (wenn aktiv)
if (ring2AnimationActive) {
updateLedRing2();
}
// Button-Beleuchtung aktualisieren
updateButtonLED();
}
5V Quelle
Stromversorgung