Arduino Nano 33 BLE Klangspektrum-Visualisierer: Echtzeit-Audio-Display auf OLED
Komponenten und Zubehör
| Arduino Nano 33 BLE | × | 1 |
| Elektret-Mikrofon-Verstärker MAX9814 (mit AGC) | × | 1 |
| 0,91" OLED I2C Display 128×32 Pixel (SSD1306) | × | 1 |
| Kondensator 4,7 nF (für RC-Tiefpass, Grenzfrequenz ~17 kHz) | × | 1 |
| Kondensator 470 µF (Netzteil-Puffer) | × | 1 |
| Widerstand 2 kΩ (für RC-Tiefpass) | × | 1 |
Apps und Online-Dienste
- Arduino IDE
Über dieses Projekt
Einführung
Ein Balkendiagramm auf einem kleinen OLED reagiert in Echtzeit auf Musik und Geräusche. Tiefe Frequenzen links, hohe rechts – logarithmische Skalierung angelehnt an die menschliche Hörwahrnehmung.
Aufbau & Verkabelung
- MAX9814 GAIN-Pin: VCC → 40 dB (niedrig), GND → 50 dB (mittel), offen → 60 dB (hoch)
- MAX9814 OUT → Arduino A0
- OLED: 3,3 V, GND, SDA (A4), SCL (A5)
- 470 µF zwischen VCC und GND am Netzteil-Eingang
- RC-Tiefpass: 2 kΩ + 4,7 nF am Mikrofon-Ausgang (fc ≈ 17 kHz) – begrenzt Frequenzen oberhalb der Abtastgrenze
Störungsbehebung: 50/60 Hz Brummen
Netzfrequenz-Störungen durch benachbarte Geräte. Abhilfe: Faraday-Käfig – Schaltung in Metallgehäuse, Gehäuse an Masse legen.
Abtastrate & Nyquist
Arduino Nano 33 BLE mit analogRead() schafft ~35.000 Samples/s. Nyquist → max. 17,5 kHz. Sicherheitshalber 8 kHz Obergrenze via Tiefpass.
Maximale Abtastrate messen
#define ANALOG_PIN A0
#define SAMPLES 1000000.0
int newTime, analogValue;
void setup() {
Serial.begin(9600);
Serial.println("Sampling... please wait.");
}
void loop() {
newTime = micros();
for (int i = 0; i < (int)SAMPLES; i++) {
analogValue = analogRead(ANALOG_PIN);
}
float elapsedTimePerSampleUs = (micros() - newTime) / SAMPLES;
Serial.print("Time per sample (us): "); Serial.println(elapsedTimePerSampleUs);
Serial.print("Max sampling freq (Hz): "); Serial.println(pow(10,6) / elapsedTimePerSampleUs);
}
Echtzeit-Abtastung mit Busy-Wait:
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(); }
}
Benutzeroberfläche (OLED)
Bildschirm in 11 Frequenzzonen (logarithmisch, Zonen 3–13 von 14), angepasst an menschliches Gehör (20 Hz – 20 kHz, Erwachsene ~15–17 kHz).
Jeder Balken: gestrichelte Linien = Maximum im Band, gefülltes Rechteck = Mittelwert. Anzeige in dB (heuristische Referenz).
Funktionsweise
- Audio-Samples per
analogRead+ Busy-Wait mit fester Abtastrate (24 kHz) erfassen (1024 Samples, 2er-Potenz für FFT). - FFT (
arduinoFFTBibliothek): DC-Entfernung → Hamming-Fenster → FFT → Betragsspektrum. - Spektrum in 14 logarithmische Bänder aufteilen, 11 davon (Zone 3–13) in dB darstellen.
Zum Mikrofon-Modul MAX9814
Elektret-Mikrofon: Quasi-permanent geladenes Dielektrikum (elektrostatisches Gegenstück zum Permanentmagneten). Membranbewegung ändert Kapazität → Spannungsschwankung → FET-Verstärker.
MAX9814: Mikrofonverstärker mit automatischer Gain-Control (AGC), geringem Rauschen, Bias 1,25 V, 2 Vpp Ausgang. Drei Gain-Stufen (40/50/60 dB) per Jumper an GAIN-Pin. Attack/Release-Verhältnis anpassbar (Standard 1:4000). Ausgang DC-gekoppelt (1,25 V Bias) – für Line-In Koppelkondensator (≥1 µF, besser 33–100 µF) nötig. Versorgung 2,7–5,5 V.
OLED Display 0,91" 128×32 I2C
Keine Hintergrundbeleuchtung nötig (selbstleuchtend), niedriger Stromverbrauch. 4 Pins: VCC, GND, SDA (A4), SCL (A5). Bibliotheken: Adafruit_SSD1306, Adafruit_GFX.
Ausblick
Nächste Version: Optimiertes ADC-Reading & FFT für nRF52840 (Nano 33 BLE Prozessor).
Code
Sound Spectrum Visualizer für Arduino Nano 33 BLE
Benötigte Bibliotheken: arduinoFFT, Adafruit_GFX, Adafruit_SSD1306.
/* Sound Spectrum Visualizer for Arduino Nano 33 BLE
* @author Enrique Albertos
* GPL v3
*/
#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], 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) * 1e6;
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];
// Gestrichelte Trennlinien
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], oldMax[20];
void loop() {
// Sampling
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
fft.DCRemoval();
fft.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD);
fft.Compute(FFT_FORWARD);
fft.ComplexToMagnitude();
double median[20] = {0}, max[20] = {0};
int index = 0;
double hzPerSample = (1.0 * SAMPLING_FREQ) / SAMPLES;
double hz = 0, 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;
}
hz += hzPerSample;
}
if (sum > 0) median[index] = sum / count;
// Anzeige
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) newHeight = 1;
if (newMax < 0) 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;
// Alte Balken löschen
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);
// Neue Max-Linien
for (int j = newMax; j > newHeight; j -= 2)
display.drawFastHLine(barX, j, 7, SSD1306_WHITE);
// Neuer Median-Balken
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();
}
Schaltpläne
soundspectrumvisualizer_qLzRsVPVlc.fzz (Fritzing) + PNG-Schaltbild
Herstellungsprozess
- Klassisches Pong-Spiel auf Arduino UNO mit OLED-Display bauen – Schritt-für-Schritt-Tutorial
- K30 CO₂-Sensor: Echtzeit-Luftqualitätsmessung mit Arduino
- Kommunikationshilfe für Taubblinde: 1Sheeld + Arduino + Sprachausgabe & Anzeige
- Smartes Sparschwein: Münzprüfer mit Arduino Nano steuern
- LED per Bluetooth mit Arduino steuern – Einfache DIY-Anleitung
- Zwei Schrittmotoren mit Arduino Nano & Joystick steuern – Einfaches Tutorial
- Audio-Datenübertragung mit Arduino Nano 33 BLE Sense
- Handheld Geigerzähler mit Arduino Nano selbst bauen
- Präzise Solareinstrahlungsmessung mit Arduino UNO & Ethernet Shield
- Arduino Nano Fress-Roboter: Ultraschall-gesteuerter Mund (Schritt-für-Schritt)