#include <Adafruit_GFX.h>
#include <Adafruit_ILI9341.h>
#include <TM1637Display.h>
#include <Adafruit_NeoPixel.h>
#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_COUNT 35
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);
// =============================================================================
// 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
// =============================================================================
// SYSTEM-VARIABLEN
// =============================================================================
bool currentScreen = 0;
bool timerRunning = false;
bool timerStarted = false;
unsigned long timerStartTime = 0;
unsigned long timerPauseTime = 0;
unsigned long timerDuration = 60000;
unsigned long lastSegmentUpdate = 0;
const long segmentUpdateInterval = 10;
// Buzzer Variablen
unsigned long lastSecondBeeped = 99;
unsigned long lastBeepTime = 0;
const long beepDuration = 100;
bool beepActive = false;
// LED Ring Variablen
int activeLeds = 10;
int initialLeds = 10;
unsigned long lastBlinkTime = 0;
const long blinkIntervalLed = 500;
bool blinkState = false;
int currentBlinkingLed = 0;
bool finalRound = false;
// Timer Status Variablen
int currentRound = 1;
unsigned long currentRemainingTime = 60000;
// Display Update Variablen
unsigned long lastTftUpdate = 0;
const long tftUpdateInterval = 100; // 100ms Update für flüssige Anzeige
// 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;
const long debounceDelay = 50;
// Für kontinuierliches Ändern
unsigned long buttonUpPressTime = 0;
unsigned long buttonDownPressTime = 0;
const long initialRepeatDelay = 500;
const long repeatInterval = 100;
// Status-Variablen
char statusText[20] = "Bereit";
bool forceRedraw = true;
// =============================================================================
// HILFSFUNKTIONEN
// =============================================================================
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);
}
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);
}
uint16_t getTimeColor(int seconds) {
if (seconds > 40) return COLOR_SUCCESS; // Grün
else if (seconds > 20) return COLOR_WARNING; // Gelb
else return COLOR_DANGER; // Rot
}
uint16_t getLEDColor(int current, int max) {
float ratio = (float)current / max;
if (ratio > 0.66) return COLOR_SUCCESS; // Grün
else if (ratio > 0.33) return COLOR_WARNING; // Gelb
else return COLOR_DANGER; // Rot
}
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;
if (fillW > w-2) fillW = w-2;
if (fillW > 0) fillRoundRect(x+1, y+1, fillW, h-2, 2, color);
}
// Beschriftung - Bereich mit Panel-Farbe löschen
tft.fillRect(x, y - 10, w, 10, COLOR_PANEL);
// Beschriftung
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(x, y - 10);
tft.print(label);
// Wert anzeigen - Bereich mit Panel-Farbe löschen
tft.fillRect(x + w - 30, y - 10, 30, 10, COLOR_PANEL);
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 farbigem Verlauf
if (seconds > 0) {
int16_t fillW = (w * seconds) / 60;
if (fillW > w-2) fillW = w-2;
if (fillW > 0) {
uint16_t timeColor = getTimeColor(seconds);
fillRoundRect(x+1, y+1, fillW, h-2, 2, timeColor);
}
}
// Beschriftung oben - Bereich mit Panel-Farbe löschen
tft.fillRect(x, y - 10, w, 10, COLOR_PANEL);
// Beschriftung
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(x, y - 10);
tft.print(label);
// Wert anzeigen - Bereich mit Panel-Farbe löschen
tft.fillRect(x + w - 30, y - 10, 30, 10, COLOR_PANEL);
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);
}
void setStatus(const char* status) {
strncpy(statusText, status, sizeof(statusText) - 1);
statusText[sizeof(statusText) - 1] = '\0';
drawStatusBox();
}
void shortBeep(int freq, int duration) {
tone(BUZZER_PIN, freq, duration);
}
// =============================================================================
// TFT-DRAW FUNKTIONEN
// =============================================================================
void drawStartScreen() {
tft.fillScreen(COLOR_BG);
// Header
fillRoundRect(10, 50, 300, 50, 8, COLOR_PANEL);
drawRoundRect(10, 50, 300, 50, 8, COLOR_ACCENT);
tft.setTextColor(COLOR_ACCENT);
tft.setTextSize(2);
tft.setCursor(40, 65);
tft.print("HNGR13 BOMB TIMER");
// Hauptbereich
fillRoundRect(10, 120, 300, 70, 8, COLOR_PANEL);
drawRoundRect(10, 120, 300, 70, 8, COLOR_INFO);
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(1);
tft.setCursor(20, 130);
tft.print("Bereit zum Start");
tft.setTextColor(COLOR_INFO);
tft.setCursor(20, 150);
tft.print(">> START druecken zum Fortfahren");
// Start Button
fillRoundRect(100, 200, 120, 40, 5, COLOR_SUCCESS);
drawRoundRect(100, 200, 120, 40, 5, COLOR_TEXT);
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(2);
tft.setCursor(130, 212);
tft.print("START");
}
void drawConfigScreen() {
tft.fillScreen(COLOR_BG);
forceRedraw = true;
// Status Box (oben)
drawStatusBox();
// LED Einstellung
drawLEDSetting();
// Timer Anzeige
drawTimerDisplay();
// Control Buttons
drawControlButtons();
// Segmentanzeige und LED Ring einschalten
display.setBrightness(0x0f);
ledRing.setBrightness(100);
updateLedRing();
}
void drawStatusBox() {
// Status Box Hintergrund (oben)
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("Status:");
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(1);
tft.setCursor(70, 15);
tft.print(statusText);
}
void drawLEDSetting() {
// LED Einstellung Bereich
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("LED EINSTELLUNG");
if (!timerStarted) {
// UP/DOWN Buttons anzeigen wenn noch nicht gestartet
fillRoundRect(20, 70, 25, 15, 3, COLOR_DANGER);
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(1);
tft.setCursor(27, 73);
tft.print("-");
fillRoundRect(265, 70, 25, 15, 3, COLOR_SUCCESS);
tft.setCursor(272, 73);
tft.print("+");
tft.setTextColor(COLOR_TEXT);
tft.setTextSize(2);
tft.setCursor(120, 70);
tft.print(initialLeds);
tft.print("/35");
} else {
// Gesperrte Anzeige
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(100, 70);
tft.print("LEDs: ");
tft.print(activeLeds);
tft.print("/35 (gesperrt)");
}
}
void drawTimerDisplay() {
// Timer Anzeige Bereich (größer gemacht)
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("TIMER ANZEIGE");
// LED Fortschrittsbalken mit farbigem Verlauf
uint16_t ledColor = getLEDColor(activeLeds, initialLeds);
drawProgressBar(15, 125, 270, 12, activeLeds, initialLeds, ledColor, "Verbleibende LEDs");
// Zeit Fortschrittsbalken (mit farbigem Verlauf)
int secondsRemaining = currentRemainingTime / 1000;
drawTimeProgressBar(15, 150, 270, 12, secondsRemaining, "Verbleibende Zeit");
// Aktuelle Runde anzeigen
tft.setTextColor(COLOR_INFO); // Blaue Farbe wie der Rahmen
tft.setTextSize(1);
tft.setCursor(15, 170);
tft.print("Aktuelle Runde: ");
tft.print(currentRound);
tft.print("/");
tft.print(initialLeds);
}
void updateTimerDisplay() {
// Nur die sich ändernden Teile des Timer-Displays aktualisieren
int secondsRemaining = currentRemainingTime / 1000;
// LED-Balken aktualisieren mit farbigem Verlauf
uint16_t ledColor = getLEDColor(activeLeds, initialLeds);
drawProgressBar(15, 125, 270, 12, activeLeds, initialLeds, ledColor, "Verbleibende LEDs");
// Zeit-Balken aktualisieren
drawTimeProgressBar(15, 150, 270, 12, secondsRemaining, "Verbleibende Zeit");
// Runden-Anzeige aktualisieren - nur Textbereich löschen, nicht den Rahmen
tft.fillRect(15, 170, 150, 8, COLOR_PANEL); // Nur kleiner Bereich für Text
tft.setTextColor(COLOR_INFO); // Blaue Farbe wie der Rahmen
tft.setTextSize(1);
tft.setCursor(15, 170);
tft.print("Aktuelle Runde: ");
tft.print(currentRound);
tft.print("/");
tft.print(initialLeds);
}
void drawControlButtons() {
// Control Buttons Bereich (unten)
int buttonWidth = 120;
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("START");
} 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("PAUSE");
} 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("WEITER");
}
}
// =============================================================================
// LED RING FUNKTIONEN
// =============================================================================
void updateLedRing() {
ledRing.clear();
// Alle aktiven LEDs einschalten
for (int i = 0; i < activeLeds; i++) {
if (activeLeds > LED_COUNT * 2/3) {
ledRing.setPixelColor(i, ledRing.Color(0, 200, 0));
} else if (activeLeds > LED_COUNT / 3) {
ledRing.setPixelColor(i, ledRing.Color(200, 200, 0));
} else {
ledRing.setPixelColor(i, ledRing.Color(200, 0, 0));
}
}
// Die LED, die in dieser Runde ausgehen wird, blinken lassen
if (currentBlinkingLed >= 0 && currentBlinkingLed < LED_COUNT) {
if (blinkState) {
ledRing.setPixelColor(currentBlinkingLed, ledRing.Color(255, 255, 255));
}
}
ledRing.show();
}
void updateBlinkingLed() {
unsigned long currentMillis = millis();
if (currentMillis - lastBlinkTime >= blinkIntervalLed) {
lastBlinkTime = currentMillis;
blinkState = !blinkState;
updateLedRing();
}
}
// =============================================================================
// BUZZER FUNKTIONEN
// =============================================================================
void handleBuzzer() {
unsigned long currentMillis = millis();
if (!timerRunning || currentScreen != 1) {
if (beepActive) {
noTone(BUZZER_PIN);
beepActive = false;
}
return;
}
unsigned long elapsed = currentMillis - timerStartTime;
unsigned long remaining = (timerDuration > elapsed) ? (timerDuration - elapsed) : 0;
// Aktuelle verbleibende Zeit für TFT speichern
currentRemainingTime = remaining;
unsigned long currentSecond = remaining / 1000;
// Pieps bei jeder vollen Sekunde
if (remaining > 0 && currentSecond != lastSecondBeeped) {
lastSecondBeeped = currentSecond;
lastBeepTime = currentMillis;
tone(BUZZER_PIN, 1000, beepDuration);
beepActive = true;
}
// Buzzer nach beepDuration ausschalten
if (beepActive && (currentMillis - lastBeepTime) >= beepDuration) {
noTone(BUZZER_PIN);
beepActive = false;
}
}
// =============================================================================
// TIMER FUNKTIONEN (KORRIGIERT)
// =============================================================================
void startTimer() {
timerRunning = true;
timerStarted = true;
timerStartTime = millis();
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
currentRound = 1;
finalRound = false;
currentRemainingTime = timerDuration;
// Segmentanzeige auf Startwert setzen
display.showNumberDecEx(6000, 0b01000000, true);
setStatus("Timer laeuft");
shortBeep(1500, 200);
drawConfigScreen();
}
void pauseTimer() {
timerRunning = false;
timerPauseTime = millis();
noTone(BUZZER_PIN);
setStatus("Pausiert");
shortBeep(1200, 200);
drawConfigScreen();
}
void resumeTimer() {
timerRunning = true;
// Korrektur der Startzeit, um die Pausenzeit zu berücksichtigen
unsigned long pauseDuration = millis() - timerPauseTime;
timerStartTime += pauseDuration;
setStatus("Timer laeuft");
shortBeep(1500, 200);
drawConfigScreen();
}
void updateSegmentDisplay() {
if (!timerRunning) return;
unsigned long currentMillis = millis();
unsigned long elapsed = currentMillis - timerStartTime;
unsigned long remaining = (timerDuration > elapsed) ? (timerDuration - elapsed) : 0;
// Aktuelle verbleibende Zeit für TFT speichern
currentRemainingTime = remaining;
unsigned long seconds = remaining / 1000;
unsigned long hundredths = (remaining % 1000) / 10;
int displayValue = (seconds * 100) + hundredths;
display.showNumberDecEx(displayValue, 0b01000000, true);
// Timer abgelaufen - LED abziehen
if (remaining == 0) {
Serial.println("=== TIMER ABGELAUFEN ===");
// Eine LED abziehen
if (activeLeds > 0) {
activeLeds--;
currentRound++;
shortBeep(1200, 200);
Serial.print("LED abgezogen! Verbleibend: ");
Serial.println(activeLeds);
// LED-Ring und TFT aktualisieren
updateLedRing();
drawConfigScreen(); // Komplettes Display neu zeichnen
if (activeLeds == 0) {
finalRound = true;
currentBlinkingLed = -1;
Serial.println("=== FINALE RUNDE ===");
} else {
currentBlinkingLed = activeLeds - 1;
}
// Timer für nächste Runde zurücksetzen
timerStartTime = currentMillis;
currentRemainingTime = timerDuration;
} else if (finalRound) {
// Finale Runde abgeschlossen - Alarm
Serial.println("=== FINALER ALARM ===");
timerRunning = false;
timerStarted = false;
finalRound = false;
// Finaler Alarm
for (int i = 0; i < 5; i++) {
display.showNumberDec(8888, true);
tone(BUZZER_PIN, 2000, 200);
delay(200);
display.clear();
noTone(BUZZER_PIN);
delay(200);
}
display.showNumberDecEx(6000, 0b01000000, true);
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
currentRound = 1;
currentRemainingTime = timerDuration;
setStatus("Bereit");
drawConfigScreen();
}
}
}
// =============================================================================
// SETUP & LOOP
// =============================================================================
void setup() {
Serial.begin(115200);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUTTON_UP, INPUT_PULLUP);
pinMode(BUTTON_DOWN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(BUZZER_PIN, LOW);
// 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.begin();
tft.setRotation(1); // Korrekte Rotation für 240x320 im Querformat
tft.fillScreen(COLOR_BG);
// TM1637 initialisieren
display.setBrightness(0x0f);
display.clear();
// LED Ring initialisieren
ledRing.begin();
ledRing.setBrightness(0);
ledRing.clear();
ledRing.show();
Serial.println("System gestartet");
drawStartScreen();
}
void loop() {
unsigned long currentMillis = millis();
// Timer auf Segmentanzeige aktualisieren
if (currentScreen == 1 && timerRunning) {
if (currentMillis - lastSegmentUpdate >= segmentUpdateInterval) {
lastSegmentUpdate = currentMillis;
updateSegmentDisplay();
}
}
// LED Ring blinken lassen
if (currentScreen == 1) {
updateBlinkingLed();
}
// Buzzer steuern
handleBuzzer();
// TFT Display regelmäßig aktualisieren (für flüssige Zeit-Anzeige)
if (currentScreen == 1 && timerRunning) {
if (currentMillis - lastTftUpdate >= tftUpdateInterval) {
lastTftUpdate = currentMillis;
updateTimerDisplay(); // Nur die sich ändernden Teile aktualisieren
}
}
// Haupt-Button
bool reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonState) {
lastDebounceTime = currentMillis;
}
if ((currentMillis - lastDebounceTime) > debounceDelay) {
if (reading == LOW && !buttonPressed) {
buttonPressed = true;
if (currentScreen == 0) {
currentScreen = 1;
drawConfigScreen();
} else {
if (!timerStarted) {
startTimer();
} else {
if (timerRunning) {
pauseTimer();
} else {
resumeTimer();
}
}
}
}
if (reading == HIGH) {
buttonPressed = false;
}
}
lastButtonState = reading;
// UP Button (nur vor Timer-Start)
bool readingUp = digitalRead(BUTTON_UP);
if (readingUp != lastButtonUpState) {
lastDebounceTimeUp = currentMillis;
}
if ((currentMillis - lastDebounceTimeUp) > debounceDelay && !timerStarted) {
if (readingUp == LOW && !buttonUpPressed) {
buttonUpPressed = true;
buttonUpPressTime = currentMillis;
if (initialLeds < LED_COUNT) {
initialLeds++;
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
currentRound = 1;
updateLedRing();
shortBeep(800, 80);
drawConfigScreen();
}
} else if (readingUp == LOW && buttonUpPressed) {
if (currentMillis - buttonUpPressTime > initialRepeatDelay) {
if ((currentMillis - lastDebounceTimeUp) > repeatInterval) {
if (initialLeds < LED_COUNT) {
initialLeds++;
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
currentRound = 1;
updateLedRing();
shortBeep(800, 80);
drawConfigScreen();
lastDebounceTimeUp = currentMillis;
}
}
}
} else if (readingUp == HIGH) {
buttonUpPressed = false;
}
}
lastButtonUpState = readingUp;
// DOWN Button (nur vor Timer-Start)
bool readingDown = digitalRead(BUTTON_DOWN);
if (readingDown != lastButtonDownState) {
lastDebounceTimeDown = currentMillis;
}
if ((currentMillis - lastDebounceTimeDown) > debounceDelay && !timerStarted) {
if (readingDown == LOW && !buttonDownPressed) {
buttonDownPressed = true;
buttonDownPressTime = currentMillis;
if (initialLeds > 1) {
initialLeds--;
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
currentRound = 1;
updateLedRing();
shortBeep(600, 80);
drawConfigScreen();
}
} else if (readingDown == LOW && buttonDownPressed) {
if (currentMillis - buttonDownPressTime > initialRepeatDelay) {
if ((currentMillis - lastDebounceTimeDown) > repeatInterval) {
if (initialLeds > 1) {
initialLeds--;
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
currentRound = 1;
updateLedRing();
shortBeep(600, 80);
drawConfigScreen();
lastDebounceTimeDown = currentMillis;
}
}
}
} else if (readingDown == HIGH) {
buttonDownPressed = false;
}
}
lastButtonDownState = readingDown;
}
5V Quelle
Stromversorgung