Skip to main content

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 en lui téléchargeant notre logo créé sur CANVA (voir plus bas).

Voici le prompt:

"On souhaiterait faire un panneau lumineux "Lisboa"pour notre voyage de master. Le panneau sera en plexiglass et la lumière viendra des leds en bas par diffraction. En haut le relief sera courbé par les lettres et en bas plat pour pouvoir accrocher les leds. Pour que tout se voit bien et ne soit pas transparent on fera une gravure à l’intérieur des lettres. En bas on grave également en petit "mi6" et "2025", pour identifier notre master. On aimerait également créer un socle en bas pour le panneau afin de cacher les leds et les fils dont on aura besoin. Crée une image de ce projet. Tu trouveras la police de notre panneau en pj. On aimerait colorier la boîte en bois qui est en bas aux couleurs de Lisbonne un peu fun- des fleurs, palmiers, monuments. Mais que le tout soit organique. Peux-tu générer cette image pour qu’on puisse le visualiser. Une autre modification est qu’on aimerait que le "i" de Lisboa soit un petit cœur attaché à la barre du i pour qu’on puisse le découper au laser sans que ça se détache. "

Cette image a été notre inspiration tout le long du projet. 

Sans titre.jpg


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

Organisation du travail : 


Afin de bien nous organiser, nous nous sommes réparti les tâches.

- Olivier, ayant suivi une mineure en électronique, s’est chargé de toute la partie codage et Arduino. 

-Polina, Fatmagül et Qiancheng ont pris en main le design ainsi que la découpe de la boîte et du logo. Un groupe WhatsApp a été créé pour suivre l’avancement du projet et faciliter la communication. Nous avons veillé à remplir la page wiki au fur et à mesure des séances. La page wiki et le prototype final sont le fruit d’un travail collaboratif.


Outils & Logiciels utilisés

  • Arduino Uno

  • Bande de LED (WS2812B)

  • Microphone sonore compatible Arduino (module MAX4466)

  • Plaque de plexiglas (PMMA extrudé 6mm)

  • WhatsApp Image 2025-04-23 à 12.54.24_a05ff31c.jpg

  • 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.

  • WhatsApp Image 2025-04-28 à 17.08.55_86edd65d.jpg

  • Mesures: 
    • 33cm la longueur
    • 7 cm la hauteur
    • 7cm la largeur
    • Dimensions de l'ouverture pour le logo : 5,7 mm de largeur et 29 cm de longueur
    • WhatsApp Image 2025-04-23 à 12.54.24_f1549abf.jpg

  • image.pngWhatsApp Image 2025-04-22 à 20.37.20_5c775028.jpg

2. Préparation du logo Lisbonne

    • Création graphique sur Canva.

    image.png

    • 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.CleanShot 2025-04-28 at 10.34.35@2x.png

    • 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.

                   CleanShot 2025-04-28 at 23.20.02@2x.png


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.

image.png

  • Collage de la bande LED sous la zone du logo.

image.png

3. Circuit et Code Arduino

  1. 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.
  2. Objectif: Synchroniser l'affichage lumineux des LEDs avec le son ambiant (musique) capté par un micro.
  3. Étapes et apprentissages clés :
Circuit 

Schéma réalisé sur tinkerCad

Capture d’écran 2025-04-29 à 23.28.08 - Grande.jpeg

Capture d’écran 2025-04-29 à 23.26.44 - Grande.jpeg


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

Afin de réaliser le code, nous nous sommes aidés de ChatGPT. 

#include <FastLED.h> //bibliothèque pour controler le bandeau led

// === 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   //GRB ou RGB, dépend du type de bandeau LED
#define BRIGHTNESS           255   // Intensité max globale (0–255)

// === RÉGLAGES GÉNÉRAUX ===
#define BASELINE             360    // Niveau repos du micro (0–1023), faire des tests pour savoir 
#define NOISE_THRESHOLD      2      // Seuil pour ignorer le bruit lié à la détection du microphone (<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), temps entre 2 executions du code

// === mode 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

// === mode 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

// === mode 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

image.png

  1. 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.

  1. 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.

  1. 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 

  1. Usage prévu
  2. 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.

  1. Produits existants
  2. https://wiki.fablab.sorbonne-universite.fr/BookStack/books/petits-projets-h6V/page/trophee-lumineux
  3. Le wiki n'étant pas très documenté, nous n'avons pas pu s'en inspirer pour notre projet.
  4. trophee.jpg