Visualiseur Spectre Audio FHT – Construisez un Affichage LED Audio Époustouflant
Composants et fournitures
| Composant | Quantité | Notes |
|---|---|---|
| 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Ω | 2 | |
| Résistance traversante 390 Ω | 1 | |
| Diode 1N4007 – Haute tension, fort courant | 1 | Une 1N4004 convient aussi |
| Condensateur 100 nF | 2 | |
| Condensateur 1000 µF | 1 | |
| Bouton poussoir 12 mm (SparkFun) | 2 | Ou deux boutons de panneau pour boîtier |
| Carte prototype générique 4x6 cm | 1 | |
| Prise RCA Stéréo Femelle PCB | 1 |
Outils et machines nécessaires
- Fer à souder (générique)
- Câble Jack 3,5 mm vers RCA Stéréo
À propos de ce projet
La bibliothèque Arduino FHT
La FHT (Fast Hartley Transform) fait exactement le même travail que la FFT (Fast Fourier Transform) plus connue, mais contrairement à celle-ci, elle n'utilise que des données réelles, tandis que la FFT travaille sur des données complexes. La FHT utilise donc moitié moins de puissance de traitement et moitié moins de mémoire système.
Cette bibliothèque utilise un seul tableau pour l'échantillonnage au lieu de deux (un pour les valeurs réelles, un pour les imaginaires, utilisé par la bibliothèque FFT). Elle simplifie aussi les calculs mathématiques complexes en évitant les boucles avec calculs logarithmiques en virgule flottante complexes, en utilisant plutôt des tables numériques.
L'inconvénient est qu'elle renvoie toujours la moitié des valeurs de la taille du tableau utilisé pour l'échantillonnage, ce qui entraîne une certaine perte de résolution et de précision aux deux extrémités de la gamme audio. Cependant, dans notre cas, nous ne développons pas un instrument de mesure mais un simple gadget : nous n'avons pas besoin d'une précision extrême, juste d'allumer quelques LED au rythme de la musique.
Bien sûr, ce n'est pas l'endroit pour discuter du fonctionnement de la bibliothèque FHT. Pour approfondir, consultez le site Open Music Lab où vous trouverez toute la documentation nécessaire : explications des astuces utilisées, détails des fonctions et exemples génériques pour Arduino.
La bibliothèque FHT est si rapide qu'il a fallu insérer un délai dans le code pour réduire le scintillement. Vous remarquerez immédiatement le résultat final : l'affichage est bien plus rapide et réactif que la version utilisant la bibliothèque FFT qui semblait souvent en retard sur la musique. La moindre utilisation des ressources système a permis d'ajouter des lignes de code pour gérer des fonctions supplémentaires.
La nouvelle version
Le projet est identique à la version précédente. Ceux qui ont déjà réalisé l'ancienne version n'ont qu'à ajouter un bouton et une résistance de 10 kΩ et mettre à jour le code.
En plus de l'implémentation de la bibliothèque FHT, cette version ajoute :
- Un bouton supplémentaire pour le réglage de la luminosité ;
- La sauvegarde des réglages de couleur et luminosité dans l'EEPROM pour les recharger au démarrage suivant ;
- L'affichage du niveau de couleur et luminosité quand les boutons sont pressés et au démarrage (désactivable).
Code
Version Code FHT Arduino
/* Copyright (c) 2020 Janux
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Basé sur un projet original pour matrice LED MAX72xx et bibliothèque FFT par Shajeeb.
Section configuration basée sur le travail de Ragnar Ranøyen Homb de Norwegian Creations. */
#define LIN_OUT 1 // Sortie magnitude linéaire FHT
#define FHT_N 128 // Échantillons FHT, doit être puissance de 2
#include <FHT.h>
#define xres 32 // Colonnes totales affichage, doit être <= ÉCHANTILLONS/2
#define yres 8 // Lignes totales affichage
#define ledPIN 6 // Broche sortie contrôle LEDs
#define NUM_LEDS (xres * yres) // Total LEDs dans matrice
#include <Adafruit_NeoPixel.h>
#define colorPIN 5 // Broche changement couleur LED
#define brightnessPIN 10 // Broche changement luminosité
byte displaycolor = 0; // Valeur couleur par défaut
byte brightness = 1; // Niveau luminosité par défaut
#include <EEPROM.h>
#define CONFIG_START 32 // Adresse début mémoire
#define CONFIG_VERSION "VER01" // Version config
typedef struct {
char version[6];
byte displaycolor;
byte brightness;
} configuration_type;
configuration_type CONFIGURATION = { CONFIG_VERSION, displaycolor, brightness };
byte yvalue;
int peaks[xres];
byte state = HIGH;
byte previousState = LOW;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 100;
byte data_avgs[xres];
Adafruit_NeoPixel pixel = Adafruit_NeoPixel(NUM_LEDS, ledPIN, NEO_GRB + NEO_KHZ800);
// Filtre EQ
byte eq[32] = {
60, 65, 70, 75, 80, 85, 90, 95, 100, 100, 100, 100, 100, 100, 100, 100,
100, 100, 100, 100, 100, 100, 100, 100, 115, 125, 140, 160, 185, 200, 200, 200
};
bool EQ_ON = true; // false pour désactiver l'EQ
// 5 jeux de couleurs pour LEDs, 0 = couleur personnalisée
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, 110, 0}
};
// Caractères pour affichage réglages
byte charBitmap[] = {
0x1C, 0x10, 0x10, 0x10, 0x10, 0x1C, 0x08, 0x18, 0x08, 0x08, 0x08, 0x1C,
0x0C, 0x12, 0x04, 0x08, 0x10, 0x1E, 0x0C, 0x12, 0x02, 0x06, 0x12, 0x0C,
0x10, 0x10, 0x10, 0x14, 0x1E, 0x04, 0x1E, 0x10, 0x1E, 0x02, 0x12, 0x0C,
0x1E, 0x10, 0x10, 0x1E, 0x12, 0x1E, 0x1E, 0x02, 0x04, 0x08, 0x08, 0x08,
0x0C, 0x12, 0x0C, 0x12, 0x12, 0x0C, 0x1C, 0x12, 0x1C, 0x12, 0x12, 0x1C
};
void setup() {
pixel.begin();
ADCSRA = 0b11100101; // ADC mode free-running, prédiviseur 32 (0xe5)
ADMUX = 0b00000000; // Broche A0, référence tension externe
if (loadConfig()) {
displaycolor = CONFIGURATION.displaycolor;
brightness = CONFIGURATION.brightness;
}
pixel.setBrightness(brightness * 24 + 8);
showSettings(3, true); // true = afficher au démarrage
}
void loop() {
while (1) {
Sampling();
RearrangeFHT();
SendToDisplay();
colorChange();
brightnessChange();
delay(10); // Réduit scintillement (FHT trop rapide !)
}
}
void Sampling() {
for (int i = 0; i < FHT_N; i++) {
while (!(ADCSRA & 0x10)); // Attend fin conversion ADC (bit ADIF)
ADCSRA = 0b11110101; // Efface bit ADIF pour conversion suivante (0xf5)
byte m = ADCL;
byte j = ADCH;
int value = (j << 8) | m;
value -= 0x0200;
value <<= 6;
fht_input[i] = value / 8;
}
fht_window();
fht_reorder();
fht_run();
fht_mag_lin();
}
void RearrangeFHT() {
int step = (FHT_N / 2) / xres;
int c = 0;
for (int i = 0; i < (FHT_N / 2); i += step) {
data_avgs[c] = 0;
for (int k = 0; k < step; k++) {
data_avgs[c] += fht_lin_out[i + k];
}
data_avgs[c] /= step;
c++;
}
}
void SendToDisplay() {
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];
setColumn(i, yvalue);
}
pixel.show();
}
void setColumn(byte x, byte y) {
int led, i;
for (i = 0; i < yres; i++) {
led = GetLedFromMatrix(x, i);
if (peaks[x] > i) {
switch (displaycolor) {
case 4:
if (colors[displaycolor][i] == 0) {
pixel.setPixelColor(led, 255, 255, 255);
} else {
pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
}
break;
case 5: // Couleur par colonne
pixel.setPixelColor(led, Wheel(x * 16));
break;
case 6: // Couleur par ligne
pixel.setPixelColor(led, Wheel(i * y * 3));
break;
case 7: // Drapeaux pays :D
// Italie
// if (x < 11) pixel.setPixelColor(led, 0, 255, 0);
// if (x > 10 && x < 21) pixel.setPixelColor(led, 255, 255, 255);
// if (x > 20) pixel.setPixelColor(led, 255, 0, 0);
// Stars and Stripes
if (i < yres - 2) {
if (x & 0x01) pixel.setPixelColor(led, 0, 0, 255);
else pixel.setPixelColor(led, 255, 0, 0);
} else {
pixel.setPixelColor(led, 255, 255, 255);
}
break;
default:
pixel.setPixelColor(led, Wheel(colors[displaycolor][i]));
}
} else {
pixel.setPixelColor(led, pixel.Color(0, 0, 0));
}
}
}
int GetLedFromMatrix(byte x, byte y) {
int led;
x = xres - x - 1;
if (x & 0x01) {
led = ((x + 1) * yres - y - 1);
} else {
led = ((x + 1) * yres - yres + y);
}
return constrain(led, 0, NUM_LEDS);
}
void colorChange() {
int reading = digitalRead(colorPIN);
if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) {
displaycolor++;
if (displaycolor > 7) displaycolor = 0;
showSettings(1, true);
saveConfig();
lastDebounceTime = millis();
}
previousState = reading;
}
void brightnessChange() {
int reading = digitalRead(brightnessPIN);
if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) {
brightness++;
if (brightness > 7) brightness = 0;
pixel.setBrightness(brightness * 24 + 8);
showSettings(2, true);
saveConfig();
lastDebounceTime = millis();
}
previousState = reading;
}
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);
}
int loadConfig() {
if (EEPROM.read(CONFIG_START + 0) == CONFIG_VERSION[0] &&
EEPROM.read(CONFIG_START + 1) == CONFIG_VERSION[1] &&
EEPROM.read(CONFIG_START + 2) == CONFIG_VERSION[2] &&
EEPROM.read(CONFIG_START + 3) == CONFIG_VERSION[3] &&
EEPROM.read(CONFIG_START + 4) == CONFIG_VERSION[4]) {
for (unsigned int i = 0; i < sizeof(CONFIGURATION); i++) {
*((char*)&CONFIGURATION + i) = EEPROM.read(CONFIG_START + i);
}
return 1;
}
return 0;
}
void saveConfig() {
CONFIGURATION.displaycolor = displaycolor;
CONFIGURATION.brightness = brightness;
for (unsigned int i = 0; i < sizeof(CONFIGURATION); i++)
EEPROM.write(CONFIG_START + i, *((char*)&CONFIGURATION + i));
}
void showSettings(byte num, bool show) {
if (show) {
pixel.clear();
if (num == 1 || num == 3) {
drawChar(0, 0);
drawChar(displaycolor + 1, 5);
}
if (num == 2 || num == 3) {
drawChar(9, xres - 9);
drawChar(brightness + 1, xres - 4);
}
delay(1000);
pixel.clear();
}
}
void drawChar(byte val, byte pos) {
for (int x = 4; x >= 0; x--) {
for (int y = 5; y >= 0; y--) {
if ((charBitmap[val * 6 + 5 - y] >> x) & 0x01) {
pixel.setPixelColor(GetLedFromMatrix(4 - x + pos, y + 1), Wheel((pos > 10) * 170));
pixel.show();
}
}
}
}
// by Janux®, Dernière version le 28/06/2020.
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