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

Présentation 

Vidéo de démonstration : Plaque Lisboa

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

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

Sans titre.jpg

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


Objectifs du projet

Organisation du travail : 


Afin de bien nous organiser, nous nous sommes réparti les tâches. 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


Étapes de réalisation

1. Design du boîtier en bois

2. Préparation du logo Lisbonne

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


3. Découpe et assemblage

image.png

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.jpegCapture d’écran 2025-04-29 à 23.26.44 - Grande.jpeg

20250430_114524 - Grande.jpeg

Bandeau LED 
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. Nous avons dû trouver un microphone optimal pour notre besoin.

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 

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.

Soudures

➤ Pour des raisons de places, nous n'avons pas utilisés de breadboard. Nous avons donc soudé les pattes de certains composants tels que le bouton poussoir ci-dessus.

Alimentation

Utilisation d'un simple pile 9V branchée à l'entrée prévue dans l'Arduino UNO.

Code complet et commenté
#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 //type de LED
#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 : en secondes, vitesse du transition du fond lumineux, 
#define BG_BRIGHTNESS_PCT    100     // 0–100 % : intensité lumineuse minimale (% en fonction de BRIGHTNESS)
#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);
}

Afin de réaliser le code, nous nous sommes aidés de ChatGPT, modèle o4-mini-high. Nous avons constaté une réelle différence de performance entre ce modèle et le modèle 4o, tant sur la rapidité des réponses que sur leurs pertinences.

Exemple de prompt : "Je travaille sur un projet Arduino, avec un bandeau LED, un potentiomètre, un microphone MAX4466, un bouton poussoir. Ajoute au code que je t'ai envoyé un mode de couleur style vu-mètre au couleurs du Portugal : le 30% des premières led sont en vert, 5% des leds suivantes sont couleur or (avec 1 LED minimum), les 65% suivants sont en rouge. [le code présent ci-dessus]"


Résultat final

WhatsApp Image 2025-04-30 at 10.31.55.jpeg

  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.

Défis rencontrés


  1. Perspectives d’amélioration

Usage prévu

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

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

Revision #38
Created 31 March 2025 13:43:07 by Nguyen Olivier
Updated 24 September 2025 09:23:22 by Jiang Qiancheng