Si alguna vez intentaste reproducir sonido de calidad con un microcontrolador y quedó todo distorsionado, el problema casi seguro fue el conversor digital analógico. En este tutorial vas a entender el protocolo I2S, la forma correcta de mover audio digital sin pérdida de calidad en un ESP32, y vas a armar tres proyectos con lo aprendido: capturar sonido con un micrófono digital, reproducir música desde la memoria interna y reproducir un archivo de audio desde una tarjeta microSD. Al final vas a tener las bases para construir tu propio reproductor de audio.

Por qué se necesita el protocolo I2S

Para reproducir un archivo de audio con un microcontrolador hay que pensar en toda la cadena digital: el audio se guarda en una microSD, el micro lo lee y lo manda a un parlante. El problema aparece en la salida, donde la señal digital debe convertirse en analógica mediante un DAC (conversor digital analógico):

  • Arduino y ESP8266: no tienen DAC interno, así que tendrías que armar uno con componentes externos.
  • ESP32: sí tiene DAC interno, pero de solo 8 bits de resolución. Como el audio típico es de 16 bits, perderías mucha calidad.

La solución es el protocolo I2S (Inter IC Sound), que transporta entre 4 y 32 bits por muestra sin degradar la señal, y un decodificador externo con amplificador integrado como el MAX98357A.

Esquema de la cadena de audio digital: microSD, microcontrolador y parlante

Cómo funciona el protocolo I2S por dentro

I2S usa tres líneas de comunicación. Vale la pena entenderlas porque explican los pines que verás en el código:

  • Serial Clock (SCK / BCLK): el reloj de bits que sincroniza todos los componentes. Su frecuencia es Sample Rate × Bits por canal × Número de canales. Para un archivo WAVE de 44.1 kHz, 16 bits y 2 canales, el reloj es 44.1 kHz × 16 × 2 = 1.411 MHz.
  • Word Select (WS / FS): diferencia el canal izquierdo (WS = 0) del derecho (WS = 1).
  • Serial Data (SD): la línea donde viaja el audio, con el bit más significativo primero (MSB first).

Un detalle fino que casi nadie explica: la línea WS cambia un ciclo de reloj antes de que se transmita el MSB. Ese ciclo de adelanto le da tiempo al receptor de guardar la palabra anterior y limpiar su registro de entrada para la siguiente. Además, si el largo de palabra del emisor y el receptor no coinciden, la palabra se trunca (se rellenan con 0 los bits menos significativos) o se ignoran los bits sobrantes.

Componentes de una red I2S: emisor, receptor y controlador maestro

Diagrama de tiempos del protocolo I2S mostrando las líneas SCK, WS y SD

El amplificador MAX98357A

El MAX98357A resuelve dos problemas a la vez: decodifica la señal I2S a analógica con su DAC interno y la amplifica con un amplificador clase D. Opera entre 2.7 V y 5.5 V (sirve tanto con placas de 5 V como de 3.3 V), entrega 3.2 W en un parlante de 4 Ω y 1.8 W en uno de 8 Ω. Por defecto funciona en modo mono (combina los canales izquierdo y derecho en un solo parlante); para estéreo hay que cortar el jumper de mono y soldar el canal deseado. Acepta muestreos de 8 kHz a 96 kHz, así que nuestro audio de 44.1 kHz encaja perfecto.

Placa breakout del amplificador de audio I2S MAX98357A

Proyecto 1: capturar y visualizar sonido con un micrófono I2S

En el primer proyecto capturamos sonido con un micrófono digital SPH0645 y lo visualizamos en el Serial Plotter del IDE de Arduino. Importante: conecta el micrófono I2S solo al pin de 3.3 V.

Diagrama de conexión del ESP32 NodeMCU con el micrófono I2S SPH0645

C++
#include "driver/i2s.h"

const i2s_port_t I2S_PORT = I2S_NUM_0;

void setup() {
  Serial.begin(115200);
  esp_err_t err;

  // The I2S config as per the example
  const i2s_config_t i2s_config = {
      .mode = i2s_mode_t(I2S_MODE_MASTER | I2S_MODE_RX), // Receive, not transfer
      .sample_rate = 16000,                         // 16KHz
      .bits_per_sample = I2S_BITS_PER_SAMPLE_32BIT, // could only get it to work with 32bits
      .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT, // use right channel
      .communication_format = i2s_comm_format_t(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
      .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,     // Interrupt level 1
      .dma_buf_count = 4,                           // number of buffers
      .dma_buf_len = 8                              // 8 samples per buffer (minimum)
  };

  // The pin config as per the setup
  const i2s_pin_config_t pin_config = {
      .bck_io_num = 26,   // Serial Clock (SCK)
      .ws_io_num = 25,    // Word Select (WS)
      .data_out_num = I2S_PIN_NO_CHANGE, // not used (only for speakers)
      .data_in_num = 33   // Serial Data (SD)
  };

  // Configuring the I2S driver and pins.
  // This function must be called before any I2S driver read/write operations.
  err = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL);
  if (err != ESP_OK) {
    Serial.printf("Failed installing driver: %d\n", err);
    while (true);
  }
  err = i2s_set_pin(I2S_PORT, &pin_config);
  if (err != ESP_OK) {
    Serial.printf("Failed setting pin: %d\n", err);
    while (true);
  }
  Serial.println("I2S driver installed.");
}

void loop() {
  // Read a single sample and log it for the Serial Plotter.
  int32_t sample = 0;
  int bytes_read = i2s_pop_sample(I2S_PORT, (char *)&sample, portMAX_DELAY); // no timeout
  if (bytes_read > 0) {
    Serial.println(sample);
  }

}

En el setup() se define la estructura de la comunicación I2S: modo RX (recepción), sample rate de 16 kHz, 32 bits por muestra, canal derecho y 4 buffers de 8 muestras. Luego se asignan los pines (SCK = 26, WS = 25, SD = 33) y se instala el driver. En el loop() se lee una muestra y se imprime al serial para verla en el plotter.

Proyecto 2: reproducir música desde la memoria interna

En el segundo proyecto reproducimos audio guardado como arreglo en la RAM interna del ESP32, usando el MAX98357A para pasar de digital a analógico. Aquí usamos la librería ESP8266Audio de Earle F. Philhower. Recuerda que el archivo .ino y el header .h con el audio deben estar en la misma carpeta.

C++
#include "AudioGeneratorAAC.h"
#include "AudioOutputI2S.h"
#include "AudioFileSourcePROGMEM.h"
#include "sampleaac.h"

AudioFileSourcePROGMEM *in;
AudioGeneratorAAC *aac;
AudioOutputI2S *out;

void setup()
{
  Serial.begin(115200);

  in = new AudioFileSourcePROGMEM(sampleaac, sizeof(sampleaac));
  aac = new AudioGeneratorAAC();
  out = new AudioOutputI2S();
  out->SetGain(0.125);
  out->SetPinout(26, 25, 22);
  aac->begin(in, out);
}

void loop()
{
  if (aac->isRunning()) {
    aac->loop();
  } else {
    aac->stop();
    Serial.printf("Sound Generator\n");
    delay(1000);
  }
}

Se incluyen los headers de la librería: AudioGeneratorAAC (decodificador AAC), AudioOutputI2S (salida I2S), AudioFileSourcePROGMEM (guarda el "archivo" como arreglo en PROGMEM) y sampleaac (el audio). Con SetGain() bajas el volumen y con SetPinout() defines los pines (SCK = 26, WS = 25, SD = 22). El generador reproduce hasta terminar el arreglo.

Proyecto 3: reproducir un WAVE desde una microSD

En el último proyecto reproducimos un archivo WAVE (o MP3) desde una microSD. El ESP32 lee el archivo y reenvía el audio digital al MAX98357A. Prepara la microSD con formato FAT16 o FAT32 y copia el archivo en la raíz. Usamos la librería ESP32-audioI2S de schreibfaul1, que se instala vía Sketch → Include Library → Add .ZIP Library.

C++
#include "Audio.h"
#include "SD.h"
#include "FS.h"

// Digital I/O used
#define SD_CS          5
#define SPI_MOSI      23
#define SPI_MISO      19
#define SPI_SCK       18
#define I2S_DOUT      25
#define I2S_BCLK      27
#define I2S_LRC       26

Audio audio;

void setup() {
  pinMode(SD_CS, OUTPUT);
  digitalWrite(SD_CS, HIGH);
  SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI);
  Serial.begin(115200);
  SD.begin(SD_CS);
  audio.setPinout(I2S_BCLK, I2S_LRC, I2S_DOUT);
  audio.setVolume(10); // 0...21
  audio.connecttoFS(SD, "Ensoniq-ZR-76-01-Dope-77.wav");
}

void loop()
{
    audio.loop();
}

// optional
void audio_info(const char *info){
  Serial.print("info        ");
  Serial.println(info);
}
void audio_id3data(const char *info){ //id3 metadata
  Serial.print("id3data     ");
  Serial.println(info);
}
void audio_eof_mp3(const char *info){ //end of file
  Serial.print("eof_mp3     ");
  Serial.println(info);
}
void audio_showstation(const char *info){
  Serial.print("station     ");
  Serial.println(info);
}
void audio_showstreaminfo(const char *info){
  Serial.print("streaminfo  ");
  Serial.println(info);
}
void audio_showstreamtitle(const char *info){
  Serial.print("streamtitle ");
  Serial.println(info);
}
void audio_bitrate(const char *info){
  Serial.print("bitrate     ");
  Serial.println(info);
}
void audio_commercial(const char *info){ //duration in sec
  Serial.print("commercial  ");
  Serial.println(info);
}
void audio_icyurl(const char *info){ //homepage
  Serial.print("icyurl      ");
  Serial.println(info);
}
void audio_lasthost(const char *info){ //stream URL played
  Serial.print("lasthost    ");
  Serial.println(info);
}
void audio_eof_speech(const char *info){
  Serial.print("eof_speech  ");
  Serial.println(info);
}

Se definen los pines de la SD (SPI) y del audio I2S. En el setup() se inicializa la SPI y la tarjeta, se ajustan los pines de salida con setPinout(), el volumen con setVolume() (0 a 21) y se conecta el archivo con connecttoFS(). En el loop() basta con llamar a audio.loop(). Las funciones opcionales al final imprimen metadatos del audio en el monitor serial.

Variantes y mejoras

Estas ideas no vienen en el tutorial original y te acercan a un reproductor real:

  • Reproductor MP3 con botones: agrega botones de play/pausa y siguiente/anterior leyendo varios archivos de la microSD. Con la librería ESP32-audioI2S puedes cambiar de pista llamando a connecttoFS() con otro nombre de archivo.
  • Radio por internet: la misma librería soporta streaming; con WiFi puedes reemplazar la microSD por connecttohost() y reproducir una emisora online.
  • Verificar el reloj con osciloscopio: conecta la línea CLK a un osciloscopio USB y confirma que la frecuencia sea 1.411 MHz para 44.1 kHz × 16 × 2, tal como predice la teoría. Es un ejercicio didáctico excelente.

Personalización para Chile

Todos los componentes están disponibles en el catálogo de MechatronicStore:

  • Placa ESP32 NodeMCU. controlador con soporte I2S por hardware
  • Módulo amplificador MAX98357A (I2S). decodificador + amplificador clase D
  • Micrófono I2S SPH0645. captura de audio digital
  • Módulo lector microSD (SPI). reproducción desde tarjeta
  • Parlante 4 Ω o 8 Ω (3 W). salida de audio
  • Tarjeta microSD 32 GB (FAT32). almacenamiento del audio
  • Protoboard 830 puntos y jumpers macho hembra. prototipado

El original menciona breakouts de Adafruit y SparkFun para el MAX98357: son idénticos entre sí, y el módulo MAX98357A del catálogo cumple exactamente la misma función.

Recursos

Versión chilena con componentes en stock local en MechatronicStore.