Cuprins:

1024 eșantioane FFT Spectrum Analyzer folosind un Atmega1284: 9 pași
1024 eșantioane FFT Spectrum Analyzer folosind un Atmega1284: 9 pași

Video: 1024 eșantioane FFT Spectrum Analyzer folosind un Atmega1284: 9 pași

Video: 1024 eșantioane FFT Spectrum Analyzer folosind un Atmega1284: 9 pași
Video: Training Midjourney Level Style And Yourself Into The SD 1.5 Model via DreamBooth Stable Diffusion 2024, Iulie
Anonim
1024 probe FFT Spectrum Analyzer folosind un Atmega1284
1024 probe FFT Spectrum Analyzer folosind un Atmega1284
1024 probe FFT Spectrum Analyzer folosind un Atmega1284
1024 probe FFT Spectrum Analyzer folosind un Atmega1284

Acest tutorial relativ ușor (având în vedere complexitatea acestui subiect) vă va arăta cum puteți face un analizor de spectru de probe foarte simplu de 1024 utilizând o placă de tip Arduino (1284 Narrow) și plotterul serial. Orice tip de placă compatibilă Arduino va funcționa, dar cu cât are mai multă memorie RAM, cu atât veți obține cea mai bună rezoluție de frecvență. Va avea nevoie de mai mult de 8 KB de RAM pentru a calcula FFT cu 1024 de mostre.

Analiza spectrului este utilizată pentru a determina principalele componente de frecvență ale unui semnal. Multe sunete (cum ar fi cele produse de un instrument muzical) sunt compuse dintr-o frecvență fundamentală și unele armonici care au o frecvență care este un multiplu întreg al frecvenței fundamentale. Analizorul de spectru vă va arăta toate aceste componente spectrale.

S-ar putea să doriți să utilizați această configurație ca un contor de frecvență sau pentru a verifica orice tip de semnale despre care bănuiți că aduce zgomot în circuitul dvs. electronic.

Ne vom concentra aici pe partea software. Dacă doriți să creați un circuit permanent pentru o anumită aplicație, va trebui să amplificați și să filtrați semnalul. Această precondiționare depinde în totalitate de semnalul pe care doriți să-l studiați, în funcție de amplitudinea, impedanța, frecvența maximă etc. etc. Puteți verifica

Pasul 1: Instalarea bibliotecii

Vom folosi biblioteca ArduinoFFT scrisă de Enrique Condes. Din moment ce dorim să economisim RAM cât mai mult posibil, vom folosi ramura de dezvoltare a acestui depozit care permite utilizarea tipului de date flotant (în loc de dublu) pentru a stoca datele eșantionate și calculate. Deci, trebuie să-l instalăm manual. Nu vă faceți griji, descărcați arhiva și decomprimați-o în dosarul bibliotecii Arduino (de exemplu, în configurația implicită Windows 10: C: / Utilizatori / _numele_utilizator_dvs._ / Documente / Arduino / biblioteci)

Puteți verifica dacă biblioteca este instalată corect compilând unul dintre exemplele furnizate, cum ar fi „FFT_01.ino”.

Pasul 2: Transformate Fourier și concepte FFT

Atenție: dacă nu suportați să vedeți vreo notație matematică, vă recomandăm să treceți la pasul 3. Oricum, dacă nu obțineți totul, luați în considerare concluzia de la sfârșitul secțiunii.

Spectrul de frecvență este obținut printr-un algoritm de transformare Fourier rapidă. FFT este o implementare digitală care aproximează conceptul matematic al Transformatei Fourier. Conform acestui concept, odată ce obțineți evoluția unui semnal urmând o axă de timp, puteți cunoaște reprezentarea acestuia într-un domeniu de frecvență, compus din valori complexe (reale + imaginare). Conceptul este reciproc, deci atunci când cunoașteți reprezentarea domeniului de frecvență, îl puteți transforma înapoi în domeniul timpului și puteți obține semnalul înapoi exact ca înainte de transformare.

Dar ce vom face cu acest set de valori complexe calculate în domeniul timpului? Ei bine, cea mai mare parte va fi lăsată în sarcina inginerilor. Pentru noi vom numi un alt algoritm care va transforma aceste valori complexe în date de densitate spectrală: adică o valoare de magnitudine (= intensitate) asociată cu fiecare bandă de frecvență. Numărul benzii de frecvență va fi același cu numărul de eșantioane.

Sunteți sigur că sunteți familiarizați cu conceptul de egalizator, ca acesta Înapoi în anii 1980, cu EQ grafic. Ei bine, vom obține același tip de rezultate, dar cu 1024 benzi în loc de 16 și o rezoluție de intensitate mult mai mare. Când egalizatorul oferă o imagine globală a muzicii, analiza spectrală fină permite calcularea precisă a intensității fiecăreia dintre cele 1024 de benzi.

Un concept perfect, dar:

  1. Deoarece FFT este o versiune digitalizată a transformatei Fourier, aceasta aproximează semnalul digital și pierde unele informații. Deci, strict vorbind, rezultatul FFT dacă este transformat înapoi cu un algoritm FFT inversat nu ar da exact semnalul original.
  2. De asemenea, teoria consideră un semnal care nu este finit, dar care este un semnal constant permanent. Deoarece îl vom digitaliza doar pentru o anumită perioadă de timp (adică eșantioane), vor fi introduse alte erori.
  3. În cele din urmă, rezoluția conversiei analogice la digitale va avea impact asupra calității valorilor calculate.

In practica

1) Frecvența de eșantionare (fs notat)

Vom preleva un semnal, adică îi vom măsura amplitudinea, la fiecare 1 / fs secunde. fs este frecvența de eșantionare. De exemplu, dacă prelevăm probe la 8 KHz, ADC (convertor analogic digital) care se află la bordul cipului va furniza o măsurare la fiecare 1/8000 de secunde.

2) Numărul de eșantioane (notat N sau eșantioane în cod)

Deoarece trebuie să obținem toate valorile înainte de a rula FFT, va trebui să le stocăm și astfel vom limita numărul de eșantioane. Algoritmul FFT are nevoie de un număr de eșantioane care este o putere de 2. Cu cât avem mai multe eșantioane, cu atât este mai bine, dar este nevoie de multă memorie, cu atât mai mult că vom avea nevoie și pentru a stoca datele transformate, care sunt valori complexe. Biblioteca Arduino FFT economisește puțin spațiu folosind

  • O matrice numită „vReal” pentru a stoca datele eșantionate și apoi partea reală a datelor transformate
  • O matrice numită „vImag” pentru a stoca partea imaginară a datelor transformate

Cantitatea necesară de RAM este egală cu 2 (matrici) * 32 (biți) * N (eșantioane).

Deci, în Atmega1284, care are 16 KB de memorie RAM, vom stoca maximum N = 16000 * 8/64 = 2000 valori. Deoarece numărul de valori trebuie să fie o putere de 2, vom stoca maximum 1024 de valori.

3) Rezoluția frecvenței

FFT va calcula valori pentru atâtea benzi de frecvență cât numărul de eșantioane. Aceste benzi se vor întinde de la 0 HZ la frecvența de eșantionare (fs). Prin urmare, rezoluția frecvenței este:

Rezoluție = fs / N

Rezoluția este mai bună atunci când este mai mică. Deci, pentru o rezoluție mai bună (mai mică) dorim:

  • mai multe probe și / sau
  • o fs mai mică

Dar…

4) Minime fs

Din moment ce vrem să vedem o mulțime de frecvențe, unele dintre ele fiind mult mai mari decât „frecvența fundamentală”, nu putem seta fs prea scăzut. De fapt, există teorema de eșantionare Nyquist – Shannon care ne obligă să avem o frecvență de eșantionare mult peste dublul frecvenței maxime pe care am dori să o testăm.

De exemplu, dacă am dori să analizăm tot spectrul de la 0 Hz pentru a spune 15 KHz, care este aproximativ frecvența maximă pe care majoritatea oamenilor o pot auzi distinct, trebuie să setăm frecvența de eșantionare la 30 KHz. De fapt, electronienii o stabilesc adesea la 2,5 (sau chiar 2,52) * frecvența maximă. În acest exemplu, acesta ar fi 2,5 * 15 KHz = 37,5 KHz. Frecvențele obișnuite de eșantionare în sunetul profesional sunt de 44,1 KHz (înregistrare audio CD), 48 KHz și mai mult.

Concluzie:

Punctele de la 1 la 4 conduc la: vrem să folosim cât mai multe eșantioane posibil. În cazul nostru, cu un dispozitiv de 16 KB de RAM, vom lua în considerare 1024 de mostre. Vrem să prelevăm probe la cea mai mică frecvență de eșantionare posibilă, atât timp cât este suficient de mare pentru a analiza cea mai mare frecvență pe care o așteptăm în semnalul nostru (cel puțin 2,5 * această frecvență).

Pasul 3: Simularea unui semnal

Simularea unui semnal
Simularea unui semnal

Pentru prima noastră încercare, vom modifica ușor exemplul TFT_01.ino dat în bibliotecă, pentru a analiza un semnal compus din

  • Frecvența fundamentală, setată la 440 Hz (muzical A)
  • A treia armonică la jumătate din puterea fundamentalului („-3 dB”)
  • A 5-a armonică la 1/4 din puterea fundamentalului ("-6 dB)

Puteți vedea în imaginea de mai sus semnalul rezultat. Arată într-adevăr foarte mult ca un semnal real pe care îl poți vedea uneori pe un osciloscop (aș numi-l „Batman”) în situația în care există o decupare a unui semnal sinusoidal.

Pasul 4: Analiza unui semnal simulat - codare

0) Includeți biblioteca

#include "arduinoFFT.h"

1) Definiții

În secțiunile de declarații, avem

octet const adcPin = 0; // A0

const uint16_t mostre = 1024; // Această valoare TREBUIE să fie întotdeauna o putere de 2 const uint16_t samplingFrequency = 8000; // Va afecta valoarea maximă a temporizatorului în timer_setup () SYSCLOCK / 8 / sampling Frecvența ar trebui să fie un număr întreg

Deoarece semnalul are a 5-a armonică (frecvența acestei armonici = 5 * 440 = 2200 Hz) trebuie să setăm frecvența de eșantionare peste 2,5 * 2200 = 5500 Hz. Aici am ales 8000 Hz.

De asemenea, declarăm matricile în care vom stoca datele brute și calculate

float vReal [mostre];

float vImag [mostre];

2) Instanțierea

Creăm un obiect ArduinoFFT. Versiunea dev a ArduinoFFT folosește un șablon, astfel încât să putem utiliza fie tipul de date flotant, fie dublu. Float (32 de biți) este suficient în ceea ce privește precizia generală a programului nostru.

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

3) Simularea semnalului prin popularea matricei vReal, în loc să fie populat cu valori ADC.

La începutul buclei, populăm matricea vReal cu:

cicluri de plutire = (((mostre) * signalFrequency) / samplingFrequency); // Numărul de cicluri de semnal pe care le va citi eșantionarea

for (uint16_t i = 0; i <samples; i ++) {vReal = float ((amplitude * (sin ((i * (TWO_PI * cycles)) / samples)))); / * Build data with positive and valori negative * / vReal + = float ((amplitudine * (sin ((3 * i * (TWO_PI * cicluri)) / mostre))) / 2.0); / * Construiți date cu valori pozitive și negative * / vReal + = float ((amplitudine * (sin ((5 * i * (TWO_PI * cicluri)) / mostre))) / 4.0); / * Construiți date cu valori pozitive și negative * / vImag = 0,0; // Partea imaginară trebuie redusă la zero în caz de buclă pentru a evita calcule greșite și revărsări}

Adăugăm o digitalizare a undei fundamentale și a celor două armonici cu o amplitudine mai mică. Decât inițializăm matricea imaginară cu zerouri. Deoarece această matrice este populată de algoritmul FFT, trebuie să o ștergem din nou înainte de fiecare nou calcul.

4) Calcul FFT

Apoi calculăm FFT și densitatea spectrală

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

FFT.compute (FFTDirection:: Forward); / * Calcul FFT * / FFT.complexToMagnitude (); / * Calculați mărimile * /

Operația FFT.windowing (…) modifică datele brute deoarece rulăm FFT pe un număr limitat de eșantioane. Primele și ultimele eșantioane prezintă o discontinuitate (nu există „nimic” pe una din părțile lor). Aceasta este o sursă de eroare. Operațiunea „fereastră” tinde să reducă această eroare.

FFT.compute (…) cu direcția „Înainte” calculează transformarea din domeniul timpului în domeniul frecvenței.

Apoi calculăm valorile de magnitudine (adică intensitate) pentru fiecare dintre benzile de frecvență. Matricea vReal este acum umplută cu valori ale mărimilor.

5) Desen grafic plotter

Să imprimăm valorile pe plotterul serial apelând funcția printVector (…)

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

Aceasta este o funcție generică care permite tipărirea datelor cu axa timpului sau axa frecvenței.

De asemenea, imprimăm frecvența benzii care are cea mai mare valoare a magnitudinii

float x = FFT.majorPeak ();

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

Pasul 5: Analiza unui semnal simulat - Rezultate

Analiza unui semnal simulat - Rezultate
Analiza unui semnal simulat - Rezultate

Vedem 3 vârfuri corespunzătoare frecvenței fundamentale (f0), armonii 3 și 5, cu jumătate și 1/4 din magnitudinea f0, așa cum era de așteptat. Putem citi în partea de sus a ferestrei f0 = 440,430114 Hz. Această valoare nu este exact 440 Hz, din toate motivele explicate mai sus, dar este foarte aproape de valoarea reală. Nu era cu adevărat necesar să arăți atât de multe zecimale nesemnificative.

Pasul 6: Analiza unui semnal real - Cablarea ADC

Analiza unui semnal real - Cablarea ADC
Analiza unui semnal real - Cablarea ADC

Deoarece știm cum să procedăm în teorie, am dori să analizăm un semnal real.

Cablarea este foarte simplă. Conectați terenurile împreună și linia de semnal la pinul A0 al plăcii dvs. printr-un rezistor de serie cu o valoare de 1 KOhm la 10 KOhm.

Acest rezistor de serie va proteja intrarea analogică și va evita soneria. Trebuie să fie cât mai mare posibil pentru a evita soneria și cât mai scăzut posibil pentru a furniza suficient curent pentru a încărca ADC rapid. Consultați fișa tehnică MCU pentru a cunoaște impedanța așteptată a semnalului conectat la intrarea ADC.

Pentru această demonstrație am folosit un generator de funcții pentru a alimenta un semnal sinusoidal de frecvență 440 Hz și amplitudine în jurul a 5 volți (cel mai bine este dacă amplitudinea este cuprinsă între 3 și 5 volți, astfel încât ADC este utilizat aproape de întreaga scală), printr-un rezistor de 1,2 KOhm.

Pasul 7: Analiza unui semnal real - codare

0) Includeți biblioteca

#include "arduinoFFT.h"

1) Declarații și instanțare

În secțiunea de declarație definim intrarea ADC (A0), numărul de eșantioane și frecvența de eșantionare, ca în exemplul anterior.

octet const adcPin = 0; // A0

const uint16_t mostre = 1024; // Această valoare TREBUIE să fie întotdeauna o putere de 2 const uint16_t samplingFrequency = 8000; // Va afecta valoarea maximă a temporizatorului în timer_setup () SYSCLOCK / 8 / sampling Frecvența ar trebui să fie un număr întreg

Creăm obiectul ArduinoFFT

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

2) Cronometru și configurare ADC

Am setat cronometrul 1 astfel încât acesta să cicleze la frecvența de eșantionare (8 KHz) și crește o întrerupere la compararea ieșirii.

void timer_setup () {

// resetează temporizatorul 1 TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; TCCR1B = bit (CS11) | bit (WGM12); // CTC, prescaler de 8 TIMSK1 = bit (OCIE1B); OCR1A = ((16000000/8) / samplingFrequency) -1; }

Și setați ADC astfel

  • Folosește A0 ca intrare
  • Declanșează automat pe fiecare temporizator 1 ieșire compară potrivirea B
  • Generează o întrerupere la finalizarea conversiei

Ceasul ADC este setat la 1 MHz, prin prescalarea ceasului sistemului (16 MHz) cu 16. Deoarece fiecare conversie durează aproximativ 13 ceasuri la scară completă, conversiile pot fi realizate la o frecvență de 1/13 = 0,076 MHz = 76 KHz. Frecvența de eșantionare ar trebui să fie semnificativ mai mică de 76 KHz pentru a permite ADC-ului să aibă timp să probeze datele. (am ales fs = 8 KHz).

void adc_setup () {

ADCSRA = bit (ADEN) | bit (ADIE) | bit (ADIF); // porniți ADC, doriți întrerupere la finalizare ADCSRA | = bit (ADPS2); // Prescalator de 16 ADMUX = bit (REFS0) | (adcPin & 7); // setarea intrării ADC ADCSRB = bit (ADTS0) | bit (ADTS2); // Timer / Counter1 Compare Match B trigger source ADCSRA | = bit (ADATE); // activați declanșarea automată}

Declarăm gestionarul de întrerupere care va fi apelat după fiecare conversie ADC pentru a stoca datele convertite în matricea vReal și ștergerea întreruperii

// ADC completează ISR

ISR (ADC_vect) {vReal [resultNumber ++] = ADC; if (resultNumber == mostre) {ADCSRA = 0; // opriți ADC}} EMPTY_INTERRUPT (TIMER1_COMPB_vect);

Puteți avea o explicație exhaustivă despre conversia ADC pe Arduino (analogRead).

3) Configurare

În funcția de configurare ștergem tabelul de date imaginare și apelăm funcțiile de configurare temporizator și ADC

zeroI (); // o funcție care setează la 0 toate datele imaginare - explicată în secțiunea anterioară

timer_setup (); adc_setup ();

3) Buclă

FFT.dcRemoval (); // Îndepărtați componenta DC a acestui semnal, deoarece ADC este menționat la masă

FFT.windowing (FFTWindow:: Hamming, FFTDirection:: Forward); // Cântărește date FFT.compute (FFTDirection:: Forward); // Calculați FFT FFT.complexToMagnitude (); // Calculați mărimile // tipărind spectrul și frecvența fundamentală f0 PrintVector (vReal, (samples >> 1), SCL_FREQUENCY); float x = FFT.majorPeak (); Serial.print ("f0 ="); Serial.print (x, 6); Serial.println ("Hz");

Îndepărtăm componenta de curent continuu deoarece ADC este trimis la masă și semnalul este centrat în jur de 2,5 volți.

Apoi calculăm datele așa cum s-a explicat în exemplul anterior.

Pasul 8: Analiza unui semnal real - Rezultate

Analiza unui semnal real - Rezultate
Analiza unui semnal real - Rezultate

Într-adevăr, vedem o singură frecvență în acest semnal simplu. Frecvența fundamentală calculată este de 440,118194 Hz. Din nou, valoarea este o aproximare foarte apropiată a frecvenței reale.

Pasul 9: Ce zici de un semnal sinusoidal tăiat?

Dar un semnal sinusoidal tăiat?
Dar un semnal sinusoidal tăiat?

Acum, permite overdrive puțin ADC prin creșterea amplitudinii semnalului peste 5 volți, astfel încât acesta este decupat. Nu împingeți prea mult ca să nu distrugeți intrarea ADC!

Putem vedea câteva armonici care apar. Decuparea semnalului creează componente de înaltă frecvență.

Ați văzut fundamentele analizei FFT pe o placă Arduino. Acum puteți încerca să modificați frecvența de eșantionare, numărul de eșantioane și parametrul de fereastră. Biblioteca adaugă, de asemenea, unii parametri pentru a calcula FFT-ul mai repede cu mai puțină precizie. Veți observa că, dacă setați frecvența de eșantionare prea mică, magnitudinile calculate vor apărea total eronate din cauza plierii spectrale.

Recomandat: