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.

matlab
% 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:

  1. fir1: Diseña un filtro FIR con ventana de Hamming (buen balance entre respuesta y simplicidad)

  2. Orden 64: Proporciona una pendiente pronunciada sin consumir demasiados recursos

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

cpp
#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:

  1. Prueba de voz: Habla cerca del micrófono y escucha por el altavoz. La voz debe escucharse clara.

  2. Prueba de ruido: Genera ruido de baja frecuencia (soplar) – debe atenuarse significativamente.

  3. Visualización OLED: Deberías ver las barras principalmente en las bandas medias (300-3400 Hz).

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *