Cambiar de canción mientras cocinas, mientras andas en bicicleta o mientras tienes las manos con grasa hasta el codo es un problema real y bastante viejo. La solución habitual es un botón, y un botón implica buscarlo, mirarlo y apretarlo. Este proyecto propone otra cosa: un reproductor de audio que lee el movimiento de tu mano. Lo agitas hacia la derecha y salta a la pista siguiente; hacia la izquierda, vuelve a la anterior; lo dejas quieto y plano sobre la mesa un segundo, y la música se detiene.

Al terminar este tutorial vas a tener un reproductor de archivos WAV funcionando con un ESP32-S3, y. más importante que el aparato. vas a entender tres cosas que se reutilizan en cualquier proyecto de audio embebido: cómo se detecta un gesto a partir de datos crudos de un acelerómetro, por qué el audio digital de calidad sale por I2S y no por el DAC del microcontrolador, y cómo convivir con un bucle que tiene que alimentar sonido sin pausas mientras hace otras cosas.

El gesto no existe: lo que existe es un delta

Antes de tocar un cable conviene entender qué es un "gesto" para un microcontrolador, porque acá está el 80% del proyecto.

Un acelerómetro no reporta movimiento. Reporta aceleración, incluida la de la gravedad. Si dejas el módulo quieto sobre la mesa, el eje Z marca aproximadamente 1 g (unos 1000 mg) y los ejes X e Y marcan cerca de cero. Si lo inclinas lentamente, esos valores cambian de forma suave, porque el vector de gravedad se está repartiendo distinto entre los ejes.

La clave del truco está ahí: una inclinación lenta y un remezón rápido producen valores parecidos, pero derivadas completamente distintas. Por eso el código no mira el valor absoluto del eje X, sino la diferencia entre la lectura actual y la anterior:

  • Si esa diferencia (el delta) supera un umbral alto, hubo una aceleración brusca: eso es un remezón.
  • El signo de la lectura en ese instante dice hacia qué lado fue el remezón, y eso separa "siguiente" de "anterior".
  • Si inclinas la placa despacio, el delta entre muestras consecutivas es pequeño aunque el valor final sea grande. No se dispara nada. Ese es exactamente el comportamiento que quieres: nadie salta tres canciones por guardar el aparato en el bolsillo.

Para el "stop" la lógica es la inversa: se pide que X e Y estén los dos por debajo de un umbral bajo durante un tiempo sostenido. Si ambos ejes horizontales están cerca de cero, toda la gravedad está cayendo sobre Z, o sea la placa está plana. Y el requisito de tiempo (un segundo completo) evita que un apoyo momentáneo detenga la música.

El hardware: dos caminos hacia el mismo aparato

Acá hay una bifurcación honesta que conviene declarar de entrada.

Camino A: una placa que ya trae todo

El proyecto original se construyó sobre una MaTouch ESP32-S3 Parallel TFT de Makerfabs, con pantalla ST7701S de 3.16 pulgadas. Es una placa que integra el microcontrolador, la pantalla, el lector microSD, el amplificador de audio y una IMU QMI8658 en un solo PCB.

Placa de desarrollo MaTouch ESP32-S3 Parallel TFT con pantalla ST7701S de 3,16 pulgadas

La ventaja es obvia: cero cableado y cero incertidumbre eléctrica. La desventaja para nosotros también: es una placa de nicho, hay que importarla, y cuando llega es difícil de reparar o de reutilizar en otro proyecto. Si ya la tienes, el código de más abajo te funciona tal cual.

Camino B: piezas sueltas (el que vamos a seguir)

La versión con módulos separados sale de componentes que se consiguen sin importar nada, y tiene una ventaja pedagógica enorme: cada bloque funcional queda a la vista y lo puedes probar por separado. Si el audio no suena, sabes exactamente qué módulo desconectar.

Placa de desarrollo ESP32-S3 usada como cerebro del reproductor de música por gestos

El corazón es un ESP32-S3. No es capricho ni marketing: el S3 trae PSRAM en las variantes N8R8/N16R8, y eso te da margen para buffers de audio y para un framebuffer de pantalla sin quedarte sin RAM interna a la primera. Además tiene USB nativo, lo que simplifica la carga del programa.

Alrededor del S3 van cuatro bloques:

Bloque Módulo Bus Para qué
Gestos MPU6050 (GY-521) I2C Leer aceleración en X, Y, Z
Audio MAX98357A I2S Convertir y amplificar el sonido
Medios Lector microSD SPI Guardar los archivos WAV
Interfaz TFT SPI ILI9341 SPI Ver el estado y depurar

Audio: por qué I2S y no el DAC del ESP32

El ESP32 clásico tiene un DAC de 8 bits y se puede hacer sonar un parlante con él. El resultado es audible y es malo: 8 bits de resolución dejan un piso de ruido muy alto, y además el DAC te obliga a poner un amplificador analógico después, que amplifica también ese ruido.

I2S resuelve las dos cosas a la vez. Es un bus digital síncrono de tres líneas que transporta muestras de audio sin convertirlas a analógico:

  • BCLK (bit clock): marca el ritmo de los bits.
  • LRCLK o word select: indica si la muestra que viene es del canal izquierdo o del derecho.
  • DOUT: los datos.

Módulo amplificador I2S MAX98357A conectado a un ESP32-S3 con su parlante

El MAX98357A recibe ese flujo digital y hace de DAC y de amplificador clase D en un solo chip, entregando alrededor de 3 W sobre un parlante de 4 Ω. La conversión ocurre dentro del módulo, a milímetros del parlante, así que la señal analógica casi no viaja y casi no recoge ruido. Por eso un módulo de dos lucas suena incomparablemente mejor que el DAC integrado.

Un detalle práctico que ahorra sustos: el MAX98357A tiene un pin SD (shutdown) que además selecciona el canal según el voltaje que se le aplique. Si lo dejas al aire, el módulo puede quedar mudo. Déjalo conectado a 3.3 V para tener el canal izquierdo activo, que es lo que corresponde a esta configuración.

Almacenamiento: la microSD y la trampa de los 44 bytes

Interfaz de tarjeta SD en el ESP32-S3 para almacenar y reproducir archivos WAV

Los archivos van en WAV y no en MP3, y eso es una decisión de diseño, no una limitación. Un MP3 hay que decodificarlo: cada bloque de audio exige trabajo de CPU antes de poder salir por I2S. Un WAV PCM ya son las muestras finales; el microcontrolador solo tiene que leerlas del archivo y empujarlas al bus. Eso deja el procesador libre para atender el acelerómetro y dibujar en la pantalla sin cortes en el sonido. Pagas el precio en espacio: un minuto de WAV a 44,1 kHz y 16 bits estéreo ocupa cerca de 10 MB. Con una microSD de 8 GB tienes espacio de sobra.

Ahora, el punto donde este proyecto se cae más seguido. El código salta los primeros 44 bytes del archivo antes de empezar a reproducir, porque ese es el largo de la cabecera WAV canónica. El problema es que 44 bytes es el caso mínimo, no una regla: muchos editores insertan bloques extra. típicamente LIST con metadatos. entre la cabecera y los datos. Si tu archivo trae uno de esos, saltar 44 bytes deja al reproductor leyendo metadatos como si fueran audio, y lo que sale por el parlante es un chirrido.

Si te pasa eso, la solución rápida es exportar de nuevo el archivo desde Audacity como WAV de 16 bits PCM sin metadatos. La solución correcta. y una buena excusa para tu primera mejora al código. es buscar la marca data dentro del archivo y empezar a leer cuatro bytes después de ella, en lugar de confiar en un número fijo.

Y una advertencia de alimentación: la microSD y el amplificador tienen picos de consumo simultáneos. Si el reproductor se reinicia justo cuando empieza una canción, casi nunca es un problema de código; es que el puerto USB del computador no está entregando corriente suficiente. Un condensador electrolítico de 470 µF entre 5 V y GND, cerca de los módulos, suele resolverlo.

Interfaz: la pantalla como herramienta de depuración

Retroiluminación de la pantalla TFT del ESP32-S3 controlada por el GPIO 46

La pantalla no es decorativa. Muestra dos datos: el valor instantáneo de aceleración en X y el estado del reproductor (PLAYING, STOPPED, NEXT >>, << PREV). Con eso puedes ver en tiempo real qué número produce tu remezón, que es justo lo que necesitas para ajustar el umbral sin adivinar.

Un detalle del hardware que confunde a mucha gente: en varias de estas placas la retroiluminación es activa en bajo. Poner el pin en LOW enciende la pantalla y ponerlo en HIGH la apaga. Si tu pantalla se queda negra y juras que el código está bien, prueba invirtiendo ese pin antes de desarmar nada.

El software

Versiones (esto importa más de lo que parece)

Las librerías gráficas y de audio del ESP32 rompen compatibilidad entre versiones con una frecuencia molesta. El proyecto original está validado con esta combinación:

  • Arduino IDE 2.3.6
  • Paquete de placas ESP32 v2.0.11 o v2.0.16
  • Librería QMI8658 v1.0.1
  • Librería JPEGDecoder v2.0.0
  • Arduino_GFX_Library (para el panel RGB)

Si usas otras versiones y aparecen errores de compilación que no entiendes, revisa esto antes que cualquier otra cosa.

El sketch completo

Este es el programa tal como fue publicado, apuntando a los pines de la placa MaTouch. Más abajo está qué cambiar para el camino B.

C++
/*
MaTouch ESP32-S3 TFT 3.16
Gesture Controlled Player 


YouTube Channel: Electronic Clinic
Website: https://www.electroniclinic.com/
https://www.patreon.com/c/ElectroniClinic

*/

#include <SPI.h>
#include <SD.h>
#include <driver/i2s.h>
#include <Arduino_GFX_Library.h>
#include <QMI8658.h>

// ================= AUDIO =================
#define I2S_BCLK    44
#define I2S_LRCLK   43
#define I2S_DOUT    19

// ================= SD ====================
#define SD_CS    42
#define SD_MOSI  40
#define SD_MISO  38
#define SD_SCK   39

// ================= TFT ===================
#define TFT_BLK 46

// ================= IMU I2C ===============
#define SDA 17
#define SCL 18

// ================= PARAMETERS ============
#define MAX_FILES        30
#define SHAKE_THRESHOLD  600    // Increased slightly to prevent accidental triggers
#define FLAT_THRESHOLD   150    // mg
#define STOP_HOLD_TIME   1000   // ms
#define SHAKE_COOLDOWN   1000   // ms (Time to ignore sensor after a shake)

// ================= TFT INIT ===============
Arduino_DataBus *bus = new Arduino_SWSPI(
    GFX_NOT_DEFINED, 45,
    39, 40, GFX_NOT_DEFINED);

Arduino_ESP32RGBPanel *rgbpanel = new Arduino_ESP32RGBPanel(
    7, 4, 5, 6,
    12, 11, 8, 16, 15,
    0, 14, 10, 9, 3, 13,
    48, 47, 1, 21, 41,
    1, 10, 8, 50,
    1, 10, 8, 20);

Arduino_RGB_Display *gfx = new Arduino_RGB_Display(
    320, 820, rgbpanel, 0, true,
    bus, GFX_NOT_DEFINED,
    st7701_type9_init_operations,
    sizeof(st7701_type9_init_operations));

// ================= GLOBALS ================
QMI8658 imu;
File audioFile;

String wavFiles[MAX_FILES];
int totalFiles = 0;
int currentTrack = 0;

float lastAccelX = 0;
unsigned long flatStartTime = 0;
unsigned long lastShakeTime = 0; // Timestamp of the last valid shake
unsigned long lastUiUpdate = 0;  // For throttling screen draws

bool nextSongRequest = false;
bool prevSongRequest = false;
bool isPlaying = true;

// ================= PROTOTYPES ============
void scanWavFiles();
void playTrack(int index);
void drawUI();
void updateIMU(float ax);
void updateStatus(const char *txt);

// ================= SETUP =================
void setup() {
  Serial.begin(115200);

  pinMode(TFT_BLK, OUTPUT);
  digitalWrite(TFT_BLK, LOW);

  gfx->begin();
  drawUI();

  // IMU
  if (!imu.begin(SDA, SCL)) {
    updateStatus("IMU FAIL");
    while (1);
  }

  imu.setAccelRange(QMI8658_ACCEL_RANGE_2G);
  imu.setAccelODR(QMI8658_ACCEL_ODR_500HZ);
  imu.setAccelUnit_mg(true);
  imu.enableSensors(QMI8658_ENABLE_ACCEL);

  // SD
  SPI.begin(SD_SCK, SD_MISO, SD_MOSI, SD_CS);
  if (!SD.begin(SD_CS)) {
    updateStatus("SD FAIL");
    while (1);
  }

  scanWavFiles();
  if (totalFiles == 0) {
    updateStatus("NO WAV FILES");
    while (1);
  }

  // I2S
  i2s_config_t i2s_config = {
    .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
    .sample_rate = 44100,
    .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
    .channel_format = I2S_CHANNEL_FMT_RIGHT_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 = true
  };

  i2s_pin_config_t pin_config = {
    .bck_io_num = I2S_BCLK,
    .ws_io_num = I2S_LRCLK,
    .data_out_num = I2S_DOUT,
    .data_in_num = I2S_PIN_NO_CHANGE
  };

  i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL);
  i2s_set_pin(I2S_NUM_0, &pin_config);

  playTrack(currentTrack);
  updateStatus("PLAYING");
}

// ================= LOOP ==================
void loop() {
  static uint8_t buffer[1024];
  size_t bytesWritten;

  // 1. Audio Playback
  if (isPlaying) {
    if (audioFile.available()) {
        int bytesRead = audioFile.read(buffer, sizeof(buffer));
        if (bytesRead > 0) {
          i2s_write(I2S_NUM_0, buffer, bytesRead, &bytesWritten, portMAX_DELAY);
        }
    } else {
        // Auto-play next song if file ends (Optional feature)
        nextSongRequest = true; 
    }
  }

  // 2. Sensor Logic
  QMI8658_Data data;
  if (imu.readSensorData(data)) {
    
    // Only update UI every 200ms to prevent audio lag
    if(millis() - lastUiUpdate > 200) {
       updateIMU(data.accelX);
       lastUiUpdate = millis();
    }

    // Check if we are in the "Cooldown" period
    bool inCooldown = (millis() - lastShakeTime < SHAKE_COOLDOWN);

    if (!inCooldown) {
        float deltaX = abs(data.accelX - lastAccelX);
        
        // -------- SHAKE DETECTION --------
        if (deltaX > SHAKE_THRESHOLD) {
          // If magnitude is high enough, decide direction
          if (data.accelX > 0) {
             nextSongRequest = true;
          } else {
             prevSongRequest = true;
          }
          
          // SET COOLDOWN: Do not accept input for 1 second
          lastShakeTime = millis(); 
          
          // Reset Flat detection logic
          flatStartTime = 0; 
        }
    }

    lastAccelX = data.accelX;

    // -------- FLAT STOP DETECTION ----
    // Only check flat if not shaking
    if (!inCooldown) {
        bool flat =
          abs(data.accelX) < FLAT_THRESHOLD &&
          abs(data.accelY) < FLAT_THRESHOLD;

        if (flat) {
          if (flatStartTime == 0) flatStartTime = millis();
          else if (millis() - flatStartTime > STOP_HOLD_TIME) {
            if (isPlaying) {
              i2s_zero_dma_buffer(I2S_NUM_0);
              isPlaying = false;
              updateStatus("STOPPED");
            }
          }
        } else {
          flatStartTime = 0;
        }
    }
  }

  // -------- TRACK CHANGE -----------
  if (nextSongRequest) {
    currentTrack = (currentTrack + 1) % totalFiles;
    audioFile.close();
    playTrack(currentTrack);
    isPlaying = true;
    updateStatus("NEXT >>");
    nextSongRequest = false;
    // Clear buffer to prevent playing leftover audio from old song
    i2s_zero_dma_buffer(I2S_NUM_0); 
  }

  if (prevSongRequest) {
    currentTrack--;
    if (currentTrack < 0) currentTrack = totalFiles - 1;
    audioFile.close();
    playTrack(currentTrack);
    isPlaying = true;
    updateStatus("<< PREV");
    prevSongRequest = false;
    i2s_zero_dma_buffer(I2S_NUM_0);
  }
}

// ================= FUNCTIONS =============
void scanWavFiles() {
  File root = SD.open("/");
  while (true) {
    File f = root.openNextFile();
    if (!f) break;
    String n = f.name();
    n.toLowerCase();
    if (!f.isDirectory() && n.endsWith(".wav") && totalFiles < MAX_FILES) {
      wavFiles[totalFiles++] = String("/") + f.name();
    }
    f.close();
  }
  root.close();
}

void playTrack(int index) {
  if (audioFile) audioFile.close();
  audioFile = SD.open(wavFiles[index]);
  // WAV Header skip (simple 44 byte skip)
  if(audioFile) audioFile.seek(44);
}

void drawUI() {
  gfx->fillScreen(BLACK);
  gfx->setTextSize(3);
  gfx->setCursor(20, 20);
  gfx->println("GESTURE PLAYER");

  gfx->setTextSize(2);
  gfx->setCursor(20, 120);
  gfx->println("ACCEL X (mg)");

  gfx->setCursor(20, 260);
  gfx->println("STATUS");
}

void updateIMU(float ax) {
  // Clearing a small rect is faster than full screen, but still slow
  // We throttled this in loop()
  gfx->fillRect(20, 150, 280, 40, BLACK);
  gfx->setCursor(20, 150);
  gfx->setTextSize(3);
  gfx->printf("%8.1f", ax);
}

void updateStatus(const char *txt) {
  gfx->fillRect(20, 290, 280, 40, BLACK);
  gfx->setCursor(20, 290);
  gfx->setTextSize(3);
  gfx->println(txt);
}

Adaptar el código a un ESP32-S3 genérico con MPU6050

Acá está el trabajo real de portar el proyecto, y son tres cambios.

1. Los pines. Todos los #define del principio corresponden al ruteo interno de la MaTouch. En una placa genérica los eliges tú. Una asignación que funciona bien en un ESP32-S3 DevKitC-1:

  • I2S: BCLK en GPIO 5, LRCLK en GPIO 6, DOUT en GPIO 7
  • microSD (SPI): CS en GPIO 10, MOSI en GPIO 11, MISO en GPIO 13, SCK en GPIO 12
  • MPU6050 (I2C): SDA en GPIO 8, SCL en GPIO 9

Evita los GPIO 19 y 20 (van al USB nativo) y los GPIO 26 a 32 (los ocupa la memoria flash/PSRAM en los módulos con PSRAM octal).

2. El bloque de la pantalla. Toda la sección Arduino_ESP32RGBPanel es específica de un panel RGB paralelo. Un ILI9341 va por SPI y usa una construcción distinta de Arduino_GFX_Library. Lo más simple es reemplazar ese bloque por la inicialización SPI del ILI9341 y dejar intactas las llamadas a gfx-> del resto del programa, que no cambian.

3. La IMU, y este es el cambio que se olvida. El QMI8658 está configurado con setAccelUnit_mg(true), o sea que data.accelX viene en miligravedades. La librería habitual del MPU6050 entrega el valor crudo del ADC: con la escala por defecto de ±2 g, el rango completo de 16 bits se reparte en 32768 cuentas, así que 1 g equivale a unas 16384 cuentas.

Eso significa que SHAKE_THRESHOLD 600 y FLAT_THRESHOLD 150, que están en mg, no significan nada si los aplicas a lecturas crudas. Hay dos salidas: convertir la lectura a mg al leerla (dividir el valor crudo por 16.384) y dejar los umbrales como están, o escalar los umbrales al dominio crudo (600 mg pasan a ser unas 9830 cuentas). La primera opción es preferible, porque deja el resto del código legible en unidades físicas.

Carga y pruebas

Configuración del IDE

En el menú Herramientas, antes de subir:

  1. Placa: ESP32S3 Dev Module
  2. Puerto: el que corresponda a tu placa
  3. Flash Size: 16MB
  4. Partition Scheme: 16M Flash
  5. PSRAM: OPI PSRAM

Solución al error de carga del ESP32-S3 manteniendo presionado el botón Flash

Si la carga falla con un error de conexión, no es que la placa esté mala: es normal en el ESP32-S3. Desconecta el cable USB, mantén presionado el botón BOOT (o Flash), conecta el cable con el botón todavía apretado, y sube el programa sin soltarlo. Eso fuerza el modo de descarga.

Preparar la tarjeta

Reproducción de archivos WAV desde una tarjeta microSD con el ESP32-S3

Formatea la microSD en FAT32 y copia los archivos .wav en la raíz, no dentro de carpetas: el escaneo del programa recorre solo el directorio raíz. El límite está en 30 archivos (MAX_FILES), y no hay que codificar ningún nombre a mano. el programa los descubre al arrancar. Sirve cualquier cosa: música, podcasts, notas de voz, avisos grabados.

Ajustar los gestos

Control de la reproducción de música mediante gestos con la mano usando el ESP32-S3

Con el reproductor andando, mira el número de aceleración en la pantalla mientras haces el movimiento que quieres usar como gesto. Si tu remezón normal marca 400 y el umbral está en 600, nunca vas a cambiar de canción. Si marca 900 y el umbral está en 600, vas a saltar pistas sin querer. Ajusta SHAKE_THRESHOLD a partir de lo que ves, no de lo que dice el tutorial: depende de tu mano, del peso de la carcasa y de cómo lo sostengas.

Gestos de agitado a izquierda y derecha para cambiar de canción en el reproductor ESP32-S3

Sensor IMU QMI8658 integrado en el ESP32-S3 para detección de movimiento y gestos

Un apunte técnico que vale la pena mirar en el código, porque es una lección reutilizable: la forma obvia de evitar que un remezón dispare tres saltos seguidos es poner un delay(300) después de detectarlo. Y estaría mal. Este bucle también está alimentando el buffer de I2S; si lo bloqueas 300 ms, el buffer se vacía y el audio corta de forma audible. El programa hace lo correcto: guarda el instante del último remezón en lastShakeTime e ignora el sensor durante SHAKE_COOLDOWN milisegundos sin detenerse. En cualquier sistema que tenga que sostener un flujo continuo. audio, video, comunicación. , la regla es esa: el bloqueo se reemplaza por una marca de tiempo.

Lo mismo ocurre con lastUiUpdate: la pantalla se refresca cada 200 ms y no en cada vuelta del bucle, porque dibujar es lento y el audio no espera.

Variantes y mejoras

Control de volumen por inclinación. Ya estás leyendo los tres ejes, así que el eje Y está disponible y sin usar. Puedes mapear la inclinación lateral sostenida a un factor de volumen, escalando las muestras antes de mandarlas a i2s_write. Ojo: multiplica en 32 bits y luego recorta a 16, o vas a introducir distorsión por desbordamiento en los picos.

Reproductor sin cables de verdad. Con una batería LiPo de 3.7 V y un módulo cargador TP4056 con protección, el aparato se despega del USB. Si además agregas la lógica de dormir el ESP32-S3 cuando lleva un rato detenido. usando el propio MPU6050 como fuente de interrupción por movimiento para despertarlo. , la autonomía pasa de horas a días.

Un gesto más: golpecito para pausa. El MPU6050 tiene detección de tap por hardware, con su propio pin de interrupción. Podrías dejar el remezón para cambiar de pista y un golpecito seco sobre la carcasa para pausar, sin gastar ciclos de CPU en detectarlo.

Streaming en vez de tarjeta. El ESP32-S3 tiene WiFi. Reemplazando la lectura desde File por un cliente HTTP que traiga el audio de tu red local, el mismo bucle de I2S sirve para reproducir sin microSD. El cambio conceptual es menor de lo que parece: en ambos casos estás llenando el mismo buffer, solo cambia de dónde salen los bytes.

Personalización para Chile

Este es el listado de lo que necesitas para el camino B, con los nombres tal como se usan en el catálogo de MechatronicStore:

  • Placa ESP32-S3 DevKitC-1. el cerebro. Busca la variante con PSRAM (N8R8 o N16R8); las versiones sin PSRAM te van a quedar cortas de memoria al agregar la pantalla.
  • Módulo amplificador I2S MAX98357A. DAC y amplificador clase D en uno. Es el equivalente exacto de la etapa de audio que la MaTouch trae integrada.
  • Sensor acelerómetro y giroscopio MPU6050 (GY-521). reemplaza al QMI8658 del original. Misma función (acelerómetro de 3 ejes por I2C), distinta librería y distintas unidades: revisa la sección de adaptación del código.
  • Módulo lector de tarjeta microSD SPI. para los archivos WAV.
  • Tarjeta microSD 8GB clase 10. formateada en FAT32.
  • Parlante 3W 4 ohm 40mm. dimensionado para lo que entrega el MAX98357A.
  • Pantalla TFT SPI ILI9341 2.8 pulgadas. sustituye al panel ST7701S paralelo, que no se consigue suelto. Es más chica y va por SPI, pero para mostrar el estado y el valor del acelerómetro sobra.
  • Protoboard 830 puntos y jumpers macho hembra 20cm. para el prototipo antes de soldar.
  • Cable USB-A a USB-C 1m. alimentación y carga del programa.
  • Batería LiPo 3.7V 1000mAh. opcional, solo si vas por la versión portátil de la sección de variantes.

Dos equivalencias que conviene tener claras al comparar con el tutorial original: donde el original dice "MaTouch ESP32-S3", acá estamos usando un ESP32-S3 DevKitC-1 más módulos separados; y donde dice "QMI8658", el MPU6050 cumple la misma función de detección de gestos, con el ajuste de unidades ya explicado. El resultado final es el mismo aparato, armado con piezas que puedes reemplazar de a una cuando alguna falle.

Recursos

Versión chilena con componentes en stock local en MechatronicStore. Proyecto inspirado en el tutorial de Electronic Clinic, re armado con módulos separados y con la adaptación de código para ESP32-S3 genérico y MPU6050.