EasyFFT – Zuverlässige FFT-Bibliothek für Arduino-Projekte
Komponenten und Zubehör
| Arduino Nano R3 | × | 1 |
Apps und Online-Dienste
- Arduino IDE
Über dieses Projekt
Die Frequenzmessung aus einem erfassten Signal ist anspruchsvoll, besonders auf Arduino mit begrenzter Rechenleistung. Nullstellen-Detektion (Zero-Crossing) funktioniert nicht bei Signalüberlagerungen mehrerer Frequenzen.
Dieses Projekt bietet einen einfach zu implementierenden FFT-Code für Arduino, ohne tiefes mathematisches Verständnis zu erfordern. Ideal für Musik-, Audio- und Signalanalyse-Projekte. Die Funktionsweise der FFT wird nicht erklärt – nur die Anwendung der Funktion.
Für 3× schnellere FFT mit ~5% Genauigkeitsverlust siehe ApproxFFT.
Schritt 1: Fast Fourier Transform (FFT)
Der FFT-Algorithmus (Cooley/Tukey, 1965) gilt als einer der wichtigsten Algorithmen des 20. Jahrhunderts. Er zerlegt Signale in gerad/ungerade Anteile und reduziert komplexe Multiplikationen von N² (DFT) auf N·logN – ein massiver Vorteil bei großen Abtastwerten.
Referenzen für mathematischen Hintergrund:
- Flylib – FFT Herleitung
- Jake VanderPlas – Understanding FFT
- CNX – FFT Details
- Wikipedia – Fast Fourier Transform
Schritt 2: Code-Erklärung – Schnelle Sinus/Kosinus
FFT benötigt häufig Sinus/Kosinus-Werte. Die Arduino-Standardfunktionen sind zu langsam. Lösung: Sinus-Werte für 0–90° als Vielfache von 255 in einem Byte-Array sine_data[91] speichern (4× weniger Speicher als float). Wichtig: sine_data[] und f_peaks[] müssen als globale Variablen am Programmstart deklariert werden.
byte sine_data[91] = {
0, 4, 9, 13, 18, 22, 27, 31, 35, 40, 44, 49, 53, 57, 62, 66, 70, 75, 79, 83,
87, 91, 96, 100, 104, 108, 112, 116, 120, 124, 127, 131, 135, 139, 143, 146,
150, 153, 157, 160, 164, 167, 171, 174, 177, 180, 183, 186, 189, 192, 195,
198, 201, 204, 206, 209, 211, 214, 216, 219, 221, 223, 225, 227, 229, 231,
233, 235, 236, 238, 240, 241, 243, 244, 245, 246, 247, 248, 249, 250, 251,
252, 253, 253, 254, 254, 254, 255, 255, 255, 255
};
float f_peaks[5]; // Top 5 Frequenzen (absteigend)
Die Hilfsfunktionen sine() und cosine() nutzen diese Tabelle mit einer einzigen Float-Division – schneller als Standardbibliothek, minimaler Genauigkeitsverlust. Vorteil: ~8 ms bei 64 Samples, ~20 ms bei 128 Samples.
Schritt 3: FFT-Funktion
FFT funktioniert nur für Potenzen von 2 (2, 4, 8, 16, 32, 64, 128…). Andere Werte werden auf die nächstkleinere Potenz gerundet (z. B. 70 → 64).
Empfohlene Sample-Größen: 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048…
Auf Arduino Nano: Arrays out_r und out_im (float) limitieren nutzbare Samples auf ≤128 (RAM).
Ablauf der FFT-Funktion:
- Bit-reverse Order für Sample-Größe generieren
- Eingabedaten entsprechend umsortieren
- FFT-Berechnung (Cooley-Tukey)
- Amplituden aus Real/Imaginär Teil berechnen
- Peaks detektieren und absteigend sortieren
- Ergebnisse in globalem Array
f_peaks[]ablegen
Für Zugriff auf Rohdaten (außer Peaks) müssen lokale Variablen in globale kopiert werden.
Schritt 4: Code testen
Test mit Dreieckssignal: Sampling 10 Hz, Signal 1,25 Hz. Ergebnisse stimmen mit Scilab-FFT überein (geringe Abweichung durch schnelle Sinus-Approximation).
Ausgabe-Frequenzbins: 1,25 Hz und 3,75 Hz (Oberschwingung). Exakte Werte nicht garantiert – Werte liegen in Frequenz-Bins.
Laufzeiten (Arduino Nano):
- 16 Samples: 4 ms
- 32 Samples: 10 ms
- 64 Samples: 26 ms
- 128 Samples: 53 ms
Schritt 5: Fazit
Der Code eignet sich für Echtzeitanwendungen (~30 ms bei 64 Samples). Auflösung limitiert durch Sample-Anzahl und RAM. Mit Arduino Mega oder leistungsstärkeren Boards (Teensy, ESP32) lässt sich die Genauigkeit steigern.
Fragen, Vorschläge, Korrekturen gerne in den Kommentaren.
Vollständiger Code (EasyFFT)
/*
// Beispiel-Daten:
int data[64] = {14, 30, 35, 34, 34, 40, 46, 45, 30, 4, -26, -48, -55, -49, -37, -28, -24, -22, -13, 6, 32, 55, 65, 57, 38, 17, 1, -6, -11, -19, -34, -51, -61, -56, -35, -7, 18, 32, 35, 34, 35, 41, 46, 43, 26, -2, -31, -50, -55, -47, -35, -27, -24, -21, -10, 11, 37, 58, 64, 55, 34, 13, -1, -7};
*/
//---------------------------------------------------------------------------
// byte sine_data[91] = { ... }; // HIER EINFÜGEN (global)
// float f_peaks[5]; // HIER EINFÜGEN (global)
//---------------------------------------------------------------------------
void setup() {
Serial.begin(250000);
}
void loop() {
/*
// Beispiel-Nutzung:
FFT(data, 64, 100); // 64 Samples, 100 Hz Sampling
Serial.println(f_peaks[0]);
Serial.println(f_peaks[1]);
delay(99999);
*/
}
//----------------------------- FFT Funktion ----------------------------------------------
float FFT(int in[], int N, float Frequency) {
unsigned int data[13] = {1,2,4,8,16,32,64,128,256,512,1024,2048};
int a, c1, f, o, x;
a = N;
for (int i = 0; i < 12; i++) {
if (data[i] <= a) { o = i; }
}
int in_ps[data[o]] = {};
float out_r[data[o]] = {};
float out_im[data[o]] = {};
x = 0;
for (int b = 0; b < o; b++) {
c1 = data[b];
f = data[o] / (c1 + c1);
for (int j = 0; j < c1; j++) {
x = x + 1;
in_ps[x] = in_ps[j] + f;
}
}
for (int i = 0; i < data[o]; i++) {
if (in_ps[i] < a) out_r[i] = in[in_ps[i]];
if (in_ps[i] > a) out_r[i] = in[in_ps[i] - a];
}
int i10, i11, n1;
float e, c, s, tr, ti;
for (int i = 0; i < o; i++) {
i10 = data[i];
i11 = data[o] / data[i+1];
e = 360.0 / data[i+1];
e = 0 - e;
n1 = 0;
for (int j = 0; j < i10; j++) {
c = cosine(e * j);
s = sine(e * j);
n1 = j;
for (int k = 0; k < i11; k++) {
tr = c * out_r[i10 + n1] - s * out_im[i10 + n1];
ti = s * out_r[i10 + n1] + c * out_im[i10 + n1];
out_r[n1 + i10] = out_r[n1] - tr;
out_r[n1] = out_r[n1] + tr;
out_im[n1 + i10] = out_im[n1] - ti;
out_im[n1] = out_im[n1] + ti;
n1 = n1 + i10 + i10;
}
}
}
// Amplitude aus komplexer Zahl
for (int i = 0; i < data[o-1]; i++) {
out_r[i] = sqrt(out_r[i]*out_r[i] + out_im[i]*out_im[i]); // sqrt weglassen für mehr Speed
out_im[i] = i * Frequency / N;
}
// Peak-Detektion
x = 0;
for (int i = 1; i < data[o-1] - 1; i++) {
if (out_r[i] > out_r[i-1] && out_r[i] > out_r[i+1]) {
in_ps[x] = i;
x++;
}
}
// Nach Magnitude sortieren (absteigend)
float s = 0, c = 0;
for (int i = 0; i < x; i++) {
for (int j = c; j < x; j++) {
if (out_r[in_ps[i]] < out_r[in_ps[j]]) {
s = in_ps[i];
in_ps[i] = in_ps[j];
in_ps[j] = s;
}
}
c++;
}
// Top 5 in globales Array
for (int i = 0; i < 5; i++) {
f_peaks[i] = out_im[in_ps[i]];
}
}
float sine(int i) {
int j = i;
while (j < 0) j += 360;
while (j > 360) j -= 360;
if (j >= 0 && j < 91) return sine_data[j] / 255.0;
else if (j < 181) return sine_data[180 - j] / 255.0;
else if (j < 271) return -sine_data[j - 180] / 255.0;
else return -sine_data[360 - j] / 255.0;
}
float cosine(int i) {
int j = i;
while (j < 0) j += 360;
while (j > 360) j -= 360;
if (j >= 0 && j < 91) return sine_data[90 - j] / 255.0;
else if (j < 181) return -sine_data[j - 90] / 255.0;
else if (j < 271) return -sine_data[270 - j] / 255.0;
else return sine_data[j - 270] / 255.0;
}
//------------------------------------------------------------------------------------
Schaltplan
Herstellungsprozess
- Die Fourier-Transformation verstehen: Grundlagen, Anwendungen und Signalzerlegung
- DTMF-Decoder mit Arduino bauen – ganz ohne zusätzlichen IC
- SERENA: Eigenes Arduino Mega 2560 Alarmsystem mit TFT-LCD-Touchscreen
- Python 3 zu Arduino UNO: Einfache Steuerung & LED-Demo
- Smartes Temperatur-Monitoring für Schulen – Arduino, Raspberry Pi, Sigfox & ThingSpeak
- Effiziente 8-Bit-I/O-Port-Bibliothek für Arduino – Digitale Steuerung erweitern
- Arduino 64-Tasten-Prototyp-Keyboard-Matrix – Eigenes Keyboard mit Schieberegistern bauen
- Isolierter Analogeingang für Arduino: Spannung/Strom galvanisch trennen
- Smartes Indoor-Navigations-Roboter-Kit für Arduino-Projekte
- HS-Code für Hydraulikpumpen – Klassifizierung und Verwendung