Innehållsförteckning:

1024 Prover FFT -spektrumanalysator med hjälp av en Atmega1284: 9 steg
1024 Prover FFT -spektrumanalysator med hjälp av en Atmega1284: 9 steg

Video: 1024 Prover FFT -spektrumanalysator med hjälp av en Atmega1284: 9 steg

Video: 1024 Prover FFT -spektrumanalysator med hjälp av en Atmega1284: 9 steg
Video: How to check the speed the internet? - Python 2024, Juli
Anonim
1024 prov FFT -spektrumanalysator med hjälp av en Atmega1284
1024 prov FFT -spektrumanalysator med hjälp av en Atmega1284
1024 Prover FFT -spektrumanalysator med hjälp av en Atmega1284
1024 Prover FFT -spektrumanalysator med hjälp av en Atmega1284

Denna relativt enkla handledning (med tanke på komplexiteten i detta ämne) kommer att visa dig hur du kan göra en mycket enkel 1024 samplingsspektrumanalysator med ett Arduino -typkort (1284 smal) och seriell plotter. Alla typer av Arduino -kompatibla kort kommer att göra, men ju mer RAM -minne det har, den bästa frekvensupplösningen får du. Det kommer att behöva mer än 8 KB RAM för att beräkna FFT med 1024 prover.

Spektrumanalys används för att bestämma huvudfrekvenskomponenterna i en signal. Många ljud (som de som produceras av ett musikinstrument) består av en grundfrekvens och vissa övertoner som har en frekvens som är en heltalsmultipel av grundfrekvensen. Spektrumanalysatorn visar dig alla dessa spektralkomponenter.

Du kanske vill använda den här inställningen som en frekvensräknare eller för att kontrollera alla typer av signaler som du misstänker att det ger brus i din elektroniska krets.

Vi kommer att fokusera här på programvarudelen. Om du vill skapa en permanent krets för en specifik applikation måste du förstärka och filtrera signalen. Denna förkonditionering är helt beroende av signalen du vill studera, beroende på dess amplitud, impedans, maximala frekvens etc … Du kan kolla

Steg 1: Installera biblioteket

Vi kommer att använda ArduinoFFT -biblioteket skrivet av Enrique Condes. Eftersom vi vill spara RAM så mycket som möjligt kommer vi att använda utvecklingsgrenen av det här förvaret som gör det möjligt att använda floatdatatypen (i stället för det dubbla) för att lagra samplad och beräknad data. Så vi måste installera det manuellt. Oroa dig inte, ladda bara ner arkivet och packa upp det i din Arduino -biblioteksmapp (till exempel i Windows 10 standardkonfiguration: C: / Users / _your_user_name_ / Documents / Arduino / libraries)

Du kan kontrollera att biblioteket är korrekt installerat genom att sammanställa ett av de angivna exemplen, till exempel "FFT_01.ino."

Steg 2: Fourier Transform och FFT -koncept

Varning: om du inte tål att se någon matematisk notering kanske du vill hoppa till steg 3. Hur som helst, om du inte får allt, tänk bara på slutsatsen i slutet av avsnittet.

Frekvensspektrumet erhålls genom en Fast Fourier Transform -algoritm. FFT är en digital implementering som approximerar det matematiska konceptet Fourier Transform. Under detta koncept när du väl får utvecklingen av en signal efter en tidsaxel, kan du känna dess representation i en frekvensdomän, sammansatt av komplexa (verkliga + imaginära) värden. Konceptet är ömsesidigt, så när du känner till frekvensdomänrepresentationen kan du transformera tillbaka det till tidsdomänen och få tillbaka signalen precis som före transformationen.

Men vad ska vi göra med denna uppsättning beräknade komplexa värden i tidsdomänen? Det mesta kommer att överlåtas till ingenjörer. För oss kommer vi att kalla en annan algoritm som kommer att omvandla dessa komplexa värden till spektral densitetsdata: det är ett värde (= intensitet) som är associerat med varje frekvensband. Antalet frekvensband kommer att vara detsamma som antalet sampel.

Du är säkert bekant med equalizer -konceptet, som det här Tillbaka till 1980 -talet med den grafiska EQ. Tja, vi kommer att få samma typ av resultat men med 1024 band istället för 16 och mycket mer intensitetsupplösning. När equalizern ger en global bild av musiken tillåter den fina spektralanalysen att exakt beräkna intensiteten för vart och ett av de 1024 banden.

Ett perfekt koncept, men:

  1. Eftersom FFT är en digitaliserad version av Fourier -transformen, närmar den sig den digitala signalen och tappar lite information. Så, strikt taget, skulle resultatet av FFT om det transformeras tillbaka med en inverterad FFT -algoritm inte ge exakt den ursprungliga signalen.
  2. Teorin tar också hänsyn till en signal som inte är begränsad, men som är en ständigt konstant signal. Eftersom vi bara kommer att digitalisera det under en viss tidsperiod (dvs. prover) kommer några fler fel att införas.
  3. Slutligen kommer upplösningen av den analoga till digitala omvandlingen att påverka kvaliteten på de beräknade värdena.

I praktiken

1) Samplingsfrekvensen (noterad fs)

Vi kommer att sampla en signal, dvs mäta dess amplitud, var 1/fs sekund. fs är samplingsfrekvensen. Till exempel om vi samplar vid 8 KHz, kommer ADC (analog till digital omvandlare) som finns ombord på chipet att ge en mätning var 1/8000 sekunder.

2) Antalet prover (noterat N eller prov i koden)

Eftersom vi måste få alla värden innan vi kör FFT måste vi lagra dem och därför kommer vi att begränsa antalet prover. FFT -algoritmen behöver ett antal sampel som har en effekt på 2. Ju fler samplingar vi har desto bättre men det tar mycket minne, desto mer kommer vi också att behöva lagra de transformerade data, som är komplexa värden. Arduino FFT -biblioteket sparar lite utrymme genom att använda

  • En array som heter "vReal" för att lagra samplad data och sedan den verkliga delen av den transformerade datan
  • En array som heter "vImag" för att lagra den imaginära delen av den transformerade datan

Den nödvändiga mängden RAM är 2 (matriser) * 32 (bitar) * N (samplingar).

Så i vår Atmega1284 som har fina 16 KB RAM lagrar vi maximalt N = 16000*8/64 = 2000 värden. Eftersom antalet värden måste vara en effekt på 2 lagrar vi maximalt 1024 värden.

3) Frekvensupplösningen

FFT beräknar värden för lika många frekvensband som antalet sampel. Dessa band sträcker sig från 0 HZ till samplingsfrekvensen (fs). Därför är frekvensupplösningen:

Upplösning = fs / N

Upplösningen är bättre när den är lägre. Så för bättre upplösning (lägre) vill vi ha:

  • fler prover, och/eller
  • en lägre fs

Men…

4) Minimal fs

Eftersom vi vill se många frekvenser, några av dem är mycket högre än "grundfrekvensen" kan vi inte ställa in fs för lågt. Det finns faktiskt Nyquist – Shannon -samplingssatsen som tvingar oss att ha en samplingsfrekvens långt över dubbelt så hög som den vi skulle vilja testa.

Om vi till exempel vill analysera allt spektrum från 0 Hz för att säga 15 KHz, vilket är ungefär den maximala frekvensen som de flesta människor kan höra tydligt, måste vi ställa in samplingsfrekvensen till 30 KHz. Faktum är att elektroniker ofta ställer in det på 2,5 (eller till och med 2,52) * maxfrekvensen. I det här exemplet skulle det vara 2,5 * 15 KHz = 37,5 KHz. Vanliga samplingsfrekvenser för professionellt ljud är 44,1 KHz (ljud -CD -inspelning), 48 KHz och mer.

Slutsats:

Punkterna 1 till 4 leder till: vi vill använda så många prover som möjligt. I vårt fall med en 16 KB RAM -enhet kommer vi att överväga 1024 prover. Vi vill sampla med den lägsta samplingsfrekvensen som möjligt, så länge den är tillräckligt hög för att analysera den högsta frekvensen vi förväntar oss i vår signal (minst 2,5 * denna frekvens).

Steg 3: Simulera en signal

Simulera en signal
Simulera en signal

För vårt första försök kommer vi att modifiera TFT_01.ino -exemplet något i biblioteket för att analysera en signal som består av

  • Grundfrekvensen, inställd på 440 Hz (musikal A)
  • 3: e övertonen vid halva kraften av den grundläggande ("-3 dB")
  • 5: e övertonen vid 1/4 av kraften i den grundläggande ("-6 dB)

Du kan se på bilden ovanför den resulterande signalen. Det ser verkligen mycket ut som en riktig signal som man ibland kan se på ett oscilloskop (jag skulle kalla det "Batman") i situationer när det sker en klippning av en sinusformad signal.

Steg 4: Analys av en simulerad signal - kodning

0) Inkludera biblioteket

#inkludera "arduinoFFT.h"

1) Definitioner

I deklarationsavsnitten har vi

const byte adcPin = 0; // A0

const uint16_t samples = 1024; // Detta värde MÅSTE ALLTID vara en effekt på 2 const uint16_t samplingFrequency = 8000; // Kommer att påverka timerns maxvärde i timer_setup () SYSCLOCK/8/samplingFrekvensen ska vara ett heltal

Eftersom signalen har en femte övertoner (frekvensen för denna överton = 5 * 440 = 2200 Hz) måste vi ställa in samplingsfrekvensen över 2,5 * 2200 = 5500 Hz. Här valde jag 8000 Hz.

Vi deklarerar också de matriser där vi kommer att lagra rådata och beräknad data

float vReal [prover];

float vImag [prover];

2) Instantiering

Vi skapar ett ArduinoFFT -objekt. Dev -versionen av ArduinoFFT använder en mall så att vi kan använda antingen float eller dubbel datatyp. Float (32 bitar) är tillräckligt med avseende på programmets övergripande precision.

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

3) Simulera signalen genom att fylla i vReal -arrayen, istället för att den ska fyllas med ADC -värden.

I början av slingan fyller vi vReal -arrayen med:

flottörcykler = (((samplar) * signalFrequency) / samplingFrequency); // Antal signalcykler som samplingen kommer att läsa

for (uint16_t i = 0; i <samples; i ++) {vReal = float ((amplitud * (sin ((i * (TWO_PI * cycles)) / samples))))) / / Skapa data med positiva och negativa värden */ vReal += float ((amplitud * (sin ((3 * i * (TWO_PI * cykler))/ prover)))/ 2.0);/ * Skapa data med positiva och negativa värden */ vReal += float ((amplitud * (sin ((5 * i * (TWO_PI * cykler)) / prover))) / 4.0); / * Skapa data med positiva och negativa värden * / vImag = 0.0; // Imaginär del måste nollställas vid looping för att undvika felaktiga beräkningar och överflöd}

Vi lägger till en digitalisering av grundvågen och de två övertonerna med mindre amplitud. Än vi initierar den imaginära matrisen med nollor. Eftersom denna matris fylls av FFT -algoritmen måste vi rensa den igen före varje ny beräkning.

4) FFT -beräkning

Sedan beräknar vi FFT och spektraltätheten

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

FFT.compute (FFTDirection:: Framåt); / * Beräkna FFT */ FFT.complexToMagnitude (); / * Beräkna storheter */

FFT.windowing (…) ändrar rådata eftersom vi kör FFT på ett begränsat antal prover. De första och sista proverna presenterar en diskontinuitet (det finns "ingenting" på deras sida). Detta är en felkälla. "Fönstret" -operationen tenderar att minska detta fel.

FFT.compute (…) med riktningen "Framåt" beräknar transformationen från tidsdomänen till frekvensdomänen.

Sedan beräknar vi storleken (dvs. intensiteten) för varje frekvensband. VReal -matrisen är nu fylld med magnitudvärden.

5) Seriell plotterritning

Låt oss skriva ut värdena på seriell plotter genom att anropa funktionen printVector (…)

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

Detta är en generisk funktion som gör det möjligt att skriva ut data med en tidsaxel eller en frekvensaxel.

Vi skriver också ut frekvensen för bandet som har det högsta storleksvärdet

float x = FFT.majorPeak ();

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

Steg 5: Analys av en simulerad signal - Resultat

Analys av en simulerad signal - Resultat
Analys av en simulerad signal - Resultat

Vi ser tre spikar motsvarande grundfrekvensen (f0), den tredje och femte övertonerna, med hälften och 1/4 av f0 -storleken, som förväntat. Vi kan läsa högst upp i fönstret f0 = 440.430114 Hz. Detta värde är inte exakt 440 Hz, på grund av alla skäl som förklarats ovan, men det är mycket nära det verkliga värdet. Det var egentligen inte nödvändigt att visa så många obetydliga decimaler.

Steg 6: Analys av en verklig signal - Anslutning av ADC

Analys av en verklig signal - Anslutning av ADC
Analys av en verklig signal - Anslutning av ADC

Eftersom vi vet hur vi ska gå tillväga i teorin, skulle vi vilja analysera en verklig signal.

Ledningarna är mycket enkla. Anslut grunderna tillsammans och signalledningen till A0 -stiftet på ditt kort genom ett seriemotstånd med ett värde av 1 KOhm till 10 KOhm.

Detta seriemotstånd skyddar den analoga ingången och undviker att ringa. Det måste vara så högt som möjligt för att undvika att det ringer och så lågt som möjligt för att ge tillräckligt med ström för att ladda ADC snabbt. Se MCU -databladet för att veta den förväntade impedansen för signalen som är ansluten till ADC -ingången.

För denna demo använde jag en funktionsgenerator för att mata en sinusformad signal med frekvensen 440 Hz och amplituden runt 5 volt (det är bäst om amplituden är mellan 3 och 5 volt så att ADC används nära full skala), via ett 1,2 KOhm motstånd.

Steg 7: Analys av en verklig signal - kodning

0) Inkludera biblioteket

#inkludera "arduinoFFT.h"

1) Deklarationer och instans

I deklarationsdelen definierar vi ADC -ingången (A0), antalet samplingar och samplingsfrekvensen, som i föregående exempel.

const byte adcPin = 0; // A0

const uint16_t samples = 1024; // Detta värde MÅSTE ALLTID vara en effekt på 2 const uint16_t samplingFrequency = 8000; // Kommer att påverka timerns maxvärde i timer_setup () SYSCLOCK/8/samplingFrekvensen ska vara ett heltal

Vi skapar ArduinoFFT -objektet

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

2) Inställning av timer och ADC

Vi ställer in timer 1 så att den cyklar vid samplingsfrekvensen (8 KHz) och höjer ett avbrott vid utgångsjämförelsen.

ogiltig timer_setup () {

// reset timer 1 TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; TCCR1B = bit (CS11) | bit (WGM12); // CTC, förskalare av 8 TIMSK1 = bit (OCIE1B); OCR1A = ((16000000 /8) / provtagningsfrekvens) -1; }

Och ställ in ADC så

  • Använder A0 som ingång
  • Utlöses automatiskt på varje timer 1 -utgång jämför match B
  • Genererar ett avbrott när konverteringen är klar

ADC -klockan är inställd på 1 MHz genom att förskala systemklockan (16 MHz) med 16. Eftersom varje omvandling tar cirka 13 klockor i full skala kan omvandlingar uppnås med en frekvens av 1/13 = 0,076 MHz = 76 KHz. Samplingsfrekvensen bör vara betydligt lägre än 76 KHz för att ADC ska få tid att sampla data. (vi valde fs = 8 KHz).

void adc_setup () {

ADCSRA = bit (ADEN) | bit (ADIE) | bit (ADIF); // Slå på ADC, vill avbryta vid slutförande ADCSRA | = bit (ADPS2); // Förskalare av 16 ADMUX = bit (REFS0) | (adcPin & 7); // ställa in ADC -ingången ADCSRB = bit (ADTS0) | bit (ADTS2); // Timer/Counter1 Jämför Match B trigger source ADCSRA | = bit (ADATE); // slå på automatisk utlösning}

Vi deklarerar avbrottshanteraren som kommer att kallas efter varje ADC -konvertering för att lagra den konverterade data i vReal -arrayen och rensa avbrottet

// ADC komplett ISR

ISR (ADC_vect) {vReal [resultNumber ++] = ADC; if (resultNumber == samples) {ADCSRA = 0; // stäng av ADC}} EMPTY_INTERRUPT (TIMER1_COMPB_vect);

Du kan ha en uttömmande förklaring till ADC -konvertering på Arduino (analogRead).

3) Inställning

I installationsfunktionen rensar vi den imaginära datatabellen och ringer timer- och ADC -inställningsfunktionerna

noll (); // en funktion som satt till 0 alla imaginära data - förklarade i föregående avsnitt

timer_setup (); adc_setup ();

3) Loop

FFT.dcRemoval (); // Ta bort DC -komponenten i denna signal eftersom ADC refereras till jord

FFT.windowing (FFTWindow:: Hamming, FFTDirection:: Forward); // Väg data FFT.compute (FFTDirection:: Forward); // Beräkna FFT FFT.complexToMagnitude (); // Beräkna storheter // skriva ut spektrumet och grundfrekvensen f0 PrintVector (vReal, (prover >> 1), SCL_FREQUENCY); float x = FFT.majorPeak (); Serial.print ("f0 ="); Serial.print (x, 6); Serial.println ("Hz");

Vi tar bort DC -komponenten eftersom ADC refereras till jord och signalen är cirka 2,5 volt centrerad.

Därefter beräknar vi data enligt förklaringen i föregående exempel.

Steg 8: Analys av en verklig signal - Resultat

Analys av en verklig signal - Resultat
Analys av en verklig signal - Resultat

Vi ser faktiskt bara en frekvens i denna enkla signal. Den beräknade grundfrekvensen är 440,118194 Hz. Även här är värdet en mycket nära approximation av den verkliga frekvensen.

Steg 9: Hur är det med en urklippt sinusformad signal?

Hur är det med en urklippt sinusformad signal?
Hur är det med en urklippt sinusformad signal?

Nu kan vi överdriva ADC lite genom att öka amplituden för signalen över 5 volt, så den klipps. Tryck inte för mycket för att inte förstöra ADC -ingången!

Vi kan se några övertoner som dyker upp. Klippning av signalen skapar högfrekventa komponenter.

Du har sett grunderna för FFT -analys på ett Arduino -kort. Nu kan du försöka ändra samplingsfrekvensen, antalet samplingar och fönsterparametern. Biblioteket lägger också till en parameter för att beräkna FFT snabbare med mindre precision. Du kommer att märka att om du ställer in samplingsfrekvensen för låg, kommer de beräknade storheterna att visas helt felaktiga på grund av spektralvikning.

Rekommenderad: