#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <Adafruit_NeoPixel.h>
#include <SPI.h>
MD_MAX72XX::fontType_t numeric7Seg[] PROGMEM = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,64,0,6,126,255,195,195,255,126,6,0,0,255,255,0,0,6,243,251,219,219,223,206,
6,219,219,219,219,255,126,6,15,31,24,24,255,255,6,223,223,219,219,251,115,6,126,255,219,219,251,122,6,3,3,3,3,255,254,
6,118,255,219,219,255,118,6,78,223,219,219,255,126,2,102,102};
#define BUZZER 7
#define RING_PIN 6
#define BTN_START 11
#define BTN_WIN 12
#define DATA_PIN 8
#define CS_PIN 9
#define CLK_PIN 10
#define LED_COUNT 24
#define SHIFT_DATA_PIN 2 // DS del 74HC595
#define SHIFT_CLK_PIN 3 // SH_CP
#define SHIFT_LATCH_PIN 4 // ST_CP
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
#define MAX_DEVICES 4
byte ledStatus = 0;
MD_Parola P(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);
Adafruit_NeoPixel ring(LED_COUNT, RING_PIN, NEO_GRB + NEO_KHZ800);
// Stati
enum { IDLE, PRESTART, RUNNING, FINISHED, WON };
uint8_t state = IDLE;
// Timer
unsigned long startMs = 0;
unsigned long frozenRem = 0;
const unsigned long TOTAL_SEC = 3600UL;
unsigned long lastBeepSec = 999;
// Prestart
uint8_t blinkCnt = 0;
unsigned long lastBlinkMs = 0;
// LED Ring
int pos = 0;
unsigned long lastStepMs = 0;
const float tail[] = {0.9, 0.8, 0.7, 0.6, 0.4, 0.2};
const int tailLen = 6;
// Input debounce
unsigned long lastStartPress = 0, lastWinPress = 0;
const unsigned long DEBOUNCE = 200;
uint8_t Fase = 0;
char buf[9];
void setup() {
pinMode(BTN_START, INPUT_PULLUP);
pinMode(BTN_WIN, INPUT_PULLUP);
pinMode(BUZZER, OUTPUT);
pinMode(SHIFT_LATCH_PIN, OUTPUT);
pinMode(SHIFT_CLK_PIN, OUTPUT);
pinMode(SHIFT_DATA_PIN, OUTPUT);
spegniTuttiLED();
ring.begin();
ring.clear();
ring.show();
P.begin();
P.setFont(numeric7Seg);
P.setTextAlignment(PA_CENTER);
P.print("00:00");
}
// ******* LOOP
void loop() {
TimeManager();
switch (Fase) {
case 1:
accendiLED(1); //FASE 1
break;
case 2:
accendiLED(2); //FASE 2
break;
case 3:
accendiLED(3); //FASE 3
break;
case 4:
accendiLED(4); //FASE 4
break;
case 5:
accendiLED(5); //FASE 5
break;
case 6:
accendiLED(6); //FASE 6
break;
case 7:
accendiLED(7); //FASE 7
break;
case 8:
accendiLED(8); //FASE 8
break;
default:
spegniTuttiLED();
break;
}
}// ******* /LOOP
void TimeManager() {
unsigned long now = millis();
// Pulsanti
if (!digitalRead(BTN_START) && now - lastStartPress > DEBOUNCE) {
lastStartPress = now;
if (state == IDLE) {
state = PRESTART;
blinkCnt = 0;
lastBlinkMs = 0;
spegniTuttiLED();
}
}
if (!digitalRead(BTN_WIN) && now - lastWinPress > DEBOUNCE) {
lastWinPress = now;
if (state == RUNNING) {
frozenRem = TOTAL_SEC - (now - startMs)/1000;
state = WON;
tone(BUZZER, 1200, 800);
ring.clear();
ring.show();
} else if (state == IDLE || state == PRESTART) {
state = IDLE;
ring.clear();
ring.show();
P.print("00:00");
noTone(BUZZER);
}
}
// Prestart
if (state == PRESTART) {
if (now - lastBlinkMs > 500) {
lastBlinkMs = now;
if (blinkCnt % 2 == 0) {
P.print("60:00");
tone(BUZZER, 1000, 80);
} else P.print(" ");
if (++blinkCnt >= 10) {
tone(BUZZER, 1400, 300);
state = RUNNING;
startMs = millis();
pos = 0;
lastStepMs = millis();
lastBeepSec = 999;
Fase = 1;
}
}
return;
}
// Timer finito
if (state == RUNNING) {
unsigned long rem = TOTAL_SEC - (now - startMs)/1000;
if (rem <= 0) {
state = FINISHED;
tone(BUZZER, 400, 1200);
}
}
// Display
if (state == FINISHED) {
if (now % 1000 < 500) P.print("00:00");
else P.print(" ");
}
else if (state == WON) {
if (now % 800 < 400) {
sprintf(buf, "%02lu:%02lu", frozenRem/60, frozenRem%60);
P.print(buf);
} else P.print(" ");
}
else if (state == RUNNING) {
unsigned long rem = TOTAL_SEC - (now - startMs)/1000;
sprintf(buf, "%02lu:%02lu", rem/60, rem%60);
P.print(buf);
}
// LED Ring
if (state == FINISHED) {
if (now % 1000 < 500) ring.clear();
else for (int i=0; i<LED_COUNT; i++) ring.setPixelColor(i, ring.Color(255,0,0));
ring.show();
return;
}
if (state == WON) {
if (now - lastStepMs >= 20) {
lastStepMs = now;
ring.clear();
uint32_t green = ring.Color(0,255,0);
uint8_t r = 0, g = 255, b = 0;
for (int i=0; i<tailLen; i++) {
int p1 = (pos - i + LED_COUNT) % LED_COUNT;
int p2 = (pos - i + LED_COUNT/2) % LED_COUNT;
ring.setPixelColor(p1, 0, g*tail[i], 0);
ring.setPixelColor(p2, 0, g*tail[i], 0);
}
ring.show();
pos = (pos + 1) % LED_COUNT;
}
return;
}
if (state != RUNNING) return;
unsigned long remSec = TOTAL_SEC - (now - startMs)/1000;
bool fast = (remSec <= 300);
unsigned long baseMs = fast ? 31 : 63;
if (now - lastStepMs >= baseMs) {
lastStepMs = now;
ring.clear();
uint32_t col;
if (remSec > 1800) col = ring.Color(0,255,0);
else if (remSec > 900) col = ring.Color(180,255,0);
else if (remSec > 300) col = ring.Color(255,120,0);
else col = ring.Color(255,0,0);
uint8_t r = col >> 16, g = col >> 8, b = col;
for (int i=0; i<tailLen; i++) {
int p1 = (pos - i + LED_COUNT) % LED_COUNT;
int p2 = (pos - i + LED_COUNT/2) % LED_COUNT;
ring.setPixelColor(p1, r*tail[i], g*tail[i], b*tail[i]);
ring.setPixelColor(p2, r*tail[i], g*tail[i], b*tail[i]);
}
ring.show();
pos = (pos + 1) % LED_COUNT;
}
// Beep ultimo minuto
if (state == RUNNING) {
unsigned long currentSec = TOTAL_SEC - (now - startMs)/1000;
if (currentSec <= 60 && currentSec != lastBeepSec) {
lastBeepSec = currentSec;
if (currentSec > 0) tone(BUZZER, 500, 30);
}
}
}
// 8 LED fasi
void accendiLED(uint8_t num) {
bitSet(ledStatus,num-1);
updateLEDs();
}
void spegniLED(uint8_t num) {
bitClear(ledStatus,num-1);
updateLEDs();
}
void spegniTuttiLED() {
ledStatus = 0;
updateLEDs();
}
void updateLEDs() {
digitalWrite(SHIFT_LATCH_PIN, LOW);
shiftOut(SHIFT_DATA_PIN, SHIFT_CLK_PIN, LSBFIRST, ledStatus);
digitalWrite(SHIFT_LATCH_PIN, HIGH);
}