O CrowPanel 2.1inch-HMI ESP32 Rotary Display da Elecrow é um módulo grande e redondo que integra um microcontrolador ESP32-S3, um ecrã tátil circular IPS de 2,1 polegadas com resolução 480×480, e um encoder rotativo com botão de pressão numa única unidade.
Neste tutorial, vamos passar pelos passos essenciais para começar a usar este ecrã; desde a configuração inicial até testar as suas capacidades de visualização e entrada. Vai aprender a ler dados do encoder rotativo para uma entrada de utilizador suave e a capturar eventos de toque no ecrã circular.
Peças Necessárias
Só vai precisar do CrowPanel 2.1inch-HMI ESP32 Rotary Display neste projeto. O módulo de ecrã vem com um cabo USB e conector GPIO, por isso não é necessário hardware adicional.

CrowPanel 2.1inch-HMI ESP32 Rotary Display
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 CrowPanel 2.1inch-HMI ESP32 Rotary Display
O CrowPanel 2.1inch-HMI ESP32 Rotary Display é um módulo de ecrã que combina um ecrã IPS redondo de alta resolução, um ecrã tátil capacitivo e um encoder rotativo com funcionalidade de botão de pressão.
O módulo é construído em torno do sistema em chip ESP32‑S3R8 que utiliza um processador dual-core Xtensa LX7 de 32 bits capaz de velocidades de relógio até 240 MHz. Está equipado com 8 Mbit PSRAM e 16 Mbyte de armazenamento flash onboard.
A parte do ecrã é um painel IPS RGB circular de 2,1 polegadas (ST7701 controlador) com resolução de 480 × 480 pixels, com uma camada tátil capacitiva que suporta operação tátil em ecrã completo e uma luz de fundo.
Em termos de operação física, o hardware incorpora um mecanismo de “botão” de encoder rotativo. O botão é capaz de rotação no sentido horário e anti-horário, bem como uma pressão para dentro (funcionando como um interruptor) para entrada do utilizador.
Para conectividade e expansão, o módulo suporta interfaces sem fios e com fios. No lado sem fios, apresenta WiFi IEEE 802.11 a/b/g/n (2.4 GHz) e Bluetooth Low Energy (BLE) / Bluetooth 5.0.
No lado com fios, o módulo expõe uma tomada de carregamento/interface de 5 V que funciona como fonte de alimentação e porta de programação. Além disso, a placa fornece três interfaces de expansão: uma interface UART, uma interface I2C e um conector FPC (12 pinos) para conectividade adicional.
Definições dos Pinos
A tabela seguinte lista os pinos necessários para controlar todos os componentes do módulo de ecrã:
| Grupo de Função | Sinal / Propósito | Número do Pino | Notas |
|---|---|---|---|
| Interface I²C | SDA | 38 | Linha principal de dados I²C |
| SCL | 39 | Linha principal de relógio I²C | |
| Encoder Rotativo | Encoder A | 42 | Canal quadratura A |
| Encoder B | 4 | Canal quadratura B | |
| Luz de Fundo do Ecrã | Controlo da Luz de Fundo | 6 | Pino com capacidade PWM usado para brilho da luz de fundo do LCD |
| Controlo OLED | RESET | –1 | Sem pino de reset usado (reset por software) |
| Interface do Ecrã RGB | CS | 16 | Linha de seleção do chip |
| SCK | 2 | Relógio serial do ecrã | |
| SDA | 1 | Dados seriais do ecrã | |
| DE | 40 | Ativação de dados | |
| VSYNC | 7 | Sincronização vertical | |
| HSYNC | 15 | Sincronização horizontal | |
| PCLK | 41 | Relógio de pixel | |
| Pinos de Dados de Cor RGB (formato 5-6-5) | R0 | 46 | Bit 0 do vermelho |
| R1 | 3 | Bit 1 do vermelho | |
| R2 | 8 | Bit 2 do vermelho | |
| R3 | 18 | Bit 3 do vermelho | |
| R4 | 17 | Bit 4 do vermelho | |
| G0 | 14 | Bit 0 do verde | |
| G1 | 13 | Bit 1 do verde | |
| G2 | 12 | Bit 2 do verde | |
| G3 | 11 | Bit 3 do verde | |
| G4 | 10 | Bit 4 do verde | |
| G5 | 9 | Bit 5 do verde | |
| B0 | 5 | Bit 0 do azul | |
| B1 | 45 | Bit 1 do azul | |
| B2 | 48 | Bit 2 do azul | |
| B3 | 47 | Bit 3 do azul | |
| B4 | 21 | Bit 4 do azul | |
| Expansor de I/O PCF8574 | Endereço I²C | 0x21 | Ligado aos pinos 38 (SDA) e 39 (SCL) |
Especificações Técnicas
A tabela seguinte resume as especificações técnicas do módulo CrowPanel 2.1inch-HMI ESP32 Rotary Display:
| Categoria de Especificação | Detalhes |
|---|---|
| Processador principal | SoC: Xtensa LX7 dual-core (32-bit) no ESP32‑S3R8 (até 240 MHz) |
| Memória & armazenamento | RAM do sistema: 512 kB SRAM; PSRAM: 8 MB; Flash: 16 MB |
| Painel de ecrã | Tamanho: 2,1 polegadas; Tipo: IPS; Resolução: 480 × 480 pixels; Toque: camada tátil capacitiva em ecrã completo |
| Mecanismos de entrada do utilizador | Botão rotativo (rotação horário/anti-horário + pressão total); Entrada tátil capacitiva na superfície do ecrã |
| Conectividade sem fios | WiFi: IEEE 802.11 a/b/g/n (2.4 GHz); Bluetooth: BLE / Bluetooth 5.0 |
| Portas de interface / expansão | Entrada 5 V (carregamento e programação); Interface UART: 1× UART0 + 1× UART1 via ZX-MX 1.25-4P; Interface I²C; Conector FPC de 12 pinos (alimentação/programação e GPIO) |
| Botões & indicadores | Botão RESET onboard; Botão BOOT; Interruptor de pressão do botão rotativo (botão de confirmação); LED indicador de energia; Luz de fundo do LCD |
| Energia & voltagem | Entrada do módulo: 5 V / 1 A; Nível lógico do chip principal: 3.3 V; Operação do módulo a 5 V nominal |
| Mecânico / físico | Dimensões: 79 × 79 × 30 mm; Peso líquido: 80 g; Carcaça: liga de alumínio + plástico + acrílico |
| Faixa de temperatura | Operação: –20 °C a +65 °C; Armazenamento: –40 °C a +80 °C |
| Suporte de software / ambiente de desenvolvimento | Compatível com Arduino IDE, ESP-IDF, Lua RTOS, MicroPython, PlatformIO; Biblioteca gráfica UI: suporte LVGL |
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. No entanto, para o CrowPanel 2.1inch Display, deverá instalar uma versão específica (2.0.14) do core ESP32.
Comece por abrir o diálogo de Preferências no Arduino IDE 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“:

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 by Espressif”. Queremos as “esp32 libraries by Espressif”.
No menu dropdown selecione a Versão “2.0.14” e depois pressione o botão “INSTALL”. A captura de ecrã abaixo mostra o que deve ver no GESTOR DE PLACAS após uma instalação bem-sucedida do Core ESP32 2.0.14:

Note que pode instalar versões até 2.0.17 mas não 3.x. As bibliotecas que iremos instalar na secção seguinte não funcionarão com o core ESP32 3.x.
Seleção da Placa
Finalmente, precisamos selecionar uma placa ESP32. No caso do CrowPanel 2.1inch-HMI ESP32 Rotary Display, escolhemos a genérica “ESP32S3 Dev Module”. Para isso, clique no menu dropdown e depois em “Select other board and port…”:

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

Note que precisa de ligar a placa via cabo USB ao seu computador antes de poder selecionar uma porta COM. Ligue o cabo USB que acompanha o CrowPanel Display Module diretamente à porta rotulada “USB-5V-IN” como mostrado abaixo:

Configurações da Ferramenta
Abaixo estão as configurações que deve usar com o CrowPanel Display Module. Encontra-as no menu Tools do seu Arduino IDE.

O mais importante para os exemplos de código nas secções seguintes é definir USB CDC on Boot para “Enabled”. Isto permite enviar ou receber dados via porta serial, necessário para depuração. Também precisará de definir corretamente “OPI PSRAM”, “Huge APP” e “Flash Size” para os exemplos de código do ecrã.
Instalação das Bibliotecas
A seguir, precisamos instalar bibliotecas específicas e versões para fazer o código do CrowPanel 2.1inch-HMI ESP32 Rotary Display funcionar.
Vá ao Elecrow github repo para o CrowPanel 2.1inch-HMI Display. Clique no botão verde “<> Code” e depois em “Download ZIP” para descarregar o repositório como um ficheiro ZIP:

Depois, descompacte o ficheiro ZIP para extrair o seu conteúdo. Deve ver os seguintes ficheiros numa pasta descompactada chamada “CrowPanel-2.1inch-HMI-ESP32-Rotary-Display-480-480-IPS-Round-Touch-Knob-Screen-master”:

Ignore os ficheiros relacionados com o ecrã “CrowPanel 1.28inch …”. Só precisamos copiar o conteúdo da pasta “example/libraries” para a pasta “libraries” do Arduino IDE. No Windows, a pasta “libraries” está tipicamente localizada em:
C:\Users\<username>\OneDrive\Documents\Arduino\libraries
Como esta pasta já contém bibliotecas instaladas, recomendo que a renomeie temporariamente, por exemplo para “_libraries”, e crie uma nova pasta chamada “libraries”. Assim evita conflitos com as bibliotecas já instaladas e não as perde. Depois pode reverter facilmente esta alteração. A imagem abaixo mostra como deve ficar a sua pasta “Arduino” com as bibliotecas:

Depois copiamos os ficheiros da pasta “example/libraries” para a nova pasta “libraries” como mostrado abaixo:

Não precisa copiar as bibliotecas riscadas, pois estão relacionadas com a UI LVGL, que não vamos usar. Nas próximas secções, mostrarei alguns exemplos de código para experimentar o ecrã.
Exemplo de Código: Interface Serial
Começamos por testar a comunicação Serial. Abra o seu Arduino IDE, insira o código seguinte e carregue-o no CrowPanel Display Module.
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, verifique se as configurações de Tools e a taxa de Baud (115200) estão corretas. Especificamente, precisa que “USB CDC on Boot” esteja definido como “Enabled”.
Exemplo de Código: Encoder
No próximo exemplo de código vai aprender a usar o encoder e o interruptor do encoder. Usaremos uma abordagem baseada em interrupções para ler um encoder rotativo quadratura e o PCF8574 para ler o estado do botão do encoder.
A rotação do encoder é processada numa ISR rápida com deteção de direção, enquanto o loop principal foca-se em reportar mudanças e ler o estado do botão. Dê uma olhada rápida no código e depois discutimos os detalhes.
#include <Wire.h>
#include <PCF8574.h>
// I2C to PCF8574
#define I2C_SDA_PIN 38
#define I2C_SCL_PIN 39
#define ENCODER_CLK 42
#define ENCODER_DT 4
volatile int counter = 0;
volatile int encState = 0;
volatile int oldState = -1;
volatile bool hasChanged = true;
PCF8574 pcf8574(0x21);
void IRAM_ATTR encoder_irq() {
encState = digitalRead(ENCODER_CLK);
if (encState != oldState) {
counter += (digitalRead(ENCODER_DT) == encState) ? +1 : -1;
oldState = encState;
hasChanged = true;
}
}
void initPins() {
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
pcf8574.pinMode(P5, INPUT_PULLUP); //encoder SW
if (!pcf8574.begin()) {
Serial.println("Can't init pcf8574");
}
}
void initEncoder() {
pinMode(ENCODER_CLK, INPUT_PULLUP);
pinMode(ENCODER_DT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoder_irq, CHANGE);
}
void setup() {
Serial.begin(115200);
initPins();
initEncoder();
}
void loop() {
if (hasChanged) {
hasChanged = false;
Serial.printf("COUNTER: %d\n", counter);
}
int button = pcf8574.digitalRead(P5, true);
if (!button) {
Serial.printf("BTN pressed\n");
delay(500);
}
delay(1);
}
Importações
O sketch começa por incluir duas bibliotecas que tratam da comunicação I2C e do expansor de I/O PCF8574. Estes headers devem ser incluídos antes de poder usar Wire ou PCF8574 objetos.
#include <Wire.h> #include <PCF8574.h>
Wire.h fornece a interface I2C (TWI) padrão do Arduino. PCF8574.h encapsula o acesso I2C de baixo nível ao chip PCF8574 para que possa tratar os seus pinos quase como pinos digitais normais.
Constantes
De seguida, o código define os pinos usados para I2C e para o encoder rotativo:
#define I2C_SDA_PIN 38 #define I2C_SCL_PIN 39 #define ENCODER_CLK 42 #define ENCODER_DT 4
I2C_SDA_PIN e I2C_SCL_PIN selecionam quais os pinos ESP32 usados como SDA e SCL para o barramento I2C de hardware. No ESP32 pode-se encaminhar o I2C para vários pinos, por isso é necessário especificá-los aqui.
ENCODER_CLK e ENCODER_DT são as duas saídas quadratura do encoder rotativo. O sinal CLK é tipicamente o canal principal usado para a interrupção, e DT é o segundo canal usado para determinar a direção da rotação.
Estado Global e Variáveis Voláteis
O código usa várias variáveis globais para acompanhar o estado do encoder. Estas variáveis são marcadas volatile porque são modificadas dentro de uma rotina de serviço de interrupção.
volatile int counter = 0; volatile int encState = 0; volatile int oldState = -1; volatile bool hasChanged = true;
counter mantém a posição acumulada do encoder. Cada passo do encoder incrementa ou decrementa este valor.
encState representa o nível lógico atual do pino CLK do encoder visto no manipulador de interrupção. oldState guarda o estado anterior do CLK para que o código possa detetar transições e evitar contar o mesmo nível várias vezes.
hasChanged é usado como uma flag para sinalizar ao loop principal que o contador foi atualizado na interrupção. Marcar estas variáveis como volatile indica ao compilador que podem mudar a qualquer momento e não devem ser armazenadas em registos, o que é crítico quando são modificadas por uma ISR.
Objeto PCF8574
O código cria então uma instância global da classe PCF8574, especificando o endereço I2C do chip.
PCF8574 pcf8574(0x21);
Esta linha indica à biblioteca PCF8574 que o expansor de I/O está acessível no endereço I2C 0x21. Todas as chamadas subsequentes a pcf8574.pinMode ou pcf8574.digitalRead usarão este endereço para comunicar com esse dispositivo específico no barramento I2C.
Rotina de Serviço de Interrupção do Encoder
A parte mais sensível ao tempo do código é a rotina de serviço de interrupção (ISR) que trata das mudanças do encoder. Está colocada em IRAM usando o atributo IRAM_ATTR para que possa executar rápida e fiavelmente no ESP32.
void IRAM_ATTR encoder_irq() {
encState = digitalRead(ENCODER_CLK);
if (encState != oldState) {
counter += (digitalRead(ENCODER_DT) == encState) ? +1 : -1;
oldState = encState;
hasChanged = true;
}
}
IRAM_ATTR garante que a função é armazenada na RAM de instruções em vez de flash, evitando atrasos causados pelo acesso a flash e é recomendado para ISRs no ESP32.
Dentro da ISR, o nível atual do sinal CLK do encoder é lido usando digitalRead(ENCODER_CLK) e armazenado em encState. O código verifica então se este estado é diferente de oldState. Esta condição filtra chamadas redundantes porque a interrupção está configurada para disparar em qualquer mudança (borda ascendente ou descendente) e o manipulador pode executar várias vezes com o mesmo nível lógico.
Se o estado mudou, a direção da rotação é calculada comparando o sinal DT com o sinal CLK. A linha seguinte lê o pino DT e verifica se o seu nível corresponde ao nível atual do CLK:
counter += (digitalRead(ENCODER_DT) == encState) ? +1 : -1;
Para um encoder quadratura, esta relação indica a direção da rotação. Se DT for igual a CLK, o encoder está a mover-se numa direção e counter é incrementado; se forem diferentes, counter é decrementado.
Após atualizar o contador, oldState é atualizado para o novo estado do CLK e hasChanged é definido como true. Esta flag informa o loop principal que há novos dados do encoder para processar ou mostrar. A ISR mantém-se curta e eficiente, o que é boa prática para rotinas de interrupção.
Inicialização dos Pinos e I2C
A função initPins configura o barramento I2C e o pino PCF8574 usado para o botão de pressão do encoder.
void initPins() {
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
pcf8574.pinMode(P5, INPUT_PULLUP); //encoder SW
if (!pcf8574.begin()) {
Serial.println("Can't init pcf8574");
}
}
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN); inicializa o periférico I2C com pinos SDA e SCL personalizados.
pcf8574.pinMode(P5, INPUT_PULLUP); configura o pino P5 no PCF8574 como entrada com resistor pull-up interno. Este pino está ligado ao botão de pressão do encoder (interruptor). A configuração pull-up significa que o pino lê alto quando o botão não está pressionado e baixo quando está pressionado, assumindo que o botão liga o pino ao terra.
pcf8574.begin() inicia a comunicação com o dispositivo PCF8574 no endereço I2C configurado. Se falhar, o código imprime uma mensagem de erro via Serial.println, o que ajuda a diagnosticar problemas de ligação ou configuração de endereço.
Inicialização do Encoder
A função initEncoder configura os dois sinais do encoder e anexa uma interrupção ao pino CLK.
void initEncoder() {
pinMode(ENCODER_CLK, INPUT_PULLUP);
pinMode(ENCODER_DT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoder_irq, CHANGE);
}
pinMode(ENCODER_CLK, INPUT_PULLUP); e pinMode(ENCODER_DT, INPUT_PULLUP); configuram ambos os canais do encoder como entradas com resistores pull-up internos. Esta é uma configuração típica para encoders rotativos mecânicos, que normalmente ligam os pinos ao terra num padrão de código cinzento à medida que o botão roda.
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoder_irq, CHANGE); anexa o manipulador de interrupção encoder_irq ao pino CLK do encoder. O modo CHANGE significa que a ISR será disparada em ambas as bordas ascendente e descendente do sinal. Isto dá maior resolução e contagem mais precisa, pois o encoder é amostrado em cada transição.
Setup
A função setup é executada uma vez na inicialização e fornece uma sequência de inicialização.
void setup() {
Serial.begin(115200);
initPins();
initEncoder();
}
Serial.begin(115200); inicia a porta serial a 115200 baud, útil para depuração e monitorização. Deve ser chamada antes de qualquer Serial.print ou Serial.printf chamadas.
initPins(); inicializa o barramento I2C e o expansor de I/O PCF8574. Isto garante que o botão do encoder pode ser lido e que o hardware I2C está pronto.
initEncoder(); configura os pinos GPIO usados pelo encoder rotativo e configura a interrupção para que os eventos de rotação sejam capturados assim que o loop principal começar.
Loop
A função loop executa-se repetidamente e trata duas tarefas principais: imprimir o valor do encoder quando muda e ler o estado do botão do encoder.
void loop() {
if (hasChanged) {
hasChanged = false;
Serial.printf("COUNTER: %d\n", counter);
}
int button = pcf8574.digitalRead(P5, true);
if (!button) {
Serial.printf("BTN pressed\n");
delay(500);
}
delay(1);
}
O primeiro bloco verifica se o contador do encoder foi atualizado pela ISR. A flag hasChanged é definida como true dentro da interrupção quando a posição do encoder muda.
if (hasChanged) {
hasChanged = false;
Serial.printf("COUNTER: %d\n", counter);
}
Se hasChanged for verdadeiro, a flag é imediatamente redefinida para falso e o valor atual de counter é impresso no monitor serial usando Serial.printf. Isto evita imprimir na ISR e desacopla a lógica crítica da interrupção da saída serial mais lenta.
De seguida, o código lê o estado do botão de pressão do encoder, que está ligado ao PCF8574.
int button = pcf8574.digitalRead(P5, true);
if (!button) {
Serial.printf("BTN pressed\n");
delay(500);
}
pcf8574.digitalRead(P5, true); lê o nível lógico atual do pino P5. Passar true como segundo argumento normalmente indica à biblioteca para fazer uma leitura I2C imediata em vez de usar um valor em cache, garantindo uma leitura fresca a cada vez.
Como o pino está configurado com pull-up, um botão não pressionado lê alto (não zero), e um botão pressionado puxa a linha para terra e lê baixo (zero). Portanto, a condição if (!button) verifica o estado pressionado. Quando o botão é detectado como pressionado, o código imprime "BTN pressed" e espera 500 milissegundos. Este atraso atua como um debounce simples e evita que a mensagem seja impressa continuamente enquanto o botão está pressionado.
Finalmente, o loop termina com um pequeno atraso.
delay(1);
Este pequeno atraso dá ao scheduler a oportunidade de tratar tarefas em segundo plano e ajuda a evitar um loop muito apertado e intensivo para a CPU. Também garante que os subsistemas I2C e serial tenham algum tempo para respirar.
Saída no Monitor Serial
A captura de ecrã seguinte mostra o que deve ver no Monitor Serial quando roda o anel do encoder ou pressiona o botão do encoder:

Exemplo de Código: Ecrã e Ecrã Tátil
O sketch seguinte mostra como usar o ecrã e o touchpad. Define o fundo do ecrã a vermelho, imprime o texto “Makerguides” no centro e desenha círculos pretos onde quer que o ecrã seja tocado:

Dê uma olhada rápida no código completo abaixo antes de discutirmos os detalhes:
#include <Wire.h>
#include <Arduino_GFX_Library.h>
#include <PCF8574.h>
#include <Adafruit_CST8XX.h>
// I2C to PCF8574
#define I2C_SDA_PIN 38
#define I2C_SCL_PIN 39
#define BKL_PIN 6
#define I2C_TOUCH_ADDR 0x15
PCF8574 pcf8574(0x21);
Adafruit_CST8XX tsPanel = Adafruit_CST8XX();
Arduino_ESP32RGBPanel *bus = new Arduino_ESP32RGBPanel(
16 /* CS */, 2 /* SCK */, 1 /* SDA */,
40 /* DE */, 7 /* VSYNC */, 15 /* HSYNC */, 41 /* PCLK */,
46 /* R0 */, 3 /* R1 */, 8 /* R2 */, 18 /* R3 */, 17 /* R4 */,
14 /* G0 */, 13 /* G1 */, 12 /* G2 */, 11 /* G3 */, 10 /* G4 */, 9 /* G5 */,
5 /* B0 */, 45 /* B1 */, 48 /* B2 */, 47 /* B3 */, 21 /* B4 */
);
Arduino_ST7701_RGBPanel *gfx = new Arduino_ST7701_RGBPanel(
bus,
GFX_NOT_DEFINED, // RST pin (not used, we reset via PCF8574)
0, // rotation
false, // IPS
480, 480, // width, height
st7701_type5_init_operations,
sizeof(st7701_type5_init_operations),
true, // BGR
10, 4, 20, // hsync front porch, pulse width, back porch
10, 4, 20 // vsync front porch, pulse width, back porch
);
void initBacklight() {
pinMode(BKL_PIN, OUTPUT);
analogWrite(BKL_PIN, 200);
}
void initPins() {
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
pcf8574.pinMode(P0, OUTPUT); //tp RST
pcf8574.pinMode(P2, OUTPUT); //tp INT
pcf8574.pinMode(P3, OUTPUT); //lcd power
pcf8574.pinMode(P4, OUTPUT); //lcd reset
if (!pcf8574.begin()) {
Serial.println("Can't init pcf8574");
}
// LCD
pcf8574.digitalWrite(P3, HIGH);
delay(100);
pcf8574.digitalWrite(P4, HIGH);
delay(100);
pcf8574.digitalWrite(P4, LOW);
delay(120);
pcf8574.digitalWrite(P4, HIGH);
delay(120);
// Touchpad
pcf8574.digitalWrite(P0, HIGH);
delay(100);
pcf8574.digitalWrite(P0, LOW);
delay(120);
pcf8574.digitalWrite(P0, HIGH);
delay(120);
pcf8574.digitalWrite(P2, HIGH);
delay(120);
if (!tsPanel.begin(&Wire, I2C_TOUCH_ADDR)) {
Serial.println("No touchscreen found");
}
}
void initLCD() {
gfx->begin();
gfx->fillScreen(RED);
gfx->setTextSize(5);
gfx->setTextColor(WHITE);
}
void drawOnLCD() {
gfx->setCursor(90, 210);
gfx->print("Makerguides");
}
void setup() {
Serial.begin(115200);
initPins();
initBacklight();
initLCD();
drawOnLCD();
}
void loop() {
if (tsPanel.touched()) {
CST_TS_Point p = tsPanel.getPoint(0);
Serial.printf("TOUCH: %d, %d\n", p.x, p.y);
gfx->fillCircle(p.x, p.y, 10, BLACK);
delay(10);
}
}
Importações
Este sketch começa por incluir as bibliotecas necessárias para I2C, gráficos, expansor de I/O e controlador de toque.
#include <Wire.h> #include <Arduino_GFX_Library.h> #include <PCF8574.h> #include <Adafruit_CST8XX.h>
Wire.h é a mesma biblioteca I2C que usou antes com o PCF8574. Arduino_GFX_Library.h fornece a abstração gráfica para conduzir o painel RGB e o driver de ecrã ST7701. PCF8574.h novamente trata do expansor de I/O no barramento I2C. Adafruit_CST8XX.h é o driver para o controlador de toque capacitivo CST8xx, que está ligado via I2C e será usado para ler as coordenadas de toque do ecrã redondo.
Constantes e Configuração I2C
O código define as atribuições de pinos para o barramento I2C, controlo da luz de fundo e o endereço I2C do controlador de toque. Isto segue o mesmo padrão do exemplo anterior onde os pinos SDA e SCL foram configurados explicitamente.
#define I2C_SDA_PIN 38 #define I2C_SCL_PIN 39 #define BKL_PIN 6 #define I2C_TOUCH_ADDR 0x15
I2C_SDA_PIN e I2C_SCL_PIN selecionam os pinos ESP32 usados para o barramento I2C partilhado que conecta tanto o PCF8574 como o controlador de toque CST8xx. BKL_PIN é um pino com capacidade PWM que controla a luz de fundo do LCD. I2C_TOUCH_ADDR especifica o endereço I2C de 7 bits do controlador de toque para que o driver possa comunicar com ele.
Objetos Globais: PCF8574, Controlador de Toque, Barramento RGB e Painel
Os objetos globais configuram o acesso ao expansor de I/O, ao controlador de toque e ao ecrã.
pcf8574(0x21) é o mesmo do exemplo anterior: cria uma instância ligada ao endereço I2C 0x21. Este chip controla várias linhas de controlo como energia do LCD, reset do LCD e reset e interrupção do toque.
tsPanel é uma instância do driver Adafruit CST8xx, criada com o construtor padrão e depois inicializada com begin() usando a interface Wire partilhada e o endereço do toque.
PCF8574 pcf8574(0x21); Adafruit_CST8XX tsPanel = Adafruit_CST8XX();
Painel RGB
O próximo objeto configura o barramento de dados RGB entre o ESP32-S3 e o driver de ecrã ST7701. Lista todos os pinos de temporização e cor usados para a interface RGB paralela.
Arduino_ESP32RGBPanel *bus = new Arduino_ESP32RGBPanel( 16 /* CS */, 2 /* SCK */, 1 /* SDA */, 40 /* DE */, 7 /* VSYNC */, 15 /* HSYNC */, 41 /* PCLK */, 46 /* R0 */, 3 /* R1 */, 8 /* R2 */, 18 /* R3 */, 17 /* R4 */, 14 /* G0 */, 13 /* G1 */, 12 /* G2 */, 11 /* G3 */, 10 /* G4 */, 9 /* G5 */, 5 /* B0 */, 45 /* B1 */, 48 /* B2 */, 47 /* B3 */, 21 /* B4 */ );
Este objeto Arduino_ESP32RGBPanel define o mapeamento exato entre os GPIOs do ESP32 e os sinais do painel RGB. Os três primeiros pinos são usados como linhas de controlo para o barramento do painel (seleção do chip, relógio e dados para a interface de configuração). DE, VSYNC, HSYNC, e PCLK são os sinais de temporização, semelhantes aos de uma interface clássica de ecrã RGB. Os grupos restantes mapeiam os cinco bits vermelhos, seis verdes e cinco azuis do barramento de cor de 16 bits (formato 5-6-5) para pinos GPIO específicos.
O objeto gráfico de alto nível gfx representa o LCD controlado pelo ST7701 que é conduzido via este barramento RGB.
Arduino_ST7701_RGBPanel *gfx = new Arduino_ST7701_RGBPanel( bus, GFX_NOT_DEFINED, // RST pin (not used, we reset via PCF8574) 0, // rotation false, // IPS 480, 480, // width, height st7701_type5_init_operations, sizeof(st7701_type5_init_operations), true, // BGR 10, 4, 20, // hsync front porch, pulse width, back porch 10, 4, 20 // vsync front porch, pulse width, back porch );
O primeiro argumento é o barramento RGB definido anteriormente. O pino de reset é definido como GFX_NOT_DEFINED porque o painel é resetado usando pinos PCF8574 em vez de um GPIO direto do ESP32. O parâmetro de rotação é zero, significando a orientação padrão. A flag false IPS é uma opção do painel, e 480, 480 definem a resolução do ecrã.
O ponteiro st7701_type5_init_operations e o tamanho fornecem a sequência de inicialização de baixo nível para o driver ST7701, que a biblioteca enviará ao painel na inicialização. A flag true BGR indica ao driver para tratar os dados de cor como BGR em vez de RGB.
Finalmente, os valores de porch horizontal e vertical e pulso definem a temporização RGB, semelhantes aos parâmetros encontrados em temporização de vídeo clássica: front porch, largura do pulso e back porch para HSYNC e VSYNC.
Inicialização da Luz de Fundo
A luz de fundo é controlada separadamente da eletrónica do painel. A função initBacklight configura o pino da luz de fundo e define um brilho inicial usando PWM.
void initBacklight() {
pinMode(BKL_PIN, OUTPUT);
analogWrite(BKL_PIN, 200);
}
pinMode(BKL_PIN, OUTPUT); configura o pino da luz de fundo como saída. analogWrite(BKL_PIN, 200); ativa PWM nesse pino com um ciclo de trabalho correspondente a um nível de brilho de 200 numa escala típica de 0–255. Isto permite ajustar o brilho da luz de fundo mais tarde escrevendo um valor diferente sem alterar o hardware.
Inicialização dos Pinos e Periféricos
A função initPins configura o barramento I2C partilhado, configura os pinos PCF8574, executa sequências de reset e energia para o LCD e controlador de toque, e inicializa o driver de toque. Tem um papel semelhante ao initPins no exemplo anterior, mas com mais periféricos envolvidos.
void initPins() {
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
pcf8574.pinMode(P0, OUTPUT); //tp RST
pcf8574.pinMode(P2, OUTPUT); //tp INT
pcf8574.pinMode(P3, OUTPUT); //lcd power
pcf8574.pinMode(P4, OUTPUT); //lcd reset
if (!pcf8574.begin()) {
Serial.println("Can't init pcf8574");
}
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN); é idêntico em conceito ao sketch anterior: inicia o periférico I2C com pinos SDA e SCL personalizados. As quatro chamadas pcf8574.pinMode configuram linhas específicas do PCF8574 como saídas: P0 para reset do toque, P2 para linha de interrupção do toque, P3 para ativação da energia do LCD, e P4 para reset do LCD. Como antes, pcf8574.begin() inicializa a comunicação com o expansor e imprime uma mensagem de erro se falhar.
O bloco seguinte executa a sequência de energia e reset para o LCD. Estes atrasos precisos e padrões de comutação são frequentemente exigidos pelos controladores LCD e estão codificados segundo o datasheet do ecrã.
// LCD pcf8574.digitalWrite(P3, HIGH); delay(100); pcf8574.digitalWrite(P4, HIGH); delay(100); pcf8574.digitalWrite(P4, LOW); delay(120); pcf8574.digitalWrite(P4, HIGH); delay(120);
Definir P3 em alto ativa a linha de energia do LCD. Após um curto atraso para estabilizar a alimentação, P4 é alternado para gerar um pulso de reset adequado para o painel. A sequência alto, baixo, alto com atrasos específicos garante que o controlador ST7701 inicia num estado conhecido.
A secção do touchpad executa uma sequência de reset muito semelhante, mas nas linhas de reset e interrupção do controlador de toque.
// Touchpad pcf8574.digitalWrite(P0, HIGH); delay(100); pcf8574.digitalWrite(P0, LOW); delay(120); pcf8574.digitalWrite(P0, HIGH); delay(120); pcf8574.digitalWrite(P2, HIGH); delay(120);
Aqui, P0 é alternado para resetar o controlador CST8xx, e P2 é colocado em alto para configurar a linha de interrupção num estado definido. Estas ações garantem que o controlador de toque está pronto antes que o driver tente comunicar via I2C.
Finalmente, o controlador de toque é inicializado usando o driver Adafruit.
if (!tsPanel.begin(&Wire, I2C_TOUCH_ADDR)) {
Serial.println("No touchscreen found");
}
}
tsPanel.begin(&Wire, I2C_TOUCH_ADDR) indica ao objeto CST8xx para usar a instância global Wire e conecta-o ao endereço I2C definido. Se esta chamada falhar, o sketch imprime uma mensagem de diagnóstico.
Inicialização do LCD
A função initLCD prepara o contexto gráfico e configura um estado básico de desenho.
void initLCD() {
gfx->begin();
gfx->fillScreen(RED);
gfx->setTextSize(5);
gfx->setTextColor(WHITE);
}
gfx->begin(); inicializa o painel ST7701 via o barramento RGB e executa as operações de inicialização fornecidas. Após esta chamada, o ecrã está pronto para aceitar comandos de desenho.
gfx->fillScreen(RED); limpa todo o ecrã com um fundo vermelho. gfx->setTextSize(5); define um fator de escala de texto relativamente grande para que o texto seja facilmente legível no ecrã redondo 480×480. gfx->setTextColor(WHITE); define a cor do texto em primeiro plano para operações subsequentes de desenho de texto.
drawOnLCD
A função drawOnLCD encapsula uma ação simples de desenho, colocando um rótulo de texto na região central do ecrã.
void drawOnLCD() {
gfx->setCursor(90, 210);
gfx->print("Makerguides");
}
gfx->setCursor(90, 210); move o cursor de texto para a posição (90, 210) em coordenadas de pixels. Com um ecrã 480×480, isto é aproximadamente central dependendo do tamanho da fonte. gfx->print("Makerguides"); depois renderiza a string de texto com o tamanho e cor configurados anteriormente no ecrã.
Setup
A função setup fornece novamente uma sequência de inicialização, semelhante ao sketch anterior que configurou serial, pinos e encoder.
void setup() {
Serial.begin(115200);
initPins();
initBacklight();
initLCD();
drawOnLCD();
}
Serial.begin(115200); inicia a comunicação serial para depuração. initPins(); configura o barramento I2C, pinos PCF8574, e executa as sequências de reset do LCD e toque conforme descrito acima. initBacklight(); ativa e define o brilho da luz de fundo para que o conteúdo no ecrã seja visível. initLCD(); inicializa o driver gráfico e pinta o fundo vermelho, e drawOnLCD(); coloca a string inicial “Makerguides” no ecrã.
Loop
O loop principal verifica constantemente se o painel tátil está a ser tocado. Quando um toque é detetado, lê as coordenadas e desenha um pequeno círculo preto nessa posição.
void loop() {
if (tsPanel.touched()) {
CST_TS_Point p = tsPanel.getPoint(0);
Serial.printf("TOUCH: %d, %d\n", p.x, p.y);
gfx->fillCircle(p.x, p.y, 10, BLACK);
delay(10);
}
}
tsPanel.touched(); consulta o driver CST8xx para ver se algum ponto de toque está ativo. Se a função retornar verdadeiro, pelo menos um dedo está no ecrã. tsPanel.getPoint(0); obtém o primeiro ponto de toque como uma estrutura CST_TS_Point contendo as coordenadas x e y. Estas coordenadas são impressas no monitor serial para depuração com Serial.printf, semelhante a como imprimiu o contador do encoder e o estado do botão anteriormente.
gfx->fillCircle(p.x, p.y, 10, BLACK); desenha um círculo preenchido com raio de 10 pixels em preto na localização do toque. A chamada delay(10); fornece uma pausa curta para limitar a taxa de atualização e evitar sobrecarregar o barramento I2C e o driver gráfico com muitas operações por segundo.
Exemplo de Código: Ecrã, Toque e Encoder
Este último sketch junta os conceitos dos exemplos anteriores: configuração I2C e controlo PCF8574, ecrã RGB e tratamento de toque, e um encoder rotativo baseado em interrupção.
O código permite controlar o brilho do ecrã rodando o anel do encoder e também mostrar o valor atual do brilho (0…255) no centro do ecrã. O botão do encoder ativa uma mudança de cor do ecrã para azul e os eventos de toque continuam a ser registados como pontos pretos no ecrã.

Dê uma olhada rápida no código completo abaixo e depois discutiremos os seus detalhes.
#include <Wire.h>
#include <Arduino_GFX_Library.h>
#include <PCF8574.h>
#include <Adafruit_CST8XX.h>
// I2C to PCF8574
#define I2C_SDA_PIN 38
#define I2C_SCL_PIN 39
#define BKL_PIN 6
#define ENCODER_CLK 42
#define ENCODER_DT 4
#define I2C_TOUCH_ADDR 0x15
volatile int brightness = 100;
volatile int encState = 0;
volatile int oldState = -1;
volatile bool brightnessChanged = true;
PCF8574 pcf8574(0x21);
Adafruit_CST8XX tsPanel = Adafruit_CST8XX();
Arduino_ESP32RGBPanel *bus = new Arduino_ESP32RGBPanel(
16 /* CS */, 2 /* SCK */, 1 /* SDA */,
40 /* DE */, 7 /* VSYNC */, 15 /* HSYNC */, 41 /* PCLK */,
46 /* R0 */, 3 /* R1 */, 8 /* R2 */, 18 /* R3 */, 17 /* R4 */,
14 /* G0 */, 13 /* G1 */, 12 /* G2 */, 11 /* G3 */, 10 /* G4 */, 9 /* G5 */,
5 /* B0 */, 45 /* B1 */, 48 /* B2 */, 47 /* B3 */, 21 /* B4 */
);
Arduino_ST7701_RGBPanel *gfx = new Arduino_ST7701_RGBPanel(
bus,
GFX_NOT_DEFINED, // RST pin (not used, we reset via PCF8574)
0, // rotation
false, // IPS
480, 480, // width, height
st7701_type5_init_operations,
sizeof(st7701_type5_init_operations),
true, // BGR
10, 4, 20, // hsync front porch, pulse width, back porch
10, 4, 20 // vsync front porch, pulse width, back porch
);
void initBacklight() {
pinMode(BKL_PIN, OUTPUT);
analogWrite(BKL_PIN, brightness);
}
void initPins() {
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
pcf8574.pinMode(P0, OUTPUT); //tp RST
pcf8574.pinMode(P2, OUTPUT); //tp INT
pcf8574.pinMode(P3, OUTPUT); //lcd power
pcf8574.pinMode(P4, OUTPUT); //lcd reset
pcf8574.pinMode(P5, INPUT_PULLUP); //encoder SW
if (!pcf8574.begin()) {
Serial.println("Can't init pcf8574");
}
// LCD
pcf8574.digitalWrite(P3, HIGH);
delay(100);
pcf8574.digitalWrite(P4, HIGH);
delay(100);
pcf8574.digitalWrite(P4, LOW);
delay(120);
pcf8574.digitalWrite(P4, HIGH);
delay(120);
// Touchpad
pcf8574.digitalWrite(P0, HIGH);
delay(100);
pcf8574.digitalWrite(P0, LOW);
delay(120);
pcf8574.digitalWrite(P0, HIGH);
delay(120);
pcf8574.digitalWrite(P2, HIGH);
delay(120);
if (!tsPanel.begin(&Wire, I2C_TOUCH_ADDR)) {
Serial.println("No touchscreen found");
}
}
void IRAM_ATTR encoder_irq() {
encState = digitalRead(ENCODER_CLK);
if (encState != oldState) {
brightness += (digitalRead(ENCODER_DT) == encState) ? -5 : +5;
brightness = constrain(brightness, 5, 255);
oldState = encState;
brightnessChanged = true;
}
}
void initEncoder() {
pinMode(ENCODER_CLK, INPUT_PULLUP);
pinMode(ENCODER_DT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoder_irq, CHANGE);
}
void initLCD() {
gfx->begin();
gfx->fillScreen(RED);
gfx->setTextSize(10);
gfx->setTextColor(WHITE);
}
void updateBrightness() {
analogWrite(BKL_PIN, brightness);
gfx->fillScreen(RED);
gfx->setCursor(150, 200);
gfx->printf("%3d", brightness);
}
void setup() {
Serial.begin(115200);
initPins();
initEncoder();
initBacklight();
initLCD();
updateBrightness();
}
void loop() {
int button = pcf8574.digitalRead(P5, true);
if (!button) {
Serial.printf("BTN pressed\n");
gfx->fillScreen(BLUE);
}
if (tsPanel.touched()) {
CST_TS_Point p = tsPanel.getPoint(0);
Serial.printf("TOUCH: %d, %d\n", p.x, p.y);
gfx->fillCircle(p.x, p.y, 10, BLACK);
}
if (brightnessChanged) {
brightnessChanged = false;
updateBrightness();
}
delay(100);
}
Importações
Este sketch combina tudo dos exemplos anteriores: periféricos I2C, ecrã RGB, toque, e um encoder rotativo baseado em interrupção, e inclui as bibliotecas necessárias:
#include <Wire.h> #include <Arduino_GFX_Library.h> #include <PCF8574.h> #include <Adafruit_CST8XX.h>
Constantes e Estado Global
A secção seguinte define os pinos para I2C, luz de fundo, sinais do encoder e o endereço do controlador de toque. Também introduz várias variáveis globais volatile que guardam o brilho atual e o estado do encoder, semelhante ao exemplo anterior do contador do encoder:
// I2C to PCF8574 #define I2C_SDA_PIN 38 #define I2C_SCL_PIN 39 #define BKL_PIN 6 #define ENCODER_CLK 42 #define ENCODER_DT 4 #define I2C_TOUCH_ADDR 0x15 volatile int brightness = 100; volatile int encState = 0; volatile int oldState = -1; volatile bool brightnessChanged = true;
I2C_SDA_PIN e I2C_SCL_PIN configuram o barramento I2C partilhado, como antes. BKL_PIN é o pino PWM usado para controlar a luz de fundo do LCD. ENCODER_CLK e ENCODER_DT são os sinais quadratura do encoder rotativo, idênticos em função ao sketch anterior do encoder. I2C_TOUCH_ADDR guarda o endereço do controlador de toque CST8xx.
brightness armazena o valor atual do brilho da luz de fundo. É declarado volatile porque é modificado dentro de uma rotina de serviço de interrupção. encState e oldState são usados para detetar transições na linha CLK do encoder, e brightnessChanged é uma flag que informa o loop principal que um novo nível de brilho está disponível.
Objetos PCF8574, Toque e Ecrã
Os objetos globais para o expansor de I/O, painel de toque e barramento do ecrã são os mesmos do sketch anterior de ecrã e toque. Definem como o ESP32 interage com os chips externos e o painel RGB.
PCF8574 pcf8574(0x21); Adafruit_CST8XX tsPanel = Adafruit_CST8XX(); Arduino_ESP32RGBPanel *bus = new Arduino_ESP32RGBPanel( 16 /* CS */, 2 /* SCK */, 1 /* SDA */, 40 /* DE */, 7 /* VSYNC */, 15 /* HSYNC */, 41 /* PCLK */, 46 /* R0 */, 3 /* R1 */, 8 /* R2 */, 18 /* R3 */, 17 /* R4 */, 14 /* G0 */, 13 /* G1 */, 12 /* G2 */, 11 /* G3 */, 10 /* G4 */, 9 /* G5 */, 5 /* B0 */, 45 /* B1 */, 48 /* B2 */, 47 /* B3 */, 21 /* B4 */ );
O objeto pcf8574 está ligado ao endereço 0x21 e é responsável pela energia do LCD, reset e botão de pressão do encoder. O objeto tsPanel encapsula o controlador de toque CST8xx. O ponteiro bus define o mapeamento dos sinais RGB e de temporização para os GPIOs do ESP32 exatamente como antes, usando a sua tabela de pinos R0–R4, G0–G5 e B0–B4.
O objeto gráfico de alto nível para o painel RGB ST7701 é então criado sobre este barramento.
Arduino_ST7701_RGBPanel *gfx = new Arduino_ST7701_RGBPanel( bus, GFX_NOT_DEFINED, // RST pin (not used, we reset via PCF8574) 0, // rotation false, // IPS 480, 480, // width, height st7701_type5_init_operations, sizeof(st7701_type5_init_operations), true, // BGR 10, 4, 20, // hsync front porch, pulse width, back porch 10, 4, 20 // vsync front porch, pulse width, back porch );
Como antes, isto encapsula a sequência de inicialização de baixo nível do ST7701, parâmetros de temporização e formato de cor. O pino de reset é marcado como indefinido porque o reset é conduzido via os pinos PCF8574 durante initPins.
Inicialização da Luz de Fundo
A função de inicialização da luz de fundo aplica o valor atual de brightness ao pino PWM da luz de fundo:
void initBacklight() {
pinMode(BKL_PIN, OUTPUT);
analogWrite(BKL_PIN, brightness);
}
pinMode(BKL_PIN, OUTPUT); configura o pino da luz de fundo como saída digital. analogWrite(BKL_PIN, brightness); inicia a saída PWM neste pino usando o valor brightness como ciclo de trabalho.
Inicialização dos Pinos e Periféricos
A função initPins é uma versão estendida da que viu anteriormente. Configura o I2C, os pinos PCF8574, a sequência de energia e reset do LCD, a sequência de reset do toque e também configura o pino PCF8574 que lê o botão de pressão do encoder.
void initPins() {
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
pcf8574.pinMode(P0, OUTPUT); //tp RST
pcf8574.pinMode(P2, OUTPUT); //tp INT
pcf8574.pinMode(P3, OUTPUT); //lcd power
pcf8574.pinMode(P4, OUTPUT); //lcd reset
pcf8574.pinMode(P5, INPUT_PULLUP); //encoder SW
if (!pcf8574.begin()) {
Serial.println("Can't init pcf8574");
}
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN); inicia o barramento I2C com os pinos especificados, como nos sketches anteriores. P0, P2, P3 e P4 são configurados como saídas para controlar reset do toque, linha de interrupção do toque, energia do LCD e reset do LCD. P5 é configurado como INPUT_PULLUP porque está ligado ao interruptor do encoder. Isto espelha como configurou P5 como entrada do botão do encoder no exemplo original só do encoder.
Segue-se a sequência de temporização de energia e reset do LCD, idêntica em estrutura ao código anterior do ecrã.
// LCD pcf8574.digitalWrite(P3, HIGH); delay(100); pcf8574.digitalWrite(P4, HIGH); delay(100); pcf8574.digitalWrite(P4, LOW); delay(120); pcf8574.digitalWrite(P4, HIGH); delay(120);
P3 ativa a linha de energia do LCD, depois P4 é alternado com atrasos específicos para realizar um reset hardware do controlador ST7701.
A sequência de reset e configuração do touchpad é também igual à anterior.
// Touchpad pcf8574.digitalWrite(P0, HIGH); delay(100); pcf8574.digitalWrite(P0, LOW); delay(120); pcf8574.digitalWrite(P0, HIGH); delay(120); pcf8574.digitalWrite(P2, HIGH); delay(120);
P0 é pulsado para resetar o controlador CST8xx e P2 é colocado em alto para estabelecer um estado definido para a linha de interrupção do toque.
O controlador de toque é então inicializado. tsPanel.begin(&Wire, I2C_TOUCH_ADDR) liga o driver CST8xx ao barramento I2C partilhado e ao endereço especificado, e imprime uma mensagem de diagnóstico se o dispositivo não for encontrado:
if (!tsPanel.begin(&Wire, I2C_TOUCH_ADDR)) {
Serial.println("No touchscreen found");
}
}
Rotina de Serviço de Interrupção do Encoder
O manipulador de interrupção do encoder é semelhante à função encoder_irq anterior, mas em vez de manter um contador de posição, atualiza a variável brightness em passos de cinco.
void IRAM_ATTR encoder_irq() {
encState = digitalRead(ENCODER_CLK);
if (encState != oldState) {
brightness += (digitalRead(ENCODER_DT) == encState) ? -5 : +5;
brightness = constrain(brightness, 5, 255);
oldState = encState;
brightnessChanged = true;
}
}
IRAM_ATTR garante que a ISR está colocada na RAM de instruções para execução rápida no ESP32, como discutido anteriormente. Dentro da função, encState é definido para o nível lógico atual do pino CLK do encoder usando digitalRead(ENCODER_CLK). A condição if (encState != oldState) assegura que o código só reage quando o sinal CLK realmente muda, prevenindo múltiplas atualizações no mesmo nível.
A direção da rotação é novamente determinada comparando o sinal DT com o estado atual do CLK. Neste sketch, a condicional é invertida em relação ao exemplo anterior para dar uma sensação natural de aumento e diminuição do brilho.
A linha seguinte subtrai 5 ou adiciona 5 à variável brightness com base na fase relativa dos sinais quadratura. A rotação positiva aumenta o brilho, a rotação negativa diminui.
brightness += (digitalRead(ENCODER_DT) == encState) ? -5 : +5;
E a linha seguinte assegura que o brilho se mantém dentro de um limite inferior definido de 5 (evitando um ecrã completamente apagado) e limite superior de 255 (o valor máximo PWM para brilho total):
brightness = constrain(brightness, 5, 255);
oldState é atualizado para o novo estado do CLK, e brightnessChanged é definido como verdadeiro para notificar o loop principal que a luz de fundo e o ecrã devem ser atualizados. Como antes, todo o trabalho pesado como I/O serial e gráfico é mantido fora da ISR e tratado no loop principal.
Inicialização do Encoder
A função initEncoder configura os pinos do encoder e anexa a interrupção à linha CLK. Este é efetivamente o mesmo padrão do seu sketch original do encoder.
void initEncoder() {
pinMode(ENCODER_CLK, INPUT_PULLUP);
pinMode(ENCODER_DT, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoder_irq, CHANGE);
}
Ambos os canais do encoder são configurados como entradas com resistores pull-up internos. A interrupção é anexada ao pino CLK usando attachInterrupt, com modo CHANGE para disparar em ambas as bordas ascendente e descendente. Em cada mudança, o manipulador encoder_irq é invocado, atualizando o brilho.
Inicialização do LCD e Exibição do Brilho
A função de inicialização do LCD levanta o painel ST7701 e configura as definições de texto. É semelhante ao initLCD anterior, mas usa um tamanho de texto maior para mostrar o valor do brilho de forma destacada:
void initLCD() {
gfx->begin();
gfx->fillScreen(RED);
gfx->setTextSize(10);
gfx->setTextColor(WHITE);
}
gfx->begin(); inicializa o controlador do ecrã e envia a sequência de configuração. gfx->fillScreen(RED); define um fundo vermelho. gfx->setTextSize(10); escolhe um fator de escala grande para que o valor numérico do brilho se destaque claramente. gfx->setTextColor(WHITE); configura o branco como cor do texto para renderização.
A função updateBrightness liga o valor lógico do brilho tanto à luz de fundo física como à exibição no ecrã.
void updateBrightness() {
analogWrite(BKL_PIN, brightness);
gfx->fillScreen(RED);
gfx->setCursor(150, 200);
gfx->printf("%3d", brightness);
}
analogWrite(BKL_PIN, brightness); atualiza o ciclo de trabalho PWM, alterando efetivamente a intensidade da luz LED de fundo. O ecrã é então limpo novamente a vermelho usando gfx->fillScreen(RED);.
O cursor é colocado nas coordenadas (150, 200), e gfx->printf("%3d", brightness); imprime o brilho como um número de três dígitos.
Setup
A função setup inicializa todos os subsistemas: serial, pinos I2C e chips externos, o encoder, a luz de fundo e o LCD, e finalmente desenha o brilho inicial no ecrã.
void setup() {
Serial.begin(115200);
initPins();
initEncoder();
initBacklight();
initLCD();
updateBrightness();
}
Serial.begin(115200); inicia a UART para saída de depuração. initPins(); prepara o barramento I2C, PCF8574, LCD e controlador de toque conforme discutido anteriormente. initEncoder(); ativa a interface do encoder baseada em interrupção. initBacklight(); aplica o valor inicial brightness ao pino da luz de fundo. initLCD(); levanta o contexto gráfico, e updateBrightness(); sincroniza imediatamente a exibição no ecrã e o PWM da luz de fundo com o valor atual do brilho.
Loop
O loop principal verifica periodicamente o botão do encoder, a entrada tátil e a flag de atualização do brilho. Reage a cada um destes eventos usando os periféricos configurados anteriormente.
void loop() {
int button = pcf8574.digitalRead(P5, true);
if (!button) {
Serial.printf("BTN pressed\n");
gfx->fillScreen(BLUE);
}
if (tsPanel.touched()) {
CST_TS_Point p = tsPanel.getPoint(0);
Serial.printf("TOUCH: %d, %d\n", p.x, p.y);
gfx->fillCircle(p.x, p.y, 10, BLACK);
}
if (brightnessChanged) {
brightnessChanged = false;
updateBrightness();
}
delay(100);
}
O primeiro bloco lê o botão de pressão do encoder via PCF8574. pcf8574.digitalRead(P5, true); lê o pino P5 com uma transação I2C imediata. Como P5 está configurado como INPUT_PULLUP, o botão lê alto quando solto e baixo quando pressionado. A condição if (!button) deteta o estado pressionado. Ao pressionar, o sketch imprime uma mensagem no monitor serial e preenche o ecrã com azul, fornecendo uma indicação visual simples de que o botão foi pressionado.
O bloco seguinte trata da entrada tátil capacitiva, reutilizando a mesma lógica do exemplo anterior de desenho no LCD. Se tsPanel.touched() retornar verdadeiro, pelo menos um ponto de toque está ativo. A função tsPanel.getPoint(0); obtém o primeiro ponto de toque, que é então impresso no monitor serial. gfx->fillCircle(p.x, p.y, 10, BLACK); desenha um pequeno círculo preto nas coordenadas do toque, permitindo ao utilizador desenhar sobre o conteúdo atual do ecrã.
O terceiro bloco verifica se a ISR do encoder atualizou a variável brightness. Se brightnessChanged for verdadeiro, o loop principal limpa a flag e chama updateBrightness();. Isto aplica o novo brilho à luz de fundo e redesenha o valor numérico no ecrã. Rodar o encoder rapidamente gerará múltiplas interrupções e definirá brightnessChanged repetidamente, mas o loop garante que as atualizações de brilho são tratadas no contexto principal onde é seguro fazer operações seriais e gráficas.
O último delay(100); introduz uma pausa curta para limitar a frequência do loop e suavizar a interação do utilizador sem sobrecarregar a CPU ou o I2C.
Conclusões
Este tutorial forneceu exemplos de código para começar a usar o CrowPanel 2.1inch-HMI ESP32 Rotary Display. Veja o Wiki da Elecrow para informações adicionais e para mais exemplos de código veja o github repo.
Note que precisa instalar bibliotecas antigas e uma versão antiga do core ESP32 (2.0.14) para fazer o código funcionar. Também alguns exemplos de código lá usam a biblioteca LVGL, que evitei aqui para manter o código simples.
Se procura um módulo de ecrã semelhante com anel encoder rotativo, dê uma olhada no CrowPanel 1.28inch-HMI ESP32 Rotary Display ou no Matouch 1.28″ ToolSet_Controller. Se precisar apenas de um ecrã redondo (sem o anel encoder rotativo), o tutorial Digital Clock on CrowPanel 1.28″ Round Display pode ser útil.
Finalmente, se quiser aprender mais sobre encoders rotativos, veja o nosso tutorial How To Interface A Quadrature Rotary Encoder.
Se tiver alguma dúvida, sinta-se à vontade para deixá-la na secção de comentários.
Boas experiências 😉



