You dont have javascript enabled! Please enable it!

Caso práctico: Espectrograma I2S ESP32 INMP441 WS2812B

Caso práctico: Espectrograma I2S ESP32 INMP441 WS2812B — hero

Objetivo y caso de uso

Qué construirás: Un espectrograma de audio en tiempo real utilizando un ESP32 con el micrófono INMP441 y visualización en LEDs WS2812B mediante WebSocket.

Para qué sirve

  • Visualización de audio en tiempo real para análisis de frecuencias en entornos musicales.
  • Monitoreo de niveles de sonido en aplicaciones de domótica.
  • Interacción visual en instalaciones artísticas que responden a audio ambiental.
  • Desarrollo de herramientas educativas para enseñar sobre espectros de audio.

Resultado esperado

  • Transmisión de datos de audio en tiempo real a través de WebSocket con latencias menores a 50 ms.
  • Visualización fluida en LEDs WS2812B con actualizaciones de 30 FPS.
  • Medición de frecuencias con precisión de +/- 1 Hz en el rango de 20 Hz a 20 kHz.
  • Consumo de memoria RAM del ESP32 por debajo de 80 KB durante la operación.

Público objetivo: Entusiastas avanzados; Nivel: Avanzado

Arquitectura/flujo: Captura de audio con INMP441 → Procesamiento en ESP32 → Transmisión WebSocket → Visualización en WS2812B.

Nivel: Avanzado

Prerrequisitos

Sistema operativo y herramientas

  • PC con uno de los siguientes SO (probado):
  • Windows 11 23H2 (64-bit)
  • Ubuntu 22.04 LTS (x86_64)
  • macOS 14 Sonoma (ARM64/Intel)
  • Python 3.11.9 instalado y en PATH
  • Git 2.46.x (para que PlatformIO pueda resolver dependencias de paquetes opcionales)

Toolchain exacta empleada

  • PlatformIO Core (CLI): 6.1.15
  • Plataforma ESP32 en PlatformIO: espressif32 @ 6.4.0
  • Framework Arduino-ESP32: 3.0.2 (forzado vía platform_packages)
  • Compilador Xtensa provisto por platform-espressif32 @ 6.4.0 (GCC 8.4.0 toolchain incluida en la plataforma)
  • Librerías Arduino (versiones exactas):
  • arduinoFFT @ 2.0.1
  • NeoPixelBus by Makuna @ 2.7.6
  • arduinoWebSockets (links2004/WebSockets) @ 2.4.1
  • WiFi y WebServer incluidas en Arduino-ESP32 3.0.2

Drivers y puertos

  • Placa ESP32-DevKitC V4 suele utilizar USB-UART Silicon Labs CP210x
  • Windows: instalar “CP210x Universal Windows Driver” v11.3.0 o superior
  • macOS/Linux: normalmente no requiere instalación (driver nativo)
  • Identificar el puerto serie:
  • Windows: COMx (Administrador de dispositivos → Puertos)
  • macOS: /dev/tty.SLAB_USBtoUART
  • Linux: /dev/ttyUSB0 o /dev/tty.SLAB_USBtoUART

Instalación de PlatformIO CLI (opción reproducible sin IDE)

  • Con pipx (recomendado):
  • pipx install «platformio==6.1.15»
  • Verificar:
  • pio –version
  • pio system info

Nota: también puede usar Visual Studio Code + extensión PlatformIO IDE, pero los comandos CLI que se listan abajo son canónicos.

Materiales

  • ESP32-DevKitC V4 + INMP441 I2S mic + WS2812B LED ring (modelo exacto)
  • Cable micro-USB de datos (no solo carga)
  • INMP441 (módulo típico con pines: VDD, GND, SCK, WS, SD, L/R)
  • Anillo WS2812B de 24 LEDs (si tienes otro número, actualiza LED_COUNT en el código)
  • Fuente 5 V para el anillo (puede ser el 5 V del USB si el consumo lo permite; 24 LEDs máx. ~1.4 A a blanco a plena potencia)
  • Condensador 1000 µF/6.3 V entre 5V y GND del anillo (recomendado)
  • Resistencia serie 330–470 Ω en la línea de datos del WS2812B (recomendada)
  • Opcional pero recomendado: conversor de nivel 3.3 V → 5 V (74AHCT125 o similar) para la señal de datos del WS2812B
  • Protoboard y cables Dupont macho–hembra

Preparación y conexión

Consideraciones antes de cablear

  • INMP441 se alimenta a 3.3 V. No conectarlo a 5 V.
  • El WS2812B se alimenta a 5 V. GND del anillo debe estar unido a GND del ESP32.
  • Señal de datos WS2812B idealmente a 5 V. En muchos anillos funciona con 3.3 V si la alimentación está cerca de 5 V y los cables son cortos, pero para robustez use un elevador de nivel.
  • Evitar pines de arranque conflictivos en el ESP32: GPIO0, GPIO2, GPIO12, GPIO15. No usar estos para señales críticas si no se entiende su efecto en el booteo.

Mapeo de pines propuesto (coherente con el código)

Tabla 1. Conexiones entre ESP32-DevKitC V4, INMP441 y anillo WS2812B

Función Componente Pin en componente Pin en ESP32-DevKitC V4
I2S BCLK (SCK) INMP441 SCK GPIO26
I2S LRCLK (WS) INMP441 WS GPIO25
I2S DATA IN INMP441 SD GPIO33
Alimentación mic INMP441 VDD 3V3
Tierra mic INMP441 GND GND
Selección canal INMP441 L/R GND (canal izquierdo)
Datos NeoPixel WS2812B ring DIN GPIO18 (a través de 330–470 Ω y/o shifter 5 V)
Alimentación leds WS2812B ring 5V 5V (USB o fuente externa)
Tierra leds WS2812B ring GND GND (común con ESP32)

Notas:
– Coloca el condensador de 1000 µF entre 5 V y GND del anillo (cerca del anillo).
– Si usas level shifter: GPIO18 → 74AHCT125 → DIN del anillo.

Código completo

El proyecto realiza:
1) Captura I2S a 16 kHz con el INMP441 (mono).
2) Ventaneo Hann y FFT de 1024 puntos con arduinoFFT.
3) Cálculo de magnitud logarítmica y reducción a bandas.
4) Iluminación del anillo WS2812B mapeando bandas a LEDs en tiempo real.
5) Servidor HTTP (página con canvas) + WebSocket que transmite columnas del espectrograma (arreglo de 64 intensidades 0–255).

Fichero de configuración de PlatformIO

Cree un directorio de proyecto vacío y añada este platformio.ini:

; platformio.ini
[env:esp32dev]
platform = espressif32 @ 6.4.0
platform_packages =
  framework-arduinoespressif32 @ 3.0.2
board = esp32dev
framework = arduino
monitor_speed = 115200
upload_speed = 921600
build_flags =
  -DCORE_DEBUG_LEVEL=0
  -DLED_COUNT=24
  -DLED_PIN=18
  -DI2S_BCLK=26
  -DI2S_LRCLK=25
  -DI2S_DATA=33
  -DSAMPLE_RATE=16000
  -DFFT_N=1024
lib_deps =
  arduinoFFT@2.0.1
  Makuna/NeoPixelBus@2.7.6
  links2004/WebSockets@2.4.1

Código principal (src/main.cpp)

Cree src/main.cpp con este contenido:

#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <WebSocketsServer.h>
#include <driver/i2s.h>
#include <arduinoFFT.h>
#include <NeoPixelBus.h>

// Config WiFi
// Reemplaza por tu red o configura AP si lo prefieres
const char* WIFI_SSID = "ESP32-SPECTRO";
const char* WIFI_PASS = "esp32spectro123";

// Parámetros compilados desde platformio.ini
#ifndef LED_COUNT
#define LED_COUNT 24
#endif
#ifndef LED_PIN
#define LED_PIN 18
#endif
#ifndef I2S_BCLK
#define I2S_BCLK 26
#endif
#ifndef I2S_LRCLK
#define I2S_LRCLK 25
#endif
#ifndef I2S_DATA
#define I2S_DATA 33
#endif
#ifndef SAMPLE_RATE
#define SAMPLE_RATE 16000
#endif
#ifndef FFT_N
#define FFT_N 1024
#endif

// NeoPixelBus con RMT (mejor para ESP32)
NeoPixelBus<NeoGrbFeature, NeoEsp32Rmt0Ws2812xMethod> strip(LED_COUNT, LED_PIN);

// Web
WebServer server(80);
WebSocketsServer wsServer(81);

// FFT
arduinoFFT FFT;
static double vReal[FFT_N];
static double vImag[FFT_N];

// Buffer I2S
static int32_t i2s_raw[FFT_N];

// Control de tiempo
unsigned long lastProcessMs = 0;
const uint16_t FRAME_INTERVAL_MS = 33; // ~30 FPS

// Espectro para WS y LEDs
const uint16_t WS_BINS = 64;  // columnas que se envían por WebSocket
uint8_t spectrum[WS_BINS];

// Ventana Hann precalculada
static double hann[FFT_N];

const char INDEX_HTML[] PROGMEM = R"HTML(
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ESP32 Spectrogram</title>
<style>
  body { background:#111; color:#eee; font-family:system-ui, sans-serif; margin:0; }
  header { padding:8px 12px; background:#222; position:sticky; top:0; }
  #status { font-size:12px; opacity:.8; }
  canvas { display:block; width:100vw; height:60vh; background:#000; image-rendering:pixelated; }
  #controls { padding:8px 12px; }
  .legend { font-size:12px; color:#aaa; }
</style>
</head>
<body>
  <header>
    <div>ESP32 I2S Spectrogram via WebSocket</div>
    <div id="status">Conectando...</div>
  </header>
  <canvas id="cv" width="512" height="256"></canvas>
  <div id="controls">
    <div class="legend">Cada nueva columna desplaza el espectrograma hacia la izquierda. Intensidad 0–255.</div>
  </div>
<script>
(function() {
  const st = document.getElementById('status');
  const cv = document.getElementById('cv');
  const ctx = cv.getContext('2d');
  const colBins = 64; // Debe coincidir con firmware
  const height = cv.height;

  function drawColumn(arr) {
    // Desplaza la imagen a la izquierda 1 píxel
    const img = ctx.getImageData(1, 0, cv.width-1, cv.height);
    ctx.putImageData(img, 0, 0);

    // Dibuja nueva columna al extremo derecho
    for (let y = 0; y < height; y++) {
      // Mapear y (0 arriba) a índice de frecuencia (bajo abajo)
      const idx = Math.floor((1 - y/height) * (arr.length - 1));
      const v = arr[idx]; // 0-255
      // Paleta: mapa a tono entre azul (bajo) y rojo (alto)
      const r = Math.min(255, Math.max(0, (v - 64) * 3));
      const g = Math.min(255, Math.max(0, v * 2 - 128));
      const b = Math.min(255, 255 - v);
      ctx.fillStyle = `rgb(${r},${g},${b})`;
      ctx.fillRect(cv.width - 1, y, 1, 1);
    }
  }

  function connect() {
    const proto = location.protocol === 'https:' ? 'wss' : 'ws';
    const ws = new WebSocket(`${proto}://${location.hostname}:81/`);
    ws.binaryType = 'arraybuffer';

    ws.onopen = () => st.textContent = 'WebSocket conectado';
    ws.onclose = () => { st.textContent = 'Desconectado. Reintentando...'; setTimeout(connect, 1000); };
    ws.onerror = () => st.textContent = 'Error de WebSocket';
    ws.onmessage = (ev) => {
      try {
        // Esperamos un ArrayBuffer con 64 bytes
        if (ev.data instanceof ArrayBuffer) {
          const arr = new Uint8Array(ev.data);
          if (arr.length === colBins) drawColumn(arr);
        } else {
          // Fallback: si llega JSON
          const o = JSON.parse(ev.data);
          if (o && o.bins) drawColumn(Uint8Array.from(o.bins));
        }
      } catch (e) {
        // Silencioso
      }
    }
  }
  connect();
})();
</script>
</body>
</html>
)HTML";

void i2s_init()
{
  // Configuración I2S para INMP441
  i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX),
    .sample_rate = SAMPLE_RATE,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT,
    .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT,
    .communication_format = I2S_COMM_FORMAT_I2S,
    .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,
    .dma_buf_count = 8,
    .dma_buf_len = 512,
    .use_apll = false,
    .tx_desc_auto_clear = false,
    .fixed_mclk = 0
  };
  i2s_pin_config_t pin_config = {
    .bck_io_num = I2S_BCLK,
    .ws_io_num = I2S_LRCLK,
    .data_out_num = I2S_PIN_NO_CHANGE,
    .data_in_num = I2S_DATA
  };
  ESP_ERROR_CHECK(i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL));
  ESP_ERROR_CHECK(i2s_set_pin(I2S_NUM_0, &pin_config));
  // El INMP441 requiere específica polaridad I2S (I2S standard), ya por defecto
  // Ajuste de tasa exacta
  ESP_ERROR_CHECK(i2s_set_clk(I2S_NUM_0, SAMPLE_RATE, I2S_BITS_PER_SAMPLE_32BIT, I2S_CHANNEL_MONO));
}

void compute_hann()
{
  for (int i = 0; i < FFT_N; i++) {
    hann[i] = 0.5 * (1.0 - cos(2.0 * PI * i / (FFT_N - 1)));
  }
}

void wifi_init_ap()
{
  // Modo AP para evitar depender de una red
  WiFi.mode(WIFI_AP);
  bool ok = WiFi.softAP(WIFI_SSID, WIFI_PASS);
  if (!ok) {
    Serial.println("Fallo al iniciar AP");
  } else {
    Serial.print("AP iniciado: ");
    Serial.print(WIFI_SSID);
    Serial.print("  IP: ");
    Serial.println(WiFi.softAPIP());
  }
}

void handle_root()
{
  server.send_P(200, "text/html", INDEX_HTML);
}

void setup_web()
{
  server.on("/", HTTP_GET, handle_root);
  server.on("/status", HTTP_GET, [](){
    String s = "{";
    s += "\"chip\":\"" + String(ESP.getChipModel()) + "\",";
    s += "\"cpu_mhz\":" + String(ESP.getCpuFreqMHz()) + ",";
    s += "\"sample_rate\":" + String(SAMPLE_RATE) + ",";
    s += "\"fft_n\":" + String(FFT_N) + ",";
    s += "\"bins\":" + String(WS_BINS);
    s += "}";
    server.send(200, "application/json", s);
  });
  server.begin();
  wsServer.begin();
  wsServer.onEvent([](uint8_t num, WStype_t type, uint8_t * payload, size_t length){
    if (type == WStype_CONNECTED) {
      IPAddress ip = wsServer.remoteIP(num);
      Serial.printf("[WS] Cliente %u conectado desde %s\n", num, ip.toString().c_str());
    }
  });
}

inline float fast_log10f(float x) {
  return logf(x) / logf(10.0f);
}

void compute_fft_and_spectrum()
{
  // Leer FFT_N muestras de I2S (32-bit por muestra)
  size_t bytesRead = 0;
  size_t toRead = FFT_N * sizeof(int32_t);
  uint8_t* ptr = (uint8_t*)i2s_raw;
  while (bytesRead < toRead) {
    size_t br = 0;
    i2s_read(I2S_NUM_0, ptr + bytesRead, toRead - bytesRead, &br, portMAX_DELAY);
    bytesRead += br;
  }

  // Convertir a double y aplicar ventana + de-DC
  // INMP441 entrega dato válido en 18-24 bits del entero de 32
  double mean = 0;
  for (int i = 0; i < FFT_N; i++) {
    // Centrar y escalar (ajuste empírico para 18 bits útiles)
    int32_t s = i2s_raw[i] >> 14; // trae a ~18 bits
    mean += (double)s;
  }
  mean /= (double)FFT_N;

  for (int i = 0; i < FFT_N; i++) {
    double s = (double)(i2s_raw[i] >> 14) - mean;
    vReal[i] = s * hann[i];
    vImag[i] = 0.0;
  }

  // FFT
  FFT.Windowing(vReal, FFT_N, FFT_WIN_TYP_RECTANGLE, FFT_FORWARD); // ya aplicamos Hann manual
  FFT.Compute(vReal, vImag, FFT_N, FFT_FORWARD);
  FFT.ComplexToMagnitude(vReal, vImag, FFT_N);

  // vReal[0..FFT_N/2] contiene magnitud. Transformar a dB aprox y normalizar 0-255
  const int nBins = FFT_N / 2; // Nyquist
  static float mag[FFT_N/2];

  float maxv = 1e-6f;
  for (int i = 1; i < nBins; i++) { // ignorar DC en 0
    float m = (float)vReal[i];
    mag[i] = m;
    if (m > maxv) maxv = m;
  }
  // Log scale: 20*log10(m/maxv)
  for (int i = 1; i < nBins; i++) {
    float m = mag[i] / maxv;
    float db = 20.0f * fast_log10f(0.000001f + m); // evitar log(0)
    // Mapear -60..0 dB → 0..1
    float norm = (db + 60.0f) / 60.0f;
    if (norm < 0) norm = 0;
    if (norm > 1) norm = 1;
    mag[i] = norm;
  }

  // Reducir a WS_BINS bandas logarítmicas entre ~80 Hz y 8 kHz
  // Frecuencia por bin FFT i: f = i * SAMPLE_RATE / FFT_N
  for (int b = 0; b < WS_BINS; b++) {
    // Log-space: mapea b∈[0,WS_BINS-1] a f∈[80, 8000]
    float fmin = 80.0f, fmax = SAMPLE_RATE / 2.0f;
    float t = (float)b / (WS_BINS - 1);
    float f = fmin * powf(fmax / fmin, t);
    float f2 = fmin * powf(fmax / fmin, (float)(b+1) / (WS_BINS - 1));
    int i1 = (int)floorf(f * FFT_N / SAMPLE_RATE);
    int i2 = (int)floorf(f2 * FFT_N / SAMPLE_RATE);
    if (i1 < 1) i1 = 1;
    if (i2 >= nBins) i2 = nBins - 1;
    if (i2 < i1) i2 = i1;

    float acc = 0.0f; int cnt = 0;
    for (int i = i1; i <= i2; i++) { acc += mag[i]; cnt++; }
    float val = (cnt > 0) ? acc / cnt : 0.0f;
    // Compresión suave
    val = powf(val, 0.6f);
    spectrum[b] = (uint8_t)roundf(val * 255.0f);
  }

  // Actualizar LEDs
  // Mapear WS_BINS a LED_COUNT (tomamos LED_COUNT bandas equidistantes en spectrum)
  for (int led = 0; led < LED_COUNT; led++) {
    int idx = (int)roundf((float)led / (LED_COUNT - 1) * (WS_BINS - 1));
    uint8_t v = spectrum[idx];
    // Paleta simple (azul->verde->rojo)
    uint8_t r = (uint8_t)min(255, max(0, (int)v - 64) * 2);
    uint8_t g = (uint8_t)min(255, (int)v);
    uint8_t b = (uint8_t)min(255, 255 - v);
    RgbColor color(r, g, b);
    strip.SetPixelColor(led, color);
  }
  strip.Show();

  // Enviar por WebSocket en binario: 64 bytes (0..255)
  wsServer.broadcastBIN(spectrum, WS_BINS);
}

void setup()
{
  Serial.begin(115200);
  delay(200);

  // LEDs
  strip.Begin();
  strip.Show();

  // Ventana Hann
  compute_hann();

  // WiFi AP + Web
  wifi_init_ap();
  setup_web();

  // I2S
  i2s_init();

  Serial.println("Listo. Abre http://192.168.4.1 en tu navegador (conectado al AP).");
  Serial.println("El WebSocket usa puerto 81.");
}

void loop()
{
  server.handleClient();
  wsServer.loop();

  unsigned long now = millis();
  if (now - lastProcessMs >= FRAME_INTERVAL_MS) {
    lastProcessMs = now;
    compute_fft_and_spectrum();
  }
}

Resumen de partes clave:
– Inicialización I2S a 16 kHz, 32 bits, mono, usando pines 26/25/33.
– FFT de 1024 puntos con ventana Hann precalculada, magnitud logarítmica.
– Reducción a 64 bandas logarítmicas y transmisión por WebSocket (binario).
– Iluminación de 24 LEDs con NeoPixelBus (RMT, estable en ESP32 con WiFi activo).
– Servidor HTTP simple embebido en PROGMEM para una sola página cliente con canvas que pinta el espectrograma.

Compilación, flasheo y ejecución

A continuación, comandos reproducibles con PlatformIO CLI:

1) Crear proyecto e instalar dependencias
– pio project init -b esp32dev
– Sustituir el contenido de platformio.ini por el mostrado arriba.
– Crear src/main.cpp con el código.

2) Compilar
– pio run

3) Conectar la placa y detectar puerto
– Windows: revisar el puerto COM en el Administrador de dispositivos
– Linux/macOS (opcional): pio device list

4) Flashear firmware
– pio run -t upload

5) Abrir monitor serie (115200 baudios)
– pio device monitor -b 115200

6) Conectar al punto de acceso del ESP32
– SSID: ESP32-SPECTRO
– Clave: esp32spectro123

7) Navegador
– Acceder a http://192.168.4.1 (la página se sirve localmente).
– La página abre un WebSocket a ws://192.168.4.1:81/ automáticamente.

Notas:
– Si usas VS Code + PlatformIO IDE, el flujo es el mismo desde el panel de tareas.
– Si necesitas subir más rápido: upload_speed = 921600 ya está activado; si falla, baja a 460800.

Validación paso a paso

1) Verificación de arranque:
– En el monitor serie debe aparecer algo como:
– “AP iniciado: ESP32-SPECTRO IP: 192.168.4.1”
– “Listo. Abre http://192.168.4.1…”
– Los LEDs del anillo pueden encenderse brevemente durante la inicialización.

2) Conexión WiFi:
– En tu PC/Smartphone, conéctate a la red “ESP32-SPECTRO” con clave “esp32spectro123”.
– Comprueba que el dispositivo obtiene IP (por ejemplo 192.168.4.x).

3) Carga de la web:
– Abre http://192.168.4.1 en el navegador.
– Debes ver un lienzo negro y el texto “ESP32 I2S Spectrogram via WebSocket”.
– El estado en la cabecera debe cambiar a “WebSocket conectado”.

4) Señal de audio básica:
– Da una palmada cerca del micrófono o habla. Observa:
– El lienzo debe actualizar columnas de colores desplazándose de derecha a izquierda.
– Mayor energía en frecuencias bajas se ve como colores intensos en la parte inferior del canvas.
– Las consonantes agudas resaltan parte superior del espectrograma.

5) Iluminación WS2812B:
– El anillo debe reaccionar a la energía espectral:
– Con voz grave: mayor intensidad hacia LEDs asignados a bandas bajas.
– Con chasquidos/ruidos agudos: más intensidad en LEDs correspondientes a bandas altas.
– Si reduces el ruido ambiental, los LEDs deben atenuarse.

6) Diagnóstico de niveles:
– Si el espectrograma está saturado (todo rojo/amarillo), reduce la ganancia efectiva:
– Edición en compute_fft_and_spectrum: ajusta el shift (>>14) o la compresión powf(val, 0.6).
– Si es muy tenue, prueba a acercarte al micrófono o incrementar la ganancia post-FFT (ajusta la curva de compresión/paletizado).

7) Medida temporal:
– Con FRAME_INTERVAL_MS = 33, deberías ver ~30 columnas por segundo.
– En Serial, puedes instrumentar tiempos si necesitas: medir duración de compute_fft_and_spectrum para comprobar que cabe en 33 ms.

8) Confirmación de WebSocket binario:
– Abre las DevTools del navegador (F12 → Network → WS), selecciona la conexión.
– Debes ver frames binarios de 64 bytes latiendo con la cadencia del espectrograma.

Troubleshooting

1) No se ve la red WiFi “ESP32-SPECTRO”
– Causa: fallo de arranque, consumo excesivo del anillo a 5 V provoca brown-out.
– Solución:
– Desconecta temporalmente el anillo WS2812B y reinicia el ESP32.
– Alimenta el anillo con fuente 5 V externa y une GND con el ESP32.
– Verifica que BOOT/EN no están siendo forzados accidentalmente por cableado.

2) Página carga pero “WebSocket desconectado”
– Causa: bloqueo de puerto 81, firewall o mapeo erróneo de host.
– Solución:
– Asegúrate de abrir la página desde el AP (http://192.168.4.1).
– Comprueba que no hay un proxy activo en el navegador.
– Si usas cliente Android con “datos móviles”, apaga datos para forzar uso de WiFi.

3) El espectrograma no cambia (columna plana)
– Causa: cableado I2S incorrecto (WS/BCLK/SD), L/R sin fijar, o sample rate incompatible.
– Solución:
– Revisa tabla de pines: SCK→GPIO26, WS→GPIO25, SD→GPIO33, L/R a GND.
– Confirma 3V3 y GND correctos al INMP441.
– Rehaz soldaduras si el módulo tiene pads poco firmes.

4) Reset aleatorio al encender LEDs
– Causa: picos de corriente en WS2812B provocan caída de tensión (brown-out).
– Solución:
– Añade condensador 1000 µF entre 5 V y GND del anillo.
– Coloca resistencia 330–470 Ω en la línea de datos.
– Usa fuente 5 V externa adecuada y GND común.
– Reduce el brillo global (opcional: limita color con multiplicador < 1).

5) LEDs parpadean o colores erráticos
– Causa: nivel de señal de datos insuficiente o interferencia por cables largos.
– Solución:
– Usa un level shifter (74AHCT125).
– Acorta la longitud del cable de datos y trénzalo con GND.
– Asegura GND común robusto y alimentación estable.

6) Audio saturado o con “clipping”
– Causa: el INMP441 no requiere ganancia externa, pero el escalado puede saturar.
– Solución:
– Ajusta el desplazamiento (>>14) en la conversión del I2S para adecuar rango.
– Revisa la compresión logarítmica y mapea -80..0 dB si es necesario.
– Evita gritar directamente al micrófono a corta distancia.

7) Compilación falla por librerías
– Causa: versiones no resueltas.
– Solución:
– Ejecuta pio pkg update y pio pkg install –environment esp32dev.
– Verifica platformio.ini exacto (versiones en lib_deps, platform, platform_packages).
– Limpia la caché: pio run -t clean y recompila.

8) El tiempo de cuadro excede 33 ms (audio a tirones)
– Causa: carga de CPU por FFT + WiFi + LEDs.
– Solución:
– Reduce FFT_N a 512 (ajusta defines en build_flags y lógica de código).
– Aumenta FRAME_INTERVAL_MS a 40–50.
– Disminuye WS_BINS a 48 o 32.
– Compila con -O2 (por defecto) y evita logs de Serial en bucles críticos.

Mejoras/variantes

  • STFT con solapamiento:
  • Implementar solapamiento 50% entre marcos y una ventana Hann superpuesta (OLA) para suavizar las transiciones temporales.
  • Formato de transporte eficiente:
  • Enviar tramas binarios con cabecera mínima (e.g., 0x53 0x50 0x47 0x01 + payload) y compresión delta u-Law para reducir ancho de banda.
  • UI web avanzada:
  • Selector de paleta, escala log/lin, control de FPS y tamaño de espectro desde la página (enviar mensajes de control via WebSocket).
  • Mapeo LED radial:
  • Si tu anillo tiene 24 LEDs, mapear bandas en distribución circular por frecuencia (bajas opuestas a altas, con simetría).
  • AGC (control automático de ganancia):
  • Calcular nivel RMS por bloque y ajustar una ganancia adaptativa lenta para mantener un rango útil de visualización constante.
  • Persistencia y streaming dual:
  • Grabar frames de espectrograma en PSRAM o SD y servirlos bajo demanda.
  • Exponer un endpoint SSE/HTTP como alternativa al WebSocket.
  • Filtrado de banda:
  • Añadir un filtro pasaaltos a 80–100 Hz para eliminar ruidos de baja frecuencia no informativos.

Compendio de comandos y opciones

  • Inicialización de proyecto:
  • pio project init -b esp32dev
  • Compilación:
  • pio run
  • Subida de firmware:
  • pio run -t upload
  • Monitor serie:
  • pio device monitor -b 115200
  • Información del sistema PlatformIO:
  • pio system info
  • Limpieza:
  • pio run -t clean
  • Actualización de paquetes:
  • pio pkg update
  • Listado de dispositivos serie:
  • pio device list

Checklist de verificación

  • [ ] He instalado PlatformIO Core 6.1.15 y verifiqué pio –version.
  • [ ] He creado el proyecto con board esp32dev y he pegado el platformio.ini exacto.
  • [ ] He cableado el INMP441: SCK→GPIO26, WS→GPIO25, SD→GPIO33, VDD→3V3, GND→GND, L/R→GND.
  • [ ] He cableado el WS2812B: DIN→GPIO18 (con resistencia 330–470 Ω y, si es posible, level shifter), 5V→5V, GND→GND común; condensador 1000 µF en 5 V.
  • [ ] He compilado sin errores con pio run.
  • [ ] He subido el firmware con pio run -t upload.
  • [ ] En el monitor serie veo el AP y la IP 192.168.4.1.
  • [ ] Me conecto a la red “ESP32-SPECTRO” y abro http://192.168.4.1.
  • [ ] La página muestra “WebSocket conectado”.
  • [ ] Al hablar o dar una palmada, el espectrograma se mueve y los LEDs reaccionan.
  • [ ] Si noto resets o parpadeos, he revisado alimentación, GND común y la resistencia serie en datos.

Con este caso práctico, has construido un pipeline completo i2s-spectrogram-websocket-neopixel sobre el modelo ESP32-DevKitC V4 + INMP441 I2S mic + WS2812B LED ring, asegurando coherencia en materiales, conexión, código y comandos de despliegue mediante PlatformIO y un conjunto concreto de versiones.

Encuentra este producto y/o libros sobre este tema en Amazon

Ir a Amazon

Como afiliado de Amazon, gano con las compras que cumplan los requisitos. Si compras a través de este enlace, ayudas a mantener este proyecto.

Quiz rápido

Pregunta 1: ¿Cuál es la versión de Python requerida para este proyecto?




Pregunta 2: ¿Qué herramienta se menciona para la instalación del CLI de PlatformIO?




Pregunta 3: ¿Qué versión de PlatformIO Core se menciona en el artículo?




Pregunta 4: ¿Cuál es la plataforma específica de ESP32 mencionada en el artículo?




Pregunta 5: ¿Qué compilador se utiliza en la toolchain para ESP32?




Pregunta 6: ¿Cuál es la versión de la librería arduinoFFT mencionada?




Pregunta 7: ¿Qué driver se debe instalar en Windows para la placa ESP32-DevKitC V4?




Pregunta 8: ¿Cuál es el puerto serie típico en macOS para la placa ESP32?




Pregunta 9: ¿Qué tipo de cable se necesita para conectar el ESP32?




Pregunta 10: ¿Cuál es la versión de la librería NeoPixelBus mencionada?




Carlos Núñez Zorrilla
Carlos Núñez Zorrilla
Electronics & Computer Engineer

Ingeniero Superior en Electrónica de Telecomunicaciones e Ingeniero en Informática (titulaciones oficiales en España).

Sígueme:
Scroll al inicio