Skip to Content

Analizador de espectro con ESP32 y MAX4466

Analizador de espectro con ESP32 y MAX4466

En este tutorial aprenderás a construir un Analizador de Espectro de Frecuencia simple con un ESP32. Usaremos el micrófono MAX4466 y la librería arduinoFFT para medir las frecuencias en la señal de audio y mostrar el espectro de frecuencias en un OLED.

Comencemos con las piezas que necesitarás para este proyecto.

Piezas necesarias

Necesitarás un ESP32, una pantalla OLED y el módulo de micrófono MAX4466. Yo estoy usando el ESP32 lite como microprocesador, ya que tiene una interfaz para carga de batería y así podrías usar el Analizador de Espectro fácilmente con batería para hacerlo portátil. Pero cualquier otro ESP32 también funcionará.

ESP32 lite Lolin32

ESP32 lite

USB data cable

Cable USB de datos

Micrófono MAX4466

OLED display

Pantalla OLED

Dupont wire set

Juego de cables Dupont

Half_breadboard56a

Protoboard

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Analizador de Espectro de Frecuencia

Un Analizador de Espectro de Frecuencia es un dispositivo que visualiza y analiza el contenido espectral de señales y muestra la amplitud o potencia de una señal a lo largo de su rango de frecuencias. Básicamente te indica cuánta potencia de la señal está distribuida en frecuencias o bandas de frecuencia específicas.

La siguiente imagen muestra un espectro de frecuencia. El rango de frecuencias está en el eje x y la magnitud de la señal se mide en Decibel como se muestra en el eje y:

Ejemplo de un Espectro de Frecuencia (source)

Dependiendo de la precisión y el rango de frecuencia, los analizadores de espectro pueden ser muy costosos. A continuación una imagen del Analizador de Espectro Siglent Technologies SSA3021X que puede analizar frecuencias de radio desde 9 kHz hasta 2.1 GHz.

Siglent Technologies SSA3021X Spectrum Analyzer
Analizador de Espectro Siglent Technologies SSA3021X (source)

Espectro de Frecuencia de Audio

Una aplicación común de los analizadores de espectro es visualizar la distribución de frecuencias dentro de unaseñal de audio. El rango de frecuencia o audición de una señal de audio que los humanos pueden escuchar va de 20 a 20,000 Hz (=20KHz). Sin embargo, esos son los extremos y el rango auditivo disminuye con la edad. Para adultos, el límite superior suele estar alrededor de 15,000 Hz. Si quieres conocer tu límite superior, puedes probar este pequeño test de audición.

La siguiente imagen muestra la pantalla de un típico Analizador de Espectro de Audio. Puedes ver las diferentes bandas de frecuencia (barras verdes) que van desde 20Hz hasta 20KHz.

BDS PP-131P Digital Audio Spectrum Analyzer
Analizador Digital de Espectro de Audio BDS PP-131P (source)

En este tutorial, vamos a construir un Analizador de Espectro de Audio nosotros mismos. Sin embargo, no esperes demasiado. Como usamos un ESP32 barato, un OLED pequeño y un micrófono MAX4466 simple, nuestro Analizador de Espectro no será muy preciso.

Su rango de frecuencia será solo de 125Hz a 8000 Hz y solo tendrá 7 bandas de frecuencia, pero será suficiente para visualizar y animar el contenido de frecuencia de la música.

Funcionamiento de un Analizador de Espectro

¿Cómo funciona nuestro pequeño Analizador de Espectro de Audio? El micrófono convierte las ondas sonoras en impulsos eléctricos. Estos impulsos débiles son amplificados por el MAX4466 y enviados al Convertidor Analógico-Digital (ADC) del ESP32. Si lees la salida del ADC y la representas gráficamente, la señal de audio detectada podría verse así:

Time domain signal composed of cosine waves
Señal en dominio del tiempo compuesta por ondas coseno (source)

En el eje x está el tiempo y en el eje y la amplitud de la señal. Esta señal se llama señal en dominio del tiempo, ya que se reporta a lo largo del tiempo pero no muestra las frecuencias dentro de la señal.

La señal anterior está compuesta en realidad por señales coseno con frecuencias de 10, 20, 30, 40 y 50 Hz con amplitudes crecientes. Si aplicamos un método matemático llamado Fast Fourier Transformation (FFT), podemos extraer estas frecuencias de la señal. La gráfica a continuación muestra la misma señal después de aplicar la FFT:

Frequency domain signal composed of cosine waves
Señal en dominio de la frecuencia compuesta por ondas coseno (source)

La señal ahora está en el dominio de la frecuencia con la frecuencia en el eje x (en lugar del tiempo). Puedes ver claramente los picos en 10, 20, 30, 40 y 50 Hz, que revelan las frecuencias que componen la señal de entrada. Para realizar la FFT dejamos que el ESP32 muestree la señal de audio obtenida del ADC y luego llamamos a la arduinoFFT library para extraer las frecuencias. El espectro de frecuencias resultante se muestra en el OLED.

Sin embargo, no mostraremos las frecuencias individuales en el espectro, sino que las agruparemos en bandas de frecuencia que son más fáciles de leer y comúnmente usadas en analizadores de espectro de audio. Las bandas de frecuencia serán alrededor de 125, 250, 500, 1000, 2000, 4000 y 8000 Hz, como se muestra a continuación:

Frequency Spectrum on OLED
Espectro de Frecuencia en OLED

Módulo de Micrófono MAX4466

El módulo de micrófono MAX4466 es una placa breakout con un micrófono electret de 20-20KHz y e lMAX4466 circuito integrado preamplificador. En la parte trasera de la placa hay un pequeño potenciómetro que permite ajustar la ganancia de 25x a 125x. La imagen a continuación muestra el frente y la parte trasera de la placa:

Frontal y trasera del módulo de micrófono MAX4466

El módulo funciona con 2.4…5.5V para VCC con una corriente de alimentación en reposo muy baja de <24μA. La salida tendrá un sesgo de VCC/2. Así que cuando está perfectamente en silencio, el voltaje de salida estará en VCC/2 V.

Si quieres aprender más sobre el módulo de micrófono MAX4466, echa un vistazo al Detect sound with MAX4466 and Arduino tutorial.

Conectando MAX4466 y OLED al ESP32

En esta sección primero conectamos el módulo de micrófono MAX4466 al ESP32 y probamos su funcionamiento. Luego añadimos el OLED al circuito y escribimos el código para el Analizador de Espectro:

Conectando MAX4466 al ESP32

Para conectar el MAX4466 a un ESP32, comienza conectando la tierra (GND) del ESP32 y del módulo MAX4466 (cable azul). Luego conecta VCC del MAX4466 a la salida de 3.3V del ESP32 (cable rojo). Finalmente, conecta el GPIO 4 del ESP32 al pin OUT del MAX4466 (cable naranja):

Connecting MAX4466 to ESP32
Conectando MAX4466 al ESP32

Ten en cuenta que en lugar del GPIO 4 puedes usar cualquier otro pin siempre que pueda leer una señal analógica.

Código de prueba para MAX4466

Después de conectar el micrófono MAX4466 al ESP32, hagamos una pequeña prueba para ver si todo funciona como se espera. El siguiente código lee la señal del micrófono en el pin 4 y la imprime en el Monitor Serial:

const byte micPin = 4;

void setup() {
  Serial.begin(115200);
  pinMode(micPin, INPUT);
}

void loop() {
  int value = analogRead(micPin);
  Serial.println(value);
  delay(100);  
}

Si subes este código, abres el Serial Plotter y haces ruido, deberías poder ver fluctuaciones en la señal de audio relacionadas con el volumen del ruido:

Audio signal on Serial Plotter
Señal de audio en Serial Plotter

Si no ves ningún cambio en la señal de audio, entonces o tu cableado está mal o el pin GPIO para el micrófono en el código es incorrecto.

Añadiendo OLED al ESP32

En el siguiente paso añadimos el OLED al ESP32. Primero, conecta GND y VCC del OLED a las líneas de alimentación existentes (cables azul y rojo). Luego conecta SCL al pin 23 y SDA al pin 18 del ESP32, como se muestra a continuación:

Connecting MAX4466 and OLED with ESP32
Conectando MAX4466 y OLED con ESP32

Los GPIO 23 y 18 son los pines hardware I2C para el ESP32 lite. Si usas una placa ESP32 diferente, tus pines para hardware I2C pueden variar. Si no estás seguro de qué pines usar, echa un vistazo al Find I2C and SPI default pins tutorial. La imagen a continuación muestra el circuito completo en una protoboard:

Audio Spectrum Analyzer circuit on breadboard
Circuito del Analizador de Espectro de Audio en protoboard

Código para mostrar el espectrograma de frecuencia

Antes de poder escribir el código para calcular y mostrar el espectrograma de frecuencia, necesitamos instalar la arduinoFFT library. Abre el LIBRARY MANAGER, escribe «arduinoFFT» en la barra de búsqueda y presiona elINSTALL botón. Después de una instalación exitosa deberías ver lo siguiente:

arduinoFFT library installed in Library Manager
Librería arduinoFFT instalada en Library Manager

Además, necesitaremos instalar la Adafruit_SSD1306 Library para controlar el OLED. Escribe «Adafruit_SSD1306» en la barra de búsqueda y como antes presionaINSTALL para instalar la librería:

Adafruit_SSD1306 library installed in Library Manager
Librería Adafruit_SSD1306 instalada en Library Manager

Con eso listo, ahora podemos empezar a escribir el código. El siguiente código para un visualizador de espectro de audio lee la entrada de audio de un micrófono, realiza una Transformada Rápida de Fourier (FFT) para analizar el espectro de frecuencias y muestra los resultados en un OLED. La siguiente imagen muestra la salida que crea y el significado de los elementos visuales:

Visual Elements of the Spectrum Analyzer
Elementos visuales del Analizador de Espectro

En las siguientes secciones veremos este código función por función. El código está basado en el ejemplo de ESP32_FFT_VU pero ha sido dividido en funciones más pequeñas y ligeramente modificado para mejorar la legibilidad.

#include "arduinoFFT.h"
#include "Adafruit_SSD1306.h"

const int micPin = 4;
const int volume = 300;        // Adjust depending on audio volume 
const int noise = 2000;        // Noise filter, smaller signal is ignored  
const int nSamples = 256;      // Must be a power of 2
const int sampleFreq = 40000;  // Hz, 40000 or less 

double vReal[nSamples];
double vImag[nSamples];
byte peaks[] = { 0, 0, 0, 0, 0, 0, 0 };

ArduinoFFT<double> FFT = ArduinoFFT<double>(vReal, vImag, nSamples, sampleFreq);
Adafruit_SSD1306 oled(128, 64, &Wire, -1);

void displayScale() {
  oled.clearDisplay();
  oled.setCursor(0, 0);
  oled.print(".1 .2 .5 1K 2K 4K 8K");
}

void displayBands() {
  for (int i = 2; i < (nSamples / 2); i++) {    
    if (vReal[i] > noise) {                               
      if (i <= 2) displayBand(0, vReal[i]);              // 125Hz
      if (i > 3 && i <= 5) displayBand(1, vReal[i]);     // 250Hz
      if (i > 5 && i <= 7) displayBand(2, vReal[i]);     // 500Hz
      if (i > 7 && i <= 15) displayBand(3, vReal[i]);    // 1000Hz
      if (i > 15 && i <= 30) displayBand(4, vReal[i]);   // 2000Hz
      if (i > 30 && i <= 53) displayBand(5, vReal[i]);   // 4000Hz
      if (i > 53 && i <= 200) displayBand(6, vReal[i]);  // 8000Hz
      if (i > 200) displayBand(7, vReal[i]);             // 16000Hz
    }
  }
}

void calcFFT() {
  FFT.windowing(vReal, nSamples, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.compute(vReal, vImag, nSamples, FFT_FORWARD);
  FFT.complexToMagnitude(vReal, vImag, nSamples);
}

void sampleAudio() {
  static unsigned long newTime, oldTime;
  long period_us = round(1000000.0 / sampleFreq);

  for (int i = 0; i < nSamples; i++) {
    newTime = micros() - oldTime;
    oldTime = newTime;
    vReal[i] = analogRead(micPin);
    vImag[i] = 0;
    while (micros() < (newTime + period_us))
      ;
  }
}

void displayPeaks() {
  for (byte band = 0; band <= 6; band++) {
    oled.drawFastHLine(18 * band, 64 - peaks[band], 14, WHITE);
  }
}

void decayPeaks() {
  if (millis() % 4 == 0) {
    for (byte band = 0; band <= 6; band++) {
      peaks[band] = max(0, peaks[band] - 1);
    }
  }
}

void displayBand(int band, double magnitude) {
  int dmax = 50;
  int dsize = min((int)(magnitude / volume), dmax);
  if (band == 7) {
    oled.drawFastHLine(18 * 6, 0, 14, WHITE);
  }  
  for (int s = 0; s <= dsize; s += 2) {
    oled.drawFastHLine(18 * band, 64 - s, 14, WHITE);
  }
  if (dsize > peaks[band]) { 
    peaks[band] = dsize; 
  }
}

void setup() {
  oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  oled.setTextSize(1);
  oled.setTextColor(WHITE);

  pinMode(micPin, INPUT);
}

void loop() {
  displayScale();
  sampleAudio();
  calcFFT();
  displayBands();
  displayPeaks();
  decayPeaks();
  oled.display();
  delay(1);
}

Librerías

El código comienza incluyendo las librerías necesarias. Está la libreríaarduinoFFT para calcular la Transformada Rápida de Fourier (FFT) y la librería Adafruit_SSD1306, que se necesita para controlar el OLED:

#include "arduinoFFT.h"
#include "Adafruit_SSD1306.h"

Constantes

Luego definimos algunas constantes. micPin define el pin GPIO al que está conectado el output del MAX4466. La constante volume te permite ajustar la magnitud del espectro de frecuencia mostrado según el volumen de audio. Si no ves barras, baja este valor y si las barras son muy grandes, incluso con sonidos débiles, aumenta este valor.

De manera similar, la constante de ruido te permite filtrar el ruido de bajo nivel. Si ves demasiado ruido en el espectro de frecuencia (fluctuaciones aleatorias incluso sin sonido), aumenta este valor.

nSamples define cuántas muestras de audio se toman y sampleFreq es la frecuencia de muestreo. Para microcontroladores más lentos que el ESP32 puede que tengas que bajar estos valores.

const int micPin = 4;
const int volume = 300;        // Adjust depending on audio volume 
const int noise = 2000;        // Noise filter, smaller signal is ignored  
const int nSamples = 256;      // Must be a power of 2
const int sampleFreq = 40000;  // Hz, 40000 or less 

Variables

Hay tres variables. vReal y vImag son arrays que almacenan la parte real e imaginaria de la señal y el array peaks lleva el seguimiento de las magnitudes máximas para cada barra de frecuencia.

double vReal[nSamples];
double vImag[nSamples];
byte peaks[] = { 0, 0, 0, 0, 0, 0, 0 };

Objetos

Finalmente tenemos los objetos. El objeto FFT contiene las funciones necesarias para calcular la Transformada Rápida de Fourier, y el objeto oled contiene las funciones para controlar la pantalla OLED.

ArduinoFFT<double> FFT = ArduinoFFT<double>(vReal, vImag, nSamples, sampleFreq);
Adafruit_SSD1306 oled(128, 64, &Wire, -1);

displayScale

La función displayScale() prepara la pantalla OLED limpiando cualquier contenido previo e imprimiendo etiquetas para las bandas de frecuencia. Esto ayuda al usuario a entender qué banda corresponde a qué frecuencia. La función usa este código:

void displayScale() {
  oled.clearDisplay();
  oled.setCursor(0, 0);
  oled.print(".1 .2 .5 1K 2K 4K 8K");
}

La función displayBands() procesa el resultado de la FFT almacenado en el array vReal y decide a qué banda de frecuencia pertenece cada bin de la FFT. Filtra señales por debajo de un umbral de ruido y luego llama a displayBand() con el índice de la banda y la magnitud de la señal:

void displayBands() {
  for (int i = 2; i < (nSamples / 2); i++) {
    if (vReal[i] > noise) {
      if (i <= 2) displayBand(0, vReal[i]);
      if (i > 3 && i <= 5) displayBand(1, vReal[i]);
      if (i > 5 && i <= 7) displayBand(2, vReal[i]);
      if (i > 7 && i <= 15) displayBand(3, vReal[i]);
      if (i > 15 && i <= 30) displayBand(4, vReal[i]);
      if (i > 30 && i <= 53) displayBand(5, vReal[i]);
      if (i > 53 && i <= 200) displayBand(6, vReal[i]);
      if (i > 200) displayBand(7, vReal[i]);
    }
  }
}

La función calcFFT() realiza el análisis FFT real. Aplica una ventana de Hamming para suavizar la señal y luego calcula la FFT, transformando las partes real e imaginaria en magnitudes que se almacenan de nuevo en vReal:

void calcFFT() {
  FFT.windowing(vReal, nSamples, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.compute(vReal, vImag, nSamples, FFT_FORWARD);
  FFT.complexToMagnitude(vReal, vImag, nSamples);
}

La función sampleAudio() recoge nSamples muestras analógicas del micrófono. Lee la señal a intervalos regulares, calculados usando sampleFreq, y la almacena en el array vReal. vImag se establece en cero ya que la entrada es puramente real:

void sampleAudio() {
  static unsigned long newTime, oldTime;
  long period_us = round(1000000.0 / sampleFreq);

  for (int i = 0; i < nSamples; i++) {
    newTime = micros() - oldTime;
    oldTime = newTime;
    vReal[i] = analogRead(micPin);
    vImag[i] = 0;
    while (micros() < (newTime + period_us))
      ;
  }
}

La función displayPeaks() muestra valores máximos persistentes de cada banda de frecuencia dibujando líneas horizontales cortas donde el pico ocurrió por última vez. Estas líneas ayudan a visualizar los niveles máximos recientes de volumen en cada banda:

void displayPeaks() {
  for (byte band = 0; band <= 6; band++) {
    oled.drawFastHLine(18 * band, 64 - peaks[band], 14, WHITE);
  }
}

La función decayPeaks() reduce lentamente el valor pico con el tiempo, simulando un efecto de decaimiento. Se ejecuta aproximadamente cada 4 milisegundos y baja el pico de cada banda en 1, hasta un mínimo de 0:

void decayPeaks() {
  if (millis() % 4 == 0) {
    for (byte band = 0; band <= 6; band++) {
      peaks[band] = max(0, peaks[band] - 1);
    }
  }
}

La función displayBand() dibuja barras verticales para cada banda en el OLED. Escala el tamaño de la barra basado en la magnitud de la señal dividida por el parámetro volume. Si el índice de la banda es 7, se dibuja una barra especial en la parte superior de la pantalla. La función también actualiza el array peaks si la barra actual supera el pico anterior:

void displayBand(int band, double magnitude) {
  int dmax = 50;
  int dsize = min((int)(magnitude / volume), dmax);
  if (band == 7) {
    oled.drawFastHLine(18 * 6, 0, 14, WHITE);
  }
  for (int s = 0; s <= dsize; s += 2) {
    oled.drawFastHLine(18 * band, 64 - s, 14, WHITE);
  }
  if (dsize > peaks[band]) {
    peaks[band] = dsize;
  }
}

La función setup() inicializa la pantalla OLED y configura el pin del micrófono como entrada. Esto prepara el sistema para comenzar a muestrear y mostrar datos de audio:

void setup() {
  oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  oled.setTextSize(1);
  oled.setTextColor(WHITE);
  pinMode(micPin, INPUT);
}

Finalmente, la función loop() es el ciclo principal de ejecución. Repite todos los pasos necesarios para capturar audio, procesarlo con FFT, visualizar los datos y actualizar la pantalla OLED:

void loop() {
  displayScale();
  sampleAudio();
  calcFFT();
  displayBands();
  displayPeaks();
  decayPeaks();
  oled.display();
  delay(1);
}

Juntas, estas funciones forman un ciclo completo que analiza continuamente la entrada de audio y muestra un espectro visual en la pantalla OLED.

Tonos de prueba

Puedes probar el Analizador de Espectro reproduciendo algunos tonos de prueba. Aquí tienes enlaces a vídeos de Youtube que reproducen tonos con frecuencias específicas:

El Analizador de Espectro debería mostrar una barra más grande/creciente para la banda de frecuencia en la que cae el tono de prueba. Por ejemplo, para un tono de 2000 Hz deberías ver una barra más grande en la banda de 2KHz:

2KHz tone detected by Spectrum Analyzer
Tono de 2KHz detectado por el Analizador de Espectro

La altura de la barra dependerá del volumen. Probablemente tendrás que ajustar la constante volume o el gain control para el MAX4466 para obtener una señal adecuada. Puedes encontrar el potenciómetro para ajustar la ganancia en la parte trasera del módulo MAX4466:

Gain control of MAX4466 module
Control de ganancia del módulo MAX4466

Conclusiones

En este tutorial aprendiste a construir un Analizador de Espectro de Frecuencia usando el módulo de micrófono MAX4466, un OLED y un ESP32.

Si quieres mejorar la precisión y el rango de frecuencia del Analizador de Espectro, podrías usar un Convertidor Analógico-Digital externo para mayor resolución y un micrófono I2C para un mejor rango.

De manera similar, si quieres una pantalla más grande y a color, podrías reemplazar el OLED por una pantalla TFT. Echa un vistazo a los tutoriales de Interface TFT ILI9341 Touch Display with ESP32 y tal vez al deHow to configure TFT_eSPI Library for TFT display.

La placa ESP32 lite tiene una interfaz para batería LiPo, lo que facilita usar el Analizador de Espectro con una batería LiPo de 4.2V. Sin embargo, la placa es un poco grande, especialmente comparada con el pequeño OLED que usamos. En lugar del ESP32 lite, podrías usar un ESP32-C SuperMini para construir un Analizador de Espectro muy pequeño, bonito y portátil.

¡Feliz bricolaje ; )

Enlaces

A continuación algunos enlaces que encontré útiles al escribir este tutorial: