Tabla de contenido:

Analizador de espectro FFT de 1024 muestras con un Atmega1284: 9 pasos
Analizador de espectro FFT de 1024 muestras con un Atmega1284: 9 pasos

Video: Analizador de espectro FFT de 1024 muestras con un Atmega1284: 9 pasos

Video: Analizador de espectro FFT de 1024 muestras con un Atmega1284: 9 pasos
Video: Analizadores FFT (1/3): Tamaño de muestra 2024, Noviembre
Anonim
Analizador de espectro FFT de 1024 muestras con un Atmega1284
Analizador de espectro FFT de 1024 muestras con un Atmega1284
Analizador de espectro FFT de 1024 muestras con un Atmega1284
Analizador de espectro FFT de 1024 muestras con un Atmega1284

Este tutorial relativamente fácil (considerando la complejidad de este tema) le mostrará cómo puede hacer un analizador de espectro de 1024 muestras muy simple usando una placa tipo Arduino (1284 Narrow) y el trazador serial. Cualquier tipo de placa compatible con Arduino servirá, pero cuanta más RAM tenga, mejor resolución de frecuencia obtendrá. Necesitará más de 8 KB de RAM para calcular la FFT con 1024 muestras.

El análisis de espectro se utiliza para determinar los principales componentes de frecuencia de una señal. Muchos sonidos (como los producidos por un instrumento musical) se componen de una frecuencia fundamental y algunos armónicos que tienen una frecuencia que es un múltiplo entero de la frecuencia fundamental. El analizador de espectro le mostrará todos estos componentes espectrales.

Es posible que desee utilizar esta configuración como un contador de frecuencia o para verificar cualquier tipo de señal que sospeche que está generando algo de ruido en su circuito electrónico.

Nos centraremos aquí en la parte del software. Si desea hacer un circuito permanente para una aplicación específica, necesitará amplificar y filtrar la señal. Este preacondicionamiento es totalmente dependiente de la señal que se quiera estudiar, dependiendo de su amplitud, impedancia, frecuencia máxima etc… Puede consultar

Paso 1: instalar la biblioteca

Usaremos la biblioteca ArduinoFFT escrita por Enrique Condes. Como queremos ahorrar RAM tanto como sea posible, usaremos la rama de desarrollo de este repositorio que permite usar el tipo de datos flotante (en lugar de doble) para almacenar los datos muestreados y calculados. Entonces tenemos que instalarlo manualmente. No se preocupe, simplemente descargue el archivo y descomprímalo en la carpeta de la biblioteca de Arduino (por ejemplo, en la configuración predeterminada de Windows 10: C: / Users / _your_user_name_ / Documents / Arduino / libraries)

Puede comprobar que la biblioteca está instalada correctamente compilando uno de los ejemplos proporcionados, como "FFT_01.ino".

Paso 2: Transformada de Fourier y conceptos de FFT

Advertencia: si no puede soportar ver ninguna notación matemática, puede pasar al Paso 3. De todos modos, si no lo entiende todo, solo considere la conclusión al final de la sección.

El espectro de frecuencias se obtiene mediante un algoritmo de Transformada Rápida de Fourier. FFT es una implementación digital que se aproxima al concepto matemático de la Transformada de Fourier. Bajo este concepto una vez que se obtiene la evolución de una señal siguiendo un eje de tiempo, se puede conocer su representación en un dominio de frecuencia, compuesto por valores complejos (reales + imaginarios). El concepto es recíproco, por lo que cuando conoce la representación del dominio de frecuencia, puede transformarla de nuevo al dominio del tiempo y recuperar la señal exactamente como antes de la transformación.

Pero, ¿qué vamos a hacer con este conjunto de valores complejos calculados en el dominio del tiempo? Bueno, la mayor parte quedará en manos de los ingenieros. Para nosotros, llamaremos a otro algoritmo que transformará estos valores complejos en datos de densidad espectral: ese es un valor de magnitud (= intensidad) asociado con cada banda de frecuencia. El número de bandas de frecuencia será el mismo que el número de muestras.

Ciertamente está familiarizado con el concepto de ecualizador, como este. Regreso a la década de 1980 con el ecualizador gráfico. Bueno, obtendremos el mismo tipo de resultados pero con 1024 bandas en lugar de 16 y mucha más resolución de intensidad. Cuando el ecualizador ofrece una vista global de la música, el análisis espectral fino permite calcular con precisión la intensidad de cada una de las 1024 bandas.

Un concepto perfecto, pero:

  1. Dado que la FFT es una versión digitalizada de la transformada de Fourier, se aproxima a la señal digital y pierde algo de información. Entonces, estrictamente hablando, el resultado de la FFT si se vuelve a transformar con un algoritmo de FFT invertido no daría exactamente la señal original.
  2. También la teoría considera una señal que no es finita, pero que es una señal constante y duradera. Dado que lo digitalizaremos solo durante un cierto período de tiempo (es decir, muestras), se introducirán algunos errores más.
  3. Finalmente, la resolución de la conversión de analógico a digital afectará la calidad de los valores calculados.

En la práctica

1) La frecuencia de muestreo (indicada fs)

Tomaremos muestras de una señal, es decir, mediremos su amplitud, cada 1 / fs segundos. fs es la frecuencia de muestreo. Por ejemplo, si tomamos muestras a 8 KHz, el ADC (convertidor analógico a digital) que está integrado en el chip proporcionará una medición cada 1/8000 de segundos.

2) El número de muestras (anotado N o muestras en el código)

Como necesitamos obtener todos los valores antes de ejecutar la FFT, tendremos que almacenarlos y, por lo tanto, limitaremos el número de muestras. El algoritmo FFT necesita un número de muestras que es una potencia de 2. Cuantas más muestras tengamos, mejor, pero requiere mucha memoria, tanto más que también necesitaremos almacenar los datos transformados, que son valores complejos. La biblioteca Arduino FFT ahorra algo de espacio al usar

  • Un arreglo llamado "vReal" para almacenar los datos muestreados y luego la parte real de los datos transformados
  • Una matriz denominada "vImag" para almacenar la parte imaginaria de los datos transformados.

La cantidad necesaria de RAM equivale a 2 (matrices) * 32 (bits) * N (muestras).

Entonces, en nuestro Atmega1284 que tiene 16 KB de RAM, almacenaremos un máximo de N = 16000 * 8/64 = 2000 valores. Dado que el número de valores debe ser una potencia de 2, almacenaremos un máximo de 1024 valores.

3) La resolución de frecuencia

La FFT calculará valores para tantas bandas de frecuencia como el número de muestras. Estas bandas abarcarán desde 0 HZ hasta la frecuencia de muestreo (fs). Por lo tanto, la resolución de frecuencia es:

Fresolution = fs / N

La resolución es mejor cuando es más baja. Entonces, para una mejor resolución (más baja), queremos:

  • más muestras y / o
  • una fs más baja

Pero…

4) fs mínimo

Como queremos ver muchas frecuencias, algunas de las cuales son mucho más altas que la "frecuencia fundamental", no podemos establecer fs demasiado bajas. De hecho, existe el teorema de muestreo de Nyquist-Shannon que nos obliga a tener una frecuencia de muestreo muy por encima del doble de la frecuencia máxima que nos gustaría probar.

Por ejemplo, si quisiéramos analizar todo el espectro desde 0 Hz para decir 15 KHz, que es aproximadamente la frecuencia máxima que la mayoría de los humanos pueden escuchar claramente, tenemos que establecer la frecuencia de muestreo en 30 KHz. De hecho, los electrónicos a menudo lo establecen en 2,5 (o incluso 2,52) * la frecuencia máxima. En este ejemplo, sería 2,5 * 15 KHz = 37,5 KHz. Las frecuencias de muestreo habituales en audio profesional son 44,1 KHz (grabación de CD de audio), 48 KHz y más.

Conclusión:

Los puntos 1 a 4 conducen a: queremos utilizar tantas muestras como sea posible. En nuestro caso con un dispositivo de 16 KB de RAM, consideraremos 1024 muestras. Queremos muestrear a la frecuencia de muestreo más baja posible, siempre que sea lo suficientemente alta para analizar la frecuencia más alta que esperamos en nuestra señal (2.5 * esta frecuencia, al menos).

Paso 3: simulación de una señal

Simular una señal
Simular una señal

Para nuestro primer intento, modificaremos ligeramente el ejemplo de TFT_01.ino dado en la biblioteca, para analizar una señal compuesta de

  • La frecuencia fundamental, establecida en 440 Hz (A musical)
  • 3er armónico a la mitad de la potencia de la fundamental ("-3 dB")
  • 5to armónico a 1/4 de la potencia de la fundamental ("-6 dB)

Puedes ver en la imagen de arriba la señal resultante. De hecho, se parece mucho a una señal real que a veces se puede ver en un osciloscopio (yo lo llamaría "Batman") en una situación en la que hay un recorte de una señal sinusoidal.

Paso 4: Análisis de una señal simulada: codificación

0) Incluir la biblioteca

#include "arduinoFFT.h"

1. Definiciones

En las secciones de declaraciones, tenemos

const byte adcPin = 0; // A0

const uint16_t muestras = 1024; // Este valor SIEMPRE DEBE ser una potencia de 2 const uint16_t samplingFrequency = 8000; // Afectará el valor máximo del temporizador en timer_setup () SYSCLOCK / 8 / samplingFrequency debe ser un número entero

Dado que la señal tiene un quinto armónico (frecuencia de este armónico = 5 * 440 = 2200 Hz), debemos establecer la frecuencia de muestreo por encima de 2,5 * 2200 = 5500 Hz. Aquí elegí 8000 Hz.

También declaramos las matrices donde almacenaremos los datos sin procesar y calculados

flotar vReal [muestras];

flotar vImag [muestras];

2) instanciación

Creamos un objeto ArduinoFFT. La versión dev de ArduinoFFT usa una plantilla para que podamos usar el tipo de datos flotante o doble. Float (32 bits) es suficiente con respecto a la precisión general de nuestro programa.

ArduinoFFT FFT = ArduinoFFT (vReal, vImag, muestras, frecuencia de muestreo);

3) Simular la señal rellenando la matriz vReal, en lugar de rellenarla con valores ADC.

Al comienzo del bucle, llenamos la matriz vReal con:

ciclos de flotación = (((muestras) * frecuencia de señal) / frecuencia de muestreo); // Número de ciclos de señal que leerá el muestreo

for (uint16_t i = 0; i <samples; i ++) {vReal = float ((amplitude * (sin ((i * (TWO_PI * ciclos)) / samples)))); / * Construir datos con valores positivos y valores negativos * / vReal + = float ((amplitud * (sin ((3 * i * (TWO_PI * ciclos)) / samples))) / 2.0); / * Generar datos con valores positivos y negativos * / vReal + = float ((amplitude * (sin ((5 * i * (TWO_PI * ciclos)) / samples))) / 4.0); / * Generar datos con valores positivos y negativos * / vImag = 0.0; // La parte imaginaria debe ponerse a cero en caso de bucle para evitar cálculos incorrectos y desbordamientos}

Añadimos una digitalización de la onda fundamental y los dos armónicos con menor amplitud. Luego inicializamos la matriz imaginaria con ceros. Dado que esta matriz está poblada por el algoritmo FFT, necesitamos borrarla nuevamente antes de cada nuevo cálculo.

4) Computación FFT

Luego calculamos la FFT y la densidad espectral

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

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

La operación FFT.windowing (…) modifica los datos sin procesar porque ejecutamos la FFT en un número limitado de muestras. La primera y la última muestra presentan una discontinuidad (no hay "nada" en uno de sus lados). Ésta es una fuente de error. La operación de "ventana" tiende a reducir este error.

FFT.compute (…) con la dirección "Adelante" calcula la transformación del dominio del tiempo al dominio de la frecuencia.

Luego calculamos los valores de magnitud (es decir, intensidad) para cada una de las bandas de frecuencia. La matriz de vReal ahora está llena de valores de magnitudes.

5) Dibujo de plotter en serie

Imprimamos los valores en el trazador serial llamando a la función printVector (…)

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

Esta es una función genérica que permite imprimir datos con un eje de tiempo o un eje de frecuencia.

También imprimimos la frecuencia de la banda que tiene el valor de magnitud más alto

flotar x = FFT.majorPeak ();

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

Paso 5: Análisis de una señal simulada - Resultados

Análisis de una señal simulada - Resultados
Análisis de una señal simulada - Resultados

Vemos 3 picos correspondientes a la frecuencia fundamental (f0), los armónicos 3 y 5, con la mitad y 1/4 de la magnitud f0, como se esperaba. Podemos leer en la parte superior de la ventana f0 = 440.430114 Hz. Este valor no es exactamente 440 Hz, por todas las razones explicadas anteriormente, pero está muy cerca del valor real. Realmente no era necesario mostrar tantos decimales insignificantes.

Paso 6: Análisis de una señal real: cableado del ADC

Análisis de una señal real: cableado del ADC
Análisis de una señal real: cableado del ADC

Dado que sabemos cómo proceder en teoría, nos gustaría analizar una señal real.

El cableado es muy sencillo. Conecte las tierras juntas y la línea de señal al pin A0 de su placa a través de una resistencia en serie con un valor de 1 KOhm a 10 KOhm.

Esta resistencia en serie protegerá la entrada analógica y evitará el timbre. Debe ser lo más alto posible para evitar el timbre y lo más bajo posible para proporcionar suficiente corriente para cargar el ADC rápidamente. Consulte la hoja de datos de la MCU para conocer la impedancia esperada de la señal conectada en la entrada ADC.

Para esta demostración utilicé un generador de funciones para alimentar una señal sinusoidal de frecuencia 440 Hz y amplitud alrededor de 5 voltios (es mejor si la amplitud está entre 3 y 5 voltios para que el ADC se use cerca de la escala completa), a través de una resistencia de 1.2 KOhm.

Paso 7: análisis de una señal real: codificación

0) Incluir la biblioteca

#include "arduinoFFT.h"

1) Declaraciones e instanciación

En la sección de declaración definimos la entrada ADC (A0), el número de muestras y la frecuencia de muestreo, como en el ejemplo anterior.

const byte adcPin = 0; // A0

const uint16_t muestras = 1024; // Este valor SIEMPRE DEBE ser una potencia de 2 const uint16_t samplingFrequency = 8000; // Afectará el valor máximo del temporizador en timer_setup () SYSCLOCK / 8 / samplingFrequency debe ser un número entero

Creamos el objeto ArduinoFFT

ArduinoFFT FFT = ArduinoFFT (vReal, vImag, muestras, frecuencia de muestreo);

2) Configuración del temporizador y ADC

Configuramos el temporizador 1 para que funcione en la frecuencia de muestreo (8 KHz) y genere una interrupción en la comparación de salida.

void timer_setup () {

// reiniciar el temporizador 1 TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; TCCR1B = bit (CS11) | bit (WGM12); // CTC, preescalador de 8 TIMSK1 = bit (OCIE1B); OCR1A = ((16000000/8) / frecuencia de muestreo) -1; }

Y configure el ADC para que

  • Utiliza A0 como entrada
  • Se dispara automáticamente en cada salida del temporizador 1 comparar coincidencias B
  • Genera una interrupción cuando se completa la conversión

El reloj ADC se establece en 1 MHz, preescalando el reloj del sistema (16 MHz) en 16. Dado que cada conversión toma aproximadamente 13 relojes a escala completa, las conversiones se pueden lograr a una frecuencia de 1/13 = 0.076 MHz = 76 KHz. La frecuencia de muestreo debe ser significativamente inferior a 76 KHz para que el ADC tenga tiempo de muestrear los datos. (elegimos fs = 8 KHz).

void adc_setup () {

ADCSRA = bit (ADEN) | bit (ADIE) | bit (ADIF); // enciende ADC, quiere interrumpir al finalizar ADCSRA | = bit (ADPS2); // Prescaler de 16 ADMUX = bit (REFS0) | (adcPin y 7); // configurando la entrada ADC ADCSRB = bit (ADTS0) | bit (ADTS2); // Temporizador / Contador1 Comparar fuente de disparo Match B ADCSRA | = bit (ADATE); // activa el disparo automático}

Declaramos el controlador de interrupciones que se llamará después de cada conversión de ADC para almacenar los datos convertidos en la matriz vReal y borrar la interrupción.

// ADC completo ISR

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

Puede tener una explicación exhaustiva sobre la conversión de ADC en Arduino (analogRead).

3) Configuración

En la función de configuración, borramos la tabla de datos imaginarios y llamamos a las funciones de configuración del temporizador y del ADC

zeroI (); // una función que pone a 0 todos los datos imaginarios - explicado en la sección anterior

timer_setup (); adc_setup ();

3) bucle

FFT.dcRemoval (); // Elimine el componente de CC de esta señal ya que el ADC está referenciado a tierra

FFT.windowing (FFTWindow:: Hamming, FFTDirection:: Adelante); // Pesar datos FFT.compute (FFTDirection:: Forward); // Calcular FFT FFT.complexToMagnitude (); // Calcular magnitudes // imprimir el espectro y la frecuencia fundamental f0 PrintVector (vReal, (samples >> 1), SCL_FREQUENCY); flotar x = FFT.majorPeak (); Serial.print ("f0 ="); Serial.print (x, 6); Serial.println ("Hz");

Eliminamos el componente de CC porque el ADC está referenciado a tierra y la señal se centra alrededor de 2,5 voltios aproximadamente.

Luego, calculamos los datos como se explica en el ejemplo anterior.

Paso 8: Análisis de una señal real - Resultados

Análisis de una señal real - Resultados
Análisis de una señal real - Resultados

De hecho, solo vemos una frecuencia en esta simple señal. La frecuencia fundamental calculada es 440,118194 Hz. Aquí nuevamente el valor es una aproximación muy cercana a la frecuencia real.

Paso 9: ¿Qué pasa con una señal sinusoidal recortada?

¿Qué pasa con una señal sinusoidal recortada?
¿Qué pasa con una señal sinusoidal recortada?

Ahora sobreexcite un poco el ADC aumentando la amplitud de la señal por encima de 5 voltios, para que quede recortada. ¡No presiones demasiado para no destruir la entrada ADC!

Podemos ver aparecer algunos armónicos. El recorte de la señal crea componentes de alta frecuencia.

Ha visto los fundamentos del análisis FFT en una placa Arduino. Ahora puede intentar cambiar la frecuencia de muestreo, el número de muestras y el parámetro de ventana. La biblioteca también agrega algunos parámetros para calcular la FFT más rápido con menos precisión. Notará que si establece la frecuencia de muestreo demasiado baja, las magnitudes calculadas aparecerán totalmente erróneas debido al plegamiento espectral.

Recomendado: