Skip to Content

Primeros pasos con MaTouch AI ESP32S3 2.8″ TFT ST7789V

Primeros pasos con MaTouch AI ESP32S3 2.8″ TFT ST7789V

El MaTouch AI ESP32-S3 con pantalla TFT ST7789V de 2,8 pulgadas de Makerfabs es una placa de desarrollo compacta orientada a proyectos que combinan conectividad inalámbrica, salida gráfica y aprendizaje automático integrado.

Construida alrededor del microcontrolador ESP32-S3, integra procesadores dual-core Xtensa LX7, Wi-Fi y Bluetooth Low Energy (BLE). La adición de una pantalla TFT de 2,8 pulgadas y 240×320 basada en el controlador ST7789V permite gráficos a todo color, haciendo la placa adecuada para interfaces de usuario, visualización de datos y aplicaciones embebidas que requieren interacción en tiempo real.

Makerfabs ha posicionado la placa como una plataforma versátil para desarrolladores que quieren explorar visión por computadora, reconocimiento de audio e interfaces gráficas sin necesidad de módulos externos. En este tutorial aprenderás a empezar con la MaTouch AI ESP32S3 2.8″ TFT ST7789V.

Partes necesarias

Necesitarás una placa MaTouch AI ESP32S3 2.8″ TFT ST7789V y un cable USB-C para programar la placa y probar los ejemplos de código.

MaTouch AI ESP32S3 2.8″ TFT ST7789V

Cable USB C

Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.

Hardware de la MaTouch AI ESP32S3 2.8″ TFT ST7789V

La MaTouch ESP32-S3 2.8″ TFT está construida sobre el microcontrolador ESP32-S3, que combina una CPU dual-core Xtensa® LX7 funcionando hasta a 240 MHz con 16 MB de memoria flash y 8 MB de PSRAM.

Esta base de hardware asegura que la placa pueda manejar tareas complejas como procesamiento de audio en tiempo real, renderizado gráfico e inferencia de aprendizaje automático directamente en el dispositivo. La conectividad inalámbrica incluye Wi-Fi 802.11 b/g/n y Bluetooth 5.0 LE, haciendo la placa versátil para aplicaciones IoT y conectadas.

La siguiente foto muestra la parte trasera de la placa con los componentes individuales etiquetados:

Back of MaTouch AI ESP32S3 2.8" TFT ST7789V
Parte trasera de MaTouch AI ESP32S3 2.8″ TFT ST7789V

Pantalla y táctil

La placa cuenta con una pantalla TFT LCD de 2,8 pulgadas controlada por el controlador ST7789V con una resolución de 320 × 240 píxeles (QVGA) y soporte para 65K colores. Se conecta mediante una interfaz SPI. Un panel táctil capacitivo multi-touch (GT911) soporta hasta cinco puntos táctiles simultáneos, permitiendo interfaces intuitivas para paneles de control o visualización de datos. La imagen a continuación muestra la parte frontal de la placa con la pantalla y la cámara:

Front of Back of MaTouch AI ESP32S3 2.8" TFT ST7789V
Parte frontal y trasera de MaTouch AI ESP32S3 2.8″ TFT ST7789V

Subsistema de audio

La entrada de audio es manejada por micrófonos MEMS digitales INMP441 duales, proporcionando captura de sonido estéreo para aplicaciones como reconocimiento de voz o detección de audio. Para la salida, la placa integra un amplificador I²S MAX98357, entregando hasta 3.2 W en un altavoz de 4 Ω, permitiendo la reproducción directa de audio o respuestas de voz sin necesidad de un DAC o amplificador externo.

Almacenamiento y expansión

Para almacenamiento y registro de datos, la placa incluye una ranura para tarjeta MicroSD que soporta tarjetas de hasta 32 GB. Los headers GPIO están disponibles para periféricos adicionales como sensores o actuadores. La imagen a continuación muestra el pinout de los dos puertos GPIO (J1 y J3) accesibles en la parte trasera de la placa:

Pinout of GPIO ports
Pinout de puertos GPIO

Soporte para cámara

La placa integra una interfaz para cámara compatible con la cámara OV3660. Esto permite implementar aplicaciones de visión por computadora como clasificación de imágenes, detección de objetos o captura de video simple cuando se combina con las capacidades de aceleración AI del ESP32-S3.

Gestión de energía

La placa puede alimentarse mediante una interfaz USB Tipo-C (4.0 V–5.25 V) e incluye un circuito de carga TP4056 para baterías de ion-litio o polímero de litio. También cuenta con un conector para batería, un interruptor físico y un medidor de combustible MAX17048, que permite al sistema monitorizar la capacidad y estado de carga de la batería.

Interfaces y control

Para el desarrollo, la placa ofrece tanto una interfaz USB a UART (CH340K) como conectividad USB nativa, brindando flexibilidad en la programación y depuración. Los botones físicos de Boot y Reset permiten control a bajo nivel durante la carga de firmware o solución de problemas.

LED y reloj

Un LED RGB WS2812 proporciona indicación visual de estado y puede programarse para notificaciones o retroalimentación al usuario. Y un módulo RTC (PCF8563T) asegura una medición precisa del tiempo.

Resumen de especificaciones técnicas

CaracterísticaEspecificación
ControladorESP32-S3, CPU dual-core Xtensa LX7, hasta 240 MHz
Memoria16 MB Flash, 8 MB PSRAM
InalámbricoWi-Fi 802.11 b/g/n, Bluetooth 5.0 LE
PantallaTFT LCD de 2.8″, 320 × 240 (QVGA), controlador ST7789V, interfaz SPI
Panel táctilCapacitivo, GT911, multi-touch de 5 puntos
Entrada de audioMicrófonos MEMS digitales INMP441 de doble canal
Salida de audioAmplificador I²S MAX98357, 3.2 W @ 4 Ω
AlmacenamientoRanura para tarjeta MicroSD (hasta 32 GB)
CámaraInterfaz compatible con OV3660
LED RGB1 × LED programable WS2812
RTCReloj en tiempo real PCF8563T
BateríaPuerto para batería con interruptor, cargador TP4056, medidor de combustible MAX17048
Interfaces USBUSB a UART (CH340K), USB nativo
BotonesBoot y Reset
Fuente de alimentaciónUSB Tipo-C 5 V (4.0–5.25 V)
Expansión2 puertos GPIO

Puedes encontrar el esquema de la placa en el siguiente enlace:

Instalación del núcleo ESP32

Si es tu primer proyecto con una placa de la serie ESP32, necesitarás instalar primero el núcleo ESP32. Si ya tienes instaladas placas ESP32 en tu Arduino IDE, puedes saltarte esta sección.

Comienza abriendo el diálogo de Preferencias seleccionando “Preferences…” en el menú “File”. Esto abrirá el diálogo de Preferencias mostrado abajo.

En la pestaña Settings encontrarás un cuadro de edición al final del diálogo etiquetado “Additional boards manager URLs“:

Additional boards manager URLs in Preferences
URLs adicionales para el gestor de placas en Preferencias

En este campo copia la siguiente URL:

https://espressif.github.io/arduino-esp32/package_esp32_dev_index.json

Esto permitirá que el Arduino IDE sepa dónde encontrar las librerías del núcleo ESP32. A continuación instalaremos las placas ESP32 usando el Gestor de Placas.

Abre el Gestor de Placas vía «Tools -> Boards -> Board Manager». Verás el Gestor de Placas en la barra lateral izquierda. Escribe «ESP32» en el campo de búsqueda arriba y deberías ver dos tipos de placas ESP32; las «Arduino ESP32 Boards» y las placas «esp32 de Espressif». Queremos las librerías «esp32 de Espressif». Haz clic en el botón INSTALL y espera hasta que la descarga e instalación terminen.

Install ESP32 Core libraries
Instalar librerías núcleo ESP32

Selección de placa

Finalmente necesitamos seleccionar una placa ESP32. En el caso de la MaTouch AI ESP32S3, elegimos el módulo genérico «ESP32S3 Dev Module». Para ello, haz clic en el menú desplegable y luego en «Select other board and port…»:

Drop-down Menu for Board Selection
Menú desplegable para selección de placa

Esto abrirá un diálogo donde puedes escribir «esp32s3 dev» en la barra de búsqueda. Verás la placa «ESP32S3 Dev Module» bajo Boards. Haz clic en ella y en el puerto COM para activarla y luego en OK:

Board Selection Dialog "ESP32S3 Dev Module" board
Diálogo de selección de placa «ESP32S3 Dev Module»

Ten en cuenta que debes conectar la placa mediante el cable USB a tu ordenador antes de poder seleccionar un puerto COM. La placa tiene dos puertos USB, uno nativo y otro para TTL/UART. Para comunicación serial con la placa debes usar el puerto etiquetado «USB TTL», que es el más cercano a la esquina:

USB TTL Port for Serial Communication
Puerto USB TTL para comunicación serial

Configuración de herramientas

En las siguientes secciones encontrarás ejemplos de código para los distintos componentes hardware de la placa. Algunos requieren memoria considerable y necesitarás las siguientes configuraciones para que funcionen. Puedes encontrarlas en el menú Tools:

Settings for  MaTouch AI ESP32S3 2.8" TFT ST7789V
Configuraciones para MaTouch AI ESP32S3 2.8″ TFT ST7789V

Lo más importante es que Flash Size esté configurado a 16MB(128MB), Partition Scheme a 16M Flash (3MB APP/9.9MB FATFS), PSRAM a OPI PSRAM. Las demás configuraciones deben ser las predeterminadas.

Ejemplo de código: Interfaz Serial

Comenzamos probando la comunicación Serial. Abre tu Arduino IDE, introduce el siguiente código y súbelo a la MaTouch AI ESP32S3.

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

void loop() {
  Serial.println("Makerguides");
  delay(2000);
}

Luego abre el Monitor Serial y deberías ver el texto «Makerguides» impreso cada dos segundos. Si no, asegúrate de que el cable USB esté conectado al puerto correcto de la placa.

Ejemplo de código: LED RGB

A continuación probamos el LED RGB integrado. Necesitarás instalar la Adafruit_NeoPixel librería para usar este ejemplo. Simplemente cambia el color del LED RGB de rojo, a verde, a azul cada 200 milisegundos:

#include "Adafruit_NeoPixel.h"

const byte dataIn = 0;

Adafruit_NeoPixel pixels(1, dataIn, NEO_GRB + NEO_KHZ800);

void setup() {
  pixels.begin();
  pixels.clear();
  pixels.setBrightness(50);
  pixels.show();
}

void loop() {
  pixels.setPixelColor(0, 255, 0, 0);  // red
  pixels.show();
  delay(200);

  pixels.setPixelColor(0, 0, 255, 0);  // green
  pixels.show();
  delay(200);

  pixels.setPixelColor(0, 0, 0, 255);  // blue
  pixels.show();
  delay(200);
}

Si necesitas más información sobre el LED RGB WS2812 y cómo usarlo, echa un vistazo a los LED Ring Clock with WS2812 y los Use WS2812B LED Strip with Arduino tutoriales.

Ejemplo de código: GPIO

La placa tiene cuatro pines GPIO (IO4, IO5, IO6, IO7). Para probar los GPIO conectamos un LED con una resistencia de 220 Ohm a GND y a uno de los pines GPIO. En este ejemplo uso IO4:

Connecting LED to GND and IO4
Conexión del LED a GND y IO4

Ahora podemos usar el programa típico de parpadeo para encender y apagar el LED en GPIO4:

#define LED_PIN 4

void setup() {
  pinMode(LED_PIN, OUTPUT);
}

void loop() {
  digitalWrite(LED_PIN, HIGH);  
  delay(1000);                      
  digitalWrite(LED_PIN, LOW);   
  delay(1000);                      
}

Ejemplo de código: Monitor de carga de batería

La placa MaTouch AI ESP32-S3 viene con un conector para batería LiPo externa y un circuito de carga basado en el TP4056:

Battery charging circuit
Circuito de carga de batería (source)

Además, hay un MAX17048 fuel gauge que permite monitorizar la carga de la batería. Está conectado vía interfaz I2C a SDA=39 y SCL=38:

MAX17048 fuel gauge
Medidor de combustible MAX17048 (source)

El siguiente ejemplo de código muestra cómo usar el medidor de combustible integrado para medir el voltaje y la capacidad restante de la batería. Ten en cuenta que necesitarás instalar la MAX17048 Library para que este código funcione.

#include "Wire.h"
#include "MAX17048.h"

#define SDA 39
#define SCL 38

MAX17048 power;

void setup() {
  Serial.begin(115200);
  Wire.begin(SDA, SCL);
  power.attatch(Wire);
}

void loop() {
  float volts = power.voltage();
  int pcnt = power.percent();
  Serial.printf("%.2fV (%d%%)\n", volts, pcnt);
  delay(3000);
}

Este código imprimirá el voltaje actual y la capacidad restante (en porcentaje) de la batería en el Monitor Serial.

Ejemplo de código: Pantalla y pantalla táctil

El siguiente ejemplo muestra cómo usar la pantalla y la pantalla táctil. Imprime el texto «Makerguides» en el centro de la pantalla y si tocas la pantalla se dibuja un pequeño círculo rojo en el punto táctil. Mira el ejemplo a continuación:

Antes de ejecutar el siguiente código necesitarás instalar la Adafruit-GFX-Library, la Adafruit-ST7735-Library y la BitBank Capacitive Touch Sensor Library (bb_captouch). Todas pueden instalarse vía el Gestor de Librerías del Arduino IDE.

Echa un vistazo rápido al código primero y luego discutiremos algunos detalles.

#include <Adafruit_GFX.h>      
#include <Adafruit_ST7789.h>   
#include <bb_captouch.h>

#define TFT_BLK 45
#define TFT_RES -1

#define TFT_SCLK  48
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_SD_CS   47
#define TFT_CS 40
#define TFT_DC 21

#define TOUCH_INT 14
#define TOUCH_SDA 39
#define TOUCH_SCL 38
#define TOUCH_RST 18

#define SCREEN_H 320
#define SCREEN_W 240


Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RES);
BBCapTouch touch;


void setup() {
  pinMode(TFT_BLK, OUTPUT);
  digitalWrite(TFT_BLK, HIGH);

  tft.init(SCREEN_W, SCREEN_H);
  tft.setRotation(0);          
  tft.fillScreen(ST77XX_BLACK);

  tft.setTextColor(ST77XX_YELLOW);
  tft.setTextSize(2);
  tft.setCursor(50, 150);
  tft.println("Makerguides");

  touch.init(TOUCH_SDA, TOUCH_SCL, TOUCH_RST, TOUCH_INT);
}

void loop() {
  TOUCHINFO ti;
  if (touch.getSamples(&ti)) {
    int x = SCREEN_W - ti.x[0];
    int y = SCREEN_H - ti.y[0];
    tft.fillCircle(x, y, 5, ST77XX_RED);
  }
}

Librerías

Primero incluimos las librerías gráficas y de pantalla de Adafruit, que proporcionan funciones de dibujo para el controlador ST7789, y el bb_captouch.h header, que da acceso al panel táctil capacitivo.

Constantes

Luego definimos los pines usados para conectar la pantalla TFT y el controlador táctil. Se asignan constantes para el pin de retroiluminación, los pines de control de pantalla y los pines I²C usados por el panel táctil. También se definen el ancho y alto de la pantalla para simplificar cálculos posteriores.

Objetos

Después se crea un objeto Adafruit_ST7789 llamado tft que representa la conexión a la pantalla TFT. También se crea un objeto BBCapTouch llamado touch que se usará para leer eventos táctiles del sensor capacitivo.

Configuración

En la función setup, primero se configura el pin de retroiluminación como salida y se enciende. Luego se inicializa la pantalla TFT a una resolución de 240 por 320 píxeles. La rotación se establece en cero, usando la pantalla en orientación vertical. Se limpia toda la pantalla a negro y el color del texto se pone en amarillo. El programa establece un tamaño de fuente de dos, mueve el cursor a la posición (50, 150) e imprime el texto “Makerguides” en la pantalla. Tras configurar la pantalla, se inicializa el controlador táctil capacitivo con los pines I²C y el pin de reset correctos.

Bucle

En la función loop comprobamos continuamente si se ha detectado un nuevo evento táctil. Si hay datos táctiles disponibles, se lee la primera muestra en las variables x y y. Estas coordenadas se transforman para que coincidan con el sistema de coordenadas de la pantalla restándolas del ancho y alto de la pantalla. Finalmente, dibujamos un círculo rojo con radio de cinco píxeles en la ubicación táctil.

Ejemplo de código: Reloj en tiempo real

La placa MaTouch AI ESP32-S3 contiene un PCF8563T para proporcionar un reloj en tiempo real (RTC). Mira el esquema abajo:

Real Time Clock (RTC) circuit
Circuito de reloj en tiempo real (RTC) (source)

En este ejemplo usaremos el RTC para mantener la hora y mostrar la hora actual en la pantalla TFT. Se verá así:

Necesitarás instalar la RTCLib para que el siguiente código funcione. Echa un vistazo rápido al código primero y luego discutiremos algunos detalles:

#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>
#include <RTClib.h>
#include <Wire.h>

#define TFT_BLK 45
#define TFT_RES -1

#define TFT_SCLK 48
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_CS 40
#define TFT_DC 21

#define SCREEN_W 240
#define SCREEN_H 320

// RTC pins
#define RTC_SCL 38
#define RTC_SDA 39
#define RTC_INT 15

Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RES);
RTC_PCF8563 rtc_pcf;

void rtc_pcf_init() {
  if (!rtc_pcf.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    while (1) delay(10);
  }

  rtc_pcf.adjust(DateTime(F(__DATE__), F(__TIME__)));
}

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

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

  tft.init(SCREEN_W, SCREEN_H);
  tft.setRotation(0);
  tft.fillScreen(ST77XX_BLACK);
  tft.setTextColor(ST77XX_YELLOW, ST77XX_BLACK);
  tft.setTextSize(4);

  Wire.begin(RTC_SDA, RTC_SCL);
  rtc_pcf_init();
}

void loop() {
  static char buf[16];

  DateTime now = rtc_pcf.now();
  sprintf(buf, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());

  tft.setCursor(20, 130);
  tft.print(buf);

  delay(500);
}

Librerías

Comenzamos incluyendo las librerías gráficas y de pantalla de Adafruit para dibujar en la pantalla, la librería RTClib para acceder al reloj en tiempo real, y la librería Wire para comunicación I²C.

Constantes

Luego se definen los pines para la pantalla TFT, incluyendo chip select, data/command y pines SPI. También se definen el ancho y alto de la pantalla. Se listan los pines para el RTC, con RTC_SDA y RTC_SCL asignados a las líneas de datos y reloj I²C, y RTC_INT definido pero no usado en este programa.

Objetos

Luego creamos un objeto Adafruit_ST7789 llamado tft para representar la pantalla. El objeto RTC_PCF8563 llamado rtc_pcf se crea para comunicarse con el reloj en tiempo real.

rtc_pcf_init

La función rtc_pcf_init() intenta inicializar el RTC. Si el dispositivo no se encuentra en el bus I²C, el programa imprime un mensaje de error y se detiene. Si el RTC está presente, su hora se ajusta a la fecha y hora de compilación del programa usando rtc_pcf.adjust(). Esto asegura que el reloj comience con un punto de referencia conocido.

Configuración

En la función setup, se inicia la comunicación serial para depuración. Se habilita el pin de retroiluminación de la pantalla TFT y se inicializa la pantalla a 240 por 320 píxeles. La rotación se pone a cero, la pantalla se limpia a negro y el color del texto se pone amarillo sobre fondo negro. El tamaño del texto se aumenta para facilitar la lectura. Se inicializa la interfaz I²C con los pines SDA y SCL definidos, y se arranca el RTC con rtc_pcf_init().

Bucle

La función loop se ejecuta repetidamente y obtiene la hora actual del RTC con rtc_pcf.now(). La hora se formatea como cadena en formato “HH:MM:SS” usando sprintf, y se almacena en un buffer. El cursor se posiciona en las coordenadas (20, 130), cerca del centro de la pantalla, y la cadena de hora se dibuja en amarillo. Luego el bucle espera medio segundo antes de repetir, para que la pantalla se actualice dos veces por segundo.

En resumen, este código inicializa una pantalla TFT y un reloj en tiempo real PCF8563, ajusta el reloj a la hora de compilación y luego muestra continuamente la hora actual en horas, minutos y segundos en el centro de la pantalla.

Ejemplo de código: Reproducir archivo de audio

La placa MaTouch AI ESP32-S3 tiene un amplificador Clase D MAX98357A integrado para manejar un pequeño altavoz con 3.2W a 4Ω.

Amplifier circuit
Circuito amplificador (source)

En el siguiente ejemplo de código reproducimos un archivo WAV almacenado en la tarjeta SD:

#include <driver/i2s_std.h>
#include "FS.h"
#include "SD.h"
#include "SPI.h"

#define SCLK    48
#define MISO    12
#define MOSI    13
#define SD_CS   47

// Speaker pins
#define I2S_OUT_BCLK  20
#define I2S_OUT_LRC   1
#define I2S_OUT_DOUT  19

#define SAMPLE_RATE   16000U

static i2s_chan_handle_t tx_chan;

void SpeakerInit() {
  i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER);
  ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_chan, NULL));

  // Standard mode, 16-bit mono
  i2s_std_config_t std_cfg = {
    .clk_cfg  = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
    .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
                                                    I2S_SLOT_MODE_MONO),
    .gpio_cfg = {
      .mclk = I2S_GPIO_UNUSED,
      .bclk = (gpio_num_t)I2S_OUT_BCLK,
      .ws   = (gpio_num_t)I2S_OUT_LRC,
      .dout = (gpio_num_t)I2S_OUT_DOUT,
      .din  = I2S_GPIO_UNUSED,
    },
  };
  std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_LEFT;  // left channel only

  ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_chan, &std_cfg));
  ESP_ERROR_CHECK(i2s_channel_enable(tx_chan));
}

void playWav(const char *filename) {
  File file = SD.open(filename);
  if (!file || file.size() <= 44) {
    Serial.println("Invalid or missing WAV file.");
    return;
  }

  file.seek(44); // skip header
  uint8_t buffer[1024];
  size_t bytesRead, bytesWritten;

  while ((bytesRead = file.read(buffer, sizeof(buffer))) > 0) {
    // optional ×2 volume boost
    for (int i = 0; i < bytesRead; i += 2) {
      int16_t *s = (int16_t *)&buffer[i];
      *s <<= 1;
    }
    i2s_channel_write(tx_chan, buffer, bytesRead, &bytesWritten, portMAX_DELAY);
  }

  file.close();
  i2s_channel_disable(tx_chan);
  Serial.println("Playback finished.");
}

void setup() {
  Serial.begin(115200);
  SPI.begin(SCLK, MISO, MOSI);

  if (!SD.begin(SD_CS, SPI, 80'000'000)) {
    Serial.println(F("ERROR: SD mount failed!"));
    return;
  }

  SpeakerInit();
  delay(500);
  playWav("/LightMusic.wav");
}

void loop() {}

Librerías

Al inicio del código se incluyen las librerías necesarias. El driver I²S se usa para comunicarse con un chip de audio digital externo, las librerías SD y SPI proporcionan acceso a la tarjeta SD, y la librería FS define la interfaz del sistema de archivos.

Constantes

Luego se definen los números de pines para el bus SPI que conecta a la tarjeta SD, y para las señales I²S que conectan al hardware de salida de audio. La tasa de muestreo se fija en 16,000 muestras por segundo, que define la velocidad de reproducción del audio. Se declara un manejador para el canal de transmisión I²S para enviar datos de audio al altavoz.

SpeakerInit

La función SpeakerInit() configura y habilita la salida I²S. Primero crea una configuración de canal por defecto para el canal I²S 1 operando en modo maestro. Luego inicializa un nuevo canal, guardando el manejador de transmisión en tx_chan.

Después configura una configuración estándar I²S: el reloj se define para la tasa de muestreo de 16 kHz, la configuración de ranura especifica audio mono de 16 bits en formato Philips I²S, y la configuración GPIO asigna los pines correctos para bit clock, word select y salida de datos. La máscara de ranura se restringe solo al canal izquierdo, ya que se reproduce audio mono. Finalmente, el canal se inicializa en modo estándar y se habilita, dejando el hardware listo para la reproducción de audio.

playWav

La función playWav(const char *filename) abre un archivo WAV desde la tarjeta SD. Verifica si el archivo existe y es mayor de 44 bytes, porque los primeros 44 bytes de un archivo WAV son el encabezado y no contienen datos de sonido reales.

Si el archivo es válido, el programa salta el encabezado con file.seek(44). Luego lee repetidamente bloques de 1024 bytes en un buffer. Antes de escribir cada bloque en la interfaz I²S, las muestras se amplifican opcionalmente: cada muestra firmada de 16 bits se desplaza un bit a la izquierda, duplicando efectivamente su amplitud.

Los datos de audio procesados se escriben luego en el canal de transmisión I²S usando i2s_channel_write(). Cuando se alcanza el final del archivo, se cierra el archivo, se deshabilita el canal I²S y se imprime un mensaje indicando que la reproducción ha terminado.

Configuración

En la función setup(), se inicializa el puerto serial para depuración. Se inicia el bus SPI en los pines especificados y se monta la tarjeta SD. Si el montaje falla, se imprime un error y el programa se detiene. De lo contrario, se inicializa el altavoz I²S, una breve espera asegura estabilidad, y se llama a la función playWav("/LightMusic.wav") para comenzar la reproducción del archivo almacenado en la tarjeta SD.

Bucle

La función loop() queda vacía, ya que toda la acción ocurre una vez en setup().

En resumen, este código configura el ESP32-S3 para reproducir un archivo WAV almacenado en la tarjeta SD a través de un altavoz conectado por I²S. Configura la tarjeta SD y la salida I²S, salta el encabezado del archivo, transmite los datos de audio crudos al hardware I²S y produce sonido audible desde la pista almacenada.

Ejemplo de código: Grabar audio

La placa MaTouch AI ESP32-S3 viene con dos INMP441 micrófonos de salida digital. Mira el esquema abajo:

Microphone circuit
Circuito de micrófono (source)

En el siguiente ejemplo de código grabamos 10 segundos de audio desde el micrófono y lo almacenamos como un archivo WAV en la tarjeta SD:

#include <driver/i2s_std.h>
#include "FS.h"
#include "SD.h"
#include "SPI.h"

#define SCLK  48
#define MISO  12
#define MOSI  13
#define SD_CS 47

// Microphone pins
#define I2S_IN_BCLK  42
#define I2S_IN_LRC   2
#define I2S_IN_DIN   41

#define SAMPLE_RATE   16000U
#define SAMPLE_BITS   16
#define RECORD_TIME   10   // seconds

static i2s_chan_handle_t rx_chan;

void writeWavHeader(File &file, uint32_t dataSize) {
  uint32_t chunkSize = 36 + dataSize;
  uint16_t audioFormat = 1; // PCM
  uint16_t numChannels = 1;
  uint32_t sampleRate = SAMPLE_RATE;   // copy macro into variable
  uint16_t bitsPerSample = SAMPLE_BITS; // copy macro into variable
  uint32_t byteRate = sampleRate * numChannels * bitsPerSample / 8;
  uint16_t blockAlign = numChannels * bitsPerSample / 8;

  file.seek(0);
  file.write((const uint8_t *)"RIFF", 4);
  file.write((uint8_t *)&chunkSize, 4);
  file.write((const uint8_t *)"WAVE", 4);
  file.write((const uint8_t *)"fmt ", 4);

  uint32_t subChunk1Size = 16;
  file.write((uint8_t *)&subChunk1Size, 4);
  file.write((uint8_t *)&audioFormat, 2);
  file.write((uint8_t *)&numChannels, 2);
  file.write((uint8_t *)&sampleRate, 4);
  file.write((uint8_t *)&byteRate, 4);
  file.write((uint8_t *)&blockAlign, 2);
  file.write((uint8_t *)&bitsPerSample, 2);

  file.write((const uint8_t *)"data", 4);
  file.write((uint8_t *)&dataSize, 4);
}


void MicInit() {
  i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
  ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_chan));  // RX channel only

  i2s_std_config_t std_cfg = {
    .clk_cfg  = I2S_STD_CLK_DEFAULT_CONFIG(SAMPLE_RATE),
    .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT,
                                                    I2S_SLOT_MODE_MONO),
    .gpio_cfg = {
      .mclk = I2S_GPIO_UNUSED,
      .bclk = (gpio_num_t)I2S_IN_BCLK,
      .ws   = (gpio_num_t)I2S_IN_LRC,
      .dout = I2S_GPIO_UNUSED,
      .din  = (gpio_num_t)I2S_IN_DIN,
    },
  };
  std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_RIGHT;

  ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_chan, &std_cfg));
  ESP_ERROR_CHECK(i2s_channel_enable(rx_chan));
}

void recordWav(const char *filename) {
  File file = SD.open(filename, FILE_WRITE);
  if (!file) {
    Serial.println("Failed to open file for writing!");
    return;
  }

  // Reserve header space
  for (int i = 0; i < 44; i++) file.write((uint8_t)0);

  const size_t bufferSize = 1024;
  uint8_t buffer[bufferSize];
  size_t bytesRead;
  uint32_t totalBytes = 0;

  uint32_t startMs = millis();
  while ((millis() - startMs) < RECORD_TIME * 1000) {
    if (i2s_channel_read(rx_chan, buffer, bufferSize, &bytesRead, portMAX_DELAY) == ESP_OK) {
      file.write(buffer, bytesRead);
      totalBytes += bytesRead;
    }
  }

  // Write real header
  writeWavHeader(file, totalBytes);
  file.close();

  Serial.printf("Recording finished: %s (%lu bytes)\n", filename, (unsigned long)totalBytes);
}

void setup() {
  Serial.begin(115200);
  SPI.begin(SCLK, MISO, MOSI);

  if (!SD.begin(SD_CS, SPI, 80'000'000)) {
    Serial.println("SD mount failed!");
    return;
  }

  MicInit();
  delay(2000);
  Serial.println("Recording...");
  recordWav("/mic_record.wav");
}

void loop() {}

Librerías

Al inicio se incluyen las librerías necesarias. El driver I²S proporciona acceso a la interfaz de hardware de audio, las librerías FS y SD permiten crear y escribir archivos en la tarjeta SD, y la librería SPI maneja la comunicación con la tarjeta SD.

Constantes

Luego se definen constantes para los pines SPI usados por la tarjeta SD y para los pines I²S conectados al micrófono digital. La tasa de muestreo se fija en 16 kHz, la resolución en 16 bits por muestra, y la duración de grabación en diez segundos. Se declara un manejador para el canal de recepción I²S para capturar datos del micrófono.

writeWavHeader

La función writeWavHeader() escribe un encabezado WAV correcto de 44 bytes al inicio del archivo. Construye el contenedor RIFF, especifica el formato de audio como PCM, define un canal para audio mono, y establece la tasa de muestreo, profundidad de bits y tasa de bytes. Luego escribe la sección “data” seguida del tamaño total de las muestras de audio. Esto asegura que el archivo resultante pueda reproducirse en cualquier software estándar de audio.

MicInit

La función MicInit() configura la interfaz I²S para recibir audio del micrófono. Crea un nuevo canal I²S en modo maestro con configuraciones por defecto. La configuración estándar especifica un reloj para la tasa de muestreo de 16 kHz, formato de ranura Philips I²S con muestras de 16 bits, y modo de canal mono. La configuración GPIO asigna los pines correctos para bit clock, word select y entrada de datos, dejando sin usar el reloj maestro y la salida de datos. Finalmente, el canal se inicializa en modo estándar y se habilita, listo para capturar muestras de audio.

recordWav

La función recordWav(const char *filename) realiza la grabación real. Abre un archivo en la tarjeta SD para escritura. Si no puede crearse el archivo, reporta un error y sale. Para dejar espacio para el encabezado WAV, escribe 44 bytes cero al inicio del archivo. Luego asigna un buffer de 1024 bytes. Un bucle corre durante el tiempo definido por RECORD_TIME. Durante este periodo, los datos de audio se leen del canal I²S al buffer y se escriben directamente en el archivo. Se mantiene un total acumulado de bytes de audio grabados. Al terminar el periodo de grabación, la función rebobina al inicio del archivo y escribe el encabezado WAV real con los tamaños correctos. Luego cierra el archivo y se imprime un mensaje con el tamaño final del archivo.

Configuración

En la función setup() se inicia el puerto serial para depuración. Se inicializa el bus SPI en los pines dados y se monta la tarjeta SD. Si el montaje falla, el programa reporta un error y no continúa. Si tiene éxito, se inicializa el micrófono, una breve espera da tiempo al hardware para estabilizarse, y un mensaje anuncia el inicio de la grabación. Luego se llama a la función recordWav("/mic_record.wav") para capturar y guardar diez segundos de audio en la tarjeta SD.

Bucle

La función loop() está vacía, ya que la tarea de grabación se realiza solo una vez al iniciar.

En resumen, este programa inicializa la tarjeta SD y el micrófono I²S, graba diez segundos de audio a 16 kHz en un buffer, escribe los datos en la tarjeta SD, añade un encabezado WAV correcto y produce un archivo de audio estándar que puede reproducirse en cualquier dispositivo.

Ejemplo de código: Cámara y pantalla

En este último ejemplo capturamos video en vivo desde el módulo de cámara conectado al ESP32-S3 y lo mostramos directamente en la pantalla TFT.

Para este ejemplo necesitarás instalar la Arduino_GFX librería de moononournation. Abre el GESTOR DE LIBRERÍAS, escribe «GFX Library for Arduino» en la barra de búsqueda, encuentra la «GFX Library for Arduino» de Moon y haz clic en el botón INSTALL:

Installation of GFX Library for Arduino
Instalación de la librería GFX para Arduino

A continuación está el código completo para transmitir video desde la cámara a la pantalla. Échale un vistazo rápido y luego discutiremos los detalles:

#include <Arduino_GFX_Library.h>
#include "esp_camera.h"

// === Camera pins ===
#define PWDN_GPIO_NUM    -1
#define RESET_GPIO_NUM   -1
#define XCLK_GPIO_NUM     9   // CSI_MCLK
#define SIOD_GPIO_NUM    39   // TWI_SDA
#define SIOC_GPIO_NUM    38   // TWI_SCK
#define Y9_GPIO_NUM      46   // CSI D7
#define Y8_GPIO_NUM       3   // CSI D6
#define Y7_GPIO_NUM       8   // CSI D5
#define Y6_GPIO_NUM      16   // CSI D4
#define Y5_GPIO_NUM       6   // CSI D3
#define Y4_GPIO_NUM       4   // CSI D2
#define Y3_GPIO_NUM       5   // CSI D1
#define Y2_GPIO_NUM       7   // CSI D0
#define VSYNC_GPIO_NUM   11   // CSI VSYNC
#define HREF_GPIO_NUM    10   // CSI HSYNC
#define PCLK_GPIO_NUM    17   // CSI PCLK

// === TFT pins ===
#define TFT_BLK          45
#define TFT_RES          -1
#define TFT_CS           40
#define TFT_DC           21
#define MOSI             13
#define MISO             12
#define SCLK             48


Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(
  TFT_DC, TFT_CS, SCLK, MOSI, MISO, HSPI, true
);
Arduino_GFX *gfx = new Arduino_ST7789(
  bus, TFT_RES, 1 , true 
);

// === Camera init ===
void camera_init_s3() {
  camera_config_t config;
  config.ledc_channel = LEDC_CHANNEL_0;
  config.ledc_timer = LEDC_TIMER_0;
  config.pin_d0 = Y2_GPIO_NUM;
  config.pin_d1 = Y3_GPIO_NUM;
  config.pin_d2 = Y4_GPIO_NUM;
  config.pin_d3 = Y5_GPIO_NUM;
  config.pin_d4 = Y6_GPIO_NUM;
  config.pin_d5 = Y7_GPIO_NUM;
  config.pin_d6 = Y8_GPIO_NUM;
  config.pin_d7 = Y9_GPIO_NUM;
  config.pin_xclk = XCLK_GPIO_NUM;
  config.pin_pclk = PCLK_GPIO_NUM;
  config.pin_vsync = VSYNC_GPIO_NUM;
  config.pin_href = HREF_GPIO_NUM;
  config.pin_sccb_sda = SIOD_GPIO_NUM;
  config.pin_sccb_scl = SIOC_GPIO_NUM;
  config.pin_pwdn = PWDN_GPIO_NUM;
  config.pin_reset = RESET_GPIO_NUM;
  config.xclk_freq_hz = 20000000;

  config.frame_size   = FRAMESIZE_QVGA;    // 320x240
  config.pixel_format = PIXFORMAT_RGB565;  // direct TFT compatible
  config.grab_mode    = CAMERA_GRAB_WHEN_EMPTY;
  config.fb_location  = CAMERA_FB_IN_PSRAM;
  config.jpeg_quality = 12;
  config.fb_count     = 2;

  esp_err_t err = esp_camera_init(&config);
  if (err != ESP_OK) {
    Serial.printf("Camera init failed with error 0x%x\n", err);
    return;
  }

  sensor_t *s = esp_camera_sensor_get();
  if (s->id.PID == OV3660_PID) {
    s->set_hmirror(s, 1); 
    s->set_vflip(s, 1);
  }
}

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

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

  gfx->begin();
  gfx->fillScreen(BLACK);

  camera_init_s3();
}

void loop() {
  camera_fb_t *fb = esp_camera_fb_get();
  if (!fb) {
    Serial.println("Camera capture failed");
    delay(100);
    return;
  }

  gfx->draw16bitBeRGBBitmap(0, 0, (uint16_t *)fb->buf, fb->width, fb->height);
  esp_camera_fb_return(fb);
}

Librerías

Al inicio, incluimos las librerías para la pantalla (Arduino_GFX_Library.h) y la cámara (esp_camera.h).

Constantes

La primera sección define las conexiones hardware. Se listan los pines de la cámara para la interfaz paralela: ocho pines de datos (Y2–Y9), pines de reloj y sincronización (XCLK, PCLK, HREF y VSYNC), y los pines I²C (SIOD y SIOC) que configuran el sensor de la cámara. También se definen los pines de la pantalla TFT para chip select, data/command, líneas de comunicación SPI y retroiluminación.

Objetos

Luego se crean dos objetos para la pantalla. El primero, bus, representa la conexión SPI a la pantalla, y el segundo, gfx, representa el controlador ST7789. Estos objetos provienen de la librería Arduino GFX, que ofrece funciones gráficas eficientes para muchas pantallas.

camera_init_s3

La función camera_init_s3() configura e inicializa la cámara. Se llena una estructura camera_config_t con las asignaciones de pines y parámetros correctos. El reloj externo para la cámara se fija en 20 MHz. El tamaño de cuadro se establece en FRAMESIZE_QVGA, que corresponde a 320×240 píxeles, coincidiendo con la resolución TFT.

El formato de píxel se fija en PIXFORMAT_RGB565, que produce un formato de color de 16 bits que la TFT puede usar directamente sin conversión. El modo de captura se establece para capturar cuadros cuando el buffer está vacío, los buffers de cuadro se almacenan en PSRAM, y se usan dos buffers para mejorar el rendimiento.

Después de llenar la configuración, esp_camera_init() inicia el driver de la cámara. Si la inicialización falla, se imprime un código de error. Si el sensor es un OV3660, se aplica configuración extra para ajustar orientación y brillo.

Configuración

En la función setup(), se inicia el puerto serial para depuración. Se habilita la retroiluminación TFT y se inicializa la pantalla. La pantalla se limpia a negro antes de iniciar la cámara llamando a camera_init_s3().

Bucle

La función loop() captura y muestra cuadros repetidamente. Cada llamada a esp_camera_fb_get() obtiene un puntero al último buffer de cuadro. Si la captura falla, se imprime un mensaje y el programa espera brevemente antes de intentar de nuevo. Si tiene éxito, el buffer de cuadro se dibuja en la TFT usando gfx->draw16bitBeRGBBitmap(). Esta función transfiere directamente los datos de píxeles RGB565 del buffer de la cámara a la pantalla en la posición (0,0). Una vez mostrado el cuadro, se llama a esp_camera_fb_return(fb) para liberar el buffer al driver para que pueda reutilizarse.

En resumen, este programa inicializa la cámara ESP32-S3 y la pantalla TFT ST7789, captura cuadros en formato RGB565 y los transmite directamente a la pantalla. El resultado es una vista previa en vivo de la cámara mostrada en tiempo real en la TFT.

Conclusiones

Este tutorial te ha proporcionado ejemplos de código para comenzar con la MaTouch AI ESP32-S3 con pantalla TFT ST7789V de 2,8 pulgadas.

Para más ejemplos, consulta Makerfabs’s Github repo for the Matouch display y los Wiki Page. Ten en cuenta, sin embargo, que la mayoría de los ejemplos allí usan la librería lvgl, que he evitado aquí para mantener la complejidad baja. Además, algunos ejemplos no funcionan con el núcleo 3.x actual.

Si tienes alguna pregunta, no dudes en dejarla en la sección de comentarios.

¡Feliz bricolaje! 😉