Visualiseur de spectre sonore Arduino Nano 33 BLE : affichage audio temps réel
Composants et fournitures
| Arduino Nano 33 BLE | ×1 |
| Amplificateur microphone électret MAX9814 | ×1 |
| Écran OLED I2C 0,91" 128×32 pixels | ×1 |
| Condensateur 4,7 nF (selon filtre RC passe-bas et fréquence de coupure choisie) | ×1 |
| Condensateur 470 µF | ×1 |
| Résistance 2 kΩ (selon filtre RC passe-bas et fréquence de coupure choisie) | ×1 |
Applications et services en ligne
- Arduino IDE
À propos du projet
Introduction
Observez un graphique à barres réagir à la musique et aux sons sur un petit écran OLED. Les basses fréquences à gauche, les aigus à droite.
Construction
Connexions
- Connecter le gain du microphone (GAIN) selon la sensibilité souhaitée :
- VCC → GAIN : 40 dB, faible sensibilité
- GND → GAIN : 50 dB, sensibilité moyenne
- GAIN flottant : 60 dB, haute sensibilité
- Broche analogique A0 → sortie (OUT) de l'ampli micro
- 3,3 V, GND, SDA (ou A4) et SCL (ou A5) → écran I2C SSD1306
- Condensateur 470 µF entre VCC et GND sur le bus d'alimentation

Bruit de basse fréquence ?
Le bruit provient des appareils électriques à proximité fonctionnant sur le secteur (50 Hz ou 60 Hz selon les pays). Solution simple : cage de Faraday. Mettre le circuit dans une boîte métallique reliée à la masse. Bien mettre le circuit à la terre.


Avec analogRead() standard sur Arduino Nano 33 BLE, on obtient ~35 000 échantillons/seconde. Le théorème de Nyquist limite donc à 17,5 kHz max. Mais comme on doit lire l'horloge et faire des comparaisons dans une boucle d'attente active, on restera prudent avec une limite supérieure de 8 kHz. Un filtre RC passe-bas simple évite les fréquences au-delà.


R = 180 Ω, C = 0,1 µF → fréquence de coupure fc = 8842 Hz
Ici : R = 2 kΩ, C = 4,7 nF → fc = 17 000 Hz

Calcul approximatif du taux d'échantillonnage maximal
analogRead() standard est trop lent pour l'audio. Estimons le maximum.
Petit programme de test :
/* Calcul approximatif du taux d'échantillonnage max avec AnalogRead */
#define ANALOG_PIN A0
#define SAMPLES 1000000.0
int newTime;
int analogValue;
void setup() {
Serial.begin(9600);
Serial.println("Echantillonnage... patientez.");
}
void loop() {
newTime = micros();
for (int i = 0; i < (int)SAMPLES; i++) {
analogValue = analogRead(ANALOG_PIN);
}
float elapsedTimePerSampleUs = (micros() - newTime) / SAMPLES;
Serial.print("Temps par échantillon (µs): ");
Serial.println(elapsedTimePerSampleUs);
Serial.print("Fréq. d'échantillonnage max (Hz): ");
Serial.println(pow(10,6) / elapsedTimePerSampleUs);
}

Résultat : ~35 000 échantillons/seconde. Nyquist → limite ~17 kHz. Avec la boucle d'attente active, on vise prudemment 8 kHz.
// Prise d'échantillons
for (int i = 0; i < SAMPLES; i++) {
unsigned long newTime = micros();
int value = analogRead(ANALOG_PIN);
vReal[i] = value;
vImag[i] = 0;
while (micros() < (newTime + sampling_period_us)) {
yield();
}
}
Interface utilisateur

L'écran est divisé en 11 zones de fréquence avec une échelle logarithmique, similaire à la perception auditive humaine.
L'oreille humaine perçoit de ~20 Hz à 20 kHz (nourrissons légèrement au-delà, adultes ~15-17 kHz). On utilise les zones 3 à 13 (11 zones).

Chaque barre représente :
- Valeur maximale dans la bande → traits espacés
- Valeur moyenne dans la bande → rectangle plein
Valeurs affichées en décibels avec une référence choisie heuristiquement.
Comment ça marche ?
Les échantillons audio sont convertis en spectre de fréquences par transformée de Fourier rapide (FFT). Le spectre est divisé et groupé en 14 zones, dont 11 sont représentées en dB.
À propos du module microphone



Microphone électret
L'électret est un diélectrique quasi-permanemment chargé. Fabriqué en chauffant un matériau céramique, le plaçant dans un champ magnétique, puis le laissant refroidir dans ce champ. C'est l'équivalent électrostatique d'un aimant permanent. Dans un micro électret, une tranche de ce matériau sert de diélectrique d'un condensateur dont le diaphragme forme une plaque. La pression sonore déplace le diaphragme, variant la capacitance. Grâce à la charge fixe du diélectrique, la tension aux bornes varie aussi. Le condensateur électret est connecté à l'entrée d'un amplificateur FET intégré. Petits, excellente sensibilité, large réponse en fréquence, coût très faible.
Amplificateur microphone MAX9814
Amplificateur micro avec contrôle de gain automatique (AGC), biais micro à faible bruit. Quelques options configurables sur le breakout :
- 3 réglages de gain : 40 dB, 50 dB, 60 dB (défaut). Jumper GAIN vers VCC = 40 dB, vers GND = 50 dB, flottant = 60 dB.
- Biais micro bas bruit interne : 1,25 V, 2 Vpp. Sortie ~2 Vpp max sur biais DC 1,25 V → compatible convertisseurs A/N jusqu'à 3,3 V.
- Ratio attaque/relâchement modifiable : défaut 1:4000, ou 1:2000, 1:500.
Pour connexion directe sur entrée Ligne : condensateur de blocage/découplage en série (>1 µF, selon système, essayer 33-100 µF). Circuit de couplage : laisse passer l'alternatif, bloque le continu. En audio, évite que le composante DC ne distorde le signal. Efficacité dépend de paramètres dépendants de la fréquence : perte d'insertion, résistance série équivalente, fréquence de résonance série.
Alimentation : 2,7 V à 5,5 V
Sensibilité
Rapport tension sortie (analogique) ou valeur numérique (digital) sur pression d'entrée. Spécification clé. Permet de déterminer l'amplitude du signal de sortie pour une entrée acoustique connue.
La sensibilité est la réponse électrique à une entrée acoustique de référence standard : onde sinusoïdale 1 kHz à 94 dB SPL (1 Pa). Sensibilité plus élevée = niveau de sortie plus fort pour entrée acoustique fixe. En dB, généralement négatif : sensibilité plus élevée = valeur absolue plus petite.
Pour micros analogiques : spécifiée en dBV (référence 1,0 V eff).
Niveau de référence et réponse en fréquence
La réponse en fréquence décrit le niveau de sortie sur le spectre. Limites haute/basse = points où la réponse est 3 dB sous le niveau de référence à 1 kHz. Niveau de référence à 1 kHz normalisé à 0 dB.
Écran OLED I2C 0,91" 128×32 pixels
Petit écran ~1" de diagonale. 128×32 pixels OLED blancs individuels, pilotés par puce contrôleur. Pas de rétroéclairage → consommation réduite.
Modèle 4 broches, communication I2C. Pas de broche RESET. Connexion aux broches I2C de l'Arduino Nano : SDA (A4), SCL (A5).
Bibliothèques nécessaires : adafruit_SSD1306.h et adafruit_GFX.h. Instructions d'installation ci-dessous.
Utilisation des bibliothèques Adafruit. Pensez à acheter chez eux pour les soutenir.



Prochaines étapes
L'Arduino Nano 33 BLE peut faire mieux pour les lectures analogiques. Prochaine version : FFT et entrée analogique optimisées pour le nRF52840.
Code
Visualiseur de spectre sonore pour Arduino Nano 33 BLE (Arduino)
Utilise : arduinoFFT, Adafruit_GFX, Adafruit_SSD1306
/* Sound Spectrum Visualizer for Arduino Nano 33 BLE
Visualiseur sonore basé sur Arduino
@author Enrique Albertos
Matériel requis:
- Arduino ou carte compatible
- Écran OLED SSD1306 I2C 128x32
- Amplificateur microphone électret MAX9814
- Optionnel: batterie pour usage portable (sinon alimentation USB)
Logiciel requis:
- arduinoFFT https://github.com/kosme/arduinoFFT
- Adafruit_GFX https://github.com/adafruit/Adafruit-GFX-Library
- Adafruit_SSD1306 https://github.com/adafruit/Adafruit_SSD1306
Connexions:
- Mic GAIN: VCC pour 40dB, GND pour 50dB, flottant pour 60dB
- Broche analogique 0 → sortie ampli micro
- +3.3V, GND, SDA (A4) et SCL (A5) → SSD1306 I2C
Licence GPL v3 ou ultérieure.
*/
#include "arduinoFFT.h"
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SAMPLES 1024
#define SAMPLING_FREQ 24000
#define AMPLITUDE 100
#define FREQUENCY_BANDS 14
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 32
#define BARWIDTH 11
#define BARS 11
#define ANALOG_PIN A0
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
double vImag[SAMPLES];
double vReal[SAMPLES];
unsigned long sampling_period_us;
arduinoFFT fft = arduinoFFT(vReal, vImag, SAMPLES, SAMPLING_FREQ);
float reference = log10(50.0);
double coutoffFrequencies[FREQUENCY_BANDS];
void setup() {
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
for (;;);
}
display.clearDisplay();
display.display();
display.setRotation(0);
display.invertDisplay(false);
sampling_period_us = (1.0 / SAMPLING_FREQ) * pow(10.0, 6);
double basePot = pow(SAMPLING_FREQ / 2.0, 1.0 / FREQUENCY_BANDS);
coutoffFrequencies[0] = basePot;
for (int i = 1; i < FREQUENCY_BANDS; i++) {
coutoffFrequencies[i] = basePot * coutoffFrequencies[i - 1];
}
for (int i = 0; i < BARS - 1; i++) {
for (int j = 0; j < SCREEN_HEIGHT; j += 4) {
display.writePixel((i + 1) * BARWIDTH + 2, j, SSD1306_WHITE);
}
}
display.drawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SSD1306_WHITE);
}
int oldHeight[20];
int oldMax[20];
double maxInFreq;
void loop() {
for (int i = 0; i < SAMPLES; i++) {
unsigned long newTime = micros();
int value = analogRead(ANALOG_PIN);
vReal[i] = value;
vImag[i] = 0;
while (micros() < (newTime + sampling_period_us)) {
yield();
}
}
fft.DCRemoval();
fft.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
fft.Compute(FFT_FORWARD);
fft.ComplexToMagnitude();
double median[20];
double max[20];
int index = 0;
double hzPerSample = (1.0 * SAMPLING_FREQ) / SAMPLES;
double hz = 0;
double maxinband = 0;
double sum = 0;
int count = 0;
for (int i = 2; i < (SAMPLES / 2); i++) {
count++;
sum += vReal[i];
if (vReal[i] > max[index]) max[index] = vReal[i];
if (hz > coutoffFrequencies[index]) {
median[index] = sum / count;
sum = 0.0;
count = 0;
index++;
max[index] = 0;
median[index] = 0;
}
hz += hzPerSample;
}
if (sum > 0.0) {
median[index] = sum / count;
if (median[index] > maxinband) maxinband = median[index];
}
int bar = 0;
for (int i = FREQUENCY_BANDS - 1; i >= 3; i--) {
int newHeight = 0;
int newMax = 0;
if (median[i] > 0 && max[i] > 0) {
newHeight = 20.0 * (log10(median[i]) - reference);
newMax = 20.0 * (log10(max[i]) - reference);
}
if (newHeight < 0 || newMax < 0) {
newHeight = 1;
newMax = 1;
}
if (newHeight >= SCREEN_HEIGHT - 2) newHeight = SCREEN_HEIGHT - 3;
if (newMax >= SCREEN_HEIGHT - 2) newMax = SCREEN_HEIGHT - 3;
int barX = bar * BARWIDTH + 5;
if (oldHeight[i] > newHeight) {
display.fillRect(barX, newHeight + 1, 7, oldHeight[i], SSD1306_BLACK);
}
if (oldMax[i] > newHeight) {
for (int j = oldMax[i]; j > newHeight; j -= 2) {
display.drawFastHLine(barX, j, 7, SSD1306_BLACK);
}
}
for (int j = newMax; j > newHeight; j -= 2) {
display.drawFastHLine(barX, j, 7, SSD1306_WHITE);
}
display.fillRect(barX, 1, 7, newHeight, SSD1306_WHITE);
oldMax[i] = newMax;
oldHeight[i] = newHeight;
bar++;
}
display.drawFastHLine(0, SCREEN_HEIGHT - 1, SCREEN_WIDTH, SSD1306_WHITE);
display.display();
}
Schémas
soundspectrumvisualizer_qLzRsVPVlc.fzz

Processus de fabrication
- Créer un jeu Pong classique sur Arduino UNO avec écran OLED – Tutoriel étape par étape
- Capteur CO2 K30 : Surveillance en temps réel de la qualité de l'air intérieur
- Faciliter la communication sourd-aveugle avec 1Sheeld et Arduino
- Construire une Tirelire Intelligente : Contrôler un Accepteur de Pièces avec Arduino Nano
- Contrôler une LED par Bluetooth avec Arduino et MIT App Inventor
- Contrôler 2 moteurs pas-à-pas avec Arduino Nano et joystick – Tutoriel Visuino
- Transmission de données audio avec Arduino Nano 33 BLE Sense
- Compteur Geiger Portable avec Arduino Nano : Construction Pas à Pas
- Mesure précise du rayonnement solaire avec Arduino UNO et shield Ethernet