Sommario:

Analizzatore di spettro FFT da 1024 campioni utilizzando un Atmega1284: 9 passaggi
Analizzatore di spettro FFT da 1024 campioni utilizzando un Atmega1284: 9 passaggi

Video: Analizzatore di spettro FFT da 1024 campioni utilizzando un Atmega1284: 9 passaggi

Video: Analizzatore di spettro FFT da 1024 campioni utilizzando un Atmega1284: 9 passaggi
Video: fft dell'oscilloscopio e analizzatore di spettro a confronto 2024, Luglio
Anonim
Analizzatore di spettro FFT da 1024 campioni utilizzando un Atmega1284
Analizzatore di spettro FFT da 1024 campioni utilizzando un Atmega1284
Analizzatore di spettro FFT da 1024 campioni utilizzando un Atmega1284
Analizzatore di spettro FFT da 1024 campioni utilizzando un Atmega1284

Questo tutorial relativamente semplice (considerando la complessità di questo argomento) ti mostrerà come puoi realizzare un semplice analizzatore di spettro da 1024 campioni usando una scheda di tipo Arduino (1284 Narrow) e il plotter seriale. Qualsiasi tipo di scheda compatibile con Arduino andrà bene, ma più RAM ha, la migliore risoluzione di frequenza che otterrai. Avranno bisogno di più di 8 KB di RAM per calcolare la FFT con 1024 campioni.

L'analisi dello spettro viene utilizzata per determinare le principali componenti di frequenza di un segnale. Molti suoni (come quelli prodotti da uno strumento musicale) sono composti da una frequenza fondamentale e da alcune armoniche che hanno una frequenza che è un multiplo intero della frequenza fondamentale. L'analizzatore di spettro ti mostrerà tutti questi componenti spettrali.

Potresti voler usare questa configurazione come un frequenzimetro o per controllare qualsiasi tipo di segnale che sospetti stia portando del rumore nel tuo circuito elettronico.

Ci concentreremo qui sulla parte software. Se desideri realizzare un circuito permanente per un'applicazione specifica, dovrai amplificare e filtrare il segnale. Questo precondizionamento è totalmente dipendente dal segnale che si desidera studiare, a seconda della sua ampiezza, impedenza, frequenza massima ecc… Puoi controllare

Passaggio 1: installazione della libreria

Useremo la libreria ArduinoFFT scritta da Enrique Condes. Poiché vogliamo risparmiare RAM il più possibile, utilizzeremo il ramo di sviluppo di questo repository che consente di utilizzare il tipo di dati float (invece di double) per memorizzare i dati campionati e calcolati. Quindi dobbiamo installarlo manualmente. Non preoccuparti, basta scaricare l'archivio e decomprimerlo nella cartella della libreria Arduino (ad esempio nella configurazione predefinita di Windows 10: C:\Users\_your_user_name_\Documents\Arduino\libraries)

Puoi verificare che la libreria sia installata correttamente compilando uno degli esempi forniti, come "FFT_01.ino".

Passaggio 2: trasformata di Fourier e concetti FFT

Avvertimento: se non riesci a sopportare di vedere alcuna notazione matematica, potresti voler saltare al passaggio 3. Comunque, se non ottieni tutto, considera la conclusione alla fine della sezione.

Lo spettro di frequenza è ottenuto tramite un algoritmo di trasformata di Fourier veloce. FFT è un'implementazione digitale che approssima il concetto matematico della trasformata di Fourier. Sotto questo concetto una volta ottenuta l'evoluzione di un segnale lungo un asse temporale, si può conoscere la sua rappresentazione in un dominio di frequenza, composto da valori complessi (reali + immaginari). Il concetto è reciproco, quindi quando conosci la rappresentazione nel dominio della frequenza puoi trasformarla di nuovo nel dominio del tempo e recuperare il segnale esattamente come prima della trasformazione.

Ma cosa faremo con questo insieme di valori complessi calcolati nel dominio del tempo? Bene, la maggior parte sarà lasciata agli ingegneri. Per noi chiameremo un altro algoritmo che trasformerà questi valori complessi in dati di densità spettrale: cioè un valore di grandezza (= intensità) associato a ciascuna banda di frequenza. Il numero di banda di frequenza sarà lo stesso del numero di campioni.

Sicuramente conosci il concetto di equalizzatore, come questo Ritorno agli anni '80 con l'EQ grafico. Bene, otterremo lo stesso tipo di risultati ma con 1024 bande invece di 16 e una risoluzione di intensità molto maggiore. Quando l'equalizzatore fornisce una visione globale della musica, l'analisi spettrale fine consente di calcolare con precisione l'intensità di ciascuna delle 1024 bande.

Un concetto perfetto, ma:

  1. Poiché la FFT è una versione digitalizzata della trasformata di Fourier, si avvicina al segnale digitale e perde alcune informazioni. Quindi, a rigor di termini, il risultato della FFT se trasformato di nuovo con un algoritmo FFT invertito non darebbe esattamente il segnale originale.
  2. Anche la teoria considera un segnale che non è finito, ma che è un segnale costante perenne. Dal momento che lo digitalizzeremo solo per un certo periodo di tempo (es. campioni), verranno introdotti alcuni errori in più.
  3. Infine, la risoluzione della conversione da analogico a digitale avrà un impatto sulla qualità dei valori calcolati.

In pratica

1) La frequenza di campionamento (nota fs)

Campioniamo un segnale, ovvero misuriamo la sua ampiezza, ogni 1/fs di secondo. fs è la frequenza di campionamento. Ad esempio, se campioniamo a 8 KHz, l'ADC (convertitore da analogico a digitale) che è a bordo del chip fornirà una misurazione ogni 1/8000 di secondo.

2) Il numero di campioni (indicato con N o campioni nel codice)

Poiché dobbiamo ottenere tutti i valori prima di eseguire la FFT, dovremo memorizzarli e quindi limiteremo il numero di campioni. L'algoritmo FFT ha bisogno di un numero di campioni che è una potenza di 2. Più campioni abbiamo meglio è ma richiede molta memoria, tanto più avremo bisogno anche di memorizzare i dati trasformati, che sono valori complessi. La libreria Arduino FFT consente di risparmiare spazio utilizzando

  • Un array chiamato "vReal" per memorizzare i dati campionati e quindi la parte reale dei dati trasformati
  • Un array chiamato "vImag" per memorizzare la parte immaginaria dei dati trasformati

La quantità di RAM necessaria è pari a 2 (array) * 32 (bit) * N (campioni).

Quindi nel nostro Atmega1284 che ha ben 16 KB di RAM memorizzeremo un massimo di N = 16000*8 / 64 = 2000 valori. Poiché il numero di valori deve essere una potenza di 2, memorizzeremo un massimo di 1024 valori.

3) La risoluzione in frequenza

La FFT calcolerà i valori per tante bande di frequenza quanti sono i campioni. Queste bande spazieranno da 0 HZ alla frequenza di campionamento (fs). Quindi, la risoluzione in frequenza è:

Fresoluzione = fs / N

La risoluzione è migliore quando è inferiore. Quindi per una migliore risoluzione (inferiore) vogliamo:

  • più campioni e/o
  • un fs inferiore

Ma…

4) Minimo fs

Dal momento che vogliamo vedere molte frequenze, alcune delle quali sono molto più alte della "frequenza fondamentale", non possiamo impostare fs troppo basso. Esiste infatti il teorema di campionamento di Nyquist-Shannon che ci obbliga ad avere una frequenza di campionamento ben al di sopra del doppio della frequenza massima che vorremmo testare.

Ad esempio, se volessimo analizzare tutto lo spettro da 0 Hz per dire 15 KHz, che è approssimativamente la frequenza massima che la maggior parte degli umani può sentire distintamente, dobbiamo impostare la frequenza di campionamento a 30 KHz. Infatti gli elettronici spesso la fissano a 2,5 (o anche 2,52) * la frequenza massima. In questo esempio sarebbe 2,5 * 15 KHz = 37,5 KHz. Le normali frequenze di campionamento nell'audio professionale sono 44,1 KHz (registrazione CD audio), 48 KHz e oltre.

Conclusione:

I punti da 1 a 4 portano a: vogliamo utilizzare il maggior numero possibile di campioni. Nel nostro caso con un dispositivo da 16 KB di RAM prenderemo in considerazione 1024 campioni. Vogliamo campionare alla frequenza di campionamento più bassa possibile, purché sia sufficientemente alta per analizzare la frequenza più alta che ci aspettiamo nel nostro segnale (2,5 * questa frequenza, almeno).

Passaggio 3: simulazione di un segnale

Simulazione di un segnale
Simulazione di un segnale

Per il nostro primo tentativo, modificheremo leggermente l'esempio TFT_01.ino fornito nella libreria, per analizzare un segnale composto da

  • La frequenza fondamentale, impostata a 440 Hz (LA musicale)
  • 3a armonica a metà della potenza della fondamentale ("-3 dB")
  • 5a armonica a 1/4 della potenza della fondamentale ("-6 dB)

Potete vedere nell'immagine sopra il segnale risultante. Sembra davvero molto simile a un segnale reale che a volte si può vedere su un oscilloscopio (lo chiamerei "Batman") in situazioni in cui c'è un clipping di un segnale sinusoidale.

Fase 4: Analisi di un segnale simulato - Codifica

0) Includere la libreria

#include "arduinoFFT.h"

1) Definizioni

Nelle sezioni delle dichiarazioni, abbiamo

const byte adcPin = 0; // A0

const uint16_t campioni = 1024; // Questo valore DEVE SEMPRE essere una potenza di 2 const uint16_t samplingFrequency = 8000; // Influirà sul valore massimo del timer in timer_setup() SYSCLOCK/8/samplingFrequency dovrebbe essere un numero intero

Poiché il segnale ha una quinta armonica (frequenza di questa armonica = 5 * 440 = 2200 Hz) dobbiamo impostare la frequenza di campionamento sopra 2,5*2200 = 5500 Hz. Qui ho scelto 8000 Hz.

Dichiariamo anche gli array in cui memorizzeremo i dati grezzi e calcolati

float vReal[campioni];

float vImag[campioni];

2) Istanziazione

Creiamo un oggetto ArduinoFFT. La versione dev di ArduinoFFT utilizza un modello in modo da poter utilizzare il tipo di dati float o double. Float (32 bit) è sufficiente per la precisione complessiva del nostro programma.

ArduinoFFT FFT = ArduinoFFT(vReal, vImag, campioni, frequenza di campionamento);

3) Simulare il segnale popolando l'array vReal, invece di popolarlo con i valori ADC.

All'inizio del Loop andiamo a popolare l'array vReal con:

cicli float = (((samples) * signalFrequency) / samplingFrequency); //Numero di cicli di segnale che il campionamento leggerà

for (uint16_t i = 0; i < campioni; i++) { vReal = float((ampiezza * (sin((i * (TWO_PI * cicli))) / campioni))));/* Crea dati con positivi e valori negativi*/ vReal += float((ampiezza * (sin((3 * i * (TWO_PI * cicli)) / campioni))) / 2.0);/* Crea dati con valori positivi e negativi*/ vReal += float((ampiezza * (sin((5 * i * (TWO_PI * cicli)) / campioni))) / 4.0);/* Crea dati con valori positivi e negativi*/ vImag = 0.0; //La parte immaginaria deve essere azzerata in caso di loop per evitare calcoli errati e overflow }

Aggiungiamo una digitalizzazione dell'onda fondamentale e delle due armoniche con minore ampiezza. Quindi inizializziamo l'array immaginario con zeri. Poiché questo array è popolato dall'algoritmo FFT, dobbiamo cancellarlo di nuovo prima di ogni nuovo calcolo.

4) Calcolo FFT

Quindi calcoliamo la FFT e la densità spettrale

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

FFT.compute(Direzione FFT::Avanti); /* Calcola FFT */ FFT.complexToMagnitude(); /* Calcola grandezze */

L'operazione FFT.windowing(…) modifica i dati grezzi perché eseguiamo la FFT su un numero limitato di campioni. Il primo e l'ultimo campione presentano una discontinuità (non c'è "niente" da uno dei loro lati). Questa è una fonte di errore. L'operazione di "finestra" tende a ridurre questo errore.

FFT.compute(…) con la direzione "Forward" calcola la trasformazione dal dominio del tempo al dominio della frequenza.

Quindi calcoliamo i valori di grandezza (cioè intensità) per ciascuna delle bande di frequenza. L'array vReal è ora riempito con i valori delle grandezze.

5) Disegno seriale del plotter

Stampiamo i valori sul plotter seriale chiamando la funzione printVector(…)

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

Questa è una funzione generica che permette di stampare i dati con un asse del tempo o un asse della frequenza.

Stampiamo anche la frequenza della banda che ha il valore di magnitudine più alto

float x = FFT.majorPeak();

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

Passaggio 5: analisi di un segnale simulato - Risultati

Analisi di un segnale simulato - Risultati
Analisi di un segnale simulato - Risultati

Vediamo 3 picchi corrispondenti alla frequenza fondamentale (f0), la 3a e la 5a armonica, con metà e 1/4 della magnitudine f0, come previsto. Possiamo leggere nella parte superiore della finestra f0= 440,430114 Hz. Questo valore non è esattamente 440 Hz, per tutte le ragioni spiegate sopra, ma è molto vicino al valore reale. Non era proprio necessario mostrare tanti decimali insignificanti.

Passaggio 6: analisi di un segnale reale - Cablaggio dell'ADC

Analisi di un segnale reale - Cablaggio dell'ADC
Analisi di un segnale reale - Cablaggio dell'ADC

Poiché sappiamo come procedere in teoria, vorremmo analizzare un segnale reale.

Il cablaggio è molto semplice. Collega le masse insieme e la linea del segnale al pin A0 della tua scheda tramite un resistore in serie con un valore da 1 KOhm a 10 KOhm.

Questo resistore in serie proteggerà l'ingresso analogico ed eviterà lo squillo. Deve essere il più alto possibile per evitare squilli e il più basso possibile per fornire una corrente sufficiente per caricare rapidamente l'ADC. Fare riferimento alla scheda tecnica MCU per conoscere l'impedenza prevista del segnale collegato all'ingresso ADC.

Per questa demo ho utilizzato un generatore di funzioni per alimentare un segnale sinusoidale di frequenza 440 Hz e ampiezza intorno ai 5 volt (è meglio se l'ampiezza è compresa tra 3 e 5 volt quindi si usa l'ADC vicino al fondo scala), tramite un resistore da 1.2 KOhm.

Passaggio 7: Analisi di un segnale reale - Codifica

0) Includere la libreria

#include "arduinoFFT.h"

1) Dichiarazioni e istanze

Nella sezione dichiarazione definiamo l'ingresso ADC (A0), il numero di campioni e la frequenza di campionamento, come nell'esempio precedente.

const byte adcPin = 0; // A0

const uint16_t campioni = 1024; // Questo valore DEVE SEMPRE essere una potenza di 2 const uint16_t samplingFrequency = 8000; // Influirà sul valore massimo del timer in timer_setup() SYSCLOCK/8/samplingFrequency dovrebbe essere un numero intero

Creiamo l'oggetto ArduinoFFT

ArduinoFFT FFT = ArduinoFFT(vReal, vImag, campioni, frequenza di campionamento);

2) Impostazione timer e ADC

Impostiamo il timer 1 in modo che cicli alla frequenza di campionamento (8 KHz) e sollevi un'interruzione sul confronto dell'uscita.

void timer_setup(){

// resetta il timer 1 TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; TCCR1B = bit (CS11) | bit (WGM12); // CTC, prescaler di 8 TIMSK1 = bit (OCIE1B); OCR1A = ((16000000 / 8) / frequenza di campionamento) -1; }

E imposta l'ADC in modo che sia

  • Utilizza A0 come input
  • Si attiva automaticamente su ogni uscita del timer 1 confronto confronto B
  • Genera un interrupt quando la conversione è completa

Il clock dell'ADC è impostato a 1 MHz, prescalando il clock di sistema (16 MHz) di 16. Poiché ogni conversione richiede circa 13 clock a fondo scala, le conversioni possono essere ottenute a una frequenza di 1/13 = 0,076 MHz = 76 KHz. La frequenza di campionamento dovrebbe essere significativamente inferiore a 76 KHz per consentire all'ADC di avere il tempo di campionare i dati. (abbiamo scelto fs = 8 KHz).

void adc_setup() {

ADCSRA = bit (ADEN) | bit (ADIE) | bit (ADIF); // attiva ADC, desidera l'interruzione al completamento ADCSRA |= bit (ADPS2); // Prescaler di 16 ADMUX = bit (REFS0) | (adcPin & 7); // impostazione dell'ingresso ADC ADCSRB = bit (ADTS0) | bit (ADTS2); // Timer/Contatore1 Confronta sorgente trigger Match B ADCSRA |= bit (ADATE); // attiva l'attivazione automatica }

Dichiariamo il gestore di interrupt che verrà chiamato dopo ogni conversione ADC per memorizzare i dati convertiti nell'array vReal e cancellare l'interrupt

// ADC ISR completo

ISR (ADC_vect) { vReal[numerorisultato++] = ADC; if(numerorisultato == campioni) { ADCSRA = 0; // disattiva l'ADC } } EMPTY_INTERRUPT (TIMER1_COMPB_vect);

Puoi avere una spiegazione esauriente sulla conversione ADC su Arduino (analogRead).

3) Configurazione

Nella funzione di configurazione cancelliamo la tabella dei dati immaginari e chiamiamo le funzioni di impostazione timer e ADC

zeroI(); // una funzione che imposta a 0 tutti i dati immaginari - spiegata nella sezione precedente

timer_setup(); adc_setup();

3) Ciclo

FFT.dcRimozione(); // Rimuove la componente DC di questo segnale poiché l'ADC fa riferimento a massa

FFT.windowing(FFTWindow::Hamming, FFTDirection::Forward); // Pesa i dati FFT.compute(FFTDirection::Forward); // Calcola FFT FFT.complexToMagnitude(); // Calcola le grandezze // stampa lo spettro e la frequenza fondamentale f0 PrintVector(vReal, (samples >> 1), SCL_FREQUENCY); float x = FFT.majorPeak(); Serial.print("f0="); Serial.print(x, 6); Serial.println("Hz");

Rimuoviamo il componente DC perché l'ADC fa riferimento a massa e il segnale è centrato intorno a 2,5 volt circa.

Quindi calcoliamo i dati come spiegato nell'esempio precedente.

Passaggio 8: analisi di un segnale reale - Risultati

Analisi di un segnale reale - Risultati
Analisi di un segnale reale - Risultati

Infatti vediamo solo una frequenza in questo semplice segnale. La frequenza fondamentale calcolata è 440,118194 Hz. Anche in questo caso il valore è un'approssimazione molto vicina alla frequenza reale.

Passaggio 9: che dire di un segnale sinusoidale tagliato?

Che dire di un segnale sinusoidale tagliato?
Che dire di un segnale sinusoidale tagliato?

Ora lasciamo sovraccaricare un po' l'ADC aumentando l'ampiezza del segnale sopra i 5 volt, in modo che venga tagliato. Non spingere troppo per non distruggere l'ingresso ADC!

Possiamo vedere apparire alcune armoniche. Il clipping del segnale crea componenti ad alta frequenza.

Hai visto i fondamenti dell'analisi FFT su una scheda Arduino. Ora puoi provare a cambiare la frequenza di campionamento, il numero di campioni e il parametro di windowing. La libreria aggiunge anche alcuni parametri per calcolare la FFT più velocemente con meno precisione. Noterai che se imposti la frequenza di campionamento troppo bassa, le grandezze calcolate appariranno totalmente errate a causa del ripiegamento spettrale.

Consigliato: