#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#define HARDWARE_TYPE MD_MAX72XX::PAROLA_HW
#define MAX_DEVICES 8
#define CS_PIN 10
#define BUTTON_PIN 2
#define BUZZER_PIN 3
MD_Parola myDisplay(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
// =====================
// TIMER
// =====================
unsigned long countdownTime = 10UL * 1000UL;
unsigned long startMillis = 0;
bool lastButtonState = HIGH;
unsigned long lastPress = 0;
char buffer[12];
// =====================
// STATE MACHINE
// =====================
enum State {
INTRO_SCROLL,
SHOW_TIMER,
RUN_TIMER,
END_ANIM
};
State state = INTRO_SCROLL;
// =====================
// END ANIMATION
// =====================
unsigned long animStart = 0;
unsigned long lastFrame = 0;
unsigned long lastNoteTime = 0;
int frame = 0;
int melodyIndex = 0;
// =====================
// LOGO / FX
// =====================
const char* logoFrames[] = {
" [] ",
" [[][]] ",
"[[####]]",
" [[][]] ",
" [] ",
};
const char* fxFrames[] = {
" . . ",
" .. .. ",
" ..... ",
"*******",
" ..... ",
" .. .. ",
" . . "
};
// =====================
// 🎵 MARCHÉ MILITAIRE
// =====================
int melody[] = {
// 🥁 INTRO MARCHÉE
440, 440, 440, 330,
440, 440, 440, 330,
// 🪖 MOTIF MILITAIRE
523, 523, 587, 523,
659, 587, 523, 0,
// 🥁 RYTHME MARCHE
392, 392, 440, 392,
494, 440, 392, 0,
// 🎺 FANFARE
523, 587, 659, 784,
659, 587, 523, 0
};
int noteDurations[] = {
180, 180, 180, 300,
180, 180, 180, 300,
160, 160, 160, 200,
200, 200, 300, 300,
160, 160, 160, 200,
200, 200, 300, 300,
180, 180, 180, 250,
250, 250, 300, 400
};
const int melodySize = sizeof(melody) / sizeof(int);
// =====================
// SETUP
// =====================
void setup() {
pinMode(BUTTON_PIN, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
myDisplay.begin();
myDisplay.setIntensity(0);
myDisplay.displayClear();
myDisplay.displayText(
"Partie en cours",
PA_CENTER,
30,
0,
PA_SCROLL_LEFT,
PA_SCROLL_LEFT
);
}
// =====================
// LOOP
// =====================
void loop() {
bool buttonState = digitalRead(BUTTON_PIN);
// =====================
// BUTTON
// =====================
if (lastButtonState == HIGH && buttonState == LOW) {
if (millis() - lastPress > 200) {
lastPress = millis();
if (state == INTRO_SCROLL) {
state = SHOW_TIMER;
unsigned long totalSeconds = countdownTime / 1000;
snprintf(buffer, sizeof(buffer), "%02lu:%02lu",
totalSeconds / 60,
totalSeconds % 60);
myDisplay.displayClear();
myDisplay.displayText(buffer, PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
}
else if (state == SHOW_TIMER) {
state = RUN_TIMER;
startMillis = millis();
}
}
}
lastButtonState = buttonState;
// =====================
// INTRO
// =====================
if (state == INTRO_SCROLL) {
if (myDisplay.displayAnimate()) {
myDisplay.displayReset();
}
}
// =====================
// SHOW TIMER
// =====================
if (state == SHOW_TIMER) {
myDisplay.displayAnimate();
}
// =====================
// RUN TIMER
// =====================
if (state == RUN_TIMER) {
myDisplay.displayAnimate();
unsigned long elapsed = millis() - startMillis;
if (elapsed >= countdownTime) {
state = END_ANIM;
animStart = millis();
frame = 0;
melodyIndex = 0;
lastFrame = millis();
lastNoteTime = millis();
}
else {
unsigned long left = countdownTime - elapsed;
unsigned int min = left / 60000;
unsigned int sec = (left % 60000) / 1000;
snprintf(buffer, sizeof(buffer), "%02u:%02u", min, sec);
static char lastBuffer[12] = "";
if (strcmp(buffer, lastBuffer) != 0) {
strcpy(lastBuffer, buffer);
myDisplay.displayClear();
myDisplay.displayText(buffer, PA_CENTER, 0, 0, PA_PRINT, PA_NO_EFFECT);
}
if (left <= 10000) {
tone(BUZZER_PIN, 2000, 120);
}
}
}
// =====================
// END ANIMATION (20s + MILITARY MUSIC)
// =====================
if (state == END_ANIM) {
unsigned long elapsed = millis() - animStart;
// =====================
// 🎵 MARCH MILITAIRE
// =====================
if (millis() - lastNoteTime > noteDurations[melodyIndex]) {
lastNoteTime = millis();
int note = melody[melodyIndex];
if (note == 0) {
noTone(BUZZER_PIN);
} else {
tone(BUZZER_PIN, note, noteDurations[melodyIndex]);
}
melodyIndex++;
if (melodyIndex >= melodySize) {
melodyIndex = 0;
}
}
// =====================
// 🎬 ANIMATION
// =====================
if (millis() - lastFrame > 200) {
lastFrame = millis();
myDisplay.displayClear();
int phase = (frame / 25) % 3;
if (phase == 0) {
myDisplay.displayText(
logoFrames[frame % 5],
PA_CENTER,
0,
0,
PA_PRINT,
PA_NO_EFFECT
);
}
else if (phase == 1) {
myDisplay.displayText(
fxFrames[frame % 7],
PA_CENTER,
0,
0,
PA_WIPE_CURSOR,
PA_WIPE_CURSOR
);
}
else {
const char* txt;
switch (frame % 6) {
case 0: txt = "####"; break;
case 1: txt = "****"; break;
case 2: txt = "0000"; break;
case 3: txt = "////"; break;
case 4: txt = "XXXX"; break;
default: txt = "===="; break;
}
myDisplay.displayText(
txt,
PA_CENTER,
0,
0,
PA_SCROLL_LEFT,
PA_SCROLL_LEFT
);
}
frame++;
}
myDisplay.displayAnimate();
// =====================
// ⏱ FIN 20 SECONDES
// =====================
if (elapsed > 20000) {
noTone(BUZZER_PIN);
state = INTRO_SCROLL;
myDisplay.displayClear();
myDisplay.displayText(
"Partie en cours",
PA_CENTER,
30,
0,
PA_SCROLL_LEFT,
PA_SCROLL_LEFT
);
myDisplay.displayReset();
}
return;
}
}