Arduino FFT Lichtorgel mit Fade-Funktion

  • Beitrags-Autor:
  • Beitrag zuletzt geändert am:1. August 2021

Kurzinformationen

Kosten: 5-20€

Bauzeit: 1 Tag

Kenntnisse: 2/5

Worum gehts?

Der Bau einer Lichtorgel ist nicht nur ein Muss für jeden RGB-Freak, sondern bietet auch einen Einstieg in die wunderbare Welt der Elektronik und das Programmieren eines Arduinos.

Diese Anleitung nutzt einfache Komponenten, die sich bei jedem Hobby-Bastler zuhause finden lassen. Die Erkennung der Frequenzbereiche erfolgt mittels Fourier-Transformation (kurz: FFT, mehr dazu im Folgenden), sodass ein Equalizer-IC entfallen kann.  

light organ complete

Die prinzipielle Idee

Was bedeutet das im Detail - Erläuterung

lightorgan arduino schematic diagramm

Das Herzstück dieses Projektes soll ein Arduino Nano sein. Mit nur einem Preis von wenigen Euro ist er verhältnismäßig günstig und erfüllt an dieser Stelle seinen Zweck.

Der Arduino soll über einen Analog-Pin die Frequenzbereiche eines Audiosignals analysieren und anhand dessen die Lautstärke der Bässe, Mitten und Höhen ermitteln. Über die Digitalausgänge werden dann die LEDs oder die einzelnen Kanäle des LED-Streifens gesteuert.

So weit so gut. Aber warum werden die LEDs an den Kanälen nicht direkt sondern über Transistoren gesteuert? Die Digitalpins eines Arduinos können nur etwa 40mA liefern. Benötigt ein angeschlossenes Element mehr Strom, verwendet man häufig, wie hier, einen Transistor (mehr dazu in meinem Kapitel über Transistoren). 

Es sollte jedoch nicht mit unbegrenzt viel Strom geschaltet werden. Die 1kΩ Widerstände an den Basis-Pins verhindern, dass der Transistor an dieser Stelle zu einer Nebelmaschine wird.

Nun leben wir im 21. Jahrhundert und die meiste Musik wird in Stereo abgespielt, bedeutet, der Arduino müsste zwei Kanäle abtasten. Wir wollen es uns aber erst einmal einfach machen. Leider kann man den linken und rechten Audiokanal nicht einfach verknüpfen. Mein Schaltplan zeigt, dass ich noch zwei 10kΩ Widerstände dazwischen schalte, um einen direkten Kurzschluss zu verhindern – Nicht ideal, aber es tut. 

Hat man jedoch kein Audiosignal angeschlossen, misst der Arduino sehr diffuse Werte und dementsprechend sehen auch die Ergebnisse auf den LEDs aus. Der 100kΩ Widerstand dient in diesem Fall als sogenannter Pull-Down-Widerstand und legt den Eingang auf Masse. Wird nun eine Quelle angeschlossen, ist dieser Widerstand jedoch vernachlässigbar gering und hat somit keinen Effekt mehr auf die Schaltung fisiks.

Ein Widerstand blieb aber noch unerklärt: Der 5,1kΩ Widerstand zwischen dem AREF und 3,3V Pin des Arduinos.

Ein Arduino vergleicht an einem Analogpin die angelegte Spannung mit einer internen Vergleichsspannung (bei einem Arduino Nano in der Regel 5V, ansonsten hier nachschlagen). Dabei entsteht ein Wert zwischen 0 und 1023 (bei 0V 0, bei 2,5V ~512 und bei 5V 1023). Die Auflösung liegt hierbei bei allen gewöhnlichen Arduinos bei 10bit – mehr als genug. 

Der AREF-Pin bietet nun die Möglichkeit eine solche Vergleichsspannung selbst festzulegen. Das kann der Fall sein, wenn die maximal zu messende Spannung unter 5V liegt. Das Herabsetzen der Vergleichsspannung auf die maximale Messspannung erhöht die Genauigkeit. Die neue Vergleichsspannung muss aber auch zwischen 0V und 5V liegen. 

Ein Beispiel: Gehen die Pegel bei dem gewählten Audiosignal auf maximal 3V, kann  der AREF-Pin auf die 3,3V des Microchips des Arduino gelegt werden. Der Widerstand verhindert hierbei einen zu großen Strom. Der Arduino würde somit letztendlich bei einem 1V einen Wert von etwa 310 messen. (Gewöhnlich Audiopegel liegen bei etwa 1V.)

Das wichtigste jedoch zuletzt: POWER

Meine verwendeten LED-Streifen benötigen 12V. Ein passendes altes Netzteil aus dem Fundus dient in meinem Fall als Spannungsversorgung. 

Diese soll auch den Arduino selbst mitversorgen. Der Atmega-328 des Arduino-Nanos wird zwar von einem 3,3V-Spannungsregler auf dem Board direkt gespeist, jedoch ist der nur für 5 bis 12V ausgelegt und selbst bei den 20mA, die der Arduino Nano im normalen Betrieb benötigt, wird dieser sehr schnell sehr warm. Ein zusätzlicher 7805 Festspannungsregler regelt die 12V ohne Probleme auf 5V herunter und kann die Wärme entspannt über das große Gehäuse ableiten. 

Die Kondensatoren stabilisieren die Stromversorgung. Auf der Platine habe ich sie jedoch weggelassen, da sie für dieses simple Projekt nicht essentiell sind. 

Für einen ersten Testlauf können jedoch erstmal 3 LEDs ohne Transistoren angeschlossen werden (die 470Ω Vorwiderstände als Schutz gegen einen zu hohen Strom durch die LEDs nicht vergessen!) und der Arduino über die USB-Buchse mit Strom versorgt werden. Breadboards und, wer ganz sicher gehen will, ein abgeregeltes Labornetzteil, sind keine schlechte Möglichkeit, hier aber nicht notwendig.

Also! Auf geht’s ans Teile suchen und Programmieren.

Doch was ist eigentlich eine schnelle Fourier Analyse (FFT)?

Bevor wir uns dem eigentlichen Programm widmen, soll dieses Kapitel die Grundlagen für das Verständnis einer schnellen Fourier Transformation mit dem Arduino legen.

Betrachtet man einen sehr kurzen Zeitraum eines Audiosignals, so kann dieses immer näherungsweis als Summe verschiedener Schwingungen dargestellt werden. Jede Schwingung besitzt dabei seine eigene Frequenz (=Tonhöhe) und Amplitude (=Lautstärke).

fourier-transform-fft-arduino

Mithilfe einer Fourier-Transformation können aus einem sehr kurzen Stück Audiosignal die ursprünglichen Frequenzen und Amplituden analysiert werden (mehr dazu hier). Hierzu wird hauptsächlich die schnelle Fourier Transformation, welche lediglich eine Verbesserung der diskreten Fourier Transformation darstellt, verwendet. Beide Algorithmen nutzen Matrizenmultiplikation um aus den gegeben Kurven die Frequenzbereiche zu berechnen. 

Glücklicherweise gibt es bereits Arduino-Bibliotheken für exakt diesen Zweck. Ich verwende die arduinoFFT-Bibliothek von Enrique Condes (Github). Zum Installieren verwenden wir den Librarymanager, welcher in Ihrer Arduino IDE unter Sketch > Include Library > Manage Libraries… zu finden ist. Suchen Sie nach arduinoFFT und wählen Sie „Installieren“ für die richtige Bibliothek.

install_arduino_fft_library

Das Programm

Denken Sie für ihre Lichtorgel daran, gegebenenfalls die LED Pins in dem LED[]-Array und den Analogpin unter AUDIO anzupassen. Bei mir liegen die LEDS an den Pins 9, 10 und 11 und der Audioeingang an A0.

#include <arduinoFFT.h>

#define SAMPLES 64  // must be a power of 2
#define AUDIO A0



// defines how much brigthness the LEDs will loose each loop
// 1024 is the maximum --> the organ will be very responsive
// lower will smootly the the leds if no new peak is detected 
#define decayPerLoop 1024 


const int LED[] = {9,10,11};



double vReal[SAMPLES], vImag[SAMPLES];
double sums[] = {1024,1024,1024}; 



arduinoFFT FFT = arduinoFFT();      


void setup()
{
  // define the analog voltage reference
  analogReference(DEFAULT);

  // set all digital pins with an LED to an output
  for(int i = i; i < sizeof(LED); i++)
  {
    pinMode(LED[i], OUTPUT);
  }
}

    
void loop()
{   
    // collect Samples
    for(int i=0; i < SAMPLES; i++)
    {
      int sample = analogRead(AUDIO); // read the voltage at the analog pin
      vReal[i] = sample/4-128;        // compress the data and getting rid of DC-Voltage 
      vImag[i] = 0;                   // reset old imaginary part of the FFT
    }
    
    // let the fourier transform do his magic
    FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
    FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
    FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);

    // calculate average loudness of the different frequency bands
    sums[0] = calculateBrightness(vReal, vImag, 1, 3, sums[0]);  //Bass
    sums[1] = calculateBrightness(vReal, vImag, 4, 40, sums[1]); //Mids
    sums[2] = calculateBrightness(vReal, vImag, 40, 63, sums[2]); //High

    // apply the brightness to the different led channels
    for(int i = 0; i < 3; i++){ analogWrite(LED[i], sums[i]); }

    // wait one millisecond for the next analysis
    delay(1);
}

double calculateBrightness(double vReal[], double vImag[], int first, int last, double oldSum){
  double newSum = 0;

  // sum up all absolute value of the complex numbers (it's just the pythagorean theorem)
  for(int i=first; i < last; i++)  
  {
    newSum += sqrt(vReal[i] * vReal[i] + vImag[i] * vImag[i]);
  }
  newSum = newSum/(last-first-1);         // divide to get the the average loudness
  newSum = constrain(newSum,0,5000);      // crop the number above 5000 
  newSum = map(newSum,0,5000,0,1024);     // map it between 0 and 1024

  oldSum -= decayPerLoop;                 // substract the decay from the old brightness
  if(newSum > oldSum){ oldSum = newSum; } // if the old brightness is now lower then the new, the new brightness will be outputted

  return oldSum;
}

In Zeile 42 bis 47 wird in jedem Loop ein Datensatz mit 64 Messwerten aufgenommen und in vReal gespeichert. Dieser Datensatz ist das kurze Audiosignal, welches nun analysiert werden soll. Das erledigen die nächsten drei Zeilen Code.

Das Ergebnis sind zwei Arrays: vReal und vImag. Die beiden Werte gleicher Indices stellen eine komplexe Zahl dar. Was ist nun eine komplexe Zahl?

Eine komplexe Zahl besitz im Prinzip zwei verschiedene Werte in einer Zahl – einen realen und einen imaginären Teil. Der imaginäre Teil besitzt die Einheit i. Man kann eine komplexe Zahl auf einem senkrechten und einem waagerechten Zahlenstrahlen darstellen. C = 3 + 2i würde wie folgt aussehen:

komplexe-zahl

Die Betrag der komplexen Zahl kann mit dem Satz des Pythagoras berechnet werden. 

complex_number

Dieser Betrag entspricht der Lautstärke einer bestimmten Frequenz. Um den Durchschnitt eines Frequenzbereiches berechnen zu können, werden diese, wie in Zeile 72, aufsummiert. 

Doch woher weiß ich, welcher Index der Arrays welcher Frequenz entspricht? Dies hängt von der Anzahl an samples n und der Frequenz f, mit der der Arduino die Messdaten aufnehmen kann, ab. i stellt den Index in den Arrays vReal und vImag dar.

fft_frequency

Ein Arduino Nano besitzt eine Taktung von 16MHz. Die beste Performance erreichte meine Lichtorgel mit  64 samples.  Ein Messvorgang eines Analogpins benötigt des Weiteren 13 cycles des Arduinos. Somit kann der Arduino mit folgender Frequenz neue Datensätze aufnehmen:

calculate_fADC

Die Frequenz für einen bestimmten Index lautet somit:

calculate_fi

Da der erste Messwert meist keine brauchbaren Ergebnisse liefert, stellen der zweite und dritte Index (600Hz und 900Hz) für mich den Bass, der 4. bis 39. Index die Mitten und alles darüber die Höhen dar. In den Zeilen 55 bis 57 werden diese Parameter der Funktion calculateBrightness übergeben, welche den Durchschnitt der Lautstärken berechnet, übergeben.

Der letzte Schritt besteht nun daraus diese Helligkeiten als PWM-Signal an den LED-Pins auszugeben (Zeile 60). PWM oder Pulsweitenmodulation ist eine einfache Form der digitalen Datenübertragung. Hierbei blinkt die LED mit einer, für das Auge nicht erkennbaren Frequenz. Das sogenannte Puls-Pausen-Verhältnis bestimmt dabei wie lang die LED ein- bzw. ausgeschaltet ist. Je länger sie an bleibt, desto heller erscheint sie. 

pwm-wave

Der Befehl analogWrite erledigt für uns einen Großteil der Arbeit und nimmt einen Wert zwischen 0 und 255 entgegen. 0 steht dabei für aus und 255 für maximale Helligkeit. Da der Wertbereich von analogRead zwischen 0 und 1023 liegt, muss das aufgenommene Signal komprimiert werden, was durch das Teilen mit 4 passiert (Zeile 45).

Als kleines Extra sollte meine Lichtorgel die LED-Kanäle langsam dimmen und bei einem hellerem Wert wieder aufleuchten. Aus diesem Grund kann sogenannter decayPerLoop festgelegt werden. Die alte Helligkeit wird pro Loop solange um diesen Wert verringert bis ein neuer Helligkeitswert größer als der aktuelle Helligkeitswert ist. Dies ist in Zeile 78 und 79 zu sehen.

Diese Funktion kann ausgeschaltet werden, in dem der Decay auf 1023 gesetzt wird. Allerdings ist dieser Fade-Effekt bei mir nur mit 20 tatsächlich sichtbar. Darunter reagiert die Lichtorgel nicht schnell genug. (Video folgt) 

Optimierungsphase

Nun haben Sie Ihre Lichtorgel gebaut und programmiert, aber das Ergebnis ist noch nicht zufriedenstellend. Dieses Kapitel soll dabei helfen die Lichtorgel auf das Audiosignal und andere Gegebenheiten anzupassen.

Die Lichtorgel ist nicht hell genug

Möglicherweise ist das Audiosignal zu schwach. Ich verwende zu diesem Zweck den Kopfhörerausgang meines Mischpults, welcher noch einen zusätzlichen Verstärker besitzt. Es ist immer ratsam für die besten Ergebnisse die Audioquelle und jedes Zwischenelement auf maximal Lautstärke zu stellen.

Die Lichtorgel zeigt keine Veränderung bei gegebenem Audiosignal 

Hier ist es ratsam sich die Helligkeitswerte und andere Variablen, wie die Samples, über den seriellen Monitor ausgeben zu lassen. Das kann Aufschluss auf falsche Anschlussbelegung an dem Analog- oder den Digitalpins legen. Mehr dazu hier. (Der serielle Monitor kann in der Arduino IDE mit Strg+Shift+M geöffnet werden)

Bei Fragen kontaktieren Sie mich über Instagram oder info@the-techblog.de

Der Nachteil der FFT-Methode mit einem Arduino - Disclaimer

Laut dem Nyquist-Shannon-Abtasttheorem sollte die Abtastfrequenz mindestens das doppelte der abzutastenden Frequenz betragen. Wie wir oben jedoch festgestellt haben beträgt die Abtastfrequenz etwa 19,2kHz. Die effektive Frequenz liegt somit bei ungefähr 10kHz.

Der Hörbereich eines Menschen reicht jedoch bis zu 20kHz, sprich dem doppelten dessen, was der Arduino samplen kann . Die obere Hälfte unseres Hörbereiches an Musik wird also nicht wirklich genau mitgeschnitten.

Bedenkt man das jedoch in der Kalibrierungsphase der Lichtorgel und ist mit dem Endergebnis zufrieden, kann man diese Problem missachten. 

Kurzinformationen

Kosten: 5-20€

Bauzeit: 1 Tag

Kenntnisse: 2/5