Índice:

Analisador de espectro FFT de 1024 amostras usando um Atmega1284: 9 etapas
Analisador de espectro FFT de 1024 amostras usando um Atmega1284: 9 etapas
Anonim
Analisador de espectro FFT de 1024 amostras usando um Atmega1284
Analisador de espectro FFT de 1024 amostras usando um Atmega1284
Analisador de espectro FFT de 1024 amostras usando um Atmega1284
Analisador de espectro FFT de 1024 amostras usando um Atmega1284

Este tutorial relativamente fácil (considerando a complexidade deste assunto) mostrará como você pode fazer um analisador de espectro de 1024 amostras muito simples usando uma placa do tipo Arduino (1284 Narrow) e o plotter serial. Qualquer tipo de placa compatível com Arduino serve, mas quanto mais RAM ela tiver, melhor resolução de frequência você terá. Será necessário mais de 8 KB de RAM para calcular o FFT com 1024 amostras.

A análise de espectro é usada para determinar os principais componentes de frequência de um sinal. Muitos sons (como aqueles produzidos por um instrumento musical) são compostos por uma frequência fundamental e alguns harmônicos que possuem uma frequência que é um múltiplo inteiro da frequência fundamental. O analisador de espectro mostrará todos esses componentes espectrais.

Você pode querer usar esta configuração como um contador de frequência ou para verificar qualquer tipo de sinal que você suspeita que esteja trazendo algum ruído em seu circuito eletrônico.

Vamos nos concentrar aqui na parte do software. Se quiser fazer um circuito permanente para uma aplicação específica, você precisará amplificar e filtrar o sinal. Este pré-condicionamento é totalmente dependente do sinal que você deseja estudar, dependendo de sua amplitude, impedância, frequência máxima etc … Você pode verificar

Etapa 1: Instalando a Biblioteca

Estaremos usando a biblioteca ArduinoFFT escrita por Enrique Condes. Como queremos poupar RAM o máximo possível, usaremos o branch de desenvolvimento deste repositório que permite usar o tipo de dados float (em vez de double) para armazenar os dados amostrados e calculados. Portanto, temos que instalá-lo manualmente. Não se preocupe, basta fazer o download do arquivo e descompactá-lo na pasta da biblioteca do Arduino (por exemplo, na configuração padrão do Windows 10: C: / Users / _your_user_name_ / Documents / Arduino / libraries)

Você pode verificar se a biblioteca está instalada corretamente compilando um dos exemplos fornecidos, como "FFT_01.ino".

Etapa 2: Transformada de Fourier e conceitos FFT

Aviso: se você não aguenta ver nenhuma notação matemática, pode pular para a Etapa 3. De qualquer forma, se você não entender tudo, considere a conclusão no final da seção.

O espectro de frequência é obtido por meio de um algoritmo de Transformada Rápida de Fourier. FFT é uma implementação digital que se aproxima do conceito matemático da Transformada de Fourier. Sob este conceito, uma vez que você obtenha a evolução de um sinal seguindo um eixo do tempo, você pode saber sua representação em um domínio de frequência, composto de valores complexos (reais + imaginários). O conceito é recíproco, portanto, quando você conhece a representação no domínio da frequência, pode transformá-la de volta no domínio do tempo e obter o sinal de volta exatamente como antes da transformação.

Mas o que vamos fazer com esse conjunto de valores complexos computados no domínio do tempo? Bem, a maior parte será deixada para os engenheiros. Para nós, chamaremos outro algoritmo que transformará esses valores complexos em dados de densidade espectral: ou seja, um valor de magnitude (= intensidade) associado a cada banda de frequência. O número da banda de frequência será igual ao número de amostras.

Você certamente está familiarizado com o conceito de equalizador, como este Back to the 1980s With the Graphic EQ. Bem, obteremos o mesmo tipo de resultados, mas com 1024 bandas em vez de 16 e muito mais resolução de intensidade. Quando o equalizador dá uma visão global da música, a análise espectral fina permite calcular com precisão a intensidade de cada uma das 1024 bandas.

Um conceito perfeito, mas:

  1. Como a FFT é uma versão digitalizada da transformada de Fourier, ela se aproxima do sinal digital e perde algumas informações. Então, estritamente falando, o resultado da FFT se transformado de volta com um algoritmo FFT invertido não daria exatamente o sinal original.
  2. Além disso, a teoria considera um sinal que não é finito, mas que é um sinal constante e duradouro. Uma vez que iremos digitalizá-lo apenas por um determinado período de tempo (ou seja, amostras), mais alguns erros serão introduzidos.
  3. Finalmente, a resolução da conversão analógica para digital terá impacto na qualidade dos valores calculados.

Na prática

1) A frequência de amostragem (notado fs)

Vamos amostrar um sinal, ou seja, medir sua amplitude, a cada 1 / fs segundos. fs é a frequência de amostragem. Por exemplo, se amostrarmos a 8 KHz, o ADC (conversor analógico para digital) que está integrado no chip fornecerá uma medição a cada 1/8000 de segundos.

2) O número de amostras (observado N ou amostras no código)

Como precisamos obter todos os valores antes de executar a FFT, teremos que armazená-los e, portanto, limitaremos o número de amostras. O algoritmo FFT precisa de um número de amostras que é uma potência de 2. Quanto mais amostras tivermos, melhor, mas é preciso muita memória, tanto mais que também precisaremos armazenar os dados transformados, que são valores complexos. A biblioteca Arduino FFT economiza algum espaço usando

  • Uma matriz chamada "vReal" para armazenar os dados de amostra e, em seguida, a parte real dos dados transformados
  • Uma matriz chamada "vImag" para armazenar a parte imaginária dos dados transformados

A quantidade necessária de RAM é igual a 2 (matrizes) * 32 (bits) * N (amostras).

Portanto, em nosso Atmega1284 que tem ótimos 16 KB de RAM, armazenaremos no máximo N = 16000 * 8/64 = 2.000 valores. Como o número de valores deve ser uma potência de 2, armazenaremos no máximo 1024 valores.

3) A resolução de frequência

O FFT calculará os valores para tantas bandas de frequência quanto o número de amostras. Essas bandas vão de 0 HZ à frequência de amostragem (fs). Portanto, a resolução de frequência é:

Fresolution = fs / N

A resolução é melhor quando menor. Portanto, para uma melhor resolução (menor), queremos:

  • mais amostras e / ou
  • um fs mais baixo

Mas…

4) Fs mínimo

Como queremos ver muitas frequências, algumas delas muito mais altas do que a "frequência fundamental", não podemos definir fs muito baixo. Na verdade, existe o teorema de amostragem de Nyquist-Shannon que nos força a ter uma frequência de amostragem bem acima de duas vezes a frequência máxima que gostaríamos de testar.

Por exemplo, se quisermos analisar todo o espectro de 0 Hz a, digamos, 15 KHz, que é aproximadamente a frequência máxima que a maioria dos humanos pode ouvir distintamente, temos que definir a frequência de amostragem em 30 KHz. Na verdade, os eletrônicos costumam definir em 2,5 (ou mesmo 2,52) * a frequência máxima. Neste exemplo, seria 2,5 * 15 KHz = 37,5 KHz. As frequências de amostragem usuais em áudio profissional são 44,1 KHz (gravação de CD de áudio), 48 KHz e mais.

Conclusão:

Os pontos 1 a 4 levam a: queremos usar o máximo de amostras possível. Em nosso caso, com um dispositivo de 16 KB de RAM, consideraremos 1.024 amostras. Queremos amostrar com a frequência de amostragem mais baixa possível, contanto que seja alta o suficiente para analisar a frequência mais alta que esperamos em nosso sinal (2,5 * esta frequência, pelo menos).

Etapa 3: Simulando um sinal

Simulando um Sinal
Simulando um Sinal

Para a nossa primeira tentativa, iremos modificar ligeiramente o exemplo TFT_01.ino dado na biblioteca, para analisar um sinal composto por

  • A frequência fundamental, definida para 440 Hz (musical A)
  • 3º harmônico na metade da potência do fundamental ("-3 dB")
  • 5º harmônico a 1/4 da potência do fundamental ("-6 dB)

Você pode ver na imagem acima o sinal resultante. Na verdade, ele se parece muito com um sinal real que às vezes pode ser visto em um osciloscópio (eu o chamaria de "Batman") em uma situação em que há um corte de um sinal senoidal.

Etapa 4: Análise de um sinal simulado - codificação

0) Incluir a biblioteca

#include "arduinoFFT.h"

1. Definições

Nas seções de declarações, temos

const byte adcPin = 0; // A0

amostras const uint16_t = 1024; // Este valor SEMPRE DEVE ser uma potência de 2 const uint16_t samplingFrequency = 8000; // Afetará o valor máximo do temporizador em timer_setup () SYSCLOCK / 8 / samplingFrequency deve ser um inteiro

Uma vez que o sinal tem um 5º harmônico (frequência deste harmônico = 5 * 440 = 2200 Hz), precisamos definir a frequência de amostragem acima de 2,5 * 2200 = 5500 Hz. Aqui eu escolhi 8000 Hz.

Também declaramos as matrizes onde armazenaremos os dados brutos e computados

float vReal [amostras];

float vImag [amostras];

2) Instanciação

Criamos um objeto ArduinoFFT. A versão dev do ArduinoFFT usa um modelo para que possamos usar o tipo de dados float ou double. Float (32 bits) é suficiente no que diz respeito à precisão geral do nosso programa.

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

3) Simular o sinal preenchendo o array vReal, em vez de preenchê-lo com valores ADC.

No início do Loop, preenchemos o array vReal com:

ciclos flutuantes = (((amostras) * signalFrequency) / samplingFrequency); // Número de ciclos de sinal que a amostragem irá ler

for (uint16_t i = 0; i <amostras; i ++) {vReal = float ((amplitude * (sin ((i * (TWO_PI * ciclos))) / amostras)))); / * Construir dados com positivo e valores negativos * / vReal + = float ((amplitude * (sin ((3 * i * (TWO_PI * ciclos)) / amostras))) / 2.0); / * Construir dados com valores positivos e negativos * / vReal + = float ((amplitude * (sin ((5 * i * (TWO_PI * ciclos)) / amostras))) / 4,0); / * Construir dados com valores positivos e negativos * / vImag = 0,0; // A parte imaginária deve ser zerada em caso de loop para evitar cálculos errados e estouros}

Adicionamos uma digitalização da onda fundamental e os dois harmônicos com menos amplitude. Em seguida, inicializamos a matriz imaginária com zeros. Uma vez que esta matriz é preenchida pelo algoritmo FFT, precisamos limpá-la novamente antes de cada novo cálculo.

4) Computação FFT

Em seguida, calculamos a FFT e a densidade espectral

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

FFT.compute (FFTDirection:: Forward); / * Compute FFT * / FFT.complexToMagnitude (); / * Calcular magnitudes * /

A operação FFT.windowing (…) modifica os dados brutos porque executamos a FFT em um número limitado de amostras. A primeira e a última amostra apresentam uma descontinuidade (não há "nada" de um lado). Esta é uma fonte de erro. A operação de "janelamento" tende a reduzir esse erro.

FFT.compute (…) com a direção "Forward" calcula a transformação do domínio do tempo para o domínio da frequência.

Em seguida, calculamos os valores de magnitude (ou seja, intensidade) para cada uma das bandas de frequência. O array vReal agora é preenchido com valores de magnitudes.

5) Desenho de plotter serial

Vamos imprimir os valores na plotadora serial chamando a função printVector (…)

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

Esta é uma função genérica que permite imprimir dados com um eixo de tempo ou um eixo de frequência.

Também imprimimos a frequência da banda que tem o maior valor de magnitude

float x = FFT.majorPeak ();

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

Etapa 5: Análise de um sinal simulado - Resultados

Análise de um sinal simulado - resultados
Análise de um sinal simulado - resultados

Vemos 3 picos correspondentes à frequência fundamental (f0), o 3º e o 5º harmônicos, com metade e 1/4 da magnitude de f0, como esperado. Podemos ler no topo da janela f0 = 440,430114 Hz. Este valor não é exatamente 440 Hz, por todas as razões explicadas acima, mas está muito próximo do valor real. Não era realmente necessário mostrar tantos decimais insignificantes.

Etapa 6: Análise de um Sinal Real - Fiação do ADC

Análise de um Sinal Real - Fiação do ADC
Análise de um Sinal Real - Fiação do ADC

Como sabemos como proceder na teoria, gostaríamos de analisar um sinal real.

A fiação é muito simples. Conecte os aterramentos e a linha de sinal ao pino A0 de sua placa por meio de um resistor em série com um valor de 1 KOhm a 10 KOhm.

Este resistor em série protegerá a entrada analógica e evitará o toque. Deve ser o mais alto possível para evitar toques e o mais baixo possível para fornecer corrente suficiente para carregar o ADC rapidamente. Consulte a folha de dados MCU para saber a impedância esperada do sinal conectado na entrada ADC.

Para esta demonstração eu usei um gerador de função para alimentar um sinal senoidal de frequência 440 Hz e amplitude em torno de 5 volts (é melhor se a amplitude estiver entre 3 e 5 volts para que o ADC seja usado quase em escala total), através de um resistor de 1,2 KOhm.

Etapa 7: Análise de um Sinal Real - Codificação

0) Incluir a biblioteca

#include "arduinoFFT.h"

1) Declarações e instanciação

Na seção de declaração definimos a entrada ADC (A0), o número de amostras e a frequência de amostragem, como no exemplo anterior.

const byte adcPin = 0; // A0

amostras const uint16_t = 1024; // Este valor SEMPRE DEVE ser uma potência de 2 const uint16_t samplingFrequency = 8000; // Afetará o valor máximo do temporizador em timer_setup () SYSCLOCK / 8 / samplingFrequency deve ser um inteiro

Criamos o objeto ArduinoFFT

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

2) Configuração do temporizador e ADC

Ajustamos o temporizador 1 para que ele faça um ciclo na frequência de amostragem (8 KHz) e aumente uma interrupção na comparação de saída.

void timer_setup () {

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

E definir o ADC para que

  • Usa A0 como entrada
  • Aciona automaticamente em cada saída do temporizador 1, compara correspondência B
  • Gera uma interrupção quando a conversão é concluída

O relógio ADC é ajustado para 1 MHz, pré-escalando o relógio do sistema (16 MHz) em 16. Como cada conversão leva aproximadamente 13 relógios em escala completa, as conversões podem ser obtidas a uma frequência de 1/13 = 0,076 MHz = 76 KHz. A frequência de amostragem deve ser significativamente inferior a 76 KHz para permitir que o ADC tenha tempo para amostrar os dados. (escolhemos fs = 8 KHz).

void adc_setup () {

ADCSRA = bit (ADEN) | bit (ADIE) | bit (ADIF); // liga o ADC, deseja interrupção na conclusão ADCSRA | = bit (ADPS2); // Prescaler de 16 ADMUX = bit (REFS0) | (adcPin & 7); // configurando a entrada ADC ADCSRB = bit (ADTS0) | bit (ADTS2); // Timer / Contador1 Compare a origem do acionador da Correspondência B ADCSRA | = bit (ADATE); // liga o acionamento automático}

Declaramos o manipulador de interrupção que será chamado após cada conversão ADC para armazenar os dados convertidos no array vReal e limpar a interrupção

// ADC completo ISR

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

Você pode ter uma explicação exaustiva sobre a conversão ADC no Arduino (analogRead).

3) Configuração

Na função de configuração, limpamos a tabela de dados imaginários e chamamos as funções de configuração do temporizador e ADC

zeroI (); // uma função que define como 0 todos os dados imaginários - explicado na seção anterior

timer_setup (); adc_setup ();

3) Loop

FFT.dcRemoval (); // Remova o componente DC deste sinal, uma vez que o ADC é referenciado ao aterramento

FFT.windowing (FFTWindow:: Hamming, FFTDirection:: Forward); // Pesar dados FFT.compute (FFTDirection:: Forward); // Computa FFT FFT.complexToMagnitude (); // Calcula magnitudes // imprimindo o espectro e a frequência fundamental f0 PrintVector (vReal, (samples >> 1), SCL_FREQUENCY); float x = FFT.majorPeak (); Serial.print ("f0 ="); Serial.print (x, 6); Serial.println ("Hz");

Removemos o componente DC porque o ADC é referenciado ao aterramento e o sinal está centrado em torno de 2,5 volts aproximadamente.

Em seguida, calculamos os dados conforme explicado no exemplo anterior.

Etapa 8: Análise de um Sinal Real - Resultados

Análise de um Sinal Real - Resultados
Análise de um Sinal Real - Resultados

Na verdade, vemos apenas uma frequência neste sinal simples. A frequência fundamental calculada é 440,118194 Hz. Aqui, novamente, o valor é uma aproximação muito próxima da frequência real.

Etapa 9: o que dizer de um sinal sinusoidal cortado?

Que tal um sinal sinusoidal cortado?
Que tal um sinal sinusoidal cortado?

Agora vamos sobrecarregar um pouco o ADC, aumentando a amplitude do sinal acima de 5 volts, para que seja cortado. Não force demais para não destruir a entrada do ADC!

Podemos ver alguns harmônicos aparecendo. O corte do sinal cria componentes de alta frequência.

Você viu os fundamentos da análise FFT em uma placa Arduino. Agora você pode tentar alterar a frequência de amostragem, o número de amostras e o parâmetro de janela. A biblioteca também adiciona alguns parâmetros para calcular a FFT mais rápido com menos precisão. Você notará que se definir a frequência de amostragem muito baixa, as magnitudes calculadas parecerão totalmente errôneas por causa do dobramento espectral.

Recomendado: