Skip to Content

Iniziare con CrowPanel 1.28inch-HMI ESP32 Rotary Display

Iniziare con CrowPanel 1.28inch-HMI ESP32 Rotary Display

Il CrowPanel 1.28-inch HMI ESP32 Rotary Display di Elecrow è un modulo compatto e rotondo che integra un microcontrollore ESP32-S3, un touchscreen circolare IPS da 1,28 pollici con risoluzione 240×240, un anello LED RGB e un encoder rotativo con pulsante integrato in un’unica unità.

In questo tutorial, vedremo i passaggi essenziali per iniziare con il CrowPanel; dalla configurazione iniziale al test delle sue capacità di display e input. Imparerai a controllare i LED RGB, leggere i dati dell’encoder rotativo per un input utente fluido e catturare gli eventi touch dal display circolare usando esempi di codice semplici.

Componenti necessari

Ti servirà solo una scheda CrowPanel 1.28-inch HMI ESP32 Rotary Display. Il modulo display viene fornito con un cavo USB e un connettore GPIO, quindi non è necessario altro hardware.

CrowPanel 1.28inch-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 del CrowPanel 1.28inch-HMI ESP32 Rotary Display

Il CrowPanel 1.28-inch HMI ESP32 Rotary Display è un modulo hardware integrato che combina un display IPS rotondo ad alta risoluzione, un touchscreen capacitivo, un anello di LED RGB e un encoder rotativo con funzione pulsante, tutto gestito da un microcontrollore ESP32-S3.

Il modulo è progettato per fungere da interfaccia uomo-macchina (HMI) compatta per progetti embedded e IoT dove è richiesta un’interazione visiva e di controllo in uno spazio ridotto. L’immagine sottostante mostra il fronte e il lato del modulo con le sue dimensioni:

Dimensioni del CrowPanel 1.28inch-HMI ESP32 Rotary Display (source)

Microcontrollore

Al cuore del CrowPanel c’è l’ESP32-S3R8, un processore dual-core Xtensa LX7 che opera fino a 240 MHz. Il microcontrollore integra Wi-Fi (802.11 b/g/n) e Bluetooth 5.0 LE, offrendo opzioni di connettività locale e remota. Include 8 MB di PSRAM e 16 MB di memoria flash.

Display

Il display è un pannello IPS circolare da 1,28 pollici con risoluzione 240 × 240 pixel e supporto completo a 65 K colori. Lo schermo utilizza comunicazione SPI per il trasferimento dati e offre un ampio angolo di visione fino a 178 gradi. Un controller touch capacitivo è integrato nello stesso modulo, collegato tramite interfaccia I²C, permettendo un rilevamento preciso multi-touch e il supporto ai gesti.

Per l’input fisico, il modulo include un encoder rotativo con pulsante integrato. L’encoder fornisce un feedback incrementale della posizione, adatto per la navigazione nei menu, la regolazione di parametri o controlli di selezione rotativa.

Striscia LED RGB

Il modulo include anche una striscia LED RGB WS2812 integrata composta da cinque LED indirizzabili singolarmente. I LED sono collegati in serie e possono essere controllati usando librerie comuni come Adafruit NeoPixel o FastLED.

Ogni LED WS2812 funziona a 5 V e consuma tipicamente fino a 60 mA alla massima luminosità quando tutti i canali colore (rosso, verde e blu) sono attivi a piena intensità. Pertanto, quando tutti e cinque i LED sono accesi al massimo, il consumo totale può raggiungere circa 300 mA (5 × 60 mA).

Interfacce

La scheda è alimentata tramite un connettore USB-C a 5 V e include un regolatore di tensione onboard a 3,3 V per un funzionamento stabile dell’ESP32 e dei componenti periferici. Sono disponibili ulteriori interfacce come UART, I²C, SPI, FPC e header GPIO per espansioni esterne, permettendo di collegare sensori, attuatori o altri dispositivi di controllo.

Il modulo include anche un pulsante di reset, un pulsante boot per il flashing del firmware e un LED di stato per feedback visivo durante il funzionamento. L’immagine sottostante mostra il retro del modulo con i pulsanti e le varie interfacce:

Retro del CrowPanel 1.28inch-HMI ESP32 Rotary Display (source)

Pin

La tabella seguente mostra quali pin GPIO dell’ESP32-S3 sono collegati a quali componenti del modulo. Queste informazioni sono necessarie se vuoi scrivere codice per il modulo.

ComponenteInterfaccia / FunzioneAssegnazione Pin
Display (GC9A01)SPISCLK = 10, MOSI = 11, MISO = -1, DC = 3, CS = 9, RST = 14
Retroilluminazione SchermoGPIOSCREEN_BACKLIGHT_PIN = 46
Touchscreen (CST816D)I²CSDA = 6, SCL = 7, INT = 5, RST = 13
OLED (SSD1306)I²CSDA = 38, SCL = 39
LED RGB (WS2812)Uscita DigitaleLED_PIN = 48, LED_NUM = 5
Encoder RotativoGPIOENCODER_A_PIN = 45, ENCODER_B_PIN = 42, SWITCH_PIN = 41
Indicatore di AlimentazioneGPIOPOWER_LIGHT_PIN = 40
Test I/OGPIOIO4, IO12

Specifiche Tecniche

Infine, le specifiche tecniche del modulo display CrowPanel sono riassunte nella tabella sottostante.

SpecificheDettagli
MicrocontrolloreESP32-S3R8 dual-core Xtensa LX7 @ 240 MHz
Memoria8 MB PSRAM, 16 MB Flash
Tipo di DisplayIPS TFT rotondo da 1,28 pollici
Risoluzione240 × 240 pixel
Profondità Colore65 K colori
Angolo di Visione178 gradi
Interfaccia TouchCapacitiva, comunicazione I²C
Input RotativoEncoder incrementale con pulsante
Interfacce di ComunicazioneSPI, I²C, UART, GPIO
ConnettivitàWi-Fi 802.11 b/g/n, Bluetooth 5.0 LE
Alimentazione5 V via USB-C (regolatore 3.3 V onboard)
Interfaccia di ProgrammazioneUSB-C (supporta Arduino, ESP-IDF)
Caratteristiche AggiuntivePulsanti Reset e Boot, Striscia LED RGB

Installazione del Core ESP32

Se è il tuo primo progetto con una scheda della serie ESP32, devi prima installare il core ESP32. Se le schede ESP32 sono già installate nel tuo Arduino IDE, puoi saltare questa sezione.

Inizia aprendo la finestra Preferenze selezionando “Preferences…” dal menu “File”. Si aprirà la finestra Preferenze mostrata qui sotto.

Nella scheda Settings troverai una casella di testo in fondo alla finestra etichettata “Additional boards manager URLs“:

Additional boards manager URLs in Preferences
URL aggiuntivi per il Boards Manager nelle Preferenze

In questo campo incolla il seguente URL:

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

Questo permetterà all’Arduino IDE di sapere dove trovare le librerie core ESP32. Successivamente installeremo le schede ESP32 tramite il Boards Manager.

Apri il Boards Manager tramite “Tools -> Boards -> Board Manager”. Vedrai il Boards Manager apparire nella barra laterale sinistra. Digita “ESP32” nel campo di ricerca in alto e dovresti vedere due tipi di schede ESP32; le “Arduino ESP32 Boards” e le schede “esp32 by Espressif”. Noi vogliamo le “esp32 libraries by Espressif”. Clicca sul pulsante INSTALL e attendi che il download e l’installazione siano completati.

Install ESP32 Core libraries
Installa le librerie Core ESP32

Selezione della Scheda

Infine dobbiamo selezionare una scheda ESP32. Nel caso del CrowPanel 1.28inch-HMI ESP32 Rotary Display, scegliamo la scheda generica “ESP32S3 Dev Module”. Per farlo, clicca sul menu a tendina e poi su “Select other board and port…”:

Drop-down Menu for Board Selection
Menu a tendina per la selezione della scheda

Si aprirà una finestra dove puoi digitare “esp32s3 dev” nella barra di ricerca. Vedrai la scheda “ESP32S3 Dev Module” sotto Boards. Cliccaci sopra, seleziona la porta COM per attivarla e poi clicca OK:

Board Selection Dialog "ESP32S3 Dev Module" board
Finestra di selezione scheda “ESP32S3 Dev Module”

Nota che devi collegare la scheda al computer tramite cavo USB prima di poter selezionare una porta COM. Il modulo CrowPanel Display ha due porte, una USB e una UART. Puoi usare entrambe per programmare la scheda, ma per la porta UART ti serve un convertitore USB-to-TTL, vedi sotto:

UART port of CrowPanel Display Module
Porta UART del CrowPanel Display Module (source)

Il modo più semplice è collegare il cavo USB fornito con il CrowPanel Display Module direttamente alla porta etichettata “USB” come mostrato sotto. In questo caso non serve un convertitore USB-to-TTL.

USB Port of CrowPanel Display
Porta USB del CrowPanel Display Module (source)

Impostazioni Strumenti

Di seguito le impostazioni da usare con la scheda. Le trovi nel menu Tools del tuo Arduino IDE.

Tool settings for CrowPanel 1.28inch-HMI ESP32 Rotary Display
Impostazioni strumenti per CrowPanel 1.28inch-HMI ESP32 Rotary Display

La cosa più importante per gli esempi di codice nelle sezioni successive è impostare USB CDC on Boot su “Enabled” e anche USB mode su “Hardware CDC and JTAG”. Questo ti permette di inviare o ricevere dati tramite la porta seriale, utile per il debug.

Esempio di codice: Interfaccia Serial

Iniziamo testando la comunicazione Serial. Apri il tuo Arduino IDE, inserisci il codice seguente e caricalo sul CrowPanel Display Module.

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

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

Poi apri il Serial Monitor e dovresti vedere il testo “Makerguides” stampato ogni due secondi. Se non succede, verifica che le impostazioni Tools e la velocità Baud (115200) siano corrette.

Esempio di codice: Striscia LED RGB

Proviamo ora il LED RGB integrato. Tuttavia, devi prima installare la libreria Adafruit_NeoPixel. Apri il LIBRARY MANAGER nel tuo Arduino IDE, digita “Adafruit NeoPixel” nella barra di ricerca e installa la libreria Adafruit NeoPixel di Adafruit:

Installation of Adafruit NeoPixel for Arduino
Installazione di Adafruit NeoPixel per Arduino

Il CrowPanel Display Module ha una striscia LED RGB WS2812 integrata con cinque LED. L’esempio seguente mostra come inizializzare la striscia, impostare un colore e aumentare gradualmente la luminosità usando la libreria Adafruit NeoPixel.

// ESP32 core: 3.3.2
// Adafruit_NeoPixel: 1.15.2

#include <Adafruit_NeoPixel.h>

#define LED_PIN 48
#define LED_NUM 5

Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_NUM, LED_PIN, NEO_GRB + NEO_KHZ800);

void updateBrightness(int brightness) {    
  strip.setBrightness(brightness);
  strip.show();
  delay(100);
}

void initLEDs(uint8_t r, uint8_t g, uint8_t b) {
  strip.begin();
  strip.setBrightness(100);
  for (int i = 0; i < LED_NUM; i++) {
    strip.setPixelColor(i, strip.Color(r, g, b));
  }
  strip.show();
}

void setup() {
  Serial.begin(115200);
  initLEDs(255, 255, 50); // Yellow
}

void loop() {
  for(int brightness=5; brightness<255; brightness+=5) {
    updateBrightness(brightness);  
    Serial.printf("Brightness: %d\n", brightness);  
  }
}

Importazioni

Il programma inizia includendo la libreria Adafruit_NeoPixel, che fornisce un’interfaccia semplice per controllare i LED WS2812. Questa libreria gestisce il timing rigoroso richiesto dal protocollo di comunicazione a filo singolo dei LED, permettendoti di concentrarti sulla logica di colore e animazione senza preoccuparti dei dettagli a basso livello.

#include <Adafruit_NeoPixel.h>

Costanti

Sono definite due costanti per specificare quanti LED sono collegati e quale pin GPIO li controlla. Il CrowPanel collega la striscia WS2812 al GPIO 48, e la striscia contiene cinque LED.

#define LED_PIN 48
#define LED_NUM 5

Creazione Oggetto

Successivamente viene creato un oggetto striscia NeoPixel. Il costruttore prende tre parametri: il numero di LED, il pin di controllo e la configurazione del tipo di pixel. Il flag NEO_GRB + NEO_KHZ800 indica che ogni LED usa l’ordine colore GRB (verde, rosso, blu) e opera a 800 kHz, frequenza standard per i LED WS2812.

Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_NUM, LED_PIN, NEO_GRB + NEO_KHZ800);

Questo oggetto sarà usato in tutto il programma per controllare i LED — impostare colori, regolare la luminosità e inviare aggiornamenti dati.

La updateBrightness() Funzione

Questa funzione di supporto regola dinamicamente la luminosità della striscia LED. Il metodo setBrightness() accetta un valore tra 0 (spento) e 255 (massima luminosità). Dopo aver impostato la luminosità, la funzione show() deve essere chiamata per inviare i dati aggiornati ai LED.

void updateBrightness(int brightness) {    
  strip.setBrightness(brightness);
  strip.show();
  delay(100);
}

Il breve ritardo assicura una transizione visibile e fluida tra i livelli di luminosità, creando un effetto di fade-in graduale.

La initLEDs() Funzione

Prima di poter controllare i LED, devono essere inizializzati. La funzione begin() prepara il pin dati per la comunicazione, e setBrightness(100) imposta un livello iniziale di luminosità per evitare di partire a piena intensità.

Il ciclo for itera su ciascuno dei cinque LED, assegnando loro un colore usando setPixelColor(). Il metodo strip.Color(r, g, b) converte i valori individuali di rosso, verde e blu nel formato colore a 24 bit richiesto dai LED.

void initLEDs(uint8_t r, uint8_t g, uint8_t b) {
  strip.begin();
  strip.setBrightness(100);
  for (int i = 0; i < LED_NUM; i++) {
    strip.setPixelColor(i, strip.Color(r, g, b));
  }
  strip.show();
}

Infine, show() trasmette i dati colore a tutti i LED, illuminandoli simultaneamente nel colore selezionato. Nel nostro caso sarà giallo.

Setup

La funzione setup() viene eseguita una volta all’accensione o reset dell’ESP32. Inizia inizializzando la comunicazione seriale a 115200 baud, permettendo di stampare messaggi sul monitor seriale. Poi chiama initLEDs(255, 255, 50), che accende tutti e cinque i LED con una tonalità gialla tenue.

void setup() {
  Serial.begin(115200);
  initLEDs(255, 255, 50); // Yellow
}

Loop

La funzione loop() viene eseguita continuamente dopo il setup. In questo esempio, aumenta gradualmente la luminosità da 5 a 255 a passi di 5, chiamando updateBrightness() ogni volta. Dopo ogni modifica, stampa il valore corrente di luminosità sul monitor seriale.

void loop() {
  for(int brightness=5; brightness<255; brightness+=5) {
    updateBrightness(brightness);  
    Serial.printf("Brightness: %d\n", brightness);  
  }
}

Questo crea un’animazione di fade-in fluida che si ripete ciclicamente. Assicurati di non impostare la luminosità a zero. La striscia LED rimarrà spenta, anche se cambi la luminosità in seguito. Dovrai reimpostare i colori dei LED per la striscia.

Esempio di codice: Encoder rotativo

Questo sketch si basa sull’esempio NeoPixel precedente e aggiunge il controllo in tempo reale della luminosità usando un encoder rotativo meccanico. I LED continuano a usare la libreria Adafruit NeoPixel per il timing e la gestione del colore, mentre l’encoder viene letto tramite una routine di servizio interrupt per un’interazione reattiva e senza jitter. Il risultato è una manopola hardware fluida per la luminosità della striscia WS2812 a cinque LED del CrowPanel.

// ESP32 core: 3.3.2
// Adafruit_NeoPixel: 1.15.2

#include <Adafruit_NeoPixel.h>

#define LED_PIN 48
#define LED_NUM 5
#define ENCODER_CLK 45  // A
#define ENCODER_DT 42   // B

Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_NUM, LED_PIN, NEO_GRB + NEO_KHZ800);

int brightness = 50;  // 0..255
int enc_state = 0;
int old_state = -1;
bool has_changed = true;

void IRAM_ATTR encoder_irq() {
  enc_state = digitalRead(ENCODER_CLK);
  if (enc_state != old_state) {
    brightness += (digitalRead(ENCODER_DT) == enc_state) ? -5 : +5;
    brightness = constrain(brightness, 5, 255);
    old_state = enc_state;
    has_changed = true;
  }
}

void updateBrightness() {  
  strip.setBrightness(brightness);
  strip.show();
  delay(200);
}

void initLEDs(uint8_t r, uint8_t g, uint8_t b) {
  strip.begin();
  for (int i = 0; i < LED_NUM; i++) {
    strip.setPixelColor(i, strip.Color(r, g, b));
  }
  updateBrightness();
}

void initEncoder() {
  pinMode(ENCODER_CLK, INPUT_PULLUP);
  pinMode(ENCODER_DT, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoder_irq, CHANGE);
}

void setup() {
  Serial.begin(115200);
  initEncoder();
  initLEDs(255, 255, 50);
  has_changed = true;
}

void loop() {
  if (has_changed) {
    has_changed = false;
    updateBrightness();
    Serial.printf("Brightness: %d\n", brightness);
  }
  delay(1);
}

Importazioni

Il programma riutilizza la stessa libreria NeoPixel usata prima, così il segnale critico per i LED WS2812 è completamente gestito e puoi concentrarti sulla logica di input e animazione.

#include <Adafruit_NeoPixel.h>

Costanti

La striscia LED rimane collegata al GPIO 48 e contiene cinque pixel come prima. Due costanti aggiuntive definiscono i canali A e B dell’encoder rotativo su GPIO 45 e GPIO 42.

#define LED_PIN 48
#define LED_NUM 5
#define ENCODER_CLK 45  // A
#define ENCODER_DT 42   // B

Oggetti

L’oggetto NeoPixel è configurato con gli stessi parametri spiegati nell’esempio precedente. L’ordine colore GRB e la frequenza 800 kHz corrispondono ai requisiti WS2812.

Adafruit_NeoPixel strip = Adafruit_NeoPixel(LED_NUM, LED_PIN, NEO_GRB + NEO_KHZ800);

Variabili di Stato

Lo sketch mantiene un valore globale di luminosità nell’intervallo 0-255 e traccia gli stati istantanei e precedenti dell’encoder. Un flag booleano segnala al loop principale che è necessario aggiornare la luminosità.

int brightness = 50;  // 0..255
int enc_state = 0;
int old_state = -1;
bool has_changed = true;

La variabile brightness è inizializzata intenzionalmente a un livello moderato per evitare che i LED si accendano al massimo carico. Gli stati dell’encoder sono inizializzati per rilevare correttamente il primo fronte, e has_changed parte true per forzare un rendering iniziale.

Routine di Servizio Interrupt: encoder_irq

L’encoder viene decodificato in un contesto interrupt per mantenere bassa la latenza UI. La routine viene eseguita su entrambi i fronti del canale CLK e confronta il canale DT per determinare la direzione di rotazione. Se DT è uguale a CLK, il movimento è interpretato in una direzione e si sottraggono cinque passi di luminosità; se diverso, si aggiungono cinque. La dimensione del passo di cinque è un compromesso pratico tra controllo granulare e reattività.

void IRAM_ATTR encoder_irq() {
  enc_state = digitalRead(ENCODER_CLK);
  if (enc_state != old_state) {
    brightness += (digitalRead(ENCODER_DT) == enc_state) ? -5 : +5;
    brightness = constrain(brightness, 5, 255);
    old_state = enc_state;
    has_changed = true;
  }
}

L’attributo IRAM_ATTR assicura che la routine sia posizionata nella RAM delle istruzioni, evitando attese flash sull’ESP32 durante gli interrupt. La chiamata constrain limita la luminosità a un minimo di 5 per evitare output completamente spento e un massimo di 255 per il limite superiore. Il flag has_changed rimanda l’aggiornamento effettivo dei LED al thread principale, mantenendo l’ISR breve e deterministica.

Aggiornamento Luminosità: updateBrightness

Gli aggiornamenti effettivi dei LED sono centralizzati qui. La funzione applica la luminosità corrente alla striscia e chiama show per trasmettere i dati pixel bufferizzati. Un breve ritardo rende visibili i cambiamenti successivi e evita di saturare la frequenza di aggiornamento dei LED.

void updateBrightness() {  
  strip.setBrightness(brightness);
  strip.show();
  delay(200);
}

Poiché la luminosità su NeoPixel è un moltiplicatore globale applicato al momento della trasmissione, questo approccio aggiorna l’intensità senza riscrivere i colori dei singoli pixel.

Inizializzazione LED: initLEDs

La logica di inizializzazione rispecchia l’esempio precedente ma delega la luminosità a updateBrightness così lo stesso percorso di codice è usato sia all’avvio che durante la rotazione della manopola. Tutti e cinque i pixel ricevono lo stesso colore iniziale, che puoi cambiare per feedback di stato.

void initLEDs(uint8_t r, uint8_t g, uint8_t b) {
  strip.begin();
  for (int i = 0; i < LED_NUM; i++) {
    strip.setPixelColor(i, strip.Color(r, g, b));
  }
  updateBrightness();
}

Chiamare updateBrightness alla fine garantisce che il colore iniziale appaia al livello di luminosità globale corrente.

Inizializzazione Encoder: initEncoder

I canali dell’encoder sono configurati come ingressi con pull-up interni perché molti encoder da pannello sono di tipo open-collector o con interruttori meccanici. L’interrupt è collegato alla linea CLK su entrambi i fronti così ogni scatto produce una transizione di stato che l’ISR può decodificare.

void initEncoder() {
  pinMode(ENCODER_CLK, INPUT_PULLUP);
  pinMode(ENCODER_DT, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoder_irq, CHANGE);
}

Usare interrupt sulla linea CLK, invece di fare polling nel loop, elimina passi mancati a velocità di rotazione elevate e riduce l’uso della CPU. Se il tuo encoder è particolarmente rumoroso, puoi aggiungere un semplice debounce ignorando cambiamenti entro una finestra di microsecondi o campionando entrambi i canali di nuovo dopo un breve ritardo.

Setup

L’output seriale è inizializzato per visibilità, l’encoder è armato per primo così l’input utente è catturato immediatamente, e i LED sono accesi con una tonalità gialla. Il flag di cambiamento è impostato per assicurare che il primo frame venga inviato alla striscia anche se l’encoder non si è ancora mosso.

void setup() {
  Serial.begin(115200);
  initEncoder();
  initLEDs(255, 255, 50);
  has_changed = true;
}

Loop

Il loop principale è volutamente minimale. Controlla se l’ISR ha segnalato un cambiamento di luminosità e, in tal caso, applica il nuovo livello e lo riporta via seriale.

void loop() {
  if (has_changed) {
    has_changed = false;
    updateBrightness();
    Serial.printf("Brightness: %d\n", brightness);
  }
  delay(1);
}

Questa struttura mantiene il lavoro critico e breve nell’interrupt e lascia l’I/O LED più pesante in primo piano, un modello robusto per codice UI reattivo su ESP32.

Esempio di codice: Display

Nel prossimo esempio controlleremo la luminosità del display usando l’encoder rotativo. L’immagine sotto mostra tre diversi valori di luminosità (10%, 50%, 100%) per il display:

Different brightness settings for display
Diverse impostazioni di luminosità per il display

Per prima cosa, devi installare la libreria Arduino_GFX di moononournation per poter controllare il display. Apri il LIBRARY MANAGER, digita “GFX Library for Arduino” nella barra di ricerca, trova “GFX Library for Arduino” di Moon e clicca sul pulsante INSTALL:

Installation of GFX Library for Arduino
Installazione di GFX Library for Arduino

Lo sketch seguente collega l’encoder rotativo al display TFT GC9A01 usando la libreria Arduino_GFX e aggiunge il controllo PWM della retroilluminazione. Estende i concetti dell’encoder degli esempi precedenti e ti permetterà di controllare la luminosità del display con un toggle a pulsante per la retroilluminazione.

// ESP32 core: 3.3.2
// Arduino_GFX_Library:  1.6.2

#include <Arduino_GFX_Library.h>

#define TFT_SCLK 10
#define TFT_MOSI 11
#define TFT_DC    3
#define TFT_CS    9
#define TFT_RES   14
#define TFT_BLK   46  

#define LCD_PWR_EN1 1   // LCD_3V3 rail
#define LCD_PWR_EN2 2   // LEDA_3V3 rail

#define ENCODER_CLK 45   // A
#define ENCODER_DT  42   // B
#define ENCODER_BTN 41   // SW (active-LOW)


int  brightness   = 50;   // 0..100 %
bool btn_display  = true;
int  enc_state    = 0;
int  old_state    = -1;
bool has_changed  = true;


Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(
  TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI, GFX_NOT_DEFINED, FSPI, true
);
Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RES, 0 /* rotation */, true /* IPS */);


void IRAM_ATTR encoder_irq() {
  enc_state = digitalRead(ENCODER_CLK);
  if (enc_state != old_state) {
    brightness += (digitalRead(ENCODER_DT) == enc_state) ? 1 : -1;
    brightness = constrain(brightness, 0, 100);
    old_state = enc_state;
    has_changed = true;
  }
}

void IRAM_ATTR button_irq() {
  if (!digitalRead(ENCODER_BTN)) {
    btn_display = !btn_display;
    has_changed = true;
  }
}

void drawText() {
  gfx->setTextSize(2);
  gfx->setCursor(60, 55);
  gfx->println(F("Makerguides"));
}

void updateBrightness() {
  gfx->fillRect(65, 95, 120, 65, WHITE);
  gfx->setTextSize(6);
  gfx->setCursor(65, 100);
  gfx->printf("%3d", brightness);

  int duty = map(brightness, 0, 100, 0, 255);
  analogWrite(TFT_BLK, btn_display ? duty : 0);
}

void initPower() {
  pinMode(LCD_PWR_EN1, OUTPUT);
  pinMode(LCD_PWR_EN2, OUTPUT);
  digitalWrite(LCD_PWR_EN1, HIGH);   // must be HIGH
  digitalWrite(LCD_PWR_EN2, HIGH);   // must be HIGH

  pinMode(TFT_BLK, OUTPUT);
  analogWrite(TFT_BLK, 50); 
}

void initDisplay() {
  delay(20);                 
  gfx->begin();
  gfx->fillScreen(WHITE);
  gfx->setTextColor(BLACK);
}

void initEncoder() {
  pinMode(ENCODER_BTN, INPUT_PULLUP);
  pinMode(ENCODER_CLK, INPUT_PULLUP);
  pinMode(ENCODER_DT,  INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoder_irq, CHANGE);
  attachInterrupt(digitalPinToInterrupt(ENCODER_BTN), button_irq, CHANGE);
}

void setup() {
  Serial.begin(115200);
  initPower();
  initEncoder();
  initDisplay();
  drawText();
  has_changed = true;
}

void loop() {
  if (has_changed) {
    has_changed = false;
    updateBrightness();
    Serial.printf("Brightness: %d%% (display %s)\n",
                  brightness, btn_display ? "ON" : "OFF");
  }
  delay(1);
}

Importazioni

Il programma si basa su Arduino_GFX per primitive di disegno SPI veloci e un driver per pannello GC9A01. La libreria nasconde i comandi specifici del controller e fornisce un’API coerente per inizializzazione, riempimenti colore e rendering testo.

#include <Arduino_GFX_Library.h>

Costanti

I pin definiscono la connessione SPI al TFT rotondo, le linee di abilitazione alimentazione per il pannello e il suo anodo LED, il pin di retroilluminazione pilotato in PWM e i canali e switch dell’encoder rotativo. La luminosità è espressa in percentuale per permettere un feedback utente stampabile direttamente da 0 a 100%.

#define TFT_SCLK 10
#define TFT_MOSI 11
#define TFT_DC    3
#define TFT_CS    9
#define TFT_RES   14
#define TFT_BLK   46  

#define LCD_PWR_EN1 1   // LCD_3V3 rail
#define LCD_PWR_EN2 2   // LEDA_3V3 rail

#define ENCODER_CLK 45   // A
#define ENCODER_DT  42   // B
#define ENCODER_BTN 41   // SW (active-LOW)

Variabili di Stato

Lo stato a runtime traccia la percentuale di luminosità corrente, se la retroilluminazione è abilitata, lo stato istantaneo dell’encoder e un flag di cambiamento che permette all’ISR di programmare lavoro per il thread principale. Come prima, mantenere le ISR brevi e delegare l’I/O al foreground evita problemi di temporizzazione.

int  brightness   = 50;   // 0..100 %
bool btn_display  = true;
int  enc_state    = 0;
int  old_state    = -1;
bool has_changed  = true;

Oggetti Bus e Driver Display

Lo stack Arduino_GFX è diviso in un oggetto bus e un oggetto pannello. Il bus incapsula l’host FSPI dell’ESP32 con DC, CS, SCLK e MOSI. L’oggetto pannello punta al controller GC9A01 e sa come inizializzare un pannello IPS rotondo. La rotazione è impostata a zero e il booleano finale indica che il pannello è IPS per selezionare la corretta inversione colore e gamma.

Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(
  TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI, GFX_NOT_DEFINED, FSPI, true
);
Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RES, 0 /* rotation */, true /* IPS */);

Routine di Servizio Interrupt Encoder

L’ISR dell’encoder rispecchia la logica precedente, ma la dimensione del passo è dell’uno percento per fronte per un controllo fine. La routine campiona il canale CLK per rilevare un fronte, confronta DT per determinare la direzione, aggiorna la percentuale, la limita e inverte il flag di cambiamento.

void IRAM_ATTR encoder_irq() {
  enc_state = digitalRead(ENCODER_CLK);
  if (enc_state != old_state) {
    brightness += (digitalRead(ENCODER_DT) == enc_state) ? 1 : -1;
    brightness = constrain(brightness, 0, 100);
    old_state = enc_state;
    has_changed = true;
  }
}

Routine di Servizio Interrupt Pulsante

Il pulsante dell’encoder è attivo basso. L’ISR legge il pin e inverte un booleano che rappresenta lo stato della retroilluminazione. Il disegno e l’aggiornamento PWM sono delegati al loop principale impostando il flag di cambiamento.

void IRAM_ATTR button_irq() {
  if (!digitalRead(ENCODER_BTN)) {
    btn_display = !btn_display;
    has_changed = true;
  }
}

Disegno Testo Statico

Un piccolo helper disegna un’etichetta del sito una volta all’avvio. La dimensione del testo e il cursore sono posizionati per un display rotondo 240×240. Nota che Arduino_GFX usa un cursore basato su pixel che avanza con le stampe.

void drawText() {
  gfx->setTextSize(2);
  gfx->setCursor(60, 55);
  gfx->println(F("Makerguides"));
}

Aggiornamento Luminosità e Retroilluminazione

Questa funzione aggiorna il valore numerico sullo schermo e applica il PWM al pin della retroilluminazione. L’area rettangolare dietro le cifre è cancellata in bianco per evitare effetti di ghosting durante il cambio dei numeri. La percentuale è mappata su un duty cycle a 8 bit, che analogWrite si traduce in un PWM hardware tramite LEDC dell’ESP32. Quando il toggle del display è spento, il duty è forzato a zero.

void updateBrightness() {
  gfx->fillRect(65, 95, 120, 65, WHITE);
  gfx->setTextSize(6);
  gfx->setCursor(65, 100);
  gfx->printf("%3d", brightness);

  int duty = map(brightness, 0, 100, 0, 255);
  analogWrite(TFT_BLK, btn_display ? duty : 0);
}

Inizializzazione Alimentazione

Le linee di alimentazione logica e anodo LED del pannello sono abilitate prima che vengano inviati comandi al display. Entrambe le abilitazioni sono portate alte. Il pin della retroilluminazione è configurato per PWM e impostato a bassa luminosità (50), rendendo il testo visibile ma non troppo luminoso.

void initPower() {
  pinMode(LCD_PWR_EN1, OUTPUT);
  pinMode(LCD_PWR_EN2, OUTPUT);
  digitalWrite(LCD_PWR_EN1, HIGH);   // must be HIGH
  digitalWrite(LCD_PWR_EN2, HIGH);   // must be HIGH

  pinMode(TFT_BLK, OUTPUT);
  analogWrite(TFT_BLK, 50); 
}

Inizializzazione Display

Dopo un breve ritardo per stabilizzare l’alimentazione, il driver GC9A01 viene avviato, lo schermo è pulito in bianco e il colore del testo impostato a nero. Da qui lo sketch può disegnare primitive e testo a piacere.

void initDisplay() {
  delay(20);                 
  gfx->begin();
  gfx->fillScreen(WHITE);
  gfx->setTextColor(BLACK);
}

Inizializzazione Encoder

I canali e il pulsante dell’encoder usano pull-up interni, tipici degli encoder a contatto aperto. Gli interrupt sono collegati al canale CLK per la decodifica quadratura e al pulsante per il toggle. Usare CHANGE cattura sia la pressione che il rilascio; l’ISR verifica la condizione attiva-bassa.

void initEncoder() {
  pinMode(ENCODER_BTN, INPUT_PULLUP);
  pinMode(ENCODER_CLK, INPUT_PULLUP);
  pinMode(ENCODER_DT,  INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), encoder_irq, CHANGE);
  attachInterrupt(digitalPinToInterrupt(ENCODER_BTN), button_irq, CHANGE);
}

Setup

Il sistema alimenta il pannello, arma gli interrupt dell’encoder, inizializza il display, disegna il titolo statico e imposta il flag di cambiamento per far partire subito il primo frame di luminosità e aggiornamento PWM. Il logging seriale è abilitato per visibilità durante i test.

void setup() {
  Serial.begin(115200);
  initPower();
  initEncoder();
  initDisplay();
  drawText();
  has_changed = true;
}

Loop

Il foreground controlla se le ISR hanno richiesto un aggiornamento. Quando impostato, ridisegna la luminosità numerica, imposta la luminosità della retroilluminazione e stampa una riga di stato con la percentuale e se la retroilluminazione è attualmente abilitata.

void loop() {
  if (has_changed) {
    has_changed = false;
    updateBrightness();
    Serial.printf("Brightness: %d%% (display %s)\n",
                  brightness, btn_display ? "ON" : "OFF");
  }
  delay(1);
}

Esempio di codice: Touch screen

Questo ultimo esempio mostra come leggere i dati touch dal controller capacitivo CST816D integrato nel CrowPanel 1.28-inch HMI ESP32 Rotary Display. Disegna punti rossi ovunque l’utente tocchi lo schermo. Vedi la foto d’esempio sotto:

Touch events as red dots on CrowPanel Display
Eventi touch come punti rossi sul CrowPanel Display

Il codice seguente riutilizza la stessa pipeline display Arduino_GFX introdotta prima, aggiunge la libreria Wire per letture I²C a basso livello e decodifica i registri raw del controller touch CST816D in coordinate schermo in tempo reale.

// ESP32 core: 3.3.2
// Arduino_GFX_Library:  1.6.2

#include <Wire.h>
#include <Arduino_GFX_Library.h>

#define TFT_SCLK 10
#define TFT_MOSI 11
#define TFT_MISO -1
#define TFT_DC    3
#define TFT_CS    9
#define TFT_RES   14
#define TFT_BLK   46

#define LCD_PWR_EN1 1   // LCD_3V3 rail
#define LCD_PWR_EN2 2   // LEDA_3V3 rail

#define TOUCH_INT 5
#define TOUCH_SDA 6
#define TOUCH_SCL 7
#define TOUCH_RST 13

Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(
  TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI, GFX_NOT_DEFINED, FSPI, true
);
Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RES, 0 /* rotation */, true /* IPS */);

int i2cRead(uint16_t addr, uint8_t reg_addr, uint8_t *reg_data, uint32_t length) {
  Wire.beginTransmission(addr);
  Wire.write(reg_addr);
  if (Wire.endTransmission(true)) return -1;
  Wire.requestFrom((int)addr, (int)length, (int)true);
  for (uint32_t i = 0; i < length && Wire.available(); i++) {
    *reg_data++ = Wire.read();
  }
  return 0;
}

int readTouch(int *x, int *y) {
  // CST816D @ 0x15, coordinates start at register 0x02
  uint8_t data_raw[7] = {0};
  if (i2cRead(0x15, 0x02, data_raw, 7) != 0) return 0;

  // data_raw[1] bits 7..6 = event (0:down,1:up,2:contact), low nibble high bits of X
  int event = data_raw[1] >> 6;
  if (event == 2 || event == 0) { // treat contact or down as a valid touch
    *x = (int)data_raw[2] + (int)(data_raw[1] & 0x0F) * 256;
    *y = (int)data_raw[4] + (int)(data_raw[3] & 0x0F) * 256;
    return 1;
  }
  return 0;
}

void initPins() {
  // Power rails for the LCD/backlight
  pinMode(LCD_PWR_EN1, OUTPUT);
  pinMode(LCD_PWR_EN2, OUTPUT);
  digitalWrite(LCD_PWR_EN1, HIGH);
  digitalWrite(LCD_PWR_EN2, HIGH);

  // Backlight
  pinMode(TFT_BLK, OUTPUT);
  analogWrite (TFT_BLK, 100);

  // Touch
  pinMode(TOUCH_INT, INPUT_PULLUP);
  pinMode(TOUCH_RST, OUTPUT);

  // Reset the CST816D
  digitalWrite(TOUCH_RST, LOW);
  delay(5);
  digitalWrite(TOUCH_RST, HIGH);
  delay(50);

  Wire.begin(TOUCH_SDA, TOUCH_SCL);
}

void initDisplay() {
  delay(20);              
  gfx->begin();
  gfx->fillScreen(WHITE);
  gfx->setTextColor(BLACK);
  gfx->setTextSize(3);
  gfx->setCursor(80, 105);
  gfx->print(F("Touch"));
}

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

void loop() {
  static int x, y;
  if (readTouch(&x, &y) == 1) {
    gfx->fillCircle(x, y, 5, RED);
  }
}

Importazioni

Due librerie sono incluse all’inizio del programma.
Wire.h gestisce la comunicazione sul bus I²C, usato per parlare con il controller touch.
Arduino_GFX_Library.h gestisce la comunicazione SPI con il display TFT rotondo GC9A01, permettendo di disegnare testo e forme facilmente.

#include <Wire.h>
#include <Arduino_GFX_Library.h>

Definizioni Pin

Questa sezione definisce tutte le assegnazioni pin per display, controller touch e linee di alimentazione.
I pin SPI collegano l’ESP32 al controller TFT GC9A01, mentre i pin I²C (TOUCH_SDA e TOUCH_SCL) comunicano con il chip touch CST816D. Pin di abilitazione separati controllano le linee 3.3 V che alimentano la logica LCD (LCD_PWR_EN1) e la retroilluminazione LED (LCD_PWR_EN2).

#define TFT_SCLK 10
#define TFT_MOSI 11
#define TFT_MISO -1
#define TFT_DC    3
#define TFT_CS    9
#define TFT_RES   14
#define TFT_BLK   46

#define LCD_PWR_EN1 1   // LCD_3V3 rail
#define LCD_PWR_EN2 2   // LEDA_3V3 rail

#define TOUCH_INT 5
#define TOUCH_SDA 6
#define TOUCH_SCL 7
#define TOUCH_RST 13

Oggetti Bus e Driver Display

La libreria Arduino_GFX separa il driver display in due oggetti. Arduino_ESP32SPI definisce la configurazione del bus SPI per la periferica FSPI dell’ESP32, inclusi pin dati e controllo. Arduino_GC9A01 rappresenta il controller specifico del display e gestisce comandi come inizializzazione, riempimenti colore e rendering testo.

Arduino_ESP32SPI *bus = new Arduino_ESP32SPI(
  TFT_DC, TFT_CS, TFT_SCLK, TFT_MOSI, GFX_NOT_DEFINED, FSPI, true
);
Arduino_GFX *gfx = new Arduino_GC9A01(bus, TFT_RES, 0 /* rotation */, true /* IPS */);

Questa configurazione corrisponde al display IPS rotondo 240×240 del CrowPanel.

La i2cRead() Funzione di Supporto

Questa funzione fornisce un modo generico per leggere uno o più registri da qualsiasi dispositivo I²C.
Inizia la comunicazione con l’indirizzo dispositivo specificato, scrive il registro da leggere e poi richiede un numero di byte dal dispositivo. Ogni byte è memorizzato nel buffer passato tramite reg_data. Se un passaggio fallisce, restituisce -1 per segnalare un errore.

int i2cRead(uint16_t addr, uint8_t reg_addr, uint8_t *reg_data, uint32_t length) {
  Wire.beginTransmission(addr);
  Wire.write(reg_addr);
  if (Wire.endTransmission(true)) return -1;
  Wire.requestFrom((int)addr, (int)length, (int)true);
  for (uint32_t i = 0; i < length && Wire.available(); i++) {
    *reg_data++ = Wire.read();
  }
  return 0;
}

Questa astrazione mantiene la funzione readTouch() più pulita e focalizzata sull’interpretazione dei dati touch piuttosto che sulla gestione delle transazioni bus.

Lettura Dati Touch con readTouch()

Il controller touch CST816D fornisce eventi touch e dati di coordinate via I²C all’indirizzo 0x15.
Questa funzione legge sette byte a partire dal registro 0x02, che contengono informazioni sullo stato touch corrente e le coordinate.

Se il codice evento indica un touch down o un evento di contatto (valori 0 o 2), la funzione decodifica le posizioni X e Y combinando i bit alti e bassi dei registri di coordinate.
Le coordinate valide sono restituite tramite i puntatori x e y, e la funzione restituisce 1 per segnalare un touch valido.

int readTouch(int *x, int *y) {
  uint8_t data_raw[7] = {0};
  if (i2cRead(0x15, 0x02, data_raw, 7) != 0) return 0;

  int event = data_raw[1] >> 6;
  if (event == 2 || event == 0) {
    *x = (int)data_raw[2] + (int)(data_raw[1] & 0x0F) * 256;
    *y = (int)data_raw[4] + (int)(data_raw[3] & 0x0F) * 256;
    return 1;
  }
  return 0;
}

Questa decodifica a basso livello segue il datasheet CST816D, dove i bit superiori di ogni byte di coordinata sono memorizzati nel nibble inferiore del byte precedente.

Inizializzazione Alimentazione e I/O con initPins()

Prima che il display o il controller touch possano funzionare, i GPIO appropriati devono essere configurati.
Questa funzione configura le linee di alimentazione LCD, il PWM della retroilluminazione e i pin dell’interfaccia touch. Entrambe le linee di abilitazione alimentazione LCD sono portate alte per fornire 3.3 V alla logica display e ai LED. Il pin retroilluminazione (TFT_BLK) è configurato per controllo PWM, inizializzato con un duty cycle moderato di 100 per una luminosità visibile.

La linea interrupt touch è configurata come ingresso con pull-up interno. Il pin reset CST816D è portato basso per alcuni millisecondi, poi rilasciato alto per riavviare correttamente il controller touch. Infine, il bus I²C è inizializzato sui pin SDA e SCL specificati.

void initPins() {
  pinMode(LCD_PWR_EN1, OUTPUT);
  pinMode(LCD_PWR_EN2, OUTPUT);
  digitalWrite(LCD_PWR_EN1, HIGH);
  digitalWrite(LCD_PWR_EN2, HIGH);

  pinMode(TFT_BLK, OUTPUT);
  analogWrite(TFT_BLK, 100);

  pinMode(TOUCH_INT, INPUT_PULLUP);
  pinMode(TOUCH_RST, OUTPUT);

  digitalWrite(TOUCH_RST, LOW);
  delay(5);
  digitalWrite(TOUCH_RST, HIGH);
  delay(50);

  Wire.begin(TOUCH_SDA, TOUCH_SCL);
}

Inizializzazione Display

Dopo un breve ritardo per garantire stabilità dell’alimentazione, il display GC9A01 è inizializzato. Lo schermo è pulito in bianco, il colore del testo impostato a nero e viene disegnata un’etichetta “Touch” al centro per indicare la prontezza.

void initDisplay() {
  delay(20);              
  gfx->begin();
  gfx->fillScreen(WHITE);
  gfx->setTextColor(BLACK);
  gfx->setTextSize(3);
  gfx->setCursor(80, 105);
  gfx->print(F("Touch"));
}

Questa configurazione crea una tela pulita per disegnare indicatori touch più avanti nel loop principale.

Setup

La funzione setup() è semplice. Inizializza l’interfaccia seriale per il debug, poi chiama sia initPins() che initDisplay() per preparare l’hardware.

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

A questo punto il display è attivo e il controller touch è pronto a riportare le coordinate.

Loop

Il loop principale controlla continuamente l’input touch usando la funzione readTouch(). Se viene rilevato un touch valido, disegna un piccolo cerchio rosso alle coordinate riportate usando la funzione fillCircle() della libreria Arduino_GFX. Ogni tocco crea un nuovo punto, permettendo all’utente di “disegnare” sullo schermo.

void loop() {
  static int x, y;
  if (readTouch(&x, &y) == 1) {
    gfx->fillCircle(x, y, 5, RED);
  }
}

Questo loop minimale evidenzia quanto il CST816D sia reattivo quando accesso tramite I²C e dimostra quanto facilmente si possa fornire un feedback grafico sul display GC9A01.

Conclusioni

Questo tutorial ti ha fornito esempi di codice per iniziare con il CrowPanel 1.28inch-HMI ESP32 Rotary Display.

Per altri esempi vedi Elecrows’s Github repo for the 1.28inch-HMI ESP32 Rotary Display e il Wiki Page. Nota però che molti esempi lì usano le librerie ui e lvgl, che ho evitato qui per mantenere bassa la complessità.

Se cerchi un modulo display simile con anello encoder rotativo, dai un’occhiata al Matouch 1.28″ ToolSet_Controller. Non ha l’anello LED RGB ma un Real-Time-Clock (RTC) e un motore a vibrazione per feedback aptico. Se invece ti serve solo un display rotondo (senza anello encoder rotativo), il tutorial Digital Clock on CrowPanel 1.28″ Round Display potrebbe esserti utile.

Inoltre, se vuoi imparare di più sugli encoder rotativi, dai un’occhiata al nostro tutorial How To Interface A Quadrature Rotary Encoder.

Se hai domande, sentiti libero di lasciarle nella sezione commenti.

Buon divertimento con il tinkering 😉