Skip to Content

Começar com MaTouch AI ESP32S3 2.8″ TFT ST7789V

Começar com MaTouch AI ESP32S3 2.8″ TFT ST7789V

O MaTouch AI ESP32-S3 com ecrã TFT ST7789V de 2,8 polegadas da Makerfabs é uma placa de desenvolvimento compacta destinada a projetos que combinam conectividade sem fios, saída gráfica e aprendizagem automática integrada.

Construída em torno do microcontrolador ESP32-S3, integra processadores dual-core Xtensa LX7, Wi-Fi e Bluetooth Low Energy (BLE). A adição de um ecrã TFT de 2,8 polegadas com resolução 240×320 baseado no controlador ST7789V permite gráficos a cores completos, tornando a placa adequada para interfaces de utilizador, visualização de dados e aplicações embutidas que requerem interação em tempo real.

A Makerfabs posicionou a placa como uma plataforma versátil para desenvolvedores que querem explorar visão computacional, reconhecimento de áudio e interfaces gráficas sem necessidade de módulos externos. Neste tutorial, aprenderá a começar a usar o MaTouch AI ESP32S3 com ecrã TFT ST7789V de 2,8″.

Peças Necessárias

Vai precisar de uma placa MaTouch AI ESP32S3 2.8″ TFT ST7789V e de um cabo USB-C para programar a placa e testar os exemplos de código.

MaTouch AI ESP32S3 2.8″ TFT ST7789V

Cabo 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 do MaTouch AI ESP32S3 2.8″ TFT ST7789V

O MaTouch ESP32-S3 2.8″ TFT é construído com base no microcontrolador ESP32-S3, combinando uma CPU dual-core Xtensa® LX7 a até 240 MHz com 16 MB de memória flash e 8 MB de PSRAM.

Esta base de hardware garante que a placa pode lidar com tarefas complexas como processamento de áudio em tempo real, renderização gráfica e inferência de aprendizagem automática diretamente no dispositivo. A conectividade sem fios inclui Wi-Fi 802.11 b/g/n e Bluetooth 5.0 LE, tornando a placa versátil para aplicações IoT e conectadas.

A foto seguinte mostra o verso da placa com os componentes individuais identificados:

Back of MaTouch AI ESP32S3 2.8" TFT ST7789V
Verso do MaTouch AI ESP32S3 2.8″ TFT ST7789V

Ecrã e Toque

A placa possui um ecrã TFT LCD de 2,8 polegadas controlado pelo controlador ST7789V com resolução de 320 × 240 pixels (QVGA) e suporte para 65 mil cores. Liga-se via interface SPI. Um painel multi-toque capacitivo (GT911) suporta até cinco pontos de toque simultâneos, permitindo interfaces intuitivas para painéis de controlo ou visualização de dados. A imagem abaixo mostra a frente da placa com o ecrã e a câmara:

Front of Back of MaTouch AI ESP32S3 2.8" TFT ST7789V
Frente do MaTouch AI ESP32S3 2.8″ TFT ST7789V

Subsistema de Áudio

A entrada de áudio é feita por microfones MEMS digitais INMP441 duplos, fornecendo captura de som estéreo para aplicações como reconhecimento de voz ou detecção de áudio. Para saída, a placa integra um amplificador I²S MAX98357, fornecendo até 3,2 W numa coluna de 4 Ω, permitindo reprodução direta de áudio ou respostas de voz sem necessidade de DAC ou amplificador externo.

Armazenamento e Expansão

Para armazenamento e registo de dados, a placa inclui um slot para cartão MicroSD que suporta cartões até 32 GB. Headers GPIO estão disponíveis para periféricos adicionais como sensores ou atuadores. A imagem abaixo mostra o pinout das duas portas GPIO (J1 e J3) acessíveis no verso da placa:

Pinout of GPIO ports
Pinout das portas GPIO

Suporte para Câmara

A placa integra uma interface para câmara compatível com a câmara OV3660. Isto permite implementar aplicações de visão computacional como classificação de imagens, deteção de objetos ou captura simples de vídeo quando combinado com as capacidades de aceleração AI do ESP32-S3.

Gestão de Energia

A placa pode ser alimentada via interface USB Tipo-C (4,0 V–5,25 V) e inclui um circuito de carregamento TP4056 para baterias de iões de lítio ou polímero de lítio. Também possui um conector para bateria, um interruptor físico, e um fuel gauge MAX17048, permitindo ao sistema monitorizar a capacidade e estado de carga da bateria.

Interfaces e Controlo

Para desenvolvimento, a placa oferece tanto uma interface USB para UART (CH340K) como conectividade USB nativa, dando flexibilidade na programação e depuração. Botões físicos de Boot e Reset permitem controlo de baixo nível durante a gravação do firmware ou resolução de problemas.

LED e Relógio

Um LED RGB WS2812 fornece indicação visual de estado e pode ser programado para notificações ou feedback ao utilizador. E um módulo RTC (PCF8563T) assegura a manutenção precisa do tempo.

Resumo das Especificações Técnicas

CaracterísticaEspecificação
ControladorESP32-S3, CPU dual-core Xtensa LX7, até 240 MHz
Memória16 MB Flash, 8 MB PSRAM
Sem fiosWi-Fi 802.11 b/g/n, Bluetooth 5.0 LE
EcrãTFT LCD 2,8″, 320 × 240 (QVGA), controlador ST7789V, interface SPI
Painel TátilCapacitivo, GT911, multi-toque de 5 pontos
Entrada de ÁudioMicrofones MEMS digitais INMP441 duplos
Saída de ÁudioAmplificador I²S MAX98357, 3,2 W @ 4 Ω
ArmazenamentoSlot para cartão MicroSD (até 32 GB)
CâmaraInterface OV3660 suportada
LED RGB1 × LED programável WS2812
RTCRelógio em tempo real PCF8563T
BateriaPorta para bateria com interruptor, carregador TP4056, fuel gauge MAX17048
Interfaces USBUSB para UART (CH340K), USB nativo
BotõesBoot e Reset
Fonte de AlimentaçãoUSB Tipo-C 5 V (4,0–5,25 V)
Expansão2x Portas GPIO

Pode encontrar os esquemas da placa no seguinte link:

Instalação do Core ESP32

Se este for o seu primeiro projeto com uma placa da série ESP32, precisará instalar primeiro o core ESP32. Se as placas ESP32 já estiverem instaladas no seu Arduino IDE, pode saltar esta secção.

Comece por abrir o diálogo de Preferências selecionando “Preferences…” no menu “File”. Isto abrirá o diálogo de Preferências mostrado abaixo.

Na aba Settings encontrará uma caixa de edição na parte inferior do diálogo rotulada “Additional boards manager URLs“:

Additional boards manager URLs in Preferences
URLs adicionais do gestor de placas nas Preferências

Neste campo copie a seguinte URL:

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

Isto permitirá ao Arduino IDE saber onde encontrar as bibliotecas do core ESP32. A seguir, vamos instalar as placas ESP32 usando o Gestor de Placas.

Abra o Gestor de Placas via “Tools -> Boards -> Board Manager”. O Gestor de Placas aparecerá na barra lateral esquerda. Digite “ESP32” no campo de pesquisa no topo e verá dois tipos de placas ESP32; as “Arduino ESP32 Boards” e as placas “esp32 da Espressif”. Queremos as “esp32 libraries da Espressif”. Clique no botão INSTALL e aguarde até o download e instalação terminarem.

Install ESP32 Core libraries
Instalar bibliotecas Core ESP32

Seleção da Placa

Finalmente, precisamos selecionar uma placa ESP32. No caso da placa MaTouch AI ESP32S3, escolhemos o módulo genérico “ESP32S3 Dev Module”. Para isso, clique no menu suspenso e depois em “Select other board and port…”:

Drop-down Menu for Board Selection
Menu suspenso para seleção da placa

Isto abrirá um diálogo onde pode digitar “esp32s3 dev” na barra de pesquisa. Verá a placa “ESP32S3 Dev Module” em Boards. Clique nela, selecione a porta COM para ativá-la e depois clique em OK:

Board Selection Dialog "ESP32S3 Dev Module" board
Diálogo de seleção da placa “ESP32S3 Dev Module”

Note que precisa de ligar a placa ao computador via cabo USB antes de poder selecionar uma porta COM. A placa tem duas portas USB, uma nativa e outra para TTL/UART. Para comunicação serial com a placa deve usar a porta rotulada “USB TTL”, que é a mais próxima do canto:

USB TTL Port for Serial Communication
Porta USB TTL para comunicação serial

Configurações da Ferramenta

Nas próximas secções encontrará exemplos de código para os vários componentes de hardware da placa. Alguns deles requerem memória considerável e precisará das seguintes configurações para funcionar. Pode encontrar essas configurações no menu Tools:

Settings for  MaTouch AI ESP32S3 2.8" TFT ST7789V
Configurações para MaTouch AI ESP32S3 2.8″ TFT ST7789V

Mais importante, o Flash Size está definido para 16MB(128MB), o Partition Scheme para 16M Flash (3MB APP/9.9MB FATFS), PSRAM está definido para OPI PSRAM. As outras configurações devem estar nos valores padrão.

Exemplo de Código: Interface Serial

Começamos por testar a comunicação Serial. Abra o seu Arduino IDE, insira o seguinte código e carregue-o na MaTouch AI ESP32S3.

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

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

Depois abra o Monitor Serial e deverá ver o texto “Makerguides” impresso a cada dois segundos. Se não, certifique-se de que o cabo USB está ligado à porta correta da placa.

Exemplo de Código: LED RGB

A seguir testamos o LED RGB integrado. Vai precisar de instalar a Adafruit_NeoPixel biblioteca para usar este exemplo. Ele simplesmente muda a cor do LED RGB de vermelho, para verde, para azul a cada 200 milissegundos:

#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);
}

Se precisar de mais informações sobre o LED RGB WS2812 e como usá-lo, consulte os LED Ring Clock with WS2812 e os Use WS2812B LED Strip with Arduino tutoriais.

Exemplo de Código: GPIO

A placa tem quatro pinos GPIO (IO4, IO5, IO6, IO7). Para testar o GPIO, ligamos um LED com um resistor de 220 Ohm ao GND e a um dos pinos GPIO. Neste exemplo uso o IO4:

Connecting LED to GND and IO4
Ligação do LED ao GND e IO4

Agora podemos usar o programa habitual de piscar para ligar e desligar o LED no 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);                      
}

Exemplo de Código: Monitor de Carga da Bateria

A placa MaTouch AI ESP32-S3 vem com um conector para bateria LiPo externa e um circuito de carregamento baseado no TP4056:

Battery charging circuit
Circuito de carregamento da bateria (source)

Além disso, há um MAX17048 fuel gauge que permite monitorizar a carga da bateria. Está ligado via interface I2C aos pinos SDA=39 e SCL=38:

MAX17048 fuel gauge
Fuel gauge MAX17048 (source)

O exemplo de código seguinte mostra como usar o fuel gauge integrado para medir a voltagem e a capacidade restante da bateria. Note que precisará de instalar a 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 imprime a voltagem atual e a capacidade restante (em percentagem) da bateria no Monitor Serial.

Exemplo de Código: Ecrã e Painel Tátil

O próximo exemplo de código mostra como usar o ecrã e o painel tátil. Imprime o texto “Makerguides” no centro do ecrã e, se tocar no ecrã, um pequeno círculo vermelho é desenhado no ponto de toque. Veja o exemplo abaixo:

Antes de executar o código seguinte, precisará de instalar a Adafruit-GFX-Library, a Adafruit-ST7735-Library e a BitBank Capacitive Touch Sensor Library (bb_captouch). Todas podem ser instaladas via o Library Manager do Arduino IDE.

Dê uma olhada rápida no código primeiro e depois discutiremos alguns detalhes.

#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);
  }
}

Bibliotecas

Primeiro incluímos as bibliotecas gráficas e de ecrã da Adafruit, que fornecem funções de desenho para o controlador ST7789, e o cabeçalho bb_captouch.h, que dá acesso ao painel tátil capacitivo.

Constantes

Em seguida definimos os pinos usados para ligar o ecrã TFT e o controlador tátil. São atribuídas constantes para o pino da luz de fundo, os pinos de controlo do ecrã e os pinos I²C usados pelo painel tátil. A largura e altura do ecrã também são definidas para simplificar cálculos posteriores.

Objetos

Depois é criado um objeto Adafruit_ST7789 chamado tft, que representa a ligação ao ecrã TFT. Também é criado um objeto BBCapTouch chamado touch, que será usado para ler eventos de toque do sensor capacitivo.

Configuração

Na função setup, primeiro o pino da luz de fundo do ecrã é configurado como saída e ligado. O ecrã TFT é então inicializado com resolução de 240 por 320 pixels. A rotação é definida para zero, significando que o ecrã é usado em orientação vertical. O ecrã é limpo para preto e a cor do texto é definida para amarelo. O programa define o tamanho da fonte para dois, move o cursor para a posição (50, 150) e imprime o texto “Makerguides” no ecrã. Após configurar o ecrã, o controlador tátil capacitivo é inicializado com os pinos I²C corretos e o pino de reset.

Loop

Na função loop, verificamos continuamente se um novo evento de toque foi detectado. Se houver dados de toque disponíveis, a primeira amostra é lida para as variáveis x e y. Estas coordenadas são então transformadas para corresponder ao sistema de coordenadas do ecrã, subtraindo-as da largura e altura do ecrã. Finalmente, desenhamos um círculo vermelho com raio de cinco pixels na localização do toque.

Exemplo de Código: Relógio em Tempo Real

A placa MaTouch AI ESP32-S3 contém um PCF8563T para fornecer um Relógio em Tempo Real (RTC). Veja os esquemas abaixo:

Real Time Clock (RTC) circuit
Circuito do Relógio em Tempo Real (RTC) (source)

Neste exemplo, usaremos o RTC para manter a hora e mostrar a hora atual no ecrã TFT. Ficará assim:

Vai precisar de instalar a RTCLib para que o código seguinte funcione. Dê uma olhada rápida no código primeiro e depois discutiremos alguns detalhes:

#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);
}

Bibliotecas

Começamos por incluir as bibliotecas gráficas e de ecrã da Adafruit para desenhar no ecrã, a biblioteca RTClib para aceder ao relógio em tempo real, e a biblioteca Wire para comunicação I²C.

Constantes

Em seguida, definem-se os pinos do ecrã TFT, incluindo chip select, data/command e pinos SPI. A largura e altura do ecrã também são definidas. Os pinos do RTC são listados, com RTC_SDA e RTC_SCL atribuídos às linhas de dados e relógio I²C, e RTC_INT definido mas não usado neste programa.

Objetos

Depois criamos um objeto Adafruit_ST7789 chamado tft para representar o ecrã. O objeto RTC_PCF8563 chamado rtc_pcf é criado para comunicar com o relógio em tempo real.

rtc_pcf_init

A função rtc_pcf_init() tenta inicializar o RTC. Se o dispositivo não for encontrado no barramento I²C, o programa imprime uma mensagem de erro e para. Se o RTC estiver presente, a sua hora é definida para a data e hora de compilação do programa usando rtc_pcf.adjust(). Isto garante que o relógio começa com um ponto de referência conhecido.

Configuração

Na função setup, a comunicação serial é iniciada para depuração. O pino da luz de fundo do TFT é ativado, e o ecrã é inicializado para 240 por 320 pixels. A rotação é definida para zero, o ecrã é limpo para preto, e a cor do texto é definida para amarelo sobre fundo preto. O tamanho do texto é aumentado para facilitar a leitura. A interface I²C é inicializada com os pinos SDA e SCL definidos, e o RTC é iniciado com rtc_pcf_init().

Loop

A função loop é executada repetidamente e obtém a hora atual do RTC com rtc_pcf.now(). A hora é formatada como uma string no formato “HH:MM:SS” usando sprintf, e armazenada num buffer. O cursor é posicionado nas coordenadas (20, 130), perto do centro do ecrã, e a string da hora é desenhada no ecrã a amarelo. O loop espera meio segundo antes de repetir, para que o ecrã atualize duas vezes por segundo.

Em resumo, este código inicializa um ecrã TFT e um relógio em tempo real PCF8563, define o relógio para a hora de compilação, e depois exibe continuamente a hora atual em horas, minutos e segundos no centro do ecrã.

Exemplo de Código: Reproduzir Ficheiro de Áudio

A placa MaTouch AI ESP32-S3 tem um amplificador Classe D MAX98357A integrado para ligar uma pequena coluna com 3,2W a 4Ω.

Amplifier circuit
Circuito do amplificador (source)

No exemplo de código seguinte, reproduzimos um ficheiro WAV armazenado no cartão 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() {}

Bibliotecas

No topo do código, as bibliotecas necessárias são incluídas. O driver I²S é usado para comunicar com um chip de áudio digital externo, as bibliotecas SD e SPI fornecem acesso ao cartão SD, e a biblioteca FS define a interface do sistema de ficheiros.

Constantes

São então definidos os números dos pinos para o barramento SPI que liga ao cartão SD, e para os sinais I²S que ligam ao hardware de saída de áudio. A taxa de amostragem é definida para 16.000 amostras por segundo, que define a velocidade de reprodução do áudio. É declarado um handle para o canal de transmissão I²S para enviar dados de áudio para a coluna.

SpeakerInit

A função SpeakerInit() configura e ativa a saída I²S. Primeiro, cria uma configuração padrão para o canal I²S 1 a operar em modo mestre. Depois inicializa um novo canal, guardando o handle de transmissão em tx_chan.

Em seguida, define uma configuração I²S padrão: o relógio é definido para a taxa de amostragem de 16 kHz, a configuração do slot especifica áudio mono de 16 bits no formato Philips I²S, e a configuração GPIO atribui os pinos corretos para bit clock, word select e saída de dados. A máscara do slot é restrita ao canal esquerdo apenas, pois o áudio é mono. Finalmente, o canal é inicializado em modo padrão e ativado, preparando o hardware para reprodução de áudio.

playWav

A função playWav(const char *filename) abre um ficheiro WAV do cartão SD. Verifica se o ficheiro existe e tem mais de 44 bytes, porque os primeiros 44 bytes de um ficheiro WAV são o cabeçalho e não contêm dados de som reais.

Se o ficheiro for válido, o programa ignora o cabeçalho com file.seek(44). Depois lê repetidamente blocos de 1024 bytes para um buffer. Antes de escrever cada bloco na interface I²S, as amostras são opcionalmente amplificadas: cada amostra assinada de 16 bits é deslocada para a esquerda um bit, dobrando efetivamente a sua amplitude.

Os dados de áudio processados são então escritos no canal de transmissão I²S usando i2s_channel_write(). Quando o fim do ficheiro é alcançado, o ficheiro é fechado, o canal I²S é desativado, e uma mensagem é impressa para indicar que a reprodução terminou.

Configuração

Na função setup(), a porta serial é inicializada para depuração. O barramento SPI é iniciado nos pinos especificados, e o cartão SD é montado. Se a montagem falhar, é impresso um erro e o programa para. Caso contrário, o altifalante I²S é inicializado, um pequeno atraso garante estabilidade, e a função playWav("/LightMusic.wav") é chamada para iniciar a reprodução do ficheiro armazenado no cartão SD.

Loop

A função loop() fica vazia, pois toda a ação ocorre uma vez em setup().

Em resumo, este código configura o ESP32-S3 para reproduzir um ficheiro WAV armazenado no cartão SD através de uma coluna ligada via I²S. Configura o cartão SD e a saída I²S, ignora o cabeçalho do ficheiro, transmite os dados de áudio brutos para o hardware I²S, e produz som audível da faixa de áudio armazenada.

Exemplo de Código: Gravar Áudio

A placa MaTouch AI ESP32-S3 vem com dois INMP441 microfones com saída digital. Veja os esquemas abaixo:

Microphone circuit
Circuito do microfone (source)

No exemplo de código seguinte, gravamos 10 segundos de áudio do microfone e armazenamos como ficheiro WAV no cartão 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() {}

Bibliotecas

No início, as bibliotecas necessárias são incluídas. O driver I²S fornece acesso à interface de hardware de áudio, as bibliotecas FS e SD permitem criar e escrever ficheiros no cartão SD, e a biblioteca SPI trata da comunicação com o cartão SD.

Constantes

São então definidas constantes para os pinos SPI usados pelo cartão SD e para os pinos I²S ligados ao microfone digital. A taxa de amostragem é definida para 16 kHz, a resolução para 16 bits por amostra, e a duração da gravação para dez segundos. É declarado um handle para o canal de receção I²S para capturar dados do microfone.

writeWavHeader

A função writeWavHeader() escreve um cabeçalho WAV correto de 44 bytes no início do ficheiro. Constrói o contentor RIFF, especifica o formato de áudio como PCM, define um canal para áudio mono, e define a taxa de amostragem, profundidade de bits e taxa de bytes. Depois escreve o cabeçalho da secção “data” seguido do tamanho total das amostras de áudio. Isto garante que o ficheiro resultante pode ser reproduzido por qualquer software de áudio padrão.

MicInit

A função MicInit() configura a interface I²S para receber áudio do microfone. Cria um novo canal I²S em modo mestre com configurações padrão. A configuração padrão especifica um relógio para a taxa de amostragem de 16 kHz, formato de slot Philips I²S com amostras de 16 bits, e modo de canal mono. A configuração GPIO atribui os pinos corretos para bit clock, word select e entrada de dados, deixando o relógio mestre e saída de dados sem uso. Finalmente, o canal é inicializado em modo padrão e ativado, pronto para capturar amostras de áudio.

recordWav

A função recordWav(const char *filename) realiza a gravação propriamente dita. Abre um ficheiro no cartão SD para escrita. Se o ficheiro não puder ser criado, reporta um erro e sai. Para deixar espaço para o cabeçalho WAV, escreve 44 bytes zero no início do ficheiro. Depois aloca um buffer de 1024 bytes. Um ciclo corre durante o período definido por RECORD_TIME. Durante este tempo, os dados de áudio são lidos do canal I²S para o buffer e escritos diretamente no ficheiro. É mantido um total acumulado do número de bytes de áudio gravados. Após o período de gravação, a função volta ao início do ficheiro e escreve o cabeçalho WAV real com os tamanhos corretos. O ficheiro é então fechado, e uma mensagem é impressa com o tamanho final do ficheiro.

Configuração

Na função setup(), a porta serial é iniciada para depuração. O barramento SPI é inicializado nos pinos indicados, e o cartão SD é montado. Se a montagem falhar, o programa reporta um erro e não continua. Se for bem-sucedido, o microfone é inicializado, um pequeno atraso dá tempo ao hardware para estabilizar, e uma mensagem anuncia o início da gravação. A função recordWav("/mic_record.wav") é então chamada para capturar e guardar dez segundos de áudio no cartão SD.

Loop

A função loop() está vazia, pois a tarefa de gravação é realizada apenas uma vez na inicialização.

Em resumo, este programa inicializa o cartão SD e o microfone I²S, grava dez segundos de áudio a 16 kHz num buffer, escreve os dados no cartão SD, adiciona um cabeçalho WAV correto, e produz um ficheiro de áudio padrão que pode ser reproduzido em qualquer dispositivo.

Exemplo de Código: Câmara e Ecrã

Neste último exemplo, capturamos vídeo ao vivo do módulo de câmara ligado ao ESP32-S3 e mostramos diretamente no ecrã TFT.

Para este exemplo, precisará de instalar a Arduino_GFX biblioteca por moononournation. Abra o LIBRARY MANAGER, escreva “GFX Library for Arduino” na barra de pesquisa, encontre a “GFX Library for Arduino” por Moon, e clique no botão INSTALL:

Installation of GFX Library for Arduino
Instalação da GFX Library for Arduino

Abaixo está o código completo para transmitir vídeo da câmara para o ecrã. Dê uma olhada rápida primeiro, depois discutiremos os detalhes:

#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);
}

Bibliotecas

No início, incluímos as bibliotecas para o ecrã (Arduino_GFX_Library.h) e para a câmara (esp_camera.h).

Constantes

A primeira secção define as ligações de hardware. Os pinos da câmara são listados para a interface paralela: oito pinos de dados (Y2–Y9), pinos de relógio e sincronização (XCLK, PCLK, HREF, e VSYNC), e os pinos I²C (SIOD e SIOC) que configuram o sensor da câmara. Os pinos do ecrã TFT também são definidos para chip select, data/command, linhas de comunicação SPI, e luz de fundo.

Objetos

São então criados dois objetos para o ecrã. O primeiro, bus, representa a ligação SPI ao ecrã, e o segundo, gfx, representa o controlador ST7789 propriamente dito. Estes objetos vêm da biblioteca Arduino GFX, que fornece funções gráficas eficientes para muitos ecrãs.

camera_init_s3

A função camera_init_s3() configura e inicializa a câmara. Uma estrutura camera_config_t é preenchida com as atribuições corretas dos pinos e parâmetros. O relógio externo para a câmara é definido para 20 MHz. O tamanho do frame é definido para FRAMESIZE_QVGA, que corresponde a 320×240 pixels, correspondendo à resolução do TFT.

O formato de pixel é definido para PIXFORMAT_RGB565, que produz um formato de cor de 16 bits que o TFT pode usar diretamente sem conversão. O modo de captura é definido para capturar frames sempre que o buffer está vazio, os buffers de frame são armazenados em PSRAM, e são usados dois buffers de frame para melhorar o desempenho.

Depois de preencher a configuração, esp_camera_init() inicia o driver da câmara. Se a inicialização falhar, é impresso um código de erro. Se o sensor for um OV3660, é aplicada alguma configuração extra para ajustar a orientação e o brilho.

Configuração

Na função setup(), a porta serial é iniciada para depuração. A luz de fundo do TFT é ativada, e o ecrã é inicializado. O ecrã é limpo para preto antes da câmara ser iniciada chamando camera_init_s3().

Loop

A função loop() captura e exibe frames repetidamente. Cada chamada a esp_camera_fb_get() obtém um ponteiro para o buffer do frame mais recente. Se a captura falhar, é impressa uma mensagem e o programa espera brevemente antes de tentar novamente. Se for bem-sucedido, o buffer do frame é desenhado no TFT usando gfx->draw16bitBeRGBBitmap(). Esta função transfere diretamente os dados de pixel RGB565 do buffer da câmara para o ecrã na posição (0,0). Depois de o frame ser exibido, esp_camera_fb_return(fb) é chamado para libertar o buffer de volta para o driver para que possa ser reutilizado.

Em resumo, este programa inicializa a câmara ESP32-S3 e o ecrã TFT ST7789, captura frames em formato RGB565, e transmite-os diretamente para o ecrã. O resultado é uma pré-visualização ao vivo da câmara exibida em tempo real no TFT.

Conclusões

Este tutorial forneceu exemplos de código para começar a usar o MaTouch AI ESP32-S3 com ecrã TFT ST7789V de 2,8 polegadas.

Para mais exemplos, veja Makerfabs’s Github repo for the Matouch display e o Wiki Page. Note, no entanto, que a maioria dos exemplos lá usa a biblioteca lvgl, que evitei aqui para manter a complexidade baixa. Além disso, alguns exemplos não funcionam com o core 3.x atual.

Se tiver alguma dúvida, sinta-se à vontade para deixá-la na secção de comentários.

Boas experiências a criar 😉