Visualiseur de spectre audio RGB 32 bandes – Projet matrice LED Arduino
Composants et fournitures
| Composant | Quantité |
|---|---|
| Arduino Nano R3 | 1 |
| Matrice LED RGB WS2812B 8x32 | 1 |
| Résistance 4,75 kΩ | 3 |
| Résistance 100 kΩ | 2 |
| Résistance 10 kΩ | 1 |
| Résistance traversante 390 Ω | 1 |
| Condensateur 100 nF | 2 |
| Condensateur 1000 µF | 1 |
| Bouton poussoir 12 mm (SparkFun) | 1 |
| Plaque de prototype multi-trous 4x6 cm | 1 |
Outils et machines nécessaires
- Fer à souder (générique)
- Câble splitter Jack 3,5 mm
À propos de ce projet
Le projet
Ce projet réalise un visualiseur de spectre de fréquence audio (musique) RGB 32 bandes utilisant un Arduino Nano et une matrice LED RGB 8x32 WS2812B.
Le projet original qui a inspiré celui-ci
Un grand merci à Shajeeb, auteur du projet original basé sur une matrice LED MAX72xx. J'ai seulement modifié la partie pilotage de la matrice pour l'adapter à la matrice RGB WS2812B.
Lien vers le projet original : 32-Band Audio Spectrum Visualizer Analyzer
La matrice LED RGB WS2812B
Avec une matrice LED RGB basée sur des LED SMD 5050 haute luminosité, une alimentation externe est nécessaire car la matrice peut consommer plus de 10 mA par LED. Avec toutes les LED allumées à luminosité max, la consommation peut dépasser 2,5 Ampères.
Pour cette raison, j'ai inséré une diode en série sur le +5V pour pouvoir alimenter l'Arduino en mode autonome (sans câble USB) et éviter que l'Arduino ne devienne la source d'alimentation de la matrice RGB, ce qui surchargerait ses circuits internes.
En plus de la diode d'entrée, pour protéger l'entrée de données de la matrice contre d'éventuels pics de tension, j'ai ajouté une résistance de 390 Ω en série entre la broche D6 de l'Arduino et l'entrée Data, et un condensateur 1000 µF 12V pour stabiliser la tension d'alimentation de l'Arduino.
Assemblage matériel
Comme visible sur la photo principale, j'ai réalisé le premier prototype sur une plaque multi-trous 4x6 cm en utilisant deux prises RCA audio (soudées directement sur la plaque) qui peuvent être remplacées par une prise Jack 3,5 mm femelle. Pour éviter le ronflement, il est important de faire les connexions entre la source et l'entrée audio du circuit avec un câble blindé. Autre conseil : gardez la connexion entre l'Arduino et la matrice LED la plus courte possible.
Le code
Tout le logiciel repose sur l'excellent travail de l'auteur de la procédure d'échantillonnage via la bibliothèque FFT et la réalisation finale de Shajeeb.
J'ai ajouté deux fonctions :
- GetLedFromMatrix(...) : mappe la matrice en lignes et colonnes pour adresser chacune des 256 LED via coordonnées ligne/colonne.
- SetColumn(...) : allume les LED de chaque colonne selon la valeur de crête obtenue par la numérisation audio (valeurs 0-7) et selon les couleurs prédéfinies dans un tableau bidimensionnel. Vous pouvez modifier les valeurs et les couleurs à volonté. Pour simplifier, j'ai utilisé une sous-routine
Wheel()(issue d'une démo de la bibliothèque Adafruit Neopixel) qui, à partir d'une valeur 0-255, renvoie une valeur 32 bits non signée à passer directement àsetPixelColor. Vous pouvez jouer avec, en gardant à l'esprit les limitations mémoire de l'Arduino (éviter les variables 32 bits pour stocker les couleurs RVB quand c'est possible).
Égalisation audio
Comme j'ai testé avec l'audio de la carte son intégrée à la carte mère du PC, pour améliorer la réponse en fréquence, j'ai ajouté un tableau de 32 octets qui constitue une courbe d'égalisation pour atténuer les basses et renforcer les aigus. Si vous n'en avez pas besoin, mettez simplement EQ_ON à false ou modifiez le niveau d'atténuation en changeant les 32 valeurs du tableau eq[32]. Une valeur de 100 laisse l'amplitude inchangée, une valeur inférieure atténue, une valeur supérieure accentue la bande de fréquence.
Luminosité des LED
La luminosité de la matrice est fixée dans le code à 32 (constante BRIGHTNESS). La valeur max théorique des WS2812B est 255, mais déjà au-delà de 100, la lumière blanche vire au jaune pâle. Il faudrait probablement alimenter la matrice par les deux fils centraux rouge et noir au lieu du connecteur droit.
J'expérimente encore...
Enfin, avec une luminosité max de 64, une alimentation 1A suffit probablement, sinon 2A est indispensable.
Mise à jour future
Je travaille sur une nouvelle version utilisant la bibliothèque OpenMusicLabs FHT qui s'avère plusieurs fois plus rapide que la FFT Arduino.
Restez connectés ! :)
Pardonnez mon anglais approximatif, j'ai utilisé Google Translate.
Code
Spectrum_Analyzer_WS2812B.ino
/* Copyright (c) 2019 Shajeeb TM
* Permission accordée gratuitement... (licence MIT)
* Version WS2812B par Janux */
#include <arduinoFFT.h>
#include <SPI.h>
#include <Adafruit_NeoPixel.h>
#define SAMPLES 64 // Doit être puissance de 2
#define xres 32 // Nombre total de colonnes, <= SAMPLES/2
#define yres 8 // Nombre total de lignes
#define ledPIN 6 // Broche contrôle matrice LED
#define NUM_LEDS (xres * yres)
#define BRIGHTNESS 32
#define buttonPin 5 // Broche bouton pour changer mode couleur
byte yvalue, displaycolumn, displayvalue;
int peaks[xres];
byte state = HIGH;
byte previousState = LOW;
byte displaycolor = 0;
double vReal[SAMPLES];
double vImag[SAMPLES];
byte data_avgs[xres];
arduinoFFT FFT = arduinoFFT();
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 100;
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(NUM_LEDS, ledPIN, NEO_GRB + NEO_KHZ800);
// Filtre EQ pour atténuer basses et améliorer aigus
// Utile sur carte son PC qui a souvent trop de basses et peu d'aigus
bool EQ_ON = true;
byte eq[32] = {50, 55, 60, 70, 75, 80, 85, 95, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 115, 125, 140, 160, 185, 200, 225, 255 };
// Couleurs pour chaque LED, utilisées dans setColumn, 0 = couleur personnalisée
// Calculées par la fonction Wheel ci-dessous
byte colors[][8] = {
{170, 160, 150, 140, 130, 120, 1, 1},
{1, 5, 10, 15, 20, 25, 90, 90},
{90, 85, 80, 75, 70, 65, 1, 1},
{90, 90, 90, 30, 30, 30, 1, 1},
{170, 160, 150, 140, 130, 120, 0, 0},
{170, 160, 150, 140, 130, 120, 1, 1},
{170, 160, 150, 140, 130, 120, 1, 1}
};
void setup() {
pixel.begin();
pixel.setBrightness(BRIGHTNESS);
// Configuration ADC pour échantillonnage rapide
ADCSRA = 0b11100101; // Mode free-running, prédiviseur 32
ADMUX = 0b00000000; // Pin A0, référence externe
}
void loop() {
// Échantillonnage
for (int i = 0; i < SAMPLES; i++) {
while (!(ADCSRA & 0x10)); // Attendre conversion ADC (bit ADIF)
ADCSRA = 0b11110101; // Effacer ADIF pour conversion suivante
int value = ADC - 512; // Lire ADC et soustraire offset DC
vReal[i] = value / 8; // Compresser et copier
vImag[i] = 0;
}
FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
// Réorganiser résultats FFT pour correspondre aux colonnes (xres)
int step = (SAMPLES / 2) / xres;
int c = 0;
for (int i = 0; i < (SAMPLES / 2); i += step) {
data_avgs[c] = 0;
for (int k = 0; k < step; k++) {
data_avgs[c] += vReal[i + k];
}
data_avgs[c] /= step;
c++;
}
// Affichage selon valeurs mesurées
for (int i = 0; i < xres; i++) {
if (EQ_ON) data_avgs[i] = data_avgs[i] * (float)eq[i] / 100;
data_avgs[i] = constrain(data_avgs[i], 0, 80);
data_avgs[i] = map(data_avgs[i], 0, 80, 0, yres);
yvalue = data_avgs[i];
peaks[i]--;
if (yvalue > peaks[i]) peaks[i] = yvalue;
yvalue = peaks[i];
displaycolumn = i;
displayvalue = yvalue;
setColumn(displaycolumn, displayvalue);
}
pixel.show();
displaycolorChange();
}
// Allume les LED de la colonne x selon la valeur y
void setColumn(byte x, byte y) {
byte led, i;
for (i = 0; i < yres; i++) {
led = GetLedFromMatrix(x, i);
if (peaks[x] > i) {
switch (displaycolor) {
case 4: // 0 dans tableau = couleur personnalisée pour pics
if (colors[displaycolor][i] > 0) {
pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
} else {
pixel.setPixelColor(led, 255, 255, 255); // Blanc pour pics
}
break;
case 5: // Couleur par colonne
pixel.setPixelColor(led, Wheel(x * 16));
break;
case 6: // Couleur par ligne
pixel.setPixelColor(led, Wheel(i * 36));
break;
default: // Modes 0-3, couleurs du tableau
pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
}
} else {
pixel.setPixelColor(led, 0);
}
}
}
// Calcule numéro de LED par coordonnées x,y
// Valide pour WS2812B disposition serpentine horizontale
// LED 0 en bas à droite (connecteur entrée à droite)
// x=0-31, y=0-7, retourne 0-255
byte GetLedFromMatrix(byte x, byte y) {
x = xres - x - 1;
if (x & 0x01) { // Colonnes impaires : ordre inverse
return ((x + 1) * yres - y - 1);
} else { // Colonnes paires : ordre normal
return ((x + 1) * yres - yres + y);
}
}
void displaycolorChange() {
int reading = digitalRead(buttonPin);
if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) {
displaycolor++;
if (displaycolor > 6) displaycolor = 0;
lastDebounceTime = millis();
}
previousState = reading;
}
// Utilitaire Adafruit Neopixel : 0-255 -> valeur couleur (transition R-G-B-R)
unsigned long Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if (WheelPos < 85) {
return pixel.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if (WheelPos < 170) {
WheelPos -= 85;
return pixel.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return pixel.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
Pièces personnalisées et boîtiers
Fichiers 3D : arduino_rgb_audio_analyzer_lX4Dv4Pta2.zip
Schémas
Processus de fabrication
- Analyseur de spectre audio Arduino 32 bandes – Affichage linéaire et pseudo-logarithmique
- Alerte Soif Plante Intelligente – Capteur DIY pour une Hydratation Optimale
- Horloge à Mots Italienne avec Arduino Nano, NeoPixel, LCD et RTC
- Compteur kWh Sigfox fiable avec Arduino MKR Fox 1200
- Moniteur de température Bluetooth avec Arduino Nano
- Serrure à gestes sans contact : Accès sécurisé avec APDS-9960 et Arduino
- Kit Compagnon Arduino Nano – Composants et Outils Essentiels pour l'Électronique DIY
- Circuit d'entrée analogique isolée pour Arduino : Protection contre les parasites industriels
- Test Précis de Temps de Réaction avec Arduino UNO