Inhaltsverzeichnis:

1024 Samples FFT Spectrum Analyzer mit einem Atmega1284 - Gunook
1024 Samples FFT Spectrum Analyzer mit einem Atmega1284 - Gunook

Video: 1024 Samples FFT Spectrum Analyzer mit einem Atmega1284 - Gunook

Video: 1024 Samples FFT Spectrum Analyzer mit einem Atmega1284 - Gunook
Video: How to locate exact frequency of peak in FFT spectrum 2024, November
Anonim
1024 Samples FFT Spectrum Analyzer mit einem Atmega1284
1024 Samples FFT Spectrum Analyzer mit einem Atmega1284
1024 Samples FFT Spectrum Analyzer mit einem Atmega1284
1024 Samples FFT Spectrum Analyzer mit einem Atmega1284

Dieses relativ einfache Tutorial (in Anbetracht der Komplexität dieses Themas) zeigt Ihnen, wie Sie mit einem Arduino-Board (1284 Narrow) und dem seriellen Plotter einen sehr einfachen Spektrumanalysator mit 1024 Samples erstellen können. Jede Art von Arduino-kompatiblem Board reicht aus, aber je mehr RAM es hat, desto die beste Frequenzauflösung erhalten Sie. Es wird mehr als 8 KB RAM benötigen, um die FFT mit 1024 Samples zu berechnen.

Die Spektrumanalyse wird verwendet, um die Hauptfrequenzkomponenten eines Signals zu bestimmen. Viele Klänge (wie die von einem Musikinstrument erzeugten) bestehen aus einer Grundfrequenz und einigen Harmonischen, deren Frequenz ein ganzzahliges Vielfaches der Grundfrequenz ist. Der Spektrumanalysator zeigt Ihnen all diese Spektralkomponenten an.

Vielleicht möchten Sie dieses Setup als Frequenzzähler verwenden oder jede Art von Signalen überprüfen, von denen Sie vermuten, dass sie Rauschen in Ihrer elektronischen Schaltung verursachen.

Wir konzentrieren uns hier auf den Software-Teil. Wenn Sie für eine bestimmte Anwendung eine dauerhafte Schaltung erstellen möchten, müssen Sie das Signal verstärken und filtern. Diese Vorkonditionierung hängt vollständig von dem Signal ab, das Sie untersuchen möchten, abhängig von Amplitude, Impedanz, Maximalfrequenz usw. Sie können https://www.instructables.com/id/Analog-Sensor-Sig überprüfen…

Schritt 1: Installieren der Bibliothek

Wir werden die ArduinoFFT-Bibliothek von Enrique Condes verwenden. Da wir so viel RAM wie möglich schonen möchten, verwenden wir den Entwicklungszweig dieses Repositorys, der es ermöglicht, den Float-Datentyp (anstelle von double) zum Speichern der abgetasteten und berechneten Daten zu verwenden. Wir müssen es also manuell installieren. Keine Sorge, laden Sie einfach das Archiv herunter und entpacken Sie es in Ihrem Arduino-Bibliotheksordner (z.

Sie können überprüfen, ob die Bibliothek korrekt installiert ist, indem Sie eines der bereitgestellten Beispiele wie "FFT_01.ino" kompilieren.

Schritt 2: Fourier-Transformations- und FFT-Konzepte

Warnung: Wenn Sie keine mathematische Notation sehen können, sollten Sie mit Schritt 3 fortfahren. Wenn Sie nicht alles verstehen, denken Sie einfach über die Schlussfolgerung am Ende des Abschnitts nach.

Das Frequenzspektrum wird durch einen Fast-Fourier-Transformations-Algorithmus erhalten. FFT ist eine digitale Implementierung, die sich dem mathematischen Konzept der Fourier-Transformation annähert. Nach diesem Konzept können Sie, sobald Sie die Entwicklung eines Signals entlang einer Zeitachse erhalten, seine Darstellung in einem Frequenzbereich kennen, der aus komplexen (realen + imaginären) Werten besteht. Das Konzept ist reziprok. Wenn Sie also die Darstellung im Frequenzbereich kennen, können Sie sie zurück in den Zeitbereich transformieren und das Signal genau wie vor der Transformation zurückerhalten.

Aber was machen wir mit dieser Menge berechneter komplexer Werte im Zeitbereich? Nun, das meiste wird den Ingenieuren überlassen. Für uns nennen wir einen anderen Algorithmus, der diese komplexen Werte in Spektraldichtedaten umwandelt: das ist ein Betrags-(=Intensitäts-)Wert, der jedem Frequenzband zugeordnet ist. Die Anzahl der Frequenzbänder entspricht der Anzahl der Samples.

Sicher kennen Sie das Equalizer-Konzept, wie dieses Back to the 1980s With the Graphic EQ. Nun, wir werden die gleichen Ergebnisse erhalten, aber mit 1024 Bändern statt 16 und viel mehr Intensitätsauflösung. Wenn der Equalizer einen globalen Überblick über die Musik bietet, ermöglicht die feine Spektralanalyse eine präzise Berechnung der Intensität jedes der 1024 Bänder.

Ein perfektes Konzept, aber:

  1. Da die FFT eine digitalisierte Version der Fourier-Transformation ist, nähert sie sich dem digitalen Signal an und verliert einige Informationen. Streng genommen würde das Ergebnis der FFT, wenn es mit einem invertierten FFT-Algorithmus zurücktransformiert würde, nicht genau das ursprüngliche Signal ergeben.
  2. Auch die Theorie betrachtet ein Signal, das nicht endlich ist, sondern ein immerwährendes konstantes Signal. Da wir es nur für einen bestimmten Zeitraum (z. B. Muster) digitalisieren werden, werden einige weitere Fehler eingeführt.
  3. Schließlich beeinflusst die Auflösung der Analog-Digital-Wandlung die Qualität der berechneten Werte.

In der Praxis

1) Die Abtastfrequenz (bezeichnet fs)

Wir werden ein Signal alle 1/fs Sekunden abtasten, d. h. seine Amplitude messen. fs ist die Abtastfrequenz. Wenn wir beispielsweise mit 8 KHz abtasten, liefert der ADC (Analog-Digital-Wandler), der sich auf dem Chip befindet, alle 1/8000 Sekunden eine Messung.

2) Die Anzahl der Proben (vermerkt N oder Proben im Code)

Da wir alle Werte vor dem Ausführen der FFT abrufen müssen, müssen wir sie speichern und begrenzen daher die Anzahl der Abtastungen. Der FFT-Algorithmus benötigt eine Anzahl von Samples, die eine Potenz von 2 ist. Je mehr Samples wir haben, desto besser, aber es braucht viel Speicher, umso mehr müssen wir auch die transformierten Daten speichern, die komplexe Werte sind. Die Arduino FFT-Bibliothek spart etwas Platz durch die Verwendung von

  • Ein Array namens "vReal", um die abgetasteten Daten und dann den Realteil der transformierten Daten zu speichern
  • Ein Array namens "vImag" zum Speichern des Imaginärteils der transformierten Daten

Die benötigte RAM-Menge beträgt 2 (Arrays) * 32 (Bits) * N (Samples).

In unserem Atmega1284 mit schönen 16 KB RAM speichern wir also maximal N = 16000 * 8 / 64 = 2000 Werte. Da die Anzahl der Werte eine Potenz von 2 sein muss, speichern wir maximal 1024 Werte.

3) Die Frequenzauflösung

Die FFT berechnet Werte für so viele Frequenzbänder wie die Anzahl der Abtastwerte. Diese Bänder reichen von 0 Hz bis zur Abtastfrequenz (fs). Daher ist die Frequenzauflösung:

FAuflösung = fs / N

Die Auflösung ist besser, wenn sie niedriger ist. Für eine bessere Auflösung (niedriger) wollen wir also:

  • weitere Proben und/oder
  • eine niedrigere fs

Aber…

4) Minimale fs

Da wir viele Frequenzen sehen möchten, von denen einige viel höher als die "Grundfrequenz" sind, können wir fs nicht zu niedrig einstellen. Tatsächlich gibt es das Nyquist-Shannon-Abtasttheorem, das uns zwingt, eine Abtastfrequenz zu haben, die weit über dem Doppelten der maximalen Frequenz liegt, die wir testen möchten.

Wenn wir zum Beispiel das gesamte Spektrum von 0 Hz bis sagen wir 15 KHz analysieren möchten, was ungefähr die maximale Frequenz ist, die die meisten Menschen deutlich hören können, müssen wir die Abtastfrequenz auf 30 KHz einstellen. Tatsächlich setzen Elektroniker sie oft auf 2,5 (oder sogar 2,52) * die maximale Frequenz. In diesem Beispiel wären das 2,5 * 15 KHz = 37,5 KHz. Übliche Abtastfrequenzen in professionellem Audio sind 44,1 KHz (Audio-CD-Aufnahme), 48 KHz und mehr.

Abschluss:

Die Punkte 1 bis 4 führen zu: Wir wollen möglichst viele Samples verwenden. In unserem Fall mit einem 16-KB-RAM-Gerät werden 1024 Samples berücksichtigt. Wir möchten mit der niedrigsten Abtastfrequenz wie möglich abtasten, solange diese hoch genug ist, um die höchste Frequenz zu analysieren, die wir in unserem Signal erwarten (mindestens 2,5 * diese Frequenz).

Schritt 3: Simulieren eines Signals

Simulieren eines Signals
Simulieren eines Signals

Für unseren ersten Versuch werden wir das in der Bibliothek angegebene TFT_01.ino-Beispiel leicht modifizieren, um ein Signal zu analysieren, das aus besteht

  • Die Grundfrequenz, eingestellt auf 440 Hz (Musical A)
  • 3. Harmonische bei halber Leistung der Grundwelle ("-3 dB")
  • 5. Harmonische bei 1/4 der Leistung der Grundwelle ("-6 dB)

Sie können im Bild oben das resultierende Signal sehen. Es sieht tatsächlich sehr nach einem echten Signal aus, das man manchmal auf einem Oszilloskop (ich würde es "Batman" nennen) sehen, wenn ein Sinussignal übersteuert wird.

Schritt 4: Analyse eines simulierten Signals - Codierung

0) Binden Sie die Bibliothek ein

#include "arduinoFFT.h"

1. Definitionen

In den Deklarationsabschnitten haben wir

konstantes Byte adcPin = 0; // A0

const uint16_t Samples = 1024; // Dieser Wert MUSS IMMER eine Potenz von 2 sein const uint16_t SamplingFrequency = 8000; // Beeinflusst den maximalen Wert des Timers in timer_setup() SYSCLOCK/8/samplingFrequency sollte eine ganze Zahl sein

Da das Signal eine 5. Harmonische hat (Frequenz dieser Harmonischen = 5 * 440 = 2200 Hz), müssen wir die Abtastfrequenz über 2,5 * 2200 = 5500 Hz einstellen. Hier habe ich 8000 Hz gewählt.

Wir deklarieren auch die Arrays, in denen wir die Rohdaten und die berechneten Daten speichern

float vReal[Beispiele];

float vImag[Beispiele];

2) Instanziierung

Wir erstellen ein ArduinoFFT-Objekt. Die Entwicklerversion von ArduinoFFT verwendet eine Vorlage, sodass wir entweder den Float- oder den Double-Datentyp verwenden können. Float (32 Bit) reicht im Hinblick auf die Gesamtpräzision unseres Programms aus.

ArduinoFFT FFT = ArduinoFFT (vReal, vImag, Samples, SamplingFrequency);

3) Simulieren des Signals durch Auffüllen des vReal-Arrays, anstatt es mit ADC-Werten auffüllen zu lassen.

Am Anfang der Schleife füllen wir das vReal-Array mit:

Gleitkommazyklen = (((Abtastungen) * Signalfrequenz) / Abtastfrequenz); //Anzahl der Signalzyklen, die die Abtastung liest

for (uint16_t i = 0; i < Samples; i++) { vReal = float((Amplitude * (sin((i * (TWO_PI * Zyklen)) / Samples))));/* Daten mit positiven und. erstellen negative Werte*/ vReal += float((Amplitude * (sin((3 * i * (TWO_PI * Zyklen)) / Samples))) / 2.0);/* Daten mit positiven und negativen Werten erstellen*/ vReal += float((amplitude * (sin((5 * i * (TWO_PI * cycle)) / Samples))) / 4.0);/* Daten mit positiven und negativen Werten erstellen*/ vImag = 0.0; //Imaginärteil muss bei Schleifen auf Null gesetzt werden, um falsche Berechnungen und Überläufe zu vermeiden }

Wir fügen eine Digitalisierung der Grundwelle und der beiden Harmonischen mit geringerer Amplitude hinzu. Dann initialisieren wir das imaginäre Array mit Nullen. Da dieses Array mit dem FFT-Algorithmus gefüllt ist, müssen wir es vor jeder neuen Berechnung erneut löschen.

4) FFT-Berechnung

Dann berechnen wir die FFT und die spektrale Dichte

FFT.windowing(FFTWindow::Hamming, FFTDirection::Forward);

FFT.compute(FFTDirection::Forward); /* FFT berechnen */ FFT.complexToMagnitude(); /* Größen berechnen */

Die Operation FFT.windowing(…) modifiziert die Rohdaten, da wir die FFT mit einer begrenzten Anzahl von Samples ausführen. Die erste und die letzte Probe weisen eine Diskontinuität auf (es gibt "nichts" auf einer ihrer Seiten). Dies ist eine Fehlerquelle. Der Vorgang des "Fensterns" neigt dazu, diesen Fehler zu verringern.

FFT.compute(…) mit der Richtung "Vorwärts" berechnet die Transformation vom Zeitbereich in den Frequenzbereich.

Dann berechnen wir die Betrags- (d. h. Intensitäts-) Werte für jedes der Frequenzbänder. Das vReal-Array wird jetzt mit Magnitudenwerten gefüllt.

5) Serienplotterzeichnung

Lassen Sie uns die Werte auf dem seriellen Plotter drucken, indem wir die Funktion printVector(…) aufrufen.

PrintVector(vReal, (Beispiele >> 1), SCL_FREQUENCY);

Dies ist eine generische Funktion, die es ermöglicht, Daten mit einer Zeitachse oder einer Frequenzachse zu drucken.

Wir drucken auch die Frequenz des Bandes mit dem höchsten Betragswert

float x = FFT.majorPeak();

Serial.print("f0="); Serial.print (x, 6); Serial.println("Hz");

Schritt 5: Analyse eines simulierten Signals - Ergebnisse

Analyse eines simulierten Signals - Ergebnisse
Analyse eines simulierten Signals - Ergebnisse

Wir sehen 3 Spikes entsprechend der Grundfrequenz (f0), der 3. und 5. Harmonischen, mit der Hälfte und 1/4 der Größe von f0, wie erwartet. Oben im Fenster können wir f0 = 440.430114 Hz lesen. Dieser Wert beträgt aus allen oben erläuterten Gründen nicht exakt 440 Hz, liegt aber sehr nahe am tatsächlichen Wert. Es war nicht wirklich notwendig, so viele unbedeutende Dezimalstellen anzuzeigen.

Schritt 6: Analyse eines realen Signals - Verdrahtung des ADC

Analyse eines realen Signals - Verdrahtung des ADC
Analyse eines realen Signals - Verdrahtung des ADC

Da wir theoretisch wissen, wie man vorgeht, möchten wir ein reales Signal analysieren.

Die Verkabelung ist sehr einfach. Verbinden Sie die Masse und die Signalleitung über einen Vorwiderstand mit einem Wert von 1 KOhm bis 10 KOhm mit dem A0-Pin Ihrer Platine.

Dieser Vorwiderstand schützt den analogen Eingang und vermeidet Überschwingen. Sie muss so hoch wie möglich sein, um ein Klingeln zu vermeiden, und so niedrig wie möglich, um genügend Strom zum schnellen Laden des ADC bereitzustellen. Informationen zur erwarteten Impedanz des am ADC-Eingang angeschlossenen Signals finden Sie im Datenblatt der MCU.

Für diese Demo habe ich einen Funktionsgenerator verwendet, um ein sinusförmiges Signal mit einer Frequenz von 440 Hz und einer Amplitude von etwa 5 Volt (am besten ist es, wenn die Amplitude zwischen 3 und 5 Volt liegt, damit der ADC nahe der vollen Skala verwendet wird), über einen 1,2 KOhm Widerstand.

Schritt 7: Analyse eines realen Signals - Codierung

0) Binden Sie die Bibliothek ein

#include "arduinoFFT.h"

1) Erklärungen und Instanzen

Im Deklarationsabschnitt definieren wir wie im vorherigen Beispiel den ADC-Eingang (A0), die Anzahl der Abtastwerte und die Abtastfrequenz.

konstantes Byte adcPin = 0; // A0

const uint16_t Samples = 1024; // Dieser Wert MUSS IMMER eine Potenz von 2 sein const uint16_t SamplingFrequency = 8000; // Beeinflusst den maximalen Wert des Timers in timer_setup() SYSCLOCK/8/samplingFrequency sollte eine ganze Zahl sein

Wir erstellen das ArduinoFFT-Objekt

ArduinoFFT FFT = ArduinoFFT (vReal, vImag, Samples, SamplingFrequency);

2) Timer- und ADC-Setup

Wir stellen Timer 1 so ein, dass er mit der Abtastfrequenz (8 KHz) zyklisch läuft und beim Ausgangsvergleich einen Interrupt auslöst.

void timer_setup(){

// Timer 1 zurücksetzen TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; TCCR1B = Bit (CS11) | Bit (WGM12); // CTC, Vorteiler von 8 TIMSK1 = Bit (OCIE1B); OCR1A = ((1600000 / 8) / Abtastfrequenz) -1; }

Und stellen Sie den ADC so ein

  • Verwendet A0 als Eingang
  • Löst automatisch bei jedem Timer 1-Ausgang aus, vergleichen Sie die Übereinstimmung B
  • Erzeugt einen Interrupt, wenn die Konvertierung abgeschlossen ist

Der ADC-Takt wird auf 1 MHz eingestellt, indem der Systemtakt (16 MHz) um 16 vorskaliert wird. Da jede Umwandlung ungefähr 13 Takte bei voller Skala benötigt, können Umwandlungen bei einer Frequenz von 1/13 = 0,076 MHz = 76 KHz erreicht werden. Die Abtastfrequenz sollte deutlich unter 76 kHz liegen, damit der ADC die Zeit hat, die Daten abzutasten. (wir haben fs = 8 KHz gewählt).

void adc_setup() {

ADCSRA = Bit (ADEN) | Bit (ADIE) | Bit (ADIF); // ADC einschalten, Interrupt nach Abschluss wünschen ADCSRA |= Bit (ADPS2); // Vorteiler von 16 ADMUX = Bit (REFS0) | (adcPin & 7); // Einstellen des ADC-Eingangs ADCSRB = Bit (ADTS0) | Bit (ADTS2); // Timer/Counter1 Vergleich Match B Triggerquelle ADCSRA |= bit (ADATE); // automatische Triggerung einschalten }

Wir deklarieren den Interrupt-Handler, der nach jeder ADC-Konvertierung aufgerufen wird, um die konvertierten Daten im vReal-Array zu speichern und den Interrupt zu löschen

// ADC kompletter ISR

ISR (ADC_vect) { vReal[resultNumber++] = ADC; if (resultNumber == Samples) {ADCSRA = 0; // ADC ausschalten}} EMPTY_INTERRUPT (TIMER1_COMPB_vect);

Sie können eine umfassende Erklärung zur ADC-Konvertierung auf dem Arduino (analogRead) erhalten.

3) Einrichtung

In der Setup-Funktion löschen wir die imaginäre Datentabelle und rufen die Timer- und ADC-Setup-Funktionen auf

nullI(); // eine Funktion, die alle imaginären Daten auf 0 setzt - im vorherigen Abschnitt erklärt

timer_setup(); adc_setup();

3) Schleife

FFT.dcRemoval(); // Entfernen Sie die DC-Komponente dieses Signals, da der ADC auf Masse bezogen ist

FFT.windowing(FFTWindow::Hamming, FFTDirection::Forward); // Daten wiegen FFT.compute(FFTDirection::Forward); // FFT berechnen FFT.complexToMagnitude(); // Größen berechnen // das Spektrum und die Grundfrequenz drucken f0 PrintVector(vReal, (samples >> 1), SCL_FREQUENCY); float x = FFT.majorPeak(); Serial.print("f0="); Serial.print (x, 6); Serial.println("Hz");

Wir entfernen die DC-Komponente, da der ADC auf Masse bezogen ist und das Signal ungefähr bei 2,5 Volt zentriert ist.

Dann berechnen wir die Daten wie im vorherigen Beispiel erklärt.

Schritt 8: Analyse eines echten Signals - Ergebnisse

Analyse eines realen Signals - Ergebnisse
Analyse eines realen Signals - Ergebnisse

Tatsächlich sehen wir in diesem einfachen Signal nur eine Frequenz. Die berechnete Grundfrequenz beträgt 440.118194 Hz. Auch hier ist der Wert eine sehr enge Annäherung an die tatsächliche Frequenz.

Schritt 9: Was ist mit einem abgeschnittenen Sinussignal?

Was ist mit einem abgeschnittenen Sinussignal?
Was ist mit einem abgeschnittenen Sinussignal?

Lassen Sie nun den ADC ein wenig übersteuern, indem Sie die Amplitude des Signals über 5 Volt erhöhen, so dass es abgeschnitten wird. Drücken Sie nicht zu matschig, um den ADC-Eingang nicht zu zerstören!

Wir können einige Harmonische sehen. Das Abschneiden des Signals erzeugt Hochfrequenzkomponenten.

Sie haben die Grundlagen der FFT-Analyse auf einem Arduino-Board kennengelernt. Jetzt können Sie versuchen, die Sampling-Frequenz, die Anzahl der Samples und den Windowing-Parameter zu ändern. Die Bibliothek fügt auch einige Parameter hinzu, um die FFT schneller und mit geringerer Genauigkeit zu berechnen. Sie werden feststellen, dass die berechneten Größen aufgrund der spektralen Faltung völlig falsch erscheinen, wenn Sie die Abtastfrequenz zu niedrig einstellen.

Empfohlen: