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
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.



