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ónComponentePin en componentePin en ESP32-DevKitC V4
I2S BCLK (SCK)INMP441SCKGPIO26
I2S LRCLK (WS)INMP441WSGPIO25
I2S DATA ININMP441SDGPIO33
Alimentación micINMP441VDD3V3
Tierra micINMP441GNDGND
Selección canalINMP441L/RGND (canal izquierdo)
Datos NeoPixelWS2812B ringDINGPIO18 (a través de 330–470 Ω y/o shifter 5 V)
Alimentación ledsWS2812B ring5V5V (USB o fuente externa)
Tierra ledsWS2812B ringGNDGND (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