Manufactura industrial
Internet industrial de las cosas | Materiales industriales | Mantenimiento y reparación de equipos | Programación industrial |
home  MfgRobots >> Manufactura industrial >  >> Manufacturing Technology >> Proceso de manufactura

EasyFFT – Librería FFT Fiable para Proyectos Arduino

Componentes y suministros

Arduino Nano R3 × 1

Aplicaciones y servicios en línea

Sobre este proyecto

La medición de frecuencia a partir de una señal capturada puede ser una tarea difícil, especialmente en Arduino por su menor potencia computacional. Existen métodos para capturar cruces por cero donde la frecuencia se captura comprobando cuántas veces la señal cruza la línea cero en un tiempo dado. Este método puede no funcionar cuando la señal es una combinación de varias frecuencias.

Esto es de alguna manera difícil de programar si no tienes formación en el tema. Pero siendo un aficionado, este código puede ser muy útil para varios proyectos relacionados con música, análisis de señales. El objetivo de este proyecto era preparar un código fácil de implementar en Arduino sin entrar en los detalles matemáticos.

Este proyecto no explica el funcionamiento de la FFT sino la aplicación de la función FFT. El mismo proceso también se explica en el vídeo adjunto.

Si solo te interesa la aplicación del código y no su explicación, puedes saltar directamente al paso 3.

Si necesitas realizar FFT con alta velocidad (3x) con un pequeño compromiso en precisión (alrededor del 5%), consulta mi otro artículo sobre ApproxFFT: https://create.arduino.cc/projecthub/abhilashpatel121/approxfft-fastest-fft-function-for-arduino-fd4917

Paso 1: Transformada Rápida de Fourier

Para acelerar el cálculo de la DFT se desarrolló el algoritmo FFT por James Cooley y John Tukey. Este algoritmo está considerado uno de los más importantes del siglo XX. Divide una señal en partes de secuencia impar y par, lo que reduce el número de cálculos requeridos. Al usarlo, las multiplicaciones complejas totales se reducen a NlogN, una mejora significativa. La DFT típica requiere N*N multiplicaciones complejas, mientras que la FFT solo necesita N*logN, una ventaja significativa cuando el número de muestras es alto.

Puedes consultar las referencias que usé al escribir el código para una comprensión detallada de las matemáticas detrás de la FFT:

  1. https://flylib.com/books/en/2.729.1/derivation_of_...
  2. https://jakevdp.github.io/blog/2013/08/28/understa...
  3. https://cnx.org/contents/8D0YvnW1@7.1:zmcmahhR@7/D...
  4. https://en.wikipedia.org/wiki/Fast_Fourier_transfo...

Paso 2: Explicación del código

1. Seno y Coseno rápidos:

El cálculo de FFT requiere valores de varias funciones seno y coseno múltiples veces. La función integrada de Arduino no es lo suficientemente rápida y tarda bastante en proporcionar el valor requerido, lo que hace que el código sea significativamente más lento (duplica el tiempo para 64 muestras). Para contrarrestar esto, el valor del seno para 0 a 90 grados se almacena como múltiplo de 255. Al hacer esto se elimina la necesidad de almacenar números como float y podemos guardarlo como byte, que ocupa 1/4 del espacio en Arduino. El array sine_data[] debe pegarse al principio del código para declararlo como variable global.

Aparte de sine_data, se declara un array llamado f_peaks[] como variable global. Después de cada ejecución de la función FFT, este array se actualiza. Donde f_peaks[0] es la frecuencia más dominante y los valores siguientes en orden descendente.

byte sine_data[91] = { 
  0, 4, 9, 13, 18, 22, 27, 31, 35, 40, 44, 49, 53, 57, 62, 66, 70, 75, 79, 83, 87, 
  91, 96, 100, 104, 108, 112, 116, 120, 124, 127, 131, 135, 139, 143, 146, 150, 153, 
  157, 160, 164, 167, 171, 174, 177, 180, 183, 186, 189, 192, 195, 198, 201, 204, 206, 
  209, 211, 214, 216, 219, 221, 223, 225, 227, 229, 231, 233, 235, 236, 238, 240, 241, 
  243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 253, 254, 254, 254, 255, 255, 
  255, 255 
};
float f_peaks[5];

Al tener almacenado el valor del seno para 0 a 90 grados, cualquier valor de seno o coseno puede calcularse. La función siguiente redondea el número a cero decimales y devuelve el valor de los datos almacenados. Este método solo necesita una división flotante. Esto puede reducirse aún más almacenando directamente los valores del seno (no múltiplos de 255), pero eso consume mucha memoria en Arduino.

Usar el procedimiento anterior reduce la precisión pero mejora la velocidad. Para 64 puntos, da una ventaja de 8ms y para 128 puntos una ventaja de 20ms.

Paso 3: Explicación del código: Función FFT

La FFT solo puede realizarse para tamaños de muestra de 2, 4, 8, 16, 32, 64, etc. Si el valor no es 2^n, tomará el valor inferior. Por ejemplo, si elegimos un tamaño de muestra de 70, solo considerará las primeras 64 muestras y omitirá el resto.

Siempre se recomienda tener un tamaño de muestra de 2^n, que puede ser: 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, ...

Dos arrays float out_r y out_im consumirán mucha memoria. En Arduino Nano no funcionará para muestras superiores a 128 (y en algunos casos 128) debido a la falta de memoria disponible.


unsigned int data[13] = {1,2,4,8,16,32,64,128,256,512,1024,2048};
int a,c1,f,o,x;
a = N;
for(int i=0; i<12; i++) // calculando los niveles
{
  if(data[i] <= a){ o = i; }
}
int in_ps[data[o]] = {}; // entrada para secuenciación
float out_r[data[o]] = {}; // parte real de la transformada
float out_im[data[o]] = {}; // parte imaginaria de la transformada

El flujo posterior es el siguiente:

  1. El código genera un orden de inversión de bits para el tamaño de muestra dado (detalles en referencias: paso 2)
  2. Los datos de entrada se ordenan según el orden generado
  3. Se realiza la FFT
  4. Se calcula la amplitud del número complejo
  5. Se detectan los picos y se ordenan en orden descendente
  6. Los resultados se pueden acceder desde f_peaks[]

[Para acceder a otros datos (aparte de la frecuencia pico) el código debería modificarse para que las variables locales se copien a alguna variable global predefinida]

Paso 4: Probando el código

Se proporciona una onda triangular de muestra como entrada. Para esta onda, la frecuencia de muestreo es 10 Hz y la frecuencia de la onda en sí es 1.25 Hz.

Como se muestra en la salida raw, el valor coincide con la FFT calculada por Scilab. Sin embargo, estos valores no son exactamente iguales debido a la menor precisión pero mayor velocidad de la onda seno.

En el array de frecuencias de salida, las frecuencias son 1.25 y 3.75. No es necesario obtener el valor exacto cada vez. Típicamente estos números se llaman "frequency bins" (contenedores de frecuencia), por lo que el valor de salida podría estar en cualquier lugar dentro de los contenedores especificados.

Velocidad:

En Arduino Nano toma:

Paso 5: Conclusión

Este código FFT puede usarse en aplicaciones en tiempo real. Tarda unos 30 ms en completar el cálculo. Sin embargo, su resolución está limitada por el número de muestras. El número de muestras está limitado por la memoria de Arduino. Usando Arduino Mega u otras placas de mayor rendimiento se puede mejorar la precisión.

Si tienes consultas, sugerencias o correcciones, no dudes en comentar.

Código

EasyFFT (Arduino)


/* 
// Datos de ejemplo: 
int data[64] = {14, 30, 35, 34, 34, 40, 46, 45, 30, 4, -26, -48, -55, -49, -37, -28, -24, -22, -13, 6, 32, 55, 65, 57, 38, 17, 1, -6, -11, -19, -34, -51, -61, -56, -35, -7, 18, 32, 35, 34, 35, 41, 46, 43, 26, -2, -31, -50, -55, -47, -35, -27, -24, -21, -10, 11, 37, 58, 64, 55, 34, 13, -1, -7}; 
*/
//---------------------------------------------------------------------------//
byte sine_data[91] = { 
  0, 4, 9, 13, 18, 22, 27, 31, 35, 40, 44, 49, 53, 57, 62, 66, 70, 75, 79, 83, 87, 
  91, 96, 100, 104, 108, 112, 116, 120, 124, 127, 131, 135, 139, 143, 146, 150, 153, 
  157, 160, 164, 167, 171, 174, 177, 180, 183, 186, 189, 192, 195, 198, 201, 204, 206, 
  209, 211, 214, 216, 219, 221, 223, 225, 227, 229, 231, 233, 235, 236, 238, 240, 241, 
  243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 253, 254, 254, 254, 255, 255, 
  255, 255 
}; 
// Pegar esto al principio del programa [variable global]
float f_peaks[5]; // top 5 frecuencias pico en orden descendente
//---------------------------------------------------------------------------//

void setup() {
  Serial.begin(250000);
}

void loop() {
  /* 
  // ejemplo FFT
  FFT(data, 64, 100); // para obtener los 5 valores superiores de frecuencias de X con 64 muestras a 100Hz de muestreo
  Serial.println(f_peaks[0]);
  Serial.println(f_peaks[1]);
  delay(99999);
  */
  /* después de ejecutar FFT(), las frecuencias están disponibles en f_peaks[0], f_peaks[1], f_peaks[2], f_peaks[3], f_peaks[4] */
}

//-----------------------------Función FFT----------------------------------------------//
float FFT(int in[], int N, float Frequency) {
  /* 
   * Código para realizar FFT en Arduino
   * Configuración: pegar sine_data[91] al principio del programa [variable global], pegar función FFT al final del programa
   * Términos:
   * 1. in[] : Array de datos
   * 2. N : Número de muestras (tamaño recomendado 2,4,8,16,32,64,128...)
   * 3. Frequency: frecuencia de muestreo requerida como entrada (Hz)
   * Si el tamaño de muestra no es potencia de 2 se recortará al lado inferior.
   * i.e., para 150 muestras, el código considerará las primeras 128, omitiendo el resto.
   * Para Arduino Nano, FFT de más de 128 muestras no es posible por limitación de memoria (64 recomendado)
   * Para mayor número de muestras pueden surgir problemas de memoria
   * Código por ABHILASH
   * Contacto: abhilashpatel121@gmail.com
   * Documentación: https://www.instructables.com/member/abhilash_patel/instructables/
   * 2/3/2021: cambiar tipo de dato de N de float a int para >=256 muestras
   */
  unsigned int data[13] = {1,2,4,8,16,32,64,128,256,512,1024,2048};
  int a,c1,f,o,x;
  a = N;
  for(int i=0; i<12; i++) // calculando los niveles
  {
    if(data[i] <= a){ o = i; }
  }
  int in_ps[data[o]] = {}; // entrada para secuenciación
  float out_r[data[o]] = {}; // parte real de la transformada
  float out_im[data[o]] = {}; // parte imaginaria de la transformada
  x = 0;
  for(int b=0; b a) { out_r[i] = in[in_ps[i] - a]; }
  }
  int i10, i11, n1;
  float e, c, s, tr, ti;
  for(int i=0; i a partir de aquí out_r contiene amplitud y out_im contiene frecuencia (Hz)
  for(int i=0; i out_r[i-1] && out_r[i] > out_r[i+1]) 
    { 
      in_ps[x] = i; // in_ps array usado para almacenar número de pico
      x = x + 1; 
    }
  }
  s = 0; c = 0;
  for(int i=0; i 360) { j = j - 360; }
  if(j > -1 && j < 91) { out = sine_data[j]; }
  else if(j > 90 && j < 181) { out = sine_data[180 - j]; }
  else if(j > 180 && j < 271) { out = -sine_data[j - 180]; }
  else if(j > 270 && j < 361) { out = -sine_data[360 - j]; }
  return (out / 255);
}

float cosine(int i) {
  int j = i;
  float out;
  while(j < 0) { j = j + 360; }
  while(j > 360) { j = j - 360; }
  if(j > -1 && j < 91) { out = sine_data[90 - j]; }
  else if(j > 90 && j < 181) { out = -sine_data[j - 90]; }
  else if(j > 180 && j < 271) { out = -sine_data[270 - j]; }
  else if(j > 270 && j < 361) { out = sine_data[j - 270]; }
  return (out / 255);
}
//------------------------------------------------------------------------------------//

Esquemáticos

EasyFFT – Librería FFT Fiable para Proyectos Arduino

Proceso de manufactura

  1. Construye un Decodificador DTMF Solo con Arduino – Sin CI Externos
  2. SERENA: Sistema de Alarma Personalizado Arduino Mega 2560 con Pantalla TFT LCD Táctil
  3. Control Arduino UNO desde Python 3 – Envío de Comandos y Demo LED
  4. Sistema inteligente de monitorización de temperatura para escuelas: preciso, en tiempo real y seguro
  5. Librería Eficiente de Puerto I/O de 8 Bits para Arduino – Expande el Control Digital
  6. Teclado Prototipo de 64 Teclas con Arduino: Matriz con Registros de Desplazamiento
  7. Pantalla TFT Shield de Alto Rendimiento 2.8” para Arduino Nano – Display SPI 320×240
  8. Circuito de entrada analógica aislada para Arduino
  9. Kit de Robot de Navegación Interior Inteligente para Proyectos Arduino
  10. Código HS para Bomba Hidráulica: Clasificación y Uso
  11. Entendiendo la Transformada de Fourier: Fundamentos, Aplicaciones y Descomposición de Señales