#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_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);
// =============================================================================
// 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
// =============================================================================
// TIMING-KONSTANTEN
// =============================================================================
const unsigned long TIMER_DURATION = 60000; // 60 Sekunden
const long SEGMENT_UPDATE_INTERVAL = 10; // 10ms
const long TFT_UPDATE_INTERVAL = 100; // 100ms
const long BLINK_INTERVAL = 500; // 500ms
const long BEEP_DURATION = 100; // 100ms
const long DEBOUNCE_DELAY = 50; // 50ms
const long INITIAL_REPEAT_DELAY = 500; // 500ms
const long REPEAT_INTERVAL = 100; // 100ms
// =============================================================================
// 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;
// =============================================================================
// 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;
// Buzzer Variablen
unsigned long lastSecondBeeped = 99;
// LED Ring Variablen
int activeLeds = 10;
int initialLeds = 10;
bool blinkState = false;
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;
// 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);
}
// =============================================================================
// 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 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(F("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(F("Bereit zum Start"));
tft.setTextColor(COLOR_INFO);
tft.setCursor(20, 150);
tft.print(F(">> 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(F("START"));
}
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
tft.setTextColor(COLOR_INFO);
tft.setTextSize(1);
tft.setCursor(15, 170);
tft.print(F("Aktuelle Runde: "));
tft.print(currentRound);
tft.print(F("/"));
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
tft.fillRect(15, 170, 150, 8, COLOR_PANEL);
tft.setTextColor(COLOR_INFO);
tft.setTextSize(1);
tft.setCursor(15, 170);
tft.print(F("Aktuelle Runde: "));
tft.print(currentRound);
tft.print(F("/"));
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"));
} 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"));
} 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"));
}
}
void drawConfigScreen() {
tft.fillScreen(COLOR_BG);
forceRedraw = true;
drawStatusBox();
drawLEDSetting();
drawTimerDisplay();
drawControlButtons();
// Peripherie einschalten
display.setBrightness(0x0f);
ledRing.setBrightness(LED_BRIGHTNESS);
updateLedRing();
}
// =============================================================================
// LED RING FUNKTIONEN
// =============================================================================
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();
}
void updateBlinkingLed() {
unsigned long currentMillis = millis();
if (currentMillis - lastBlinkTime >= BLINK_INTERVAL) {
lastBlinkTime = currentMillis;
blinkState = !blinkState;
updateLedRing();
}
}
// =============================================================================
// TIMER FUNKTIONEN
// =============================================================================
void startTimer() {
timerRunning = true;
timerStarted = true;
timerStartTime = millis();
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
currentRound = 1;
finalRound = false;
currentRemainingTime = timerDuration;
lastSecondBeeped = 99;
display.showNumberDecEx(6000, 0b01000000, true);
setStatus("Timer laeuft");
shortBeep(BEEP_FREQ_START, 200);
drawConfigScreen();
}
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();
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");
drawConfigScreen();
}
}
}
// =============================================================================
// BUTTON HANDLER
// =============================================================================
void handleMainButton() {
unsigned long currentMillis = millis();
bool reading = digitalRead(BUTTON_PIN);
if (reading != lastButtonState) {
lastDebounceTime = currentMillis;
}
if ((currentMillis - lastDebounceTime) > DEBOUNCE_DELAY) {
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;
}
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(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 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();
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 Handling
handleMainButton();
handleUpButton();
handleDownButton();
}
5V Quelle
Stromversorgung