Plaque LISBOA - projet final - Qiancheng, Polina, Fatmagül, Olivier
Projet Final - Plaque Lisboa 🇵🇹
• Membres : Qiancheng, Polina, Fatmagül, Olivier
• Fablab : Sorbonne Université
• Période : Avril 2025
Résumé du projet
Dans le cadre du cours de prototypage, nous avons conçu un panneau interactif lumineux représentant un logo de la ville de Lisbonne. Ce prototype est pensé comme un souvenir de notre voyage de promotion, à emmener avec nous à Lisbonne pour l’intégrer dans nos photos. L’objet associe plexiglas, LEDs et un boîtier en bois, le tout animé par un Arduino et un microphone qui réagit à la musique ambiante en synchronisant l’éclairage avec le rythme sonore.
Nous avons demandé à ChatGPT de nous générer une image de ce qu'on voulait en précisant exactement notre demande.demande en lui téléchargeant notre logo créé sur CANVA (voir plus bas).
Cette image a été notre inspiration tout le long du projet.
Objectifs du projet
-
Créer un objet-souvenir esthétique et interactif
-
Apprendre à utiliser les outils de fabrication numérique : découpe laser, design SVG, Arduino
-
Expérimenter l’interaction entre son et lumière grâce à un microphone et des LEDs
Outils & Logiciels utilisés
-
Arduino Uno
-
Bande de LED (WS2812B)
-
Microphone sonore compatible Arduino (module MAX4466)
-
Plaque de plexiglas (PMMA extrudé 6mm)
-
Panneau bois (CP peuplier 6 mm)
-
Découpeuse laser
-
Logiciels : Canva, Inkscape, Boxes.py, Arduino IDE
Étapes de réalisation
1. Design du boîtier en bois
-
Génération du modèle via le site Boxes.py, avec adaptation des dimensions à notre projet.
- Ajout d’un trou rectangulaire sur la face supérieure, conçu pour accueillir précisément le logo de Lisbonne en plexiglas.
- Ajout des ouvertures sur la face arrière de la boite pour le bouton poussoir, le potentiomètre et le micro.
-
Plusieurs tests de découpe ont été réalisés pour affiner les mesures et assurer un ajustement parfait du logo.
- Mesures:
2. Préparation du logo Lisbonne
-
-
Création graphique sur Canva.
-
Après avoir créé la version initiale du logo sur Canva et exporté au format SVG, nous avons utilisé Inkscape pour adapter le fichier à une découpe précise.
-
Notre objectif est de ne découper que la partie supérieure des lettres, afin de laisser intactes les inscriptions ou détails présents en bas du logo.
-
Dans un premier temps, nous avons utilisé l'outil de dessin de ligne pour relier et dessiner manuellement une ligne horizontale sous les lettres « L » et « a » de « Lisboa » afin d'inclure tous les éléments suivants ( "M16", "logo de tram", “2025”).
-
Ensuite nous avons sélectionné les nœuds situés sous la ligne, c’est-à-dire la partie inférieure du mot “Lisboa”.
Puis nous avons supprimé ces segments, en ne gardant que la partie supérieure du mot. - Enfin, nous utiliserons l'outil d'alignement pour aligner parfaitement les deux couches afin de former le logo complet et nous laisserons la ligne rouge entourer la périphérie de la partie supérieure afin de découper de manière réaliste uniquement la partie souhaitée.
-
3. Découpe et assemblage
-
Découpe laser de la boîte en bois
-
Insertion de l’Arduino et des autres composant à l’intérieur du boîtier.
- Collage de la bande LED sous la zone du logo.
3. Circuit et Code Arduino
- Le cœur interactif du prototype repose sur un script Arduino qui permet aux LEDs de s’animer en fonction du rythme de la musique captée par un microphone. L’objectif était de faire en sorte que le logo Lisbonne s’illumine dynamiquement, apportant une dimension vivante et festive à l’objet.
- Objectif: Synchroniser l'affichage lumineux des LEDs avec le son ambiant (musique) capté par un micro.
- Étapes et apprentissages clés :
Circuit
Schéma réalisé sur tinkerCad
Bandeau LED
-
Compréhension du fonctionnement du bandeau LED (WS2812) :
Nous avons suivi un tutoriel expliquant le contrôle de ces LEDs RGB : https://www.youtube.com/watch?v=xw8iRNxt-VU - Pour contrôler le bandeau LED :
Utilisation de la bibliothèqu FastLED téléchargeable dans Arduino IDE.
- Problème rencontré – mauvaise couleur affichées :
Lors des premiers tests, les LEDs n'affichaient pas les bonnes couleurs.
➤ Le souci venait du fait que le bandeau utilisait un ordre GRB (et non RGB comme par défaut).-
-
Problème 1 : Mauvaise couleur dans l'affichage du projet.
FastLED.addLeds<WS2812B, DATA_PIN, RGB>(leds, NUM_LEDS); // GRB ordering is typical
Il fallait remplacer RGB par GRB :
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS); // GRB ordering is typical
-
-
Test du bandeau LED
Nous avons commencé avec un code simple pour faire clignoter 9 LEDs de manière aléatoire :
-
#include <FastLED.h>
// How many leds in your strip?
#define NUM_LEDS 9
// For led chips like WS2812, which have a data line, ground, and power, you just
// need to define DATA_PIN. For led chipsets that are SPI based (four wires - data, clock,
// ground, and power), like the LPD8806 define both DATA_PIN and CLOCK_PIN
// Clock pin only needed for SPI based chipsets when not using hardware SPI
#define DATA_PIN 5
// Define the array of leds
CRGB leds[NUM_LEDS];
void setup() {
FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS); // GRB ordering is typical
}
void loop() {
for (int i=0; i<=8; i++) {
// Turn the LED on, then pause
leds[i] = CRGB::Red;
FastLED.show();
delay(500);
// Now turn the LED off, then pause
leds[i] = CRGB::Black;
FastLED.show();
delay(100);
}
}
Microphone
➤ Le but était de récupérer la valeur de l’amplitude sonore et de l’utiliser pour modifier l’intensité ou la couleur des LEDs.
- Sélection du microphone :
- Premier essai avec un buzzer piezoélectrique pour tester le concept : ne fonctionne pas
- Deuxième essai avec un microphone 3X KY-037 : non concluant car ce mic est conçu pour détecter les pics sonores forts (type clap ou bruit soudain), et ne donne pas une lecture fluide du son ambiant continu comme la musique.
- Troisième essai avec un microphone MAX4466 ✅ : essai concluant car ce mic possède une plage de sensibilité optimale dans les basses fréquences
- Branchement : Le branchement se fait au niveau avec le GND et l'entrée 3,3V. La data est reçue par le port A0
-
- Lecture du microphone par l'Arduino : Arduino lit des valeurs allant de 0 à 1023. On lit les données avec la fonction analogRead(MIC_PIN) et on traite ces données afin d'obtenir un suivi du niveau sonore.
-
- Sensibilité : Sur le microphone il y a un potard de changement de gain (le potard est une toute petite vis). Nous avons réglé le potard afin que dans le silence le micro renvoie une valeur centrée sur 360. Dans le code il défini par:
#define BASELINE 360
Potentiomètre
➤ Le niveau sonore de l'environnement peut changer, afin d'obtenir un résultat visuel satisfaisant en toutes conditions, l'utilisateur peut changer la sensibilité du microphone avec le potentiomètre
- Le potentiomètre est branchées sur le 5V, le GND et l'entrée analogique A1.
- Le potentiomètre envoie également un valeur allant de 0 à 5V se traduisant en numérique de 0 à 1023. On lit cette valeur dans le code avec :
pot = analogRead(POT_PIN);
Bouton
➤ Nous avons créé 5 modes de couleurs : arc-en-ciel animé, scintillements dynamiques, VU-meter sonore, cycle de couleurs du Portugal, et VU-meter aux couleurs du Portugal.
- Branchement en mode "pull-down externe" : Un côté du bouton est relié à la masse (GND). L'autre côté est connecté à la broche numérique D2 et à une résistance de pull-down (avec une resistance de 10kOhm)
- Fonctionnement : quand on appuie sur le bouton, on passe d'un mode à l'autre en boucle.
Alimentation
➤ Utilisation d'un simple pile 9V branchée à l'entrée prévue dans l'Arduino UNO.
Le code entier
#include <FastLED.h>
// === CONFIGURATION MATÉRIEL ===
#define DATA_PIN 5 // Broche DATA du ruban LED
#define NUM_LEDS 18 // Nombre total de LEDs (1–300)
#define MIC_PIN A0 // Entrée micro analogique (0–1023)
#define POT_PIN A1 // Entrée potentiomètre sensibilité (0–1023)
#define BUTTON_PIN 2 // Bouton poussoir pull-down externe
#define LED_TYPE WS2812B
#define COLOR_ORDER GRB
#define BRIGHTNESS 255 // Intensité max globale (0–255)
// === RÉGLAGES GÉNÉRAUX ===
#define BASELINE 360 // Niveau repos du micro (0–1023)
#define NOISE_THRESHOLD 2 // Seuil pour ignorer bruit (<1023)
#define SMOOTHING_FACTOR 0.1f // Lissage du signal (0.0 très lisse → 1.0 brut)
#define DISPLAY_SPEED 2 // Délai par frame en ms (0 = boucle max)
// === RAINBOW & SCINTILLEMENT ===
#define BG_HUE_SPEED 1 // 0–255 : vitesse du défilement HSV
#define BG_BRIGHTNESS_PCT 100 // 0–100 % : fond minimal en silence
#define BG_DEADBAND_PCT 4 // 0–100 % : zone morte pour le fond
#define SPARKLE_PCT 15 // 0–100 % : max LEDs scintillantes
#define FADE_RATE 100 // 0–255 : vitesse de fondu des étoiles
#define MIN_MAX_AMPLITUDE 3 // 0–1023 : seuil mini sensible
#define MAX_MAX_AMPLITUDE 340 // 0–1023 : seuil maxi moins sensible
// === VU-METER ===
#define PEAK_LED_DIFF 7 // 1–NUM_LEDS : LEDs en plus pour pic
#define LED_FADE_SPEED 2 // 1–255 : vitesse d’extinction
// === PORTUGAL ===
#define PORTUGAL_CYCLE_TIME 10000 // ms cycle rouge→vert (1000–60000)
#define PORTUGAL_BRIGHTNESS_WINDOW 5 // taille fenêtre lissage
// Palette “Portugal”
const uint8_t huePortugal[] = {96,0,32,160,8,128,64};
const uint8_t portugalSize = sizeof(huePortugal)/sizeof(huePortugal[0]);
// Couleurs {Green, Red, White}
const CRGB portColors[] = { CRGB::Green, CRGB::Red};
const uint8_t portColorCount= 2;
// === VARIABLES GLOBALES ===
CRGB leds[NUM_LEDS];
CRGB starLeds[NUM_LEDS];
float smoothedAmplitude = 0;
uint8_t bgHue = 0;
// VU-Meter
uint8_t ledLevel[NUM_LEDS];
bool isFading[NUM_LEDS];
int peakReference = 0;
uint8_t currentHue = huePortugal[0];
// Bouton anti-rebond
int buttonReading, lastButtonReading = LOW, buttonState = LOW;
unsigned long lastDebounceTime = 0;
const unsigned long debounceDelay = 50;
// Modes
#define MODE_RAINBOW 0
#define MODE_SCINTILLE 1
#define MODE_VUMETER 2
#define MODE_PORTUGAL 3
uint8_t mode = MODE_RAINBOW;
// Historique luminosité pour Portugal
uint8_t portugalBrightnessHistory[PORTUGAL_BRIGHTNESS_WINDOW];
uint8_t portugalHistoryCount = 0;
uint8_t portugalHistoryIndex = 0;
void setup() {
pinMode(BUTTON_PIN, INPUT);
FastLED.addLeds<LED_TYPE, DATA_PIN, COLOR_ORDER>(leds, NUM_LEDS);
FastLED.setBrightness(BRIGHTNESS);
for (int i = 0; i < NUM_LEDS; i++) {
starLeds[i] = CRGB::Black;
ledLevel[i] = 0;
isFading[i] = false;
}
Serial.begin(115200);
}
void loop() {
// 1) Bouton anti-rebond
buttonReading = digitalRead(BUTTON_PIN);
if (buttonReading != lastButtonReading) lastDebounceTime = millis();
if (millis() - lastDebounceTime > debounceDelay) {
if (buttonReading != buttonState && buttonReading == HIGH) {
mode = (mode + 1) % 4;
}
buttonState = buttonReading;
}
lastButtonReading = buttonReading;
// 2) Lecture micro & lissage exponentiel
int raw = analogRead(MIC_PIN);
int amp = abs(raw - BASELINE);
Serial.println(amp);
if (amp < NOISE_THRESHOLD) amp = 0;
smoothedAmplitude = (1 - SMOOTHING_FACTOR) * smoothedAmplitude
+ SMOOTHING_FACTOR * amp;
// 3) Seuil dynamique via potentiomètre
int pot = analogRead(POT_PIN);
//Serial.println(pot);
int dynamicMaxAmp = map(pot, 0, 1100, MAX_MAX_AMPLITUDE, MIN_MAX_AMPLITUDE);
dynamicMaxAmp = constrain(dynamicMaxAmp, MIN_MAX_AMPLITUDE, MAX_MAX_AMPLITUDE);
// 4) Modes d’affichage
if (mode == MODE_RAINBOW) {
// ARC-EN-CIEL + scintillements
bgHue += BG_HUE_SPEED;
int minBG = (BRIGHTNESS * BG_BRIGHTNESS_PCT + 50) / 100;
int dead = dynamicMaxAmp * BG_DEADBAND_PCT / 100;
uint8_t bgB;
if (smoothedAmplitude < dead) {
bgB = minBG;
} else {
bgB = map((int)smoothedAmplitude, dead, dynamicMaxAmp, minBG, BRIGHTNESS);
}
bgB = constrain(bgB, minBG, BRIGHTNESS);
fadeToBlackBy(starLeds, NUM_LEDS, FADE_RATE);
int maxSparkles = constrain((NUM_LEDS * SPARKLE_PCT + 50) / 100, 1, NUM_LEDS);
int sparkleCount = map((int)smoothedAmplitude, dead, dynamicMaxAmp, 0, maxSparkles);
sparkleCount = constrain(sparkleCount, 0, maxSparkles);
for (int i = 0; i < sparkleCount; i++) {
int idx = random(NUM_LEDS);
uint8_t h = huePortugal[random8() % portugalSize];
starLeds[idx] = CHSV(h, 255, BRIGHTNESS);
}
for (int i = 0; i < NUM_LEDS; i++) {
uint8_t h = bgHue + (i * 255 / NUM_LEDS);
CRGB bgc = CHSV(h, 200, bgB);
leds[i] = starLeds[i] ? starLeds[i] : bgc;
}
} else if (mode == MODE_SCINTILLE) {
// SCINTILLEMENT (fond unique) + scintillements
bgHue += BG_HUE_SPEED;
int minBG = (BRIGHTNESS * BG_BRIGHTNESS_PCT + 50) / 100;
int dead = dynamicMaxAmp * BG_DEADBAND_PCT / 100;
uint8_t bgB;
if (smoothedAmplitude < dead) {
bgB = minBG;
} else {
bgB = map((int)smoothedAmplitude, dead, dynamicMaxAmp, minBG, BRIGHTNESS);
}
bgB = constrain(bgB, minBG, BRIGHTNESS);
CRGB bgColor = CHSV(bgHue, 200, bgB);
fadeToBlackBy(starLeds, NUM_LEDS, FADE_RATE);
int maxSparkles2 = constrain((NUM_LEDS * SPARKLE_PCT + 50) / 100, 1, NUM_LEDS);
int sparkleCount2 = map((int)smoothedAmplitude, dead, dynamicMaxAmp, 0, maxSparkles2);
sparkleCount2 = constrain(sparkleCount2, 0, maxSparkles2);
for (int i = 0; i < sparkleCount2; i++) {
int idx = random(NUM_LEDS);
uint8_t h = huePortugal[random8() % portugalSize];
starLeds[idx] = CHSV(h, 255, BRIGHTNESS);
}
for (int i = 0; i < NUM_LEDS; i++) {
leds[i] = starLeds[i] ? starLeds[i] : bgColor;
}
} else if (mode == MODE_VUMETER) {
// VU-METER
int level = map(smoothedAmplitude, 0, dynamicMaxAmp, 0, NUM_LEDS);
level = constrain(level, 0, NUM_LEDS);
if (level > peakReference + PEAK_LED_DIFF) {
currentHue = huePortugal[(++currentHue) % portugalSize];
peakReference = level;
} else if (level < peakReference) {
peakReference = level;
}
for (int i = 0; i < NUM_LEDS; i++) {
if (i < level) {
ledLevel[i] = BRIGHTNESS; isFading[i] = false;
} else {
if (!isFading[i]) isFading[i] = true;
if (ledLevel[i] > 0) ledLevel[i] = max(0, ledLevel[i] - LED_FADE_SPEED);
}
leds[i] = CHSV(currentHue, 255, ledLevel[i]);
}
} else /* MODE_PORTUGAL */ {
// PORTUGAL : fond cyclique blanc→rouge→vert + luminosité lissée
// a) Fond cyclique
unsigned long t = millis() % PORTUGAL_CYCLE_TIME;
unsigned long segment = PORTUGAL_CYCLE_TIME / portColorCount;
uint8_t phase = t / segment;
uint8_t blendAmt = (uint8_t)((t % segment) * 255 / segment);
CRGB orderCols[2] = {portColors[1], portColors[0]};
CRGB rawBg = blend(
orderCols[phase],
orderCols[(phase + 1) % 2],
blendAmt
);
// b) Luminosité brute mappée sur le son
int dead = dynamicMaxAmp * BG_DEADBAND_PCT / 100;
int minBG = (BRIGHTNESS * BG_BRIGHTNESS_PCT + 50) / 100;
uint8_t rawB = (smoothedAmplitude < dead)
? minBG
: map((int)smoothedAmplitude, dead, dynamicMaxAmp, minBG, BRIGHTNESS);
rawB = constrain(rawB, minBG, BRIGHTNESS);
// d) Lissage par moyenne mobile
portugalBrightnessHistory[portugalHistoryIndex] = rawB;
portugalHistoryIndex = (portugalHistoryIndex + 1) % PORTUGAL_BRIGHTNESS_WINDOW;
if (portugalHistoryCount < PORTUGAL_BRIGHTNESS_WINDOW) portugalHistoryCount++;
uint32_t sum = 0;
for (int i = 0; i < portugalHistoryCount; i++) sum += portugalBrightnessHistory[i];
uint8_t smoothB = sum / portugalHistoryCount;
// e) Application et affichage du fond lissé
CRGB bgColor = rawBg;
bgColor.nscale8_video(smoothB);
fill_solid(leds, NUM_LEDS, bgColor);
}
// Affichage final
FastLED.show();
delay(DISPLAY_SPEED);
}
Résultat final
- Nous obtenons un boîtier en bois compact et élégant, surmonté du logo de Lisbonne en plexiglas. Le logo est éclairé par en dessous, la lumière se diffusant grâce à la transparence et à la diffraction, créant un effet visuel marquant. La réactivité au son ajoute une dimension vivante à l’objet.
- Défis rencontrés
-
Précision d’insertion du logo : ajustements fins nécessaires pour obtenir un bon emboîtement,
on a dû ajuster plusieurs fois son épaisseur.
-
Problèmes de tolérances de la machine laser : certaines parties trop fines ont cassé (ex: le petit cœur au-dessus du i).
-
Découpe partielle des lettres : séparation manuelle des points dans Inkscape pour un rendu fidèle au design voulu.
-
Réglages du micro : Trouver le micro ayant les bonnes caractéristiques puis calibration des seuils pour que les LEDs réagissent bien à la musique sans s'allumer tout le temps.
- Allumage : trouver le bon code qui rend un affichage des LED fluide et précis. Les réglages par défaut peuvent être saccadés et peu esthétiques.
- Perspectives d’amélioration
-
Ajouter une interrupteur pour allumer et éteindre la plaque.
-
Ajouter une fonctionnalité Bluetooth pour contrôler l’éclairage à distance via une app.
- Ajouter des modes d'animations des LED.
- Décorer la boite pour la rendre plus esthétique
- Usage prévu
- Ce prototype a été conçu pour être emmené pendant notre voyage de promotion à Lisbonne. Il servira de souvenir collectif, de décoration et de symbole de notre passage, et apparaîtra dans nos photos de groupe pour immortaliser ce moment unique.
- Produits existants
- https://wiki.fablab.sorbonne-universite.fr/BookStack/books/petits-projets-h6V/page/trophee-lumineux
- Le wiki n'étant pas très documenté, nous n'avons pas pu s'en inspirer pour notre projet.