#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
#define RELAY_PIN 12 // Neuer Pin für das 5-V-Relaismodul
#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
#define COLOR_ORANGE 0xFD20
#define COLOR_YELLOW 0xFFE0
// =============================================================================
// 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;
const long TACHO_CHECK_DURATION = 1000;
// =============================================================================
// 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;
// =============================================================================
// SYSTEM-VARIABLEN
// =============================================================================
bool currentScreen = 0;
bool timerRunning = false;
bool timerStarted = false;
unsigned long timerStartTime = 0;
unsigned long timerPauseTime = 0;
unsigned long timerDuration = TIMER_DURATION;
unsigned long lastSegmentUpdate = 0;
unsigned long lastTftUpdate = 0;
unsigned long lastBlinkTime = 0;
unsigned long lastButtonBlinkTime = 0;
unsigned long lastSecondBeeped = 99;
int activeLeds = 10;
int initialLeds = 10;
bool blinkState = false;
bool buttonBlinkState = false;
int currentBlinkingLed = 0;
bool finalRound = false;
int currentRound = 1;
unsigned long currentRemainingTime = TIMER_DURATION;
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;
unsigned long mainButtonPressTime = 0;
bool mainButtonHeld = false;
bool firstStart = true;
bool holdProgressBeeped1 = false;
bool holdProgressBeeped2 = false;
bool ring2AnimationActive = false;
unsigned long ring2AnimationStartTime = 0;
bool ring2Filling = true;
bool tachoCheckActive = false;
unsigned long tachoCheckStartTime = 0;
bool tachoCheckDone = false;
char statusText[20] = "Bereit";
bool forceRedraw = true;
// =============================================================================
// EXPLOSIONS-ANIMATION VARIABLEN
// =============================================================================
struct ExplosionParticle {
float x, y;
float vx, vy;
uint16_t color;
bool active;
};
ExplosionParticle particles[50];
int numParticles = 50;
// =============================================================================
// RELAIS-STEUERUNG
// =============================================================================
void updateRelay() {
if (timerRunning) {
digitalWrite(RELAY_PIN, HIGH); // Relais aktivieren
Serial.println("Relais: EIN - Timer läuft");
} else {
digitalWrite(RELAY_PIN, LOW); // Relais deaktivieren
Serial.println("Relais: AUS - Timer pausiert/beendet");
}
}
// =============================================================================
// EXPLOSIONS-FUNKTIONEN
// =============================================================================
void initExplosionParticles() {
for (int i = 0; i < numParticles; i++) {
particles[i].x = 160;
particles[i].y = 120;
float angle = random(0, 628) / 100.0;
float speed = random(20, 80) / 10.0;
particles[i].vx = cos(angle) * speed;
particles[i].vy = sin(angle) * speed;
int colorChoice = random(0, 5);
switch(colorChoice) {
case 0: particles[i].color = COLOR_DANGER; break;
case 1: particles[i].color = COLOR_ORANGE; break;
case 2: particles[i].color = COLOR_YELLOW; break;
case 3: particles[i].color = COLOR_WARNING; break;
default: particles[i].color = COLOR_TEXT; break;
}
particles[i].active = true;
}
}
void drawExplosionFrame(int frame, int maxFrames) {
if (frame == 0) {
tft.fillScreen(COLOR_BG);
initExplosionParticles();
}
if (frame < 5) {
uint16_t flashColor = tft.color565(255 - frame * 50, 200 - frame * 40, 0);
tft.fillScreen(flashColor);
} else {
tft.fillScreen(COLOR_BG);
}
if (frame < 15) {
int radius1 = frame * 15;
int radius2 = frame * 10;
int radius3 = frame * 5;
tft.fillCircle(160, 120, radius1, COLOR_DANGER);
tft.fillCircle(160, 120, radius2, COLOR_ORANGE);
tft.fillCircle(160, 120, radius3, COLOR_YELLOW);
}
for (int i = 0; i < numParticles; i++) {
if (particles[i].active) {
particles[i].x += particles[i].vx;
particles[i].y += particles[i].vy;
particles[i].vy += 0.3;
if (particles[i].x >= 0 && particles[i].x < 320 &&
particles[i].y >= 0 && particles[i].y < 240) {
tft.fillCircle((int)particles[i].x, (int)particles[i].y, 2, particles[i].color);
} else {
particles[i].active = false;
}
}
}
if (frame >= 5 && frame < 25) {
int textSize = 5;
if (frame < 10) {
textSize = frame - 4;
}
if (frame > 20) {
textSize = 25 - frame;
}
textSize = constrain(textSize, 1, 5);
tft.setTextSize(textSize);
tft.setTextColor(COLOR_DANGER);
int textWidth = 6 * 4 * textSize;
int textX = (320 - textWidth) / 2;
int textY = 100;
tft.setCursor(textX + 2, textY + 2);
tft.setTextColor(COLOR_BG);
tft.print(F("BOOM"));
tft.setCursor(textX, textY);
tft.setTextColor(COLOR_DANGER);
tft.print(F("BOOM"));
}
if (frame > 10) {
for (int i = 0; i < 5; i++) {
int smokeY = 120 + (frame - 10) * 3 - i * 20;
int smokeX = 160 + random(-30, 30);
int smokeSize = 10 + i * 3;
if (smokeY < 240 && smokeY > 0) {
tft.fillCircle(smokeX, smokeY, smokeSize, COLOR_TEXT_DIM);
}
}
}
}
void explosionSoundEffect(int stage) {
switch(stage) {
case 0:
tone(BUZZER_PIN, 100, 150);
break;
case 1:
tone(BUZZER_PIN, 150, 100);
break;
case 2:
tone(BUZZER_PIN, 80, 120);
break;
case 3:
tone(BUZZER_PIN, 200, 80);
break;
case 4:
tone(BUZZER_PIN, 120, 100);
break;
default:
if (stage % 2 == 0) {
tone(BUZZER_PIN, BEEP_FREQ_FINAL, 100);
}
break;
}
}
void confettiEffect(int frame) {
ledRing.clear();
ledRing2.clear();
for (int i = 0; i < LED_COUNT; i++) {
if (random(0, 100) < 60) {
uint32_t color;
int colorChoice = random(0, 6);
switch(colorChoice) {
case 0: color = ledRing.Color(255, 0, 0); break;
case 1: color = ledRing.Color(0, 255, 0); break;
case 2: color = ledRing.Color(0, 0, 255); break;
case 3: color = ledRing.Color(255, 255, 0); break;
case 4: color = ledRing.Color(255, 0, 255); break;
case 5: color = ledRing.Color(0, 255, 255); break;
}
ledRing.setPixelColor(i, color);
ledRing2.setPixelColor(i, color);
}
}
if (frame < 10 && frame % 2 == 0) {
for (int i = 0; i < LED_COUNT; i++) {
ledRing.setPixelColor(i, ledRing.Color(255, 255, 255));
ledRing2.setPixelColor(i, ledRing2.Color(255, 255, 255));
}
}
ledRing.show();
ledRing2.show();
}
void playExplosionSequence() {
const int totalFrames = 40;
const int frameDelay = 50;
ledRing.setBrightness(255);
ledRing2.setBrightness(255);
for (int frame = 0; frame < totalFrames; frame++) {
drawExplosionFrame(frame, totalFrames);
confettiEffect(frame);
if (frame % 4 < 2) {
display.showNumberDec(8888, true);
} else {
display.clear();
}
if (frame < 10) {
explosionSoundEffect(frame);
} else if (frame % 3 == 0) {
explosionSoundEffect(frame);
}
delay(frameDelay);
}
for (int i = 0; i < 3; i++) {
tone(BUZZER_PIN, BEEP_FREQ_FINAL, 200);
display.showNumberDec(8888, true);
delay(200);
noTone(BUZZER_PIN);
display.clear();
delay(200);
}
ledRing.setBrightness(LED_BRIGHTNESS);
ledRing2.setBrightness(LED_BRIGHTNESS);
}
// =============================================================================
// 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) {
const int barWidth = 200;
const int barHeight = 10;
const int barX = (320 - barWidth) / 2;
const int barY = 240;
fillRoundRect(barX, barY, barWidth, barHeight, 3, COLOR_PANEL);
drawRoundRect(barX, barY, barWidth, barHeight, 3, COLOR_TEXT_DIM);
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() {
if (!timerStarted && currentScreen == 1) {
digitalWrite(BUTTON_LED_PIN, HIGH);
} else {
digitalWrite(BUTTON_LED_PIN, LOW);
}
}
// =============================================================================
// 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) {
fillRoundRect(x, y, w, h, 3, COLOR_PANEL);
drawRoundRect(x, y, w, h, 3, COLOR_TEXT_DIM);
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);
}
}
tft.fillRect(x, y - 10, w, 10, COLOR_PANEL);
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(x, y - 10);
tft.print(label);
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) {
fillRoundRect(x, y, w, h, 3, COLOR_PANEL);
drawRoundRect(x, y, w, h, 3, COLOR_TEXT_DIM);
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);
}
}
tft.fillRect(x, y - 10, w, 10, COLOR_PANEL);
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(x, y - 10);
tft.print(label);
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);
}
// =============================================================================
// SCREEN-DRAWING FUNKTIONEN
// =============================================================================
void drawStartButton() {
const int buttonX = 100;
const int buttonY = 190;
const int buttonWidth = 120;
const int buttonHeight = 35;
if (buttonBlinkState) {
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 {
fillRoundRect(buttonX, buttonY, buttonWidth, buttonHeight, 5, COLOR_BG);
drawRoundRect(buttonX, buttonY, buttonWidth, buttonHeight, 5, COLOR_BG);
}
}
void drawStartScreen() {
tft.fillScreen(COLOR_BG);
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"));
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"));
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"));
drawStartButton();
}
void drawStatusBox() {
fillRoundRect(10, 10, 300, 30, 5, COLOR_PANEL);
drawRoundRect(10, 10, 300, 30, 5, COLOR_INFO);
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() {
fillRoundRect(10, 50, 300, 40, 5, COLOR_PANEL);
drawRoundRect(10, 50, 300, 40, 5, COLOR_WARNING);
tft.setTextColor(COLOR_WARNING);
tft.setTextSize(1);
tft.setCursor(15, 57);
tft.print(F("LED EINSTELLUNG"));
if (!timerStarted) {
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 {
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() {
fillRoundRect(10, 100, 300, 80, 5, COLOR_PANEL);
drawRoundRect(10, 100, 300, 80, 5, COLOR_INFO);
tft.setTextColor(COLOR_INFO);
tft.setTextSize(1);
tft.setCursor(15, 107);
tft.print(F("TIMER ANZEIGE"));
uint16_t ledColor = getLEDColor(activeLeds, initialLeds);
drawProgressBar(15, 125, 270, 12, activeLeds, initialLeds, ledColor, "Verbleibende LEDs");
int secondsRemaining = currentRemainingTime / 1000;
drawTimeProgressBar(15, 150, 270, 12, secondsRemaining, "Verbleibende Zeit");
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(15, 170);
tft.print(F("Aktuelle Runde: "));
tft.setTextColor(COLOR_INFO);
tft.print(currentRound);
tft.setTextColor(COLOR_TEXT_DIM);
tft.print(F("/"));
tft.setTextColor(COLOR_INFO);
tft.print(initialLeds);
}
void updateTimerDisplay() {
int secondsRemaining = currentRemainingTime / 1000;
uint16_t ledColor = getLEDColor(activeLeds, initialLeds);
drawProgressBar(15, 125, 270, 12, activeLeds, initialLeds, ledColor, "Verbleibende LEDs");
drawTimeProgressBar(15, 150, 270, 12, secondsRemaining, "Verbleibende Zeit");
tft.fillRect(15, 170, 150, 8, COLOR_PANEL);
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(15, 170);
tft.print(F("Aktuelle Runde: "));
tft.setTextColor(COLOR_INFO);
tft.print(currentRound);
tft.setTextColor(COLOR_TEXT_DIM);
tft.print(F("/"));
tft.setTextColor(COLOR_INFO);
tft.print(initialLeds);
}
void drawControlButtons() {
const int buttonWidth = 120;
const int buttonX = (320 - buttonWidth) / 2;
if (!timerStarted) {
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"));
tft.fillRect(buttonX, 225, buttonWidth, 20, COLOR_BG);
} else if (timerRunning) {
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"));
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(buttonX - 10, 225);
tft.print(F("3 Sekunden halten"));
} else {
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"));
tft.setTextColor(COLOR_TEXT_DIM);
tft.setTextSize(1);
tft.setCursor(buttonX - 10, 225);
tft.print(F("3 Sekunden halten"));
}
}
void drawConfigScreen() {
tft.fillScreen(COLOR_BG);
forceRedraw = true;
drawStatusBox();
drawLEDSetting();
drawTimerDisplay();
drawControlButtons();
display.setBrightness(0x0f);
ledRing.setBrightness(LED_BRIGHTNESS);
ledRing2.setBrightness(LED_BRIGHTNESS);
// Tacho-Check nur beim allerersten Mal starten
if (!tachoCheckDone) {
startTachoCheck();
tachoCheckDone = true;
} else {
// Normale LED-Anzeige
updateLedRing();
ledRing2.clear();
ledRing2.show();
}
if (!timerStarted || firstStart) {
display.showNumberDecEx(6000, 0b01000000, true);
} else {
unsigned long seconds = currentRemainingTime / 1000;
unsigned long hundredths = (currentRemainingTime % 1000) / 10;
int displayValue = (seconds * 100) + hundredths;
display.showNumberDecEx(displayValue, 0b01000000, true);
}
updateButtonLED();
}
// =============================================================================
// LED RING FUNKTIONEN - TACHO-CHECK ANIMATION
// =============================================================================
uint32_t getTachoColor(int position, int maxPosition) {
float ratio = (float)position / maxPosition;
uint8_t red, green, blue;
if (ratio < 0.5) {
float localRatio = ratio * 2.0;
red = (uint8_t)(localRatio * 255);
green = 255;
blue = 0;
} else {
float localRatio = (ratio - 0.5) * 2.0;
red = 255;
green = (uint8_t)(255 - (localRatio * 255));
blue = 0;
}
return ledRing.Color(red, green, blue);
}
void startTachoCheck() {
tachoCheckActive = true;
tachoCheckStartTime = millis();
}
void updateTachoCheck() {
if (!tachoCheckActive) return;
unsigned long currentMillis = millis();
unsigned long elapsed = currentMillis - tachoCheckStartTime;
if (elapsed >= TACHO_CHECK_DURATION) {
tachoCheckActive = false;
updateLedRing();
ledRing2.clear();
ledRing2.show();
return;
}
float progress = (float)elapsed / TACHO_CHECK_DURATION;
int ledsToLight = (int)(progress * LED_COUNT);
ledsToLight = constrain(ledsToLight, 0, LED_COUNT);
ledRing.clear();
ledRing2.clear();
for (int i = 0; i < ledsToLight; i++) {
uint32_t color = getTachoColor(i, LED_COUNT);
ledRing.setPixelColor(i, color);
ledRing2.setPixelColor(i, color);
}
ledRing.show();
ledRing2.show();
}
// =============================================================================
// LED RING FUNKTIONEN - ERSTER RING
// =============================================================================
void updateLedRing() {
ledRing.clear();
uint32_t baseColor;
if (activeLeds > LED_COUNT * 2 / 3) {
baseColor = ledRing.Color(0, 200, 0);
} else if (activeLeds > LED_COUNT / 3) {
baseColor = ledRing.Color(200, 200, 0);
} else {
baseColor = ledRing.Color(200, 0, 0);
}
for (int i = 0; i < activeLeds; i++) {
ledRing.setPixelColor(i, baseColor);
}
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) {
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) {
ledsToLight = (int)(progress * LED_COUNT);
} else {
ledsToLight = LED_COUNT - (int)(progress * LED_COUNT);
}
ledsToLight = constrain(ledsToLight, 0, LED_COUNT);
uint32_t animColor;
if (ring2Filling) {
animColor = ledRing2.Color(0, 0, 255);
} else {
animColor = ledRing2.Color(255, 100, 0);
}
for (int i = 0; i < ledsToLight; i++) {
ledRing2.setPixelColor(i, animColor);
}
}
ledRing2.show();
}
void startRing2Animation(bool filling) {
ring2AnimationActive = true;
ring2AnimationStartTime = millis();
ring2Filling = filling;
}
void stopRing2Animation() {
ring2AnimationActive = false;
}
void updateBlinkingLed() {
if (tachoCheckActive) return;
unsigned long currentMillis = millis();
if (currentMillis - lastBlinkTime >= BLINK_INTERVAL) {
lastBlinkTime = currentMillis;
blinkState = !blinkState;
updateLedRing();
}
}
void updateButtonBlink() {
unsigned long currentMillis = millis();
if (currentMillis - lastButtonBlinkTime >= BLINK_INTERVAL) {
lastButtonBlinkTime = currentMillis;
buttonBlinkState = !buttonBlinkState;
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;
display.showNumberDecEx(6000, 0b01000000, true);
setStatus("Timer laeuft");
shortBeep(BEEP_FREQ_START, 200);
drawConfigScreen();
updateButtonLED();
updateRelay(); // Relais einschalten
}
void pauseTimer() {
timerRunning = false;
timerPauseTime = millis();
lastSecondBeeped = 99;
setStatus("Pausiert");
shortBeep(BEEP_FREQ_PAUSE, 200);
drawConfigScreen();
updateRelay(); // Relais ausschalten
}
void resumeTimer() {
timerRunning = true;
unsigned long pauseDuration = millis() - timerPauseTime;
timerStartTime += pauseDuration;
setStatus("Timer laeuft");
shortBeep(BEEP_FREQ_START, 200);
drawConfigScreen();
updateRelay(); // Relais wieder einschalten
}
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);
if (remaining > 0 && seconds != lastSecondBeeped) {
lastSecondBeeped = seconds;
tone(BUZZER_PIN, BEEP_FREQ_NORMAL, 50);
}
if (remaining == 0) {
if (activeLeds > 0) {
activeLeds--;
currentRound++;
shortBeep(BEEP_FREQ_LED_DOWN, 200);
updateLedRing();
drawConfigScreen();
if (activeLeds == 0) {
finalRound = true;
currentBlinkingLed = -1;
} else {
currentBlinkingLed = activeLeds - 1;
}
timerStartTime = currentMillis;
currentRemainingTime = timerDuration;
lastSecondBeeped = 99;
} else if (finalRound) {
timerRunning = false;
timerStarted = false;
finalRound = false;
playExplosionSequence();
display.showNumberDecEx(6000, 0b01000000, true);
activeLeds = initialLeds;
currentBlinkingLed = activeLeds - 1;
currentRound = 1;
currentRemainingTime = timerDuration;
setStatus("Bereit");
firstStart = true;
updateButtonLED();
updateRelay(); // Relais ausschalten wenn Timer komplett beendet
drawConfigScreen();
}
}
}
// =============================================================================
// BUTTON HANDLER
// =============================================================================
void handleMainButton() {
unsigned long currentMillis = millis();
bool reading = digitalRead(BUTTON_PIN);
if (reading == LOW && lastButtonState == HIGH) {
lastDebounceTime = currentMillis;
mainButtonPressTime = currentMillis;
mainButtonHeld = false;
holdProgressBeeped1 = false;
holdProgressBeeped2 = false;
buttonPressed = true;
if (timerStarted) {
if (timerRunning) {
startRing2Animation(false);
} else {
startRing2Animation(true);
}
}
if (firstStart && currentScreen == 0) {
currentScreen = 1;
drawConfigScreen();
buttonPressed = false;
}
else if (firstStart && !timerStarted) {
startTimer();
buttonPressed = false;
}
}
if (reading == LOW && buttonPressed) {
unsigned long holdTime = currentMillis - mainButtonPressTime;
if (holdTime > 1000 && !mainButtonHeld) {
int progress = (holdTime * 100) / HOLD_DURATION;
progress = constrain(progress, 0, 100);
drawHoldProgress(progress);
if (holdTime >= 1000 && holdTime < 1100 && !holdProgressBeeped1) {
shortBeep(BEEP_FREQ_HOLD_PROGRESS, 100);
holdProgressBeeped1 = true;
}
if (holdTime >= 2000 && holdTime < 2100 && !holdProgressBeeped2) {
shortBeep(BEEP_FREQ_HOLD_PROGRESS, 100);
holdProgressBeeped2 = true;
}
}
if (ring2AnimationActive) {
updateLedRing2();
}
if (holdTime >= HOLD_DURATION && !mainButtonHeld) {
mainButtonHeld = true;
if (currentScreen == 0) {
currentScreen = 1;
drawConfigScreen();
} else {
if (!timerStarted) {
startTimer();
} else {
if (timerRunning) {
pauseTimer();
} else {
resumeTimer();
}
}
}
stopRing2Animation();
updateLedRing2();
tft.fillRect(0, 235, 320, 20, COLOR_BG);
}
}
if (reading == HIGH && lastButtonState == LOW) {
tft.fillRect(0, 235, 320, 20, COLOR_BG);
if (!mainButtonHeld && !firstStart && buttonPressed) {
shortBeep(BEEP_FREQ_HOLD_PROGRESS, 100);
}
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) {
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) {
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);
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUTTON_UP, INPUT_PULLUP);
pinMode(BUTTON_DOWN, INPUT_PULLUP);
pinMode(BUTTON_LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(RELAY_PIN, OUTPUT); // Relais-Pin als Output definieren
// Sicherstellen, dass das Relais initial ausgeschaltet ist
digitalWrite(RELAY_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
digitalWrite(BUTTON_LED_PIN, LOW);
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);
tft.fillScreen(COLOR_BG);
display.setBrightness(0x0f);
display.clear();
ledRing.begin();
ledRing.setBrightness(0);
ledRing.clear();
ledRing.show();
ledRing2.begin();
ledRing2.setBrightness(0);
ledRing2.clear();
ledRing2.show();
randomSeed(analogRead(0));
Serial.println(F("HNGR13 Bomb Timer - System gestartet"));
drawStartScreen();
}
void loop() {
unsigned long currentMillis = millis();
if (tachoCheckActive) {
updateTachoCheck();
}
if (currentScreen == 1 && timerRunning) {
if (currentMillis - lastSegmentUpdate >= SEGMENT_UPDATE_INTERVAL) {
lastSegmentUpdate = currentMillis;
updateSegmentDisplay();
}
if (currentMillis - lastTftUpdate >= TFT_UPDATE_INTERVAL) {
lastTftUpdate = currentMillis;
updateTimerDisplay();
}
}
if (currentScreen == 1 && !tachoCheckActive) {
updateBlinkingLed();
}
updateButtonBlink();
handleMainButton();
handleUpButton();
handleDownButton();
if (ring2AnimationActive && !tachoCheckActive) {
updateLedRing2();
}
updateButtonLED();
updateRelay(); // Relais-Status aktualisieren
}
5V Quelle
Stromversorgung