#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include "RTClib.h"
// ================== DISPLAY ==================
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
#define CS_PIN 53
#define MATRICI_PER_RIGA 15
#define RIGHE 13
#define MAX_DEVICES (MATRICI_PER_RIGA * RIGHE)
// ================== OGGETTI ==================
RTC_DS1307 rtc;
MD_Parola P(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// ================== STATI ==================
enum Stato { STARTUP, RUNNING };
Stato stato = STARTUP;
unsigned long startupTime;
const unsigned long STARTUP_DURATION = 4000;
unsigned long lastUpdate = 0;
char bufferOra[16];
// ================== RESET GIORNALIERO ==================
bool resetFattoOggi = false;
// ================== BUFFER PER RIGHE ==================
char testoRighe[RIGHE][40]; // buffer dedicato per ogni riga
// ================== CORSE ==================
struct Corsa {
uint8_t ora;
uint8_t minuto;
const char* destinazione;
bool attiva;
};
Corsa corse[] = {
// ===== MATTINA =====
{3, 40, "ROMA ANAGNINA", true},
{5, 31, "FROSINONE", true},
{6, 32, "CASSINO", true},
{6, 33, "ROMA ANAGNINA", true},
{6, 34, "AVEZZANO", true},
{7, 35, "ISOLA LIRI", true},
{7, 30, "FROSINONE", true},
{7, 50, "CASSINO", true},
{8, 10, "ROMA ANAGNINA", true},
{8, 35, "AVEZZANO", true},
{8, 55, "FROSINONE", true},
{9, 20, "ISOLA LIRI", true},
{9, 45, "CASSINO", true},
// ===== TARDA MATTINA =====
{10, 15, "FROSINONE", true},
{10, 45, "ROMA ANAGNINA", true},
{11, 15, "AVEZZANO", true},
{11, 45, "CASSINO", true},
// ===== POMERIGGIO =====
{12, 30, "FROSINONE", true},
{13, 00, "ISOLA LIRI", true},
{13, 30, "ROMA ANAGNINA", true},
{14, 00, "AVEZZANO", true},
{14, 30, "CASSINO", true},
{15, 00, "FROSINONE", true},
{15, 30, "ROMA ANAGNINA", true},
{16, 00, "ISOLA LIRI", true},
{16, 30, "AVEZZANO", true},
{17, 00, "CASSINO", true},
// ===== TARDO POMERIGGIO =====
{17, 30, "FROSINONE", true},
{18, 00, "ROMA ANAGNINA", true},
{18, 30, "AVEZZANO", true},
{19, 00, "CASSINO", true},
// ===== SERA =====
{19, 30, "FROSINONE", true},
{20, 00, "ISOLA LIRI", true},
{20, 30, "ROMA ANAGNINA", true},
{21, 00, "AVEZZANO", true},
{21, 30, "CASSINO", true},
{22, 00, "FROSINONE", true}
};
const uint8_t NUM_CORSE = sizeof(corse) / sizeof(corse[0]);
// =================================================
void resetCorse() {
for (uint8_t i = 0; i < NUM_CORSE; i++) {
corse[i].attiva = true;
}
}
// =================================================
void setup() {
P.begin(RIGHE);
P.setIntensity(8);
for (uint8_t i = 0; i < RIGHE; i++) {
uint8_t start = i * MATRICI_PER_RIGA;
uint8_t end = start + MATRICI_PER_RIGA - 1;
P.setZone(i, start, end);
}
// ===== RTC =====
rtc.begin();
if (!rtc.isrunning()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
P.displayZoneText(
0,
"RTC_ERR_OR_NOT_FOUND",
PA_CENTER,
0,
0,
PA_PRINT, PA_NO_EFFECT
);
return;
}
resetCorse(); // <<< FONDAMENTALE
// ===== MESSAGGIO AVVIO =====
P.displayZoneText(
0,
"INITIALIZING . . . ",
PA_CENTER,
0,
0,
PA_PRINT, PA_NO_EFFECT
);
startupTime = millis();
}
// =================================================
void loop() {
P.displayAnimate();
if (stato == STARTUP) {
if (millis() - startupTime > STARTUP_DURATION) {
stato = RUNNING;
}
return;
}
if (millis() - lastUpdate > 1000) {
DateTime now = rtc.now();
// ===== RESET A MEZZANOTTE =====
if (now.hour() == 0 && now.minute() == 0 && !resetFattoOggi) {
resetCorse();
resetFattoOggi = true;
}
if (now.hour() == 0 && now.minute() == 1) {
resetFattoOggi = false;
}
aggiornaOra(now);
aggiornaDestinazioni(now);
lastUpdate = millis();
}
}
// ================== FUNZIONI ==================
void aggiornaOra(DateTime now) {
sprintf(bufferOra, "%02d:%02d:%02d",
now.hour(), now.minute(), now.second());
P.displayZoneText(
0,
bufferOra,
PA_LEFT,
0,
0,
PA_PRINT, PA_NO_EFFECT
);
}
void aggiornaDestinazioni(DateTime now) {
// 🔹 Pulizia completa righe
for (uint8_t z = 1; z < RIGHE; z++) {
testoRighe[z][0] = '\0';
P.displayZoneText(z, testoRighe[z], PA_LEFT, 0, 0, PA_PRINT, PA_NO_EFFECT);
}
uint8_t riga = 1;
int minutiOra = now.hour() * 60 + now.minute();
for (uint8_t i = 0; i < NUM_CORSE; i++) {
if (!corse[i].attiva) continue;
int minutiCorsa = corse[i].ora * 60 + corse[i].minuto;
// Corsa partita
if (minutiOra >= minutiCorsa) {
corse[i].attiva = false;
continue;
}
// 🔹 Scrive nel buffer dedicato alla riga
snprintf(testoRighe[riga], sizeof(testoRighe[riga]),
"%02d:%02d %s",
corse[i].ora,
corse[i].minuto,
corse[i].destinazione);
// 1 minuto prima → lampeggio
if (minutiCorsa - minutiOra == 1) {
P.displayZoneText(
riga,
testoRighe[riga],
PA_LEFT,
120,
0,
PA_BLINDS,
PA_BLINDS
);
} else {
P.displayZoneText(
riga,
testoRighe[riga],
PA_LEFT,
0,
0,
PA_PRINT,
PA_NO_EFFECT
);
}
riga++;
if (riga >= RIGHE) break;
}
}