Objetivo y caso de uso
Qué construirás: Un sistema avanzado de localización acústica utilizando el ESP32-S3-DevKitC-1 y el micrófono INMP441 para capturar audio estéreo y estimar el tiempo de llegada diferido (TDOA).
Para qué sirve
- Localización de fuentes de sonido en entornos ruidosos utilizando técnicas de beamforming.
- Detección de la dirección de llegada (DoA) de sonidos en aplicaciones de robótica y automatización.
- Implementación de sistemas de seguridad acústica que responden a sonidos específicos.
- Desarrollo de asistentes de voz que mejoran la precisión en la detección de comandos.
Resultado esperado
- Precisión en la localización de fuentes de sonido con un margen de error de menos de 5 grados.
- Latencia de procesamiento de audio de menos de 50 ms para respuestas en tiempo real.
- Capacidad de manejar hasta 10 paquetes de audio por segundo para análisis en tiempo real.
- Consumo de energía optimizado para funcionamiento continuo en dispositivos portátiles.
Público objetivo: Desarrolladores y entusiastas de la electrónica; Nivel: Avanzado
Arquitectura/flujo: Captura de audio estéreo mediante INMP441, procesamiento en ESP32-S3, estimación de TDOA y DoA, salida de datos a través de MQTT.
Nivel: Avanzado
Prerrequisitos
Sistema operativo y herramientas probadas
- Windows 11 23H2 (x64) con PowerShell 7.4
- Ubuntu 22.04 LTS (x86_64) con Bash 5.1
- macOS 13 Ventura (Apple Silicon o Intel) con Terminal zsh 5.8
Toolchain exacta y versiones
- PlatformIO Core 6.1.14 (instalado vía pip)
- Especificaciones de proyecto PlatformIO:
- platform = espressif32@6.5.0
- framework = espidf (ESP-IDF 5.1.2)
- board = esp32-s3-devkitc-1
- platform packages fijados:
- platformio/toolchain-xtensa-esp32s3@12.2.0+20230208
- platformio/framework-espidf@5.1.2
- platformio/tool-esptoolpy@1.40501.0 (esptool.py 4.5.1)
- Python 3.10.x (recomendado 3.10.12) para PlatformIO
- Drivers USB:
- CP210x USB to UART Bridge (si tu placa expone puerto UART vía CP2102N). En Windows, instalar “CP210x Universal Windows Driver v10.1.10” desde Silicon Labs si el Puerto COM no aparece automáticamente.
- En ESP32-S3-DevKitC-1 puedes usar también el puerto USB nativo (GPIO19/20); no requiere driver adicional en macOS/Linux, y en Windows usa WinUSB.
Notas:
– Las versiones arriba listadas son las que se usarán en los comandos y en platformio.ini para reproducibilidad. Si ya tienes PlatformIO en VS Code, asegúrate de que el Core sea 6.1.14 o usa los comandos CLI indicados.
Materiales
- ESP32-S3-DevKitC-1 (modelo exacto de la placa)
- 2x INMP441 I2S mics (micrófonos digitales I2S, cada uno con pines: VDD, GND, SCK, WS, SD, L/R)
- Separador rígido para los micrófonos (barra, regla o impreso 3D) con distancia conocida d entre cápsulas, p. ej. 6.0 cm
- Cables Dupont macho-hembra (mínimo 10)
- Cable USB-C (para la ESP32-S3-DevKitC-1)
- Ordenador con uno de los SO soportados
- Fuente: Velcro/cinta doble cara para fijar los mics a la barra
- Opcional:
- Sonda lógica o analizador para verificar I2S
- Altavoz activo portátil para pruebas de dirección
- Trípode o base para mantener el arreglo en un plano
Objetivo del proyecto: i2s-beamforming-direction-finding con 2 micrófonos INMP441 y una ESP32-S3-DevKitC-1.
Preparación y conexión
Consideraciones de hardware
- El INMP441 es un micrófono digital I2S con salida de 24 bits (justificada a 32 bits en el bus).
- Ambos INMP441 comparten reloj de bit (BCLK, SCK), reloj de palabra (LRCLK/WS) y la línea de datos (SD). Cada micrófono solo conduce la línea SD en su canal (L o R) según el pin L/R:
- L/R = GND → canal Izquierdo (L)
- L/R = VDD → canal Derecho (R)
- Alimentación a 3.3 V. No usar 5 V.
Usaremos un único bus I2S en modo estéreo. El ESP32-S3 lee dos canales simultáneamente (L y R) a 48 kHz.
Mapeo de pines recomendado (ESP32-S3-DevKitC-1)
- Seleccionamos GPIO que no interfieran con el USB nativo ni con señales de arranque. Los siguientes son seguros en la mayoría de revisiones de la DevKitC-1:
| Señal | ESP32-S3-DevKitC-1 GPIO | INMP441 (ambos mics) |
|---|---|---|
| BCLK / SCK | GPIO12 | SCK de Mic1 y Mic2 (común) |
| LRCLK / WS | GPIO13 | WS de Mic1 y Mic2 (común) |
| DIN (I2S RX) | GPIO14 | SD de Mic1 y Mic2 (compartida) |
| VDD | 3V3 | VDD de Mic1 y Mic2 |
| GND | GND | GND de Mic1 y Mic2 |
| L/R Mic1 | — | GND (canal L) |
| L/R Mic2 | — | 3V3 (canal R) |
Notas:
– Ambos micrófonos comparten SD hacia la entrada DIN del ESP32-S3; no hay conflicto porque cada uno habla solo en su semiperiodo (L o R).
– Si tu placa INMP441 tiene pads de soldadura para L/R, asegúrate de puentear correctamente uno a GND y el otro a 3V3.
Geometría del arreglo
- Distancia entre centros acústicos de los micrófonos, d: 6.0 cm (0.06 m). Mide y anota con precisión (±0.5 mm). Esta distancia se usará en las ecuaciones.
- Arreglo lineal (dos micrófonos en línea recta). Ángulo de llegada (azimut) θ definido respecto al eje normal central, con θ ∈ [−90°, +90°].
Código completo (ESP-IDF con PlatformIO)
El ejemplo está escrito en C usando el driver I2S v2 de ESP-IDF 5.1 y cálculo de TDOA (Time Difference of Arrival) por correlación cruzada en el dominio temporal en una ventana deslizante. A partir de la TDOA se estima el ángulo: θ = arcsin(c·τ/d), donde c ≈ 343 m/s (20 °C), τ es el retardo entre canales y d la separación.
Características:
– Frecuencia de muestreo: 48 kHz
– Profundidad de palabra en bus: 32 bits (los INMP441 entregan 24 bits justificados)
– Ventana de procesamiento: N = 1024 frames (≈21.33 ms)
– Búsqueda de retraso: ±16 muestras (cubre ±9 muestas necesarias para d=6 cm a 48 kHz)
– Normalización previa (quita DC y escala por desviación típica)
– Interpolación parabólica para submuestra
Estructura:
– Inicialización I2S en modo estándar (Philips), RX maestro
– Tarea principal que:
1) Lee bloques del bus I2S
2) Demultiplexa L/R y convierte a float normalizada
3) Calcula TDOA mediante correlación
4) Estima θ y lo imprime por puerto serie
platformio.ini
; platformio.ini — Toolchain y build reproducibles
[env:esp32-s3-devkitc-1]
platform = espressif32@6.5.0
framework = espidf
board = esp32-s3-devkitc-1
; Paquetes fijados (versiones exactas)
platform_packages =
platformio/toolchain-xtensa-esp32s3@12.2.0+20230208
platformio/framework-espidf@5.1.2
platformio/tool-esptoolpy@1.40501.0
; Opciones de build/flash/monitor
monitor_speed = 115200
board_build.flash_mode = qio
build_unflags = -Os
build_flags =
-O2
-DLOG_LOCAL_LEVEL=ESP_LOG_INFO
src/main.c
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"
#include "driver/i2s_std.h"
#include "driver/gpio.h"
static const char *TAG = "BF_DF";
// Parámetros físicos
#define SPEED_OF_SOUND 343.0f // m/s a 20°C
#define MIC_DISTANCE_M 0.06f // 6 cm entre micrófonos
// Pines I2S (ESP32-S3-DevKitC-1)
#define I2S_BCLK_IO 12
#define I2S_WS_IO 13
#define I2S_SD_IO 14
// I2S y DSP
#define SAMPLE_RATE 48000
#define WORD_BITS I2S_DATA_BIT_WIDTH_32BIT
#define SLOT_MODE I2S_SLOT_MODE_STEREO
// Procesamiento
#define FRAME_LEN 1024 // frames estéreo por iteración
#define MAX_LAG_SAMPLES 16 // búsqueda de retardo ±16
#define READ_TIMEOUT_MS 1000
// Buffer I2S: 2 canales * FRAME_LEN * 4 bytes (32 bits)
static int32_t i2s_rx_buf[FRAME_LEN * 2];
// Señales normalizadas
static float xL[FRAME_LEN];
static float xR[FRAME_LEN];
// Canal I2S
static i2s_chan_handle_t rx_chan = NULL;
static esp_err_t i2s_init_std(void)
{
esp_err_t ret;
i2s_chan_config_t chan_cfg = {
.id = I2S_NUM_0,
.role = I2S_ROLE_MASTER,
.dma_desc_num = 8,
.dma_frame_num = 256,
.auto_clear = true,
.intr_priority = 0
};
ret = i2s_new_channel(&chan_cfg, NULL, &rx_chan);
if (ret != ESP_OK) return ret;
i2s_std_config_t std_cfg = {
.clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
.slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(WORD_BITS, SLOT_MODE),
.gpio_cfg = {
.mclk = I2S_GPIO_UNUSED,
.bclk = I2S_BCLK_IO,
.ws = I2S_WS_IO,
.dout = I2S_GPIO_UNUSED,
.din = I2S_SD_IO,
.invert_flags = {
.mclk_inv = false,
.bclk_inv = false,
.ws_inv = false,
}
}
};
ret = i2s_channel_init_std_mode(rx_chan, &std_cfg);
if (ret != ESP_OK) return ret;
ret = i2s_channel_enable(rx_chan);
return ret;
}
static inline float s32_to_float_norm(int32_t s)
{
// INMP441: 24 bits justificados en 32; mover 8 bits y normalizar a [-1, 1)
int32_t v24 = (s >> 8);
return (float)v24 / 8388608.0f; // 2^23
}
static void preprocess_normalize(const int32_t *src, float *dstL, float *dstR, size_t n)
{
double meanL = 0.0, meanR = 0.0;
for (size_t i = 0; i < n; ++i) {
float l = s32_to_float_norm(src[2*i + 0]);
float r = s32_to_float_norm(src[2*i + 1]);
dstL[i] = l;
dstR[i] = r;
meanL += l;
meanR += r;
}
meanL /= (double)n;
meanR /= (double)n;
double varL = 0.0, varR = 0.0;
for (size_t i = 0; i < n; ++i) {
float l = dstL[i] - (float)meanL;
float r = dstR[i] - (float)meanR;
dstL[i] = l;
dstR[i] = r;
varL += (double)l * (double)l;
varR += (double)r * (double)r;
}
varL /= (double)(n - 1);
varR /= (double)(n - 1);
float stdL = (float)sqrt(varL + 1e-12);
float stdR = (float)sqrt(varR + 1e-12);
for (size_t i = 0; i < n; ++i) {
dstL[i] /= stdL;
dstR[i] /= stdR;
}
}
// Correlación cruzada restringida a ±MAX_LAG_SAMPLES
static int est_tdoa_lag(const float *x, const float *y, size_t n, int maxLag, float *peakCorr, float *lagFrac)
{
// Cálculo NCC (Normalized Cross-Correlation) local
float bestVal = -1e9f;
int bestLag = 0;
for (int lag = -maxLag; lag <= maxLag; ++lag) {
double acc = 0.0;
size_t count = 0;
if (lag >= 0) {
for (size_t i = 0; i + lag < n; ++i) {
acc += (double)x[i + lag] * (double)y[i];
count++;
}
} else { // lag < 0
int k = -lag;
for (size_t i = 0; i + k < n; ++i) {
acc += (double)x[i] * (double)y[i + k];
count++;
}
}
float c = (count > 0) ? (float)(acc / (double)count) : 0.0f;
if (c > bestVal) {
bestVal = c;
bestLag = lag;
}
}
// Interpolación parabólica submuestral: usa valores en bestLag-1, bestLag, bestLag+1
// c(l) ~ a(l - l0)^2 + b
float c_m1 = -1e6f, c_0 = -1e6f, c_p1 = -1e6f;
// Calcular c_m1
{
double acc = 0.0;
size_t count = 0;
int lag = bestLag - 1;
if (lag >= -maxLag) {
if (lag >= 0) {
for (size_t i = 0; i + lag < n; ++i) { acc += (double)x[i + lag] * (double)y[i]; count++; }
} else {
int k = -lag;
for (size_t i = 0; i + k < n; ++i) { acc += (double)x[i] * (double)y[i + k]; count++; }
}
c_m1 = (count > 0) ? (float)(acc / (double)count) : -1e6f;
}
}
// Calcular c_0
c_0 = bestVal;
// Calcular c_p1
{
double acc = 0.0;
size_t count = 0;
int lag = bestLag + 1;
if (lag <= maxLag) {
if (lag >= 0) {
for (size_t i = 0; i + lag < n; ++i) { acc += (double)x[i + lag] * (double)y[i]; count++; }
} else {
int k = -lag;
for (size_t i = 0; i + k < n; ++i) { acc += (double)x[i] * (double)y[i + k]; count++; }
}
c_p1 = (count > 0) ? (float)(acc / (double)count) : -1e6f;
}
}
float frac = 0.0f; // offset fraccional ∈ [-0.5, 0.5] aprox.
float denom = (c_m1 - 2.0f*c_0 + c_p1);
if (fabsf(denom) > 1e-9f) {
frac = 0.5f * (c_m1 - c_p1) / denom;
if (frac > 0.5f) frac = 0.5f;
if (frac < -0.5f) frac = -0.5f;
}
if (peakCorr) *peakCorr = bestVal;
if (lagFrac) *lagFrac = frac;
return bestLag;
}
static float clampf(float v, float lo, float hi)
{
if (v < lo) return lo;
if (v > hi) return hi;
return v;
}
void app_main(void)
{
ESP_LOGI(TAG, "Inicializando I2S...");
ESP_ERROR_CHECK(i2s_init_std());
ESP_LOGI(TAG, "Iniciando captura a %d Hz, FRAME_LEN=%d", SAMPLE_RATE, FRAME_LEN);
while (1) {
size_t to_read = sizeof(i2s_rx_buf);
size_t bytes_read = 0;
esp_err_t err = i2s_channel_read(rx_chan, i2s_rx_buf, to_read, &bytes_read, pdMS_TO_TICKS(READ_TIMEOUT_MS));
if (err != ESP_OK || bytes_read != to_read) {
ESP_LOGW(TAG, "Lectura I2S incompleta: err=%d, bytes=%u", err, (unsigned)bytes_read);
continue;
}
// Preprocesado: DC removal + normalización de energía
preprocess_normalize(i2s_rx_buf, xL, xR, FRAME_LEN);
// Estimar TDOA (lag y)
float peakCorr = 0.0f, frac = 0.0f;
int lag = est_tdoa_lag(xL, xR, FRAME_LEN, MAX_LAG_SAMPLES, &peakCorr, &frac);
float lag_total = (float)lag + frac; // en muestras (submuestra)
float tau = lag_total / (float)SAMPLE_RATE; // segundos
// Ángulo de llegada (DoA) en grados
float argument = clampf((SPEED_OF_SOUND * tau) / MIC_DISTANCE_M, -1.0f, 1.0f);
float theta_rad = asinf(argument);
float theta_deg = theta_rad * 180.0f / (float)M_PI;
// Métrica de confianza simple (pico de correlación)
// Nota: valores de peakCorr cercanos a 1 implican fuerte correlación
float conf = clampf((peakCorr + 1.0f) / 2.0f, 0.0f, 1.0f);
// Salida por serie
printf("{\"deg\":%.2f,\"lag\":%.3f,\"tau_ms\":%.3f,\"peak\":%.3f}\n",
theta_deg, lag_total, tau * 1000.0f, peakCorr);
// Reporte legible
ESP_LOGI(TAG, "DoA: %7.2f deg | lag: %+6.3f samp | tau: %+7.3f ms | peak: %.3f conf: %.2f",
theta_deg, lag_total, tau * 1000.0f, peakCorr, conf);
// Pequeña pausa (opcional)
vTaskDelay(pdMS_TO_TICKS(5));
}
}
Breve explicación de partes clave:
– i2s_init_std: configura I2S en modo estándar (Philips), maestro, estéreo 32-bit por slot, 48 kHz. Se asignan pines GPIO12/13/14 a BCLK/WS/DIN respectivamente.
– preprocess_normalize: convierte 32→24 bits, quita DC (media) y normaliza por desviación típica para aproximar un pre-blanqueo; mejora la robustez de la correlación frente a señales con energía desigual.
– est_tdoa_lag: calcula la correlación cruzada de xL y xR en una ventana y busca el lag de máxima coincidencia en ±16 muestras. Se interpola con una parábola local para obtener submuestra.
– Cálculo de θ: arcsin(c·τ/d), con saturación a [-1,1] para robustez numérica. Se imprime JSON por stdout para facilitar parsing, además de un log humano.
– FRAME_LEN y MAX_LAG_SAMPLES se ajustan a la geometría y Fs. Con d=0.06 m y Fs=48 kHz, el máximo retraso absoluto es ~8.4 muestras; ±16 aporta margen.
Compilación/flash/ejecución
Instalación de PlatformIO Core 6.1.14
- Windows/macOS/Linux:
- Requiere Python 3.10.x disponible como python3 en PATH.
Comandos:
# 1) Crear y activar un entorno (opcional pero recomendado)
python3 -m venv .venv
source .venv/bin/activate # en Windows: .venv\Scripts\activate
# 2) Instalar PlatformIO Core versión exacta
python3 -m pip install --upgrade pip
python3 -m pip install platformio==6.1.14
# 3) Verificar versión
pio --version
# Debe mostrar: PlatformIO Core, version 6.1.14
Inicializar proyecto y aplicar configuración
# 4) Crear carpeta del proyecto
mkdir esp32s3_i2s_beamforming && cd esp32s3_i2s_beamforming
# 5) Inicializar proyecto para ESP32-S3-DevKitC-1
pio project init --board esp32-s3-devkitc-1
# 6) Sobrescribir platformio.ini con el contenido del bloque anterior
# (edita con tu editor y pega el contenido exacto)
# 7) Crear carpeta src y archivo main.c (pega el código del bloque anterior)
mkdir -p src
$EDITOR src/main.c
Compilar, flashear y monitorizar
- Conecta la ESP32-S3-DevKitC-1 por USB-C.
- Identifica el puerto serie:
- Windows: Revisar “Puertos (COM y LPT)” en el Administrador de dispositivos (p. ej., COM7).
- macOS: ls /dev/tty.usb* (p. ej., /dev/tty.usbmodemXXXXXXXX)
- Linux: dmesg | tail o ls /dev/ttyACM /dev/ttyUSB.
Comandos:
# 8) Compilación
pio run
# 9) Flasheo (si tu puerto no se detecta automáticamente, especifica --upload-port)
pio run --target upload --upload-port <PUERTO_SERIAL>
# 10) Monitor serie a 115200 baudios
pio device monitor -b 115200
Notas:
– Si usas el USB nativo CDC del S3, puede presentarse como /dev/ttyACM0 (Linux) o tty.usbmodem (macOS).
– Para Windows, si no aparece el COM, instala el CP210x driver.
Validación paso a paso
Objetivo: Confirmar que el sistema estima correctamente la dirección (DoA) de una fuente sonora en el plano del arreglo.
1) Comprobación eléctrica
– Verifica continuidad de:
– BCLK (SCK) común a ambos INMP441 y GPIO12
– LRCLK (WS) común y GPIO13
– SD común y GPIO14
– VDD 3V3 a ambos mics
– GND común entre placa y mics
– Verifica L/R:
– Mic1 L/R a GND (Left)
– Mic2 L/R a 3V3 (Right)
2) Salida básica por consola
– Abre el monitor serie.
– Debes ver líneas como:
– {«deg»:-5.40,»lag»:-0.700,»tau_ms»:-0.015,»peak»:0.312}
– I (BF_DF) DoA: -5.40 deg | lag: -0.700 samp | tau: -0.015 ms | peak: 0.312 conf: 0.66
– Si peak ~ 0 y deg errático, revisa conexiones/ruido amb.
3) Prueba con ruido impulsivo (aplausos)
– Coloca una fuente (tus palmas) a ~0° (frente al centro del arreglo) a 0.5–1 m.
– Esperado: θ cercano a 0°, fluctuando ±5–10°.
– Mueve la fuente a la izquierda (lado del Mic1/L):
– Esperado: θ negativo (por convención usada), p. ej., -30° a -60°.
– Mueve la fuente a la derecha (lado del Mic2/R):
– Esperado: θ positivo, p. ej., +30° a +60°.
4) Prueba con tono continuo
– Usa un altavoz con tono 1 kHz a volumen moderado.
– Colócalo a ~30° respecto al eje. Mantén la distancia y evita reflexiones.
– Esperado: θ estable ±5° con peakCorr más alto (0.4–0.8). Si peak < 0.2, aumenta SNR o reduce reverberación.
5) Verificación de cotas físicas
– Para d=0.06 m y Fs=48000 Hz, TDOA máximo teórico = d/c ≈ 0.000175 s → ~8.4 muestras.
– Observa lag entre ~-8.5 y +8.5. Lag fuera de ese rango indica cableado L/R invertido o error de parámetros.
6) Calibración de distancia
– Si nota sesgo sistemático (p. ej., siempre subestima el ángulo), mide d con más precisión y ajusta MIC_DISTANCE_M en el código.
7) Comprobación de estabilidad
– Observa conf (mapeada a partir de peakCorr). Debe aumentar con señales directas y disminuir con ruido difuso o reverberación.
8) Persistencia de resultados
– Captura 30 s de logs. Exporta a CSV/JSON para graficar θ(t) y evaluar varianza en distintas posiciones.
Troubleshooting
1) No aparece puerto serie
– Solución:
– Cambia de cable USB-C (algunos solo suministran alimentación).
– Instala driver CP210x en Windows.
– Prueba el puerto USB nativo (CDC) de la S3 si tu DevKit lo expone.
– En Linux, agrega tu usuario al grupo dialout: sudo usermod -a -G dialout $USER y reinicia sesión.
2) Lecturas I2S vacías o erráticas (bytes_read < to_read)
– Causas probables:
– Pines BCLK/WS/DIN erróneos.
– SD no compartida correctamente entre los dos micrófonos.
– Timeout por bloqueos en ISR o CPU saturada.
– Soluciones:
– Verifica el cableado y continuidad.
– Reduce temporalmente FRAME_LEN a 512 para probar.
– Asegura que READ_TIMEOUT_MS sea suficiente (>= 1000 ms ya lo es).
3) Canales invertidos o ángulos con signo opuesto
– Causa: L/R mal conectados (ambos a GND o ambos a 3V3).
– Solución:
– Mic1 L/R a GND (L), Mic2 L/R a 3V3 (R).
– Si no puedes re-cablear, intercambia xL y xR en el código.
4) Ángulos saturados cerca de ±90° aun con fuente frontal
– Causas:
– d mal medido (demasiado grande en el código).
– Reverberación fuerte creando falsas correlaciones.
– Soluciones:
– Mide d y ajusta MIC_DISTANCE_M.
– Prueba en espacio menos reverberante; acerca la fuente.
5) Distorsión o clipping en señales
– Causas:
– Sonido excesivamente fuerte en campo cercano.
– Conversión 24→float mal escalada (shift inadecuado).
– Soluciones:
– Aleja la fuente o reduce volumen.
– Ajusta s32_to_float_norm: usa división por 2^23 (8388608.0f), como está en el código.
6) CPU Load alto o watchdog reset
– Causas:
– FRAME_LEN grande combinado con logs intensivos.
– Soluciones:
– Baja la verbosidad (LOG_LOCAL_LEVEL).
– Reduce frecuencia de logs (imprimir 1 de cada 5 iteraciones).
– Ajusta -O2/-O3 en build_flags.
7) “lag” fuera de ±16 con picos falsos
– Causas:
– Señales altamente periódicas (tonos puros) con ambigüedad de fase.
– Soluciones:
– Usa ruido de banda ancha o voz/música.
– Aumenta MAX_LAG_SAMPLES a 24–32 (con coste de CPU) o añade una heurística de banda ancha (filtro pasabanda 300–3400 Hz).
8) Ningún pico (peakCorr ≈ 0)
– Causas:
– SD desconectado, WS/BCLK ausentes.
– Micrófonos defectuosos.
– Soluciones:
– Verifica con analizador lógico que BCLK y WS están presentes (BCLK ≈ 3.072 MHz a 48 kHz con 32 bits*2 canales).
– Cambia micrófono para descartar fallo.
Mejoras/variantes
- GCC-PHAT en frecuencia:
- Sustituir la correlación temporal por GCC-PHAT (FFT → X1·conj(X2)/|X1·conj(X2)| → IFFT → argmax), mejora robustez a reverberación. Puedes integrar kissFFT o esp-dsp (FFT en ESP-IDF).
- Beamforming delay-and-sum:
- Añade un barrido de ángulos θ ∈ [−90°, +90°] y aplica retardos fraccionales (interpolación lineal o Lagrange orden 3) para alinear señales y maximizar energía sumada. El ángulo con máxima SNR define DoA.
- Estimación submuestral avanzada:
- Usa ajuste por correlación generalizada con ventanas (Hann/Blackman) y parabolic peak fitting mejorado (Quinn-Fernandes).
- Calibración de ganancia y fase:
- Compensa diferencias de sensibilidad entre mics aplicando escalado por RMS y compensación de fase/miniretardos medidos con tono de referencia.
- Aumento de Fs y downsampling:
- Captura a 96 kHz y haz decimación por 2 con filtro FIR; incrementa la resolución temporal de TDOA antes de decimar.
- Filtrado adaptativo:
- Pasabanda 300–3400 Hz para voz o 200–8000 Hz para música; mejora correlación y reduce baja frecuencia (ruido HVAC).
- Extensión a N>2 micrófonos:
- Con ESP32-S3 e I2S-TDM (módulos I2S MEMS TDM) puedes formar arreglos lineales/circulares para 2D-DoA y beamforming con mayor directividad.
Checklist de verificación
- [ ] He instalado PlatformIO Core 6.1.14 con Python 3.10.x.
- [ ] He creado el proyecto con board=esp32-s3-devkitc-1 y platform=espressif32@6.5.0.
- [ ] He copiado el platformio.ini con los platform_packages exactos (toolchain-xtensa-esp32s3 12.2.0+20230208, framework-espidf 5.1.2, esptoolpy 4.5.1).
- [ ] He cableado los dos INMP441 con BCLK→GPIO12, WS→GPIO13, SD→GPIO14, VDD→3V3, GND→GND.
- [ ] L/R de Mic1 a GND (Left), L/R de Mic2 a 3V3 (Right).
- [ ] He fijado la distancia d entre micrófonos (p. ej., 6.0 cm) y la he puesto en MIC_DISTANCE_M.
- [ ] El proyecto compila (pio run) sin errores.
- [ ] El flasheo (pio run –target upload) se completa y el monitor serie muestra registros a 115200 bps.
- [ ] Al aplaudir frente a la pareja de micrófonos, la estimación de ángulo θ está cerca de 0°.
- [ ] Al mover la fuente a izquierda/derecha, θ cambia de signo y magnitud de manera coherente.
- [ ] peakCorr > 0.2 cuando la fuente está clara y cerca; si no, ajusto el entorno/volumen.
Apéndice: Notas prácticas y recomendaciones
- Longitud de cables: Mantén los cables I2S cortos y paralelos. Evita puentes largos y flojos; el SD especialmente debe ser limpio.
- Alimentación limpia: Usa el 3V3 de la DevKitC-1. Si usas protoboard, revisa falsos contactos.
- Protección ESD: Evita tocar los diafragmas de los micrófonos. Manipula por los bordes del PCB.
- Organización de logs: El formato JSON impreso permite que captures datos con pio device monitor –raw > logs.jsonl y luego los analices con Python.
- Temperatura y c: La velocidad del sonido depende de la temperatura. Para precisión, usa c ≈ 331 + 0.6·T(°C). Ajusta SPEED_OF_SOUND si trabajas lejos de 20 °C.
- Saturación por reverberación: En interiores con paredes cercanas, prueba con ventanas más cortas (FRAME_LEN=512) o filtrado pasabanda.
Con estos pasos y el código proporcionado, tendrás un sistema completo de i2s-beamforming-direction-finding con el hardware exacto ESP32-S3-DevKitC-1 + 2x INMP441 I2S mics, listo para medir direcciones de llegada en tiempo casi real y servir como base para beamforming activo (p. ej., rechazo de interferencias fuera de eje o realce de una fuente deseada).
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.




