FHT Audio Spectrum Visualizer – Beeindruckende LED-Audio-Anzeige bauen
Komponenten und Zubehör
| Arduino Nano R3 | × | 1 |
| WS2812B 8x32 RGB LED-Matrix | × | 1 |
| Widerstand 4,75 kΩ | × | 3 |
| Widerstand 100 kΩ | × | 2 |
| Widerstand 10 kΩ | × | 2 |
| Durchgangswiderstand 390 Ω | × | 1 |
| 1N4007 Diode (1N4004 funktioniert auch) | × | 1 |
| Kondensator 100 nF | × | 2 |
| Kondensator 1000 µF | × | 1 |
| Taster 12 mm (oder zwei Panel-Taster für Gehäuseeinbau) | × | 2 |
| 4x6 cm Lochrasterplatine | × | 1 |
| PCB Stereo RCA Buchse | × | 1 |
Benötigte Werkzeuge und Maschinen
- Lötkolben (generisch)
- 3,5 mm Klinkenkabel auf Stereo RCA
Über dieses Projekt
Die Arduino FHT-Bibliothek
FHT (Fast Hartley Transform) erledigt dieselbe Aufgabe wie die bekanntere FFT (Fast Fourier Transform), arbeitet aber ausschließlich mit reellen Daten, während FFT komplexe Daten benötigt. Dadurch benötigt FHT nur halb so viel Rechenleistung und Speicher.
Die Bibliothek nutzt ein einzelnes Array für die Datenerfassung statt zweier (reell/imaginär), vereinfacht komplexe Mathematik und vermeidet Schleifen mit komplexen Gleitkomma-Logarithmen durch Lookup-Tabellen.
Nachteil: Es werden nur halb so viele Werte wie die Array-Größe zurückgegeben, was zu Auflösungsverlust an den Enden des Audiobereichs führt. Für ein simples Gadget, das LEDs im Musikrhythmus blinken lässt, ist die Präzision jedoch ausreichend.
Details zur FHT-Bibliothek finden Sie auf der Open Music Lab Website mit Dokumentation, Funktionserklärungen und Arduino-Beispielen.
Die FHT-Bibliothek ist so schnell, dass ein Delay im Code nötig war, um Flackern zu reduzieren. Das Ergebnis ist deutlich flüssiger und reaktionsschneller als die FFT-Version. Der geringere Ressourcenverbrauch erlaubte zusätzliche Funktionen.
Die neue Version
Das Projekt entspricht der Vorversion; wer diese schon gebaut hat, muss nur einen Taster, einen 10 kΩ Widerstand hinzufügen und den Code aktualisieren.
Zusätzliche Funktionen dieser Version:
- Zweiter Taster für Helligkeitsanpassung
- Speichern von Farb- und Helligkeitseinstellungen im EEPROM (werden beim nächsten Einschalten geladen)
- Anzeige der aktuellen Farb- und Helligkeitsstufe beim Drücken der Taster und beim Start (deaktivierbar)
Code
FHT Code Version (Arduino)
/* Copyright (c) 2020 Janux
MIT License ... (gekürzt) */
#define LIN_OUT 1
#define FHT_N 128
#include <FHT.h>
#define xres 32
#define yres 8
#define ledPIN 6
#define NUM_LEDS (xres * yres)
#include <Adafruit_NeoPixel.h>
#define colorPIN 5
#define brightnessPIN 10
byte displaycolor = 0;
byte brightness = 1;
#include <EEPROM.h>
#define CONFIG_START 32
#define CONFIG_VERSION "VER01"
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);
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;
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}
};
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 free running, Prescaler 32
ADMUX = 0b00000000; // Pin A0, externe Referenz
if (loadConfig()) {
displaycolor = CONFIGURATION.displaycolor;
brightness = CONFIGURATION.brightness;
}
pixel.setBrightness(brightness * 24 + 8);
showSettings(3, true);
}
void loop() {
while (1) {
Sampling();
RearrangeFHT();
SendToDisplay();
colorChange();
brightnessChange();
delay(10);
}
}
void Sampling() {
for (int i = 0; i < FHT_N; i++) {
while (!(ADCSRA & 0x10));
ADCSRA = 0b11110101;
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:
pixel.setPixelColor(led, Wheel(x * 16));
break;
case 6:
pixel.setPixelColor(led, Wheel(i * y * 3));
break;
case 7:
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®, Last version 28/06/2020.
Schaltpläne


Herstellungsprozess
- 32-Band Arduino Audio-Spektrumanalysator – Linear & Pseudo-Log Darstellung
- Smart Thirst Alert: DIY Pflanzen-Alarm für optimale Bewässerung
- Italienische Word Clock mit Arduino Nano, NeoPixel, LCD & RTC
- Zuverlässiger Sigfox-kWh-Zähler mit Arduino MKR Fox 1200
- Bluetooth-Temperaturmonitor mit Arduino Nano
- Berührungsloses Gesten-Schloss: Sicherer Zugang mit APDS-9960 & Arduino
- Arduino Nano Begleiter-Set – Essentielle Bauteile & Werkzeuge für DIY-Elektronik
- USB-MIDI-Adapter für Arduino Micro / Leonardo – Komplettes Bauteil-Kit & Code
- Isolierter Analogeingang für Arduino: Spannung/Strom galvanisch trennen
- Präziser Reaktionszeit-Test mit Arduino UNO