Arduino Nano 33 BLE geluidsspectrumvisualisator: Real-time audioweergave
Onderdelen en benodigdheden
| Arduino Nano 33 BLE | 1 |
| Electret Microfoonversterker MAX9814 | 1 |
| 0.91 inch OLED I2C Display 128x32 pixels | 1 |
| Condensator 4.7 nF (voor RC-laagdoorlaatfilter, afhankelijk van keuzefrequentie) | 1 |
| Condensator 470 µF | 1 |
| Weerstand 2 kΩ (voor RC-laagdoorlaatfilter) | 1 |
Apps en online diensten
- Arduino IDE
Over dit project
Inleiding
Zie hoe een staafdiagram reageert op muziek en geluid op een kleine OLED-display. Laagste frequenties linkshandig, hoogste frequenties rechtshandig.
Opbouw
Aansluitingen
- Microfoon GAIN-pin: VCC → 40dB (lage gevoeligheid), GND → 50dB (medium), loslaten → 60dB (hoge gevoeligheid).
- Analoge pin A0 → microfoonversterker OUT.
- 3.3V, GND, SDA (A4), SCL (A5) → I2C SSD1306 display.
- 470 µF condensator tussen VCC en GND op voedingsrail.
Laagfrequente ruis? Elektronische apparaten in de buurt veroorzaken 50/60 Hz netruis. Oplossing: Faraday-kolf (metalen doos geaard om de schakeling).
De Arduino Nano 33 BLE haalt met analogRead() ongeveer 35.000 samples/seconde. Nyquist limiet → max 17.5 kHz. Met wachttijden in de meetlus nemen we een veilige bovengrens van 8 kHz. Een eenvoudig RC-laagdoorlaatfilter filtert hogere frequenties uit.
Ruw berekening maximum sample rate
Standaard analogRead() is te traag voor audio. Testprogramma toont ~35.000 samples/seconde. We gebruiken een bemonsteringsfrequentie van 24 kHz (effectief ~12 kHz max frequentie) en een lus met micros() voor timing.
// Monsters nemen
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();
}
}
Gebruikersinterface
Het scherm is verdeeld in 11 frequentie-zones op logaritmische schaal (vergelijkbaar met menselijk gehoor). Mensen horen ~20 Hz – 20 kHz (volwassenen vaak 15-17 kHz). We gebruiken zones 3 tot 13.
Elke staaf toont: maximumwaarde in de band (gestippelde lijnen) en gemiddelde waarde (gevulde rechthoek). Waarden in decibels (dB) ten opzichte van een heuristisch referentieniveau.
Hoe het werkt
- Audiomonsters worden omgezet naar frequentiespectrum via Fast Fourier Transform (FFT).
- Spectrum wordt verdeeld in 14 zones, 11 zones worden in dB weergegeven.
Over de microfoonmodule (MAX9814)
Electret microfoon: Quasi-permanent geladen dielectrum. Geluidsdruk beweegt membrane → capaciteitsverandering → spanningsverandering (via ingebouwde FET-versterker). Klein, hoge gevoeligheid, brede frequentierespons, lage kost.
MAX9814 versterker: Automatische versterkingsregeling (AGC), lage ruis, microfoonbias 1.25V, 2Vpp uitgang. Drie versterkingsstanden (40/50/60 dB) via GAIN-pin. Aanvals/valverhouding instelbaar (standaard 1:4000). Voor line-in: blokkeercondensator (>1µF, bijv. 33-100µF) nodig om DC-component te blokkeren. Voeding: 2.7V-5.5V.
Gevoeligheid: Verhouding uitgangsspanning tot ingangsdruk. Standaard referentie: 1 kHz, 94 dB SPL (1 Pa). Hogere gevoeligheid = hogere uitgang bij vaste ingang. Eenheid: dBV (referentie 1.0 V RMS).
Frequentierespons: Uitgangsniveau over frequentiebereik. -3dB punten ten opzichte van referentie op 1 kHz (genormaliseerd naar 0 dB).
0.91 inch OLED I2C Display 128x32
Klein display (~1" diagonaal), 128x32 witte OLED-pixels, geen achtergrondverlichting nodig → laag stroomverbruik. 4 pinnen, I2C-communicatie. Geen RESET-pin. Aansluiting: SDA (A4), SCL (A5). Bibliotheken: Adafruit_SSD1306.h en Adafruit_GFX.h.
Volgende stappen
Arduino Nano 33 BLE kan betere analoge reads leveren. Volgende versie: geoptimaliseerde FFT en analoge invoer voor de nRF52840-chip.
Code
Volledige sketch: Sound Spectrum Visualizer for Arduino Nano 33 BLE
- Gebruikt arduinoFFT
- Gebruikt Adafruit_GFX
- Gebruikt Adafruit_SSD1306
/* Sound Spectrum Visualizer for Arduino Nano 33 BLE
* @author Enrique Albertos
* Hardware: Arduino Nano 33 BLE, SSD1306 OLED 128x32 I2C, MAX9814 microfoonversterker
* Libraries: arduinoFFT, Adafruit_GFX, Adafruit_SSD1306
* Licentie: GPL v3
*/
#include "arduinoFFT.h"
#include
#include
#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) * 1000000.0;
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; count = 0;
index++; max[index] = 0; median[index] = 0;
}
hz += hzPerSample;
}
if (sum > 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, 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();
}
Schema
Fritzing-bestand:soundspectrumvisualizer_qLzRsVPVlc.fzz
Productieproces
- Bouw een Klassiek Pong-spel op Arduino UNO met OLED-display – Stap-voor-stap Tutorial
- K30 CO2-sensor: Real-time binnenluchtkwaliteitmonitoring met Arduino
- Communicatie voor doofblinden vergemakkelijken met 1Sheeld en Arduino
- Slimme spaarpot bouwen: Muntenacceptor besturen met Arduino Nano
- Een LED bedienen via Bluetooth met Arduino – Eenvoudige DIY Handleiding
- Twee Stappenmotors Sturen met Arduino Nano & Joystick – Simpele Tutorial
- Audio Data Overdracht met Arduino Nano 33 BLE Sense
- Bouw een handgehoude Geiger-teller met Arduino Nano
- Accurate zonnestralingsmeting met Arduino UNO en Ethernet Shield
- Arduino Nano eetrobot bouwen: stap-voor-stap handleiding
- Real-time Arduino Weerklok: OLED-display voor tijd, datum & temperatuur