Filtro pasa banda diseñado en Matlab e implementado en Esp32
Objetivo
Diseño de un filtro pasa banda para voz humana en MATLAB y su implementación en el ESP32, manteniendo la visualización OLED y agregando salida de audio.
Conceptos básicos del filtro pasa banda para voz
La voz humana se concentra en el rango de 300 Hz a 3.4 kHz (banda telefónica). Un filtro pasa banda en este rango:
-
Elimina ruidos de baja frecuencia (viento, motor) por debajo de 300 Hz
-
Atenúa frecuencias agudas por encima de 3.4 kHz que no aportan inteligibilidad
-
Mejora la claridad de la voz en aplicaciones de comunicación
Diseño del filtro en MATLAB
Primero, diseñaremos los coeficientes del filtro FIR usando MATLAB. Este enfoque te dará control preciso sobre la respuesta en frecuencia.
% Diseño de filtro pasa banda para voz humana (300 Hz - 3400 Hz) % Frecuencia de muestreo del ESP32 (debe coincidir con tu configuración I2S) fs = 16000; % Hz (la misma que usas en el código OLED) % Especificaciones del filtro low_cutoff = 300; % Frecuencia de corte inferior (Hz) high_cutoff = 3400; % Frecuencia de corte superior (Hz) filter_order = 64; % Orden del filtro (ajustable) % Diseño del filtro FIR pasa banda usando ventana de Hamming freq_norm = [low_cutoff high_cutoff] / (fs/2); fir_coeff = fir1(filter_order, freq_norm, 'bandpass', hamming(filter_order+1)); % Visualizar respuesta en frecuencia freqz(fir_coeff, 1, 1024, fs); title('Respuesta del filtro pasa banda para voz (300 Hz - 3400 Hz)'); % Exportar coeficientes para ESP32 fprintf('float fir_coeff[%d] = {', length(fir_coeff)); for i = 1:length(fir_coeff) fprintf('%0.8f', fir_coeff(i)); if i < length(fir_coeff) fprintf(', '); end if mod(i, 5) == 0 fprintf('\n '); end end fprintf('};\n');
Explicación del código MATLAB:
-
fir1: Diseña un filtro FIR con ventana de Hamming (buen balance entre respuesta y simplicidad) -
Orden 64: Proporciona una pendiente pronunciada sin consumir demasiados recursos
-
Coeficientes exportables: El formato generado se puede copiar directamente al código ESP32
Hardware para salida de audio
Para escuchar el efecto del filtrado, necesitas un DAC con amplificador. La mejor opción es el MAX98357:
Módulo amplificador MAX98357A
| Característica | Especificación |
|---|---|
| Tipo | Amplificador Clase-D con DAC I2S integrado |
| Potencia | 3.2W en altavoz de 4Ω |
| Resolución | 16, 24 o 32 bits |
| Frecuencias | 8 kHz – 96 kHz |
| Alimentación | 3.3V – 5V |
| Ganancia | 3dB, 6dB, 9dB, 12dB, 15dB (configurable) |
Altavoz recomendado
-
Impedancia: 4Ω u 8Ω
-
Potencia: 2W – 5W
-
Tamaño compacto: 20x30mm o similar
Conexiones
| MAX98357 | ESP32 |
|---|---|
| VIN | 5V (o 3.3V, pero 5V da más volumen) |
| GND | GND |
| LRC | GPIO 25 (mismo WS que el micrófono) |
| BCLK | GPIO 26 (mismo BCLK que el micrófono) |
| DIN | GPIO 33 (nuevo pin para datos de salida) |
| GAIN | GND (para 12dB de ganancia) |
Importante: El ESP32 puede compartir BCLK y WS entre entrada y salida I2S, pero necesita diferentes pines de datos.
Código ESP32 completo con filtro y salida de audio
#include <Arduino.h> #include <driver/i2s.h> #include <arduinoFFT.h> #include <Adafruit_SSD1306.h> #include <Adafruit_GFX.h> // ============================================ // CONFIGURACIÓN DE PINES Y HARDWARE // ============================================ // --- Pantalla OLED --- #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // --- Micrófono INMP441 (entrada I2S) --- #define I2S_MIC_WS 25 // Word Select (compartido) #define I2S_MIC_SD 32 // Data Out (DOUT) - entrada #define I2S_BCK 26 // Bit Clock (compartido) // --- Amplificador MAX98357 (salida I2S) --- #define I2S_OUT_SD 33 // Data In - salida al amplificador #define I2S_PORT_IN I2S_NUM_0 #define I2S_PORT_OUT I2S_NUM_1 // ============================================ // CONFIGURACIÓN DE MUESTREO // ============================================ #define SAMPLE_RATE 16000 // Frecuencia de muestreo (Hz) #define SAMPLES 512 // Número de muestras para FFT #define AUDIO_BUFFER_SIZE 512 // Buffer para audio en tiempo real // Buffers int32_t micBuffer[AUDIO_BUFFER_SIZE]; int32_t filteredBuffer[AUDIO_BUFFER_SIZE]; int32_t sampleBuffer[SAMPLES]; // Para FFT // ============================================ // COEFICIENTES DEL FILTRO PASA BANDA // Generados con MATLAB para 300Hz - 3400Hz @ 16kHz // ============================================ #define FILTER_ORDER 64 float fir_coeff[FILTER_ORDER + 1] = { // Pega aquí los coeficientes generados por MATLAB // Ejemplo (valores ilustrativos, reemplazar con los reales): -0.0001, -0.0003, -0.0005, 0.0002, 0.0015, 0.0028, 0.0031, 0.0012, -0.0028, -0.0073, // ... (64 coeficientes en total) }; // Buffer circular para el filtro FIR float filterBuffer[FILTER_ORDER + 1] = {0}; int filterIndex = 0; // ============================================ // CONFIGURACIÓN FFT Y OLED // ============================================ double vReal[SAMPLES]; double vImag[SAMPLES]; ArduinoFFT<double> FFT = ArduinoFFT<double>(vReal, vImag, SAMPLES, SAMPLE_RATE); #define NUM_BANDS 12 const int bandFreqs[NUM_BANDS + 1] = { 20, 60, 120, 200, 350, 550, 850, 1300, 2000, 3000, 4500, 7000, 8000 }; float bandValues[NUM_BANDS] = {0}; float peakValues[NUM_BANDS] = {0}; // ============================================ // CONFIGURACIÓN I2S // ============================================ void i2s_install() { // Configuración para micrófono (entrada) const i2s_config_t i2s_config_in = { .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_t)I2S_COMM_FORMAT_I2S, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 4, .dma_buf_len = 256, .use_apll = false }; // Configuración para amplificador (salida) const i2s_config_t i2s_config_out = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX), .sample_rate = SAMPLE_RATE, .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, // MAX98357 acepta 16 bits .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, .communication_format = (i2s_comm_format_t)I2S_COMM_FORMAT_I2S, .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 4, .dma_buf_len = 256, .use_apll = false, .tx_desc_auto_clear = true }; i2s_driver_install(I2S_PORT_IN, &i2s_config_in, 0, NULL); i2s_driver_install(I2S_PORT_OUT, &i2s_config_out, 0, NULL); } void i2s_setpins() { // Pines para micrófono (entrada) const i2s_pin_config_t pin_config_in = { .bck_io_num = I2S_BCK, .ws_io_num = I2S_MIC_WS, .data_out_num = -1, .data_in_num = I2S_MIC_SD }; // Pines para amplificador (salida) - comparten BCK y WS const i2s_pin_config_t pin_config_out = { .bck_io_num = I2S_BCK, .ws_io_num = I2S_MIC_WS, .data_out_num = I2S_OUT_SD, .data_in_num = -1 }; i2s_set_pin(I2S_PORT_IN, &pin_config_in); i2s_set_pin(I2S_PORT_OUT, &pin_config_out); } // ============================================ // FUNCIÓN DE FILTRO FIR // ============================================ int32_t applyFIRFilter(int32_t input) { // Desplazar buffer circular filterIndex = (filterIndex + 1) % (FILTER_ORDER + 1); filterBuffer[filterIndex] = (float)input / 32768.0; // Normalizar // Aplicar convolución float output = 0; int idx = filterIndex; for (int i = 0; i <= FILTER_ORDER; i++) { output += fir_coeff[i] * filterBuffer[idx]; idx = (idx - 1 + FILTER_ORDER + 1) % (FILTER_ORDER + 1); } // Desnormalizar y limitar output = output * 32768.0; if (output > 32767) output = 32767; if (output < -32768) output = -32768; return (int32_t)output; } // ============================================ // PROCESAMIENTO FFT Y OLED (similar al código anterior) // ============================================ void processFFT(int32_t* buffer, int len) { // Preparar datos (usar buffer filtrado para visualizar el efecto) for (int i = 0; i < min(len, SAMPLES); i++) { vReal[i] = (double)(buffer[i] >> 8); // Convertir a 24 bits vImag[i] = 0.0; } // Calcular FFT FFT.windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD); FFT.compute(vReal, vImag, SAMPLES, FFT_FORWARD); FFT.complexToMagnitude(vReal, vImag, SAMPLES); // Agrupar en bandas (mismo código que antes) for (int i = 0; i < NUM_BANDS; i++) bandValues[i] = 0; for (int i = 2; i < (SAMPLES/2); i++) { float freq = (i * 1.0 * SAMPLE_RATE) / SAMPLES; for (int b = 0; b < NUM_BANDS; b++) { if (freq >= bandFreqs[b] && freq < bandFreqs[b+1]) { bandValues[b] += vReal[i]; break; } } } // Normalizar y dibujar (código de visualización anterior) // ... (incluir la función de dibujo OLED del código anterior) } // ============================================ // SETUP Y LOOP // ============================================ void setup() { Serial.begin(115200); Serial.println("Iniciando sistema de filtrado de voz..."); // Inicializar I2S i2s_install(); i2s_setpins(); i2s_start(I2S_PORT_IN); i2s_start(I2S_PORT_OUT); Serial.println("✅ I2S inicializado"); // Inicializar OLED if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println("❌ Error OLED"); for(;;); } display.clearDisplay(); display.display(); Serial.println("✅ OLED inicializado"); delay(1000); } void loop() { size_t bytesRead, bytesWritten; // Leer bloque del micrófono esp_err_t result = i2s_read(I2S_PORT_IN, &micBuffer, sizeof(micBuffer), &bytesRead, portMAX_DELAY); if (result == ESP_OK) { int samplesRead = bytesRead / sizeof(int32_t); // Aplicar filtro a cada muestra for (int i = 0; i < samplesRead; i++) { filteredBuffer[i] = applyFIRFilter(micBuffer[i] >> 8); // Ajuste de escala } // Enviar audio filtrado al amplificador i2s_write(I2S_PORT_OUT, filteredBuffer, samplesRead * sizeof(int32_t), &bytesWritten, portMAX_DELAY); // Cada cierto tiempo, actualizar FFT y OLED static unsigned long lastFFT = 0; if (millis() - lastFFT > 50) { processFFT(filteredBuffer, samplesRead); lastFFT = millis(); } } }
Ajustes y calibración
1. Obtener coeficientes reales en MATLAB
Ejecuta el código MATLAB que te compartí y copia los coeficientes generados en el array fir_coeff.
2. Ajuste de ganancia del MAX98357
-
GAIN a GND: 12dB (buen balance)
-
GAIN a VCC: 6dB (si el audio distorsiona)
-
Sin conectar: 9dB (configuración por defecto)
3. Alimentación adecuada
El MAX98357 puede consumir hasta 1.5A a volumen alto. No lo alimentes desde el pin 3.3V del ESP32. Usa una fuente externa de 5V.
Verificación del filtrado
Para comprobar que el filtro funciona:
-
Prueba de voz: Habla cerca del micrófono y escucha por el altavoz. La voz debe escucharse clara.
-
Prueba de ruido: Genera ruido de baja frecuencia (soplar) – debe atenuarse significativamente.
-
Visualización OLED: Deberías ver las barras principalmente en las bandas medias (300-3400 Hz).
