Inhoudsopgave:

1024 monsters FFT Spectrum Analyzer met behulp van een Atmega1284 - Ajarnpa
1024 monsters FFT Spectrum Analyzer met behulp van een Atmega1284 - Ajarnpa

Video: 1024 monsters FFT Spectrum Analyzer met behulp van een Atmega1284 - Ajarnpa

Video: 1024 monsters FFT Spectrum Analyzer met behulp van een Atmega1284 - Ajarnpa
Video: EEVblog #891 - Siglent SSA3021X vs Rigol DSA815 Spectrum Analyser 2024, December
Anonim
1024 monsters FFT-spectrumanalyser met behulp van een Atmega1284
1024 monsters FFT-spectrumanalyser met behulp van een Atmega1284
1024 monsters FFT-spectrumanalyser met behulp van een Atmega1284
1024 monsters FFT-spectrumanalyser met behulp van een Atmega1284

Deze relatief eenvoudige tutorial (gezien de complexiteit van dit onderwerp) zal je laten zien hoe je een zeer eenvoudige 1024 samples spectrum analyzer kunt maken met behulp van een Arduino type board (1284 Narrow) en de seriële plotter. Elk type Arduino-compatibel bord is voldoende, maar hoe meer RAM het heeft, de beste frequentieresolutie die u krijgt. Er is meer dan 8 KB RAM nodig om de FFT te berekenen met 1024 samples.

Spectrumanalyse wordt gebruikt om de belangrijkste frequentiecomponenten van een signaal te bepalen. Veel geluiden (zoals die geproduceerd door een muziekinstrument) zijn samengesteld uit een grondfrequentie en sommige harmonischen die een frequentie hebben die een geheel veelvoud is van de grondfrequentie. De spectrumanalysator laat u al deze spectrale componenten zien.

Misschien wilt u deze opstelling gebruiken als frequentieteller of om elk soort signaal te controleren waarvan u vermoedt dat het wat ruis in uw elektronische circuit veroorzaakt.

We zullen ons hier concentreren op het softwaregedeelte. Als u een permanente schakeling voor een specifieke toepassing wilt maken, moet u het signaal versterken en filteren. Deze pre-conditionering is volledig afhankelijk van het signaal dat u wilt bestuderen, afhankelijk van de amplitude, impedantie, maximale frequentie enz. U kunt

Stap 1: De bibliotheek installeren

We zullen de ArduinoFFT-bibliotheek gebruiken die is geschreven door Enrique Condes. Omdat we RAM zoveel mogelijk willen sparen, zullen we de ontwikkeltak van deze repository gebruiken die het mogelijk maakt om het float-gegevenstype (in plaats van dubbel) te gebruiken om de gesamplede en berekende gegevens op te slaan. We moeten het dus handmatig installeren. Maak je geen zorgen, download het archief en decomprimeer het in je Arduino-bibliotheekmap (bijvoorbeeld in de standaardconfiguratie van Windows 10: C:\Users\_your_user_name_\Documents\Arduino\libraries)

U kunt controleren of de bibliotheek correct is geïnstalleerd door een van de meegeleverde voorbeelden te compileren, zoals "FFT_01.ino".

Stap 2: Fourier-transformatie en FFT-concepten

Waarschuwing: als u geen wiskundige notatie kunt zien, kunt u naar stap 3 gaan. Hoe dan ook, als u niet alles begrijpt, overweeg dan de conclusie aan het einde van de sectie.

Het frequentiespectrum wordt verkregen via een Fast Fourier Transform-algoritme. FFT is een digitale implementatie die het wiskundige concept van de Fourier-transformatie benadert. Volgens dit concept kun je, zodra je de evolutie van een signaal volgt dat een tijdas volgt, de representatie ervan kennen in een frequentiedomein, samengesteld uit complexe (reële + denkbeeldige) waarden. Het concept is wederkerig, dus als je de representatie van het frequentiedomein kent, kun je het terug naar het tijdsdomein transformeren en het signaal terugkrijgen precies zoals voor de transformatie.

Maar wat gaan we doen met deze reeks berekende complexe waarden in het tijdsdomein? Het meeste wordt overgelaten aan ingenieurs. Voor ons zullen we een ander algoritme noemen dat deze complexe waarden omzet in spectrale dichtheidsgegevens: dat is een magnitude (= intensiteit) waarde die bij elke frequentieband hoort. Het aantal frequentiebanden is hetzelfde als het aantal samples.

U kent vast het equalizerconcept, zoals dit Back to the 1980s With the Graphic EQ. Welnu, we zullen hetzelfde soort resultaten verkrijgen, maar met 1024 banden in plaats van 16 en veel meer intensiteitsresolutie. Wanneer de equalizer een globaal beeld van de muziek geeft, maakt de fijne spectrale analyse het mogelijk om de intensiteit van elk van de 1024 banden nauwkeurig te berekenen.

Een perfect concept, maar:

  1. Aangezien de FFT een gedigitaliseerde versie van de Fourier-transformatie is, benadert hij het digitale signaal en verliest hij wat informatie. Dus strikt genomen zou het resultaat van de FFT, indien teruggetransformeerd met een omgekeerd FFT-algoritme, niet precies het oorspronkelijke signaal geven.
  2. Ook houdt de theorie rekening met een signaal dat niet eindig is, maar dat een eeuwigdurend constant signaal is. Omdat we het slechts voor een bepaalde periode (d.w.z. monsters) zullen digitaliseren, zullen er nog meer fouten worden geïntroduceerd.
  3. Ten slotte zal de resolutie van de analoog naar digitaal conversie de kwaliteit van de berekende waarden beïnvloeden.

In praktijk

1) De bemonsteringsfrequentie (opgemerkt fs)

We zullen een signaal bemonsteren, d.w.z. de amplitude meten, elke 1/fs seconde. fs is de bemonsteringsfrequentie. Als we bijvoorbeeld samplen op 8 KHz, zal de ADC (analoog naar digitaal omzetter) die zich aan boord van de chip bevindt, elke 1/8000 seconde een meting geven.

2) Het aantal monsters (opgemerkt N of monsters in de code)

Omdat we alle waarden moeten krijgen voordat we de FFT uitvoeren, moeten we ze opslaan en daarom zullen we het aantal monsters beperken. Het FFT-algoritme heeft een aantal samples nodig met een macht van 2. Hoe meer samples we hebben, hoe beter, maar het neemt veel geheugen in beslag, des te meer zullen we ook de getransformeerde gegevens moeten opslaan, dat zijn complexe waarden. De Arduino FFT-bibliotheek bespaart wat ruimte door gebruik te maken van

  • Eén array met de naam "vReal" om de gesamplede gegevens op te slaan en vervolgens het reële deel van de getransformeerde gegevens
  • Eén array met de naam "vImag" om het denkbeeldige deel van de getransformeerde gegevens op te slaan

De benodigde hoeveelheid RAM is gelijk aan 2 (arrays) * 32 (bits) * N (samples).

Dus in onze Atmega1284 die een mooie 16 KB RAM heeft, zullen we maximaal N = 16000*8 / 64 = 2000 waarden opslaan. Aangezien het aantal waarden een macht van 2 moet zijn, zullen we maximaal 1024 waarden opslaan.

3) De frequentieresolutie:

De FFT berekent waarden voor net zoveel frequentiebanden als het aantal samples. Deze banden lopen van 0 Hz tot de bemonsteringsfrequentie (fs). De frequentieresolutie is dus:

Fresolutie = fs / N

De resolutie is beter als deze lager is. Dus voor een betere resolutie (lager) willen we:

  • meer monsters, en/of
  • een lagere fs

Maar…

4) Minimale fs

Omdat we veel frequenties willen zien, waarvan sommige veel hoger zijn dan de "fundamentele frequentie", kunnen we fs niet te laag instellen. In feite is er de bemonsteringsstelling van Nyquist-Shannon die ons dwingt een bemonsteringsfrequentie te hebben die ruim boven twee keer de maximale frequentie ligt die we zouden willen testen.

Als we bijvoorbeeld het hele spectrum van 0 Hz tot bijvoorbeeld 15 KHz willen analyseren, wat ongeveer de maximale frequentie is die de meeste mensen duidelijk kunnen horen, moeten we de bemonsteringsfrequentie instellen op 30 KHz. In feite stellen elektronici het vaak op 2,5 (of zelfs 2,52) * de maximale frequentie. In dit voorbeeld zou dat 2,5 * 15 KHz = 37,5 KHz zijn. Gebruikelijke bemonsteringsfrequenties in professionele audio zijn 44,1 KHz (audio-cd-opname), 48 KHz en meer.

Conclusie:

Punten 1 t/m 4 leiden tot: we willen zoveel mogelijk samples gebruiken. In ons geval met een 16 KB RAM-apparaat zullen we 1024 voorbeelden overwegen. We willen samplen op de laagst mogelijke bemonsteringsfrequentie, zolang deze hoog genoeg is om de hoogste frequentie te analyseren die we in ons signaal verwachten (minimaal 2,5 * deze frequentie).

Stap 3: Een signaal simuleren

Een signaal simuleren
Een signaal simuleren

Voor onze eerste poging zullen we het TFT_01.ino-voorbeeld in de bibliotheek enigszins wijzigen om een signaal te analyseren dat bestaat uit

  • De grondfrequentie, ingesteld op 440 Hz (musical A)
  • 3e harmonische op halve kracht van de grondtoon ("-3 dB")
  • 5e harmonische op 1/4 van de kracht van de grondtoon ("-6 dB)

U kunt in de afbeelding hierboven het resulterende signaal zien. Het lijkt inderdaad erg op een echt signaal dat je soms kunt zien op een oscilloscoop (ik zou het "Batman" noemen) in een situatie waarin er een clipping is van een sinusvormig signaal.

Stap 4: Analyse van een gesimuleerd signaal - codering

0) Voeg de bibliotheek toe

#include "arduinoFFT.h"

1. Definities

In de declaraties secties hebben we:

const-byte adcPin = 0; // A0

const uint16_t monsters = 1024; // Deze waarde MOET ALTIJD een macht van 2 zijn const uint16_t samplingFrequency = 8000; // Heeft invloed op de maximale waarde van de timer in timer_setup() SYSCLOCK/8/samplingFrequency moet een geheel getal zijn

Aangezien het signaal een 5e harmonischen heeft (frequentie van deze harmonische = 5 * 440 = 2200 Hz) moeten we de bemonsteringsfrequentie boven 2,5*2200 = 5500 Hz instellen. Hier koos ik voor 8000 Hz.

We declareren ook de arrays waar we de onbewerkte en berekende gegevens zullen opslaan

float vReal[voorbeelden];

float vImag[voorbeelden];

2) Instantie

We maken een ArduinoFFT-object. De dev-versie van ArduinoFFT gebruikt een sjabloon, zodat we het float- of het dubbele gegevenstype kunnen gebruiken. Float (32 bits) is voldoende voor de algehele precisie van ons programma.

ArduinoFFT FFT = ArduinoFFT (vReal, vImag, voorbeelden, bemonsteringsfrequentie);

3) Simulatie van het signaal door de vReal-array te vullen in plaats van deze te laten vullen met ADC-waarden.

Aan het begin van de Loop vullen we de vReal-array met:

float-cycli = (((samples) * signalFrequency) / samplingFrequency); // Aantal signaalcycli dat de bemonstering zal lezen

for (uint16_t i = 0; i < samples; i++) { vReal = float((amplitude * (sin((i * (TWO_PI * cycles)) / samples))));/* Bouw gegevens met positieve en negatieve waarden*/ vReal += float((amplitude * (sin((3 * i * (TWO_PI * cycles)) / samples))) / 2.0);/* Bouw gegevens met positieve en negatieve waarden*/ vReal += float((amplitude * (sin((5 * i * (TWO_PI * cycles)) / samples))) / 4.0);/* Bouw gegevens met positieve en negatieve waarden*/ vImag = 0.0; //Het denkbeeldige deel moet op nul worden gezet in het geval van een lus om verkeerde berekeningen en overlopen te voorkomen}

We voegen een digitalisering toe van de grondgolf en de twee harmonischen met minder amplitude. Dan initialiseren we de imaginaire array met nullen. Aangezien deze array wordt bevolkt door het FFT-algoritme, moeten we deze voor elke nieuwe berekening opnieuw wissen.

4) FFT-computergebruik

Vervolgens berekenen we de FFT en de spectrale dichtheid

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

FFT.compute (FFTDirection::Forward); /* Bereken FFT */ FFT.complexToMagnitude(); /* Bereken grootheden */

De bewerking FFT.windowing(…) wijzigt de onbewerkte gegevens omdat we de FFT op een beperkt aantal voorbeelden uitvoeren. De eerste en laatste samples vertonen een discontinuïteit (er is "niets" aan een van hun kant). Dit is een bron van fouten. De "windowing"-bewerking heeft de neiging om deze fout te verminderen.

FFT.compute(…) met de richting "Forward" berekent de transformatie van het tijdsdomein naar het frequentiedomein.

Vervolgens berekenen we de magnitude (d.w.z. intensiteit) waarden voor elk van de frequentiebanden. De vReal-array is nu gevuld met waarden voor magnitudes.

5) Seriële plottertekening:

Laten we de waarden op de seriële plotter afdrukken door de functie printVector(…)

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

Dit is een generieke functie die het mogelijk maakt om gegevens af te drukken met een tijdas of een frequentieas.

We drukken ook de frequentie af van de band met de hoogste waarde

float x = FFT.majorPeak();

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

Stap 5: Analyse van een gesimuleerd signaal - Resultaten

Analyse van een gesimuleerd signaal - Resultaten
Analyse van een gesimuleerd signaal - Resultaten

We zien 3 pieken die overeenkomen met de grondfrequentie (f0), de 3e en 5e harmonischen, met de helft en 1/4e van de f0-magnitude, zoals verwacht. We kunnen bovenaan het venster f0= 440.430114 Hz lezen. Deze waarde is niet precies 440 Hz, vanwege alle hierboven uiteengezette redenen, maar het ligt heel dicht bij de echte waarde. Het was niet echt nodig om zoveel onbeduidende decimalen te tonen.

Stap 6: Analyse van een echt signaal - Bedrading van de ADC

Analyse van een echt signaal - Bedrading van de ADC
Analyse van een echt signaal - Bedrading van de ADC

Omdat we in theorie weten hoe we te werk moeten gaan, willen we een echt signaal analyseren.

De bedrading is heel eenvoudig. Verbind de massa's met elkaar en de signaallijn met de A0-pin van je bord via een serieweerstand met een waarde van 1 KOhm tot 10 KOhm.

Deze serieweerstand beschermt de analoge ingang en vermijdt overgaan. Het moet zo hoog mogelijk zijn om rinkelen te voorkomen en zo laag mogelijk om voldoende stroom te leveren om de ADC snel op te laden. Raadpleeg het MCU-gegevensblad om de verwachte impedantie te kennen van het signaal dat is aangesloten op de ADC-ingang.

Voor deze demo heb ik een functiegenerator gebruikt om een sinusvormig signaal met een frequentie van 440 Hz en een amplitude van ongeveer 5 volt te voeden (het is het beste als de amplitude tussen 3 en 5 volt is, zodat de ADC bijna op volledige schaal wordt gebruikt), via een weerstand van 1,2 KOhm.

Stap 7: Analyse van een echt signaal - codering

0) Voeg de bibliotheek toe

#include "arduinoFFT.h"

1) Verklaringen en instantie

In het declaratiegedeelte definiëren we de ADC-ingang (A0), het aantal monsters en de bemonsteringsfrequentie, zoals in het vorige voorbeeld.

const-byte adcPin = 0; // A0

const uint16_t monsters = 1024; // Deze waarde MOET ALTIJD een macht van 2 zijn const uint16_t samplingFrequency = 8000; // Heeft invloed op de maximale waarde van de timer in timer_setup() SYSCLOCK/8/samplingFrequency moet een geheel getal zijn

We maken het ArduinoFFT-object

ArduinoFFT FFT = ArduinoFFT (vReal, vImag, samples, samplingFrequency);

2) Timer- en ADC-instelling

We hebben timer 1 zo ingesteld dat deze draait op de bemonsteringsfrequentie (8 KHz) en een onderbreking opwekt bij het vergelijken van de uitvoer.

void timer_setup(){

// reset Timer 1 TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; TCCR1B = bit (CS11) | beetje (WGM12); // CTC, prescaler van 8 TIMSK1 = bit (OCIE1B); OCR1A = ((16000000 / 8) / bemonsteringsfrequentie) -1; }

En stel de ADC zo in

  • Gebruikt A0 als invoer
  • Activeert automatisch op elke timer 1 uitgang vergelijk match B
  • Genereert een interrupt wanneer de conversie is voltooid

De ADC-klok is ingesteld op 1 MHz, door de systeemklok (16 MHz) vooraf te schalen met 16. Aangezien elke conversie ongeveer 13 klokken op volle schaal kost, kunnen conversies worden bereikt met een frequentie van 1/13 = 0,076 MHz = 76 KHz. De bemonsteringsfrequentie moet aanzienlijk lager zijn dan 76 KHz om de ADC de tijd te geven om de gegevens te samplen. (we kozen fs = 8 KHz).

ongeldig adc_setup() {

ADCSRA = bit (ADEN) | bit (ADIE) | beetje (ADIF); // zet ADC aan, wil onderbreking bij voltooiing ADCSRA |= bit (ADPS2); // Prescaler van 16 ADMUX = bit (REFS0) | (adcPin & 7); // instelling van de ADC-ingang ADCSRB = bit (ADTS0) | beetje (ADTS2); // Timer/Counter1 Vergelijk Match B-triggerbron ADCSRA |= bit (ADATE); // schakel automatische triggering in}

We declareren de interrupt-handler die na elke ADC-conversie wordt aangeroepen om de geconverteerde gegevens in de vReal-array op te slaan en de interrupt te wissen

// ADC complete ISR

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

U kunt een uitgebreide uitleg krijgen over ADC-conversie op de Arduino (analogRead).

3) Opstelling

In de setup-functie wissen we de denkbeeldige gegevenstabel en roepen we de timer- en ADC-setupfuncties op

nulI(); // een functie die alle denkbeeldige gegevens op 0 zet - uitgelegd in de vorige sectie

timer_setup(); adc_setup();

3) Lus

FFT.dcRemoval(); // Verwijder de DC-component van dit signaal omdat de ADC naar aarde verwijst

FFT.windowing(FFTWindow::Hamming, FFTDirection::Forward); // Weeggegevens FFT.compute (FFTDirection::Forward); // Bereken FFT FFT.complexToMagnitude(); // Bereken magnitudes // printen van het spectrum en de fundamentele frequentie f0 PrintVector(vReal, (samples >> 1), SCL_FREQUENCY); float x = FFT.majorPeak(); Serieel.print("f0="); Serieafdruk(x, 6); Serieel.println("Hz");

We verwijderen de DC-component omdat de ADC naar aarde wordt verwezen en het signaal ongeveer 2,5 volt is gecentreerd.

Vervolgens berekenen we de gegevens zoals uitgelegd in het vorige voorbeeld.

Stap 8: Analyse van een echt signaal - Resultaten

Analyse van een echt signaal - Resultaten
Analyse van een echt signaal - Resultaten

We zien inderdaad maar één frequentie in dit eenvoudige signaal. De berekende grondfrequentie is 440,118194 Hz. Ook hier is de waarde een zeer goede benadering van de werkelijke frequentie.

Stap 9: Hoe zit het met een geknipt sinusoïdaal signaal?

Hoe zit het met een geknipt sinusoïdaal signaal?
Hoe zit het met een geknipt sinusoïdaal signaal?

Laten we nu de ADC een beetje oversturen door de amplitude van het signaal boven 5 volt te vergroten, zodat het wordt afgekapt. Duw niet te veel om de ADC-ingang niet te vernietigen!

We kunnen enkele harmonischen zien verschijnen. Door het signaal te knippen, ontstaan hoogfrequente componenten.

Je hebt de basisprincipes van FFT-analyse gezien op een Arduino-bord. Nu kunt u proberen de bemonsteringsfrequentie, het aantal monsters en de vensterparameter te wijzigen. De bibliotheek voegt ook een parameter toe om de FFT sneller en met minder precisie te berekenen. U zult merken dat als u de bemonsteringsfrequentie te laag instelt, de berekende magnitudes totaal onjuist zullen lijken vanwege spectrale vouwing.

Aanbevolen: