In questo tutorial imparerai come costruire un interruttore timer digitale con il CrowPanel 3.5″ ESP32 Display. Sincronizzeremo l’orologio interno con un provider di tempo su internet per mantenerlo preciso e useremo la libreria TFT_eSPI per creare una bella interfaccia utente che renda facile impostare gli orari.
Un interruttore timer digitale è un dispositivo elettronico che accende o spegne automaticamente altri dispositivi a orari specifici impostati dall’utente. Di solito ha un display digitale per programmare gli orari di accensione/spegnimento. Può essere usato per controllare luci, elettrodomestici e altri dispositivi elettrici a orari prestabiliti. Per esempio, puoi usarlo per accendere le luci di casa al tramonto e spegnerle a ora di andare a dormire, oppure per programmare l’irrigazione del prato e spegnerla dopo una durata specifica per evitare eccessi d’acqua.

L’interruttore timer digitale che costruirai in questo tutorial sarà migliore della maggior parte di quelli commerciali. Il suo orologio sarà sempre preciso e non sarà necessario regolare l’ora legale. Inoltre, grazie al grande display sarà facile impostare o modificare gli orari.
Componenti necessari
Per questo progetto ti serviranno il CrowPanel 3.5″ ESP32 Display di ELECROW e l’Arduino IDE. Il pannello viene fornito con un cavo USB e un cavo DuPont a 4 pin, quindi non avrai bisogno di cavi extra.

CrowPanel 3.5″ ESP32 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.
Caratteristiche del CrowPanel 3.5″ ESP32 Display
Il CrowPanel 3.5″ ESP32 Display di ELECROW è un touchscreen resistivo con display TFT a risoluzione 480*320 e un ESP32-WROVER-B come processore di controllo integrato. Puoi acquistare il display con un elegante alloggiamento in acrilico (vedi immagine sotto), così non dovrai costruire una custodia da solo.

Inoltre, la scheda dispone di uno slot per schede TF, un’interfaccia UART, un’interfaccia I2C, un’interfaccia per altoparlante, un connettore per batteria con funzione di ricarica e una piccola porta GPIO con due pin GPIO. Vedi il pinout qui sotto:

La tabella seguente elenca quali pin GPIO sono assegnati a ciascuna delle tre interfacce IO.
| GPIO_D | IO25; IO32 |
| UART | RX(IO16); TX(IO17) |
| I2C | SDA(IO22); SCL(IO21) |
La scheda può essere alimentata tramite la porta USB (5V, 2A) o collegando una batteria LiPo standard da 3.7V al connettore BAT. Fai attenzione alla polarità del connettore e della batteria. Se il cavo USB e la batteria sono collegati contemporaneamente, la scheda caricherà la batteria (corrente massima di carica 500mA).
Puoi programmare la scheda usando vari ambienti di sviluppo come Arduino IDE, Espressif IDF, Lua RTOS e Micro Python, ed è compatibile con il LVGL graphics library. Tuttavia, in questo tutorial useremo Arduino IDE e la TFT_eSPI graphics library.
Crea la struttura del progetto
Prima di entrare nei dettagli, creiamo la struttura del progetto e la configurazione per il TFT_eSPI library.
Apri il tuo Arduino IDE e crea un progetto “digital_timer_switch” e salvalo (Salva con nome …). Questo creerà una cartella “digital_timer_switch” con il file “digital_timer_switch.ino” al suo interno. In questa cartella crea un altro file chiamato “datestrs.h” e un terzo chiamato “tft_setup.h“. La tua cartella di progetto dovrebbe apparire così

e nel tuo Arduino IDE dovresti ora avere tre schede chiamate “digital_timer_switch.ino“, “datestrs.h” e “tft_setup.h“.

Clicca sulla scheda “tft_setup.h” per aprire il file e copia il seguente codice al suo interno:
// tft_setup.h // makerguides.com #define ILI9488_DRIVER #define TFT_HEIGHT 480 #define TFT_WIDTH 320 #define TFT_BACKLIGHT_ON HIGH #define TFT_BL 27 #define TFT_MISO 12 #define TFT_MOSI 13 #define TFT_SCLK 14 #define TFT_CS 15 #define TFT_DC 2 #define TFT_RST -1 #define TOUCH_CS 33 #define SPI_FREQUENCY 27000000 #define SPI_TOUCH_FREQUENCY 2500000 #define SPI_READ_FREQUENCY 16000000 #define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH #define LOAD_FONT2 // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters #define LOAD_FONT4 // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters #define LOAD_FONT6 // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm #define LOAD_FONT7 // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-. #define LOAD_FONT8 // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-. #define LOAD_GFXFF // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts
Questo codice indica al TFT_eSPI library quale display stiamo usando. In particolare, informiamo la libreria delle dimensioni del display (TFT_WIDTH, TFT_WIDTH), del driver del display (ILI9488_DRIVER), quali pin SPI sono usati per controllarlo e quali font caricare. Senza queste impostazioni non potrai visualizzare nulla sul display.
Per informazioni più dettagliate dai un’occhiata al tutorial CrowPanel 2.8″ ESP32 Display : Easy Setup Guide. Lì spieghiamo come configurare il display da 2.8″. Ma i passaggi e le descrizioni valgono allo stesso modo per il display da 3.5″. Cambiano solo le impostazioni per le dimensioni dello schermo e il driver.
Calibrazione del touchscreen
Il CrowPanel 3.5″ Display ha un touchscreen resistivo che devi calibrare prima di poterlo usare con la libreria TFT_eSPI. Copia il codice qui sotto nel file “digital_timer_switch.ino“, compilalo e caricalo sul CrowPanel 3.5” Display.
#include "tft_setup.h"
#include "TFT_eSPI.h"
TFT_eSPI tft = TFT_eSPI();
void setup() {
Serial.begin(115200);
tft.begin();
tft.setRotation(0);
}
void loop() {
uint16_t cal[5];
tft.fillScreen(TFT_BLACK);
tft.setCursor(20, 0);
tft.setTextFont(2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.print("Touch corners ... ");
tft.calibrateTouch(cal, TFT_MAGENTA, TFT_BLACK, 15);
tft.println("done.");
Serial.printf("cal: {%d, %d, %d, %d, %d}\n",
cal[0], cal[1], cal[2], cal[3], cal[4]);
delay(10000);
}
Quando il codice è in esecuzione, il display mostra una freccia e ti chiede di toccare l’angolo verso cui punta. Questo verrà ripetuto per gli altri tre angoli. Usa la piccola penna fornita con il display e cerca di toccare gli angoli con la massima precisione possibile.

Al termine della calibrazione, il codice stampa i 5 parametri di calibrazione (coordinate degli angoli e orientamento dello schermo) sul monitor seriale. Dovresti vedere qualcosa del genere:
cal: { 260, 3518, 270, 3629, 4 }
Copia questi parametri da qualche parte, perché ti serviranno nel codice dell’interruttore timer digitale. La calibrazione si ripete ogni 10 secondi, così puoi fare più tentativi per ottenere i parametri più precisi.
Per maggiori dettagli sul processo di calibrazione dai un’occhiata al tutorial CrowPanel 2.8″ ESP32 Display : Easy Setup Guide.
Codice per l’interruttore timer digitale
In questa sezione scriveremo il codice completo per l’interruttore timer digitale. Inizia copiando il seguente codice nel file “datestrs.h” che si trova nella tua cartella di progetto digital_timer_switch.
// datestrs.h
const char *DAYSTR[] = {
"Su",
"Mo",
"Tu",
"We",
"Th",
"Fr",
"Sa"
};
const char *MONTHSTR[] = {
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
};
Poi copia tutto il codice qui sotto nel file “digital_timer_switch.ino” e sostituisci il codice di calibrazione presente.
Questo è il codice principale. Permette agli utenti di impostare allarmi basati su un programma settimanale, visualizzare l’ora corrente e attivare o disattivare gli allarmi. Il display si aggiorna in tempo reale e l’utente può interagire tramite pulsanti touch. Dai una lettura veloce e poi discuteremo i dettagli del codice.
// digital_timer_switch.ino
#include "tft_setup.h"
#include "TFT_eSPI.h"
#include "TFT_eWidget.h"
#include "stdarg.h"
#include "WiFi.h"
#include "esp_sntp.h"
#include "datestrs.h"
const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3";
const char* SSID = "SSID";
const char* PWD = "PASSWORD";
const int rs = 24, cs = 7;
const int mx = 60, my = 40;
const int gw = TFT_WIDTH - mx - 30;
const int gh = TFT_HEIGHT - my - 50;
const int cw = gw / cs;
const int ch = gh / rs;
const int alarmPin = 25;
bool schedule[rs][cs];
bool isTimeView = true;
bool isAlarmOn = false;
uint16_t cal[5] = { 260, 3518, 270, 3629, 4 };
TFT_eSPI tft = TFT_eSPI();
ButtonWidget btnView = ButtonWidget(&tft);
ButtonWidget btnAlarm = ButtonWidget(&tft);
ButtonWidget* btns[] = { &btnView, &btnAlarm };
int c2x(int c) {
return mx + gw * c / cs;
}
int r2y(int r) {
return my + gh * r / rs;
}
int x2c(int x) {
return map(x - mx, 0, gw, 0, cs);
}
int y2r(int y) {
return map(y - my, 0, gh, 0, rs);
}
void drawLabels() {
tft.setTextFont(1);
tft.setTextSize(2);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
for (int r = 0; r < rs; r++) {
tft.setCursor(24, r2y(r));
tft.printf("%02d", r);
}
for (int c = 0; c < cs; c++) {
tft.setCursor(c2x(c), 15);
tft.print(DAYSTR[c]);
}
}
void drawSlot(int r, int c) {
uint32_t color = schedule[r][c] ? TFT_WHITE : TFT_BLACK;
tft.fillRect(c2x(c) + 1, r2y(r) + 1, cw - 2, ch - 2, color);
}
void drawSchedule() {
for (int r = 0; r < rs; r++) {
for (int c = 0; c < cs; c++) {
tft.drawRect(c2x(c), r2y(r), cw, ch, TFT_DARKGREY);
drawSlot(r, c);
}
}
}
void selectSlot(int x, int y) {
int c = x2c(x);
int r = y2r(y);
if (c < 0 || c >= cs || r < 0 || r >= rs) return;
schedule[r][c] = !schedule[r][c];
drawSlot(r, c);
}
void initWiFi() {
WiFi.begin(SSID, PWD);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
}
void initSNTP() {
sntp_set_sync_interval(15 * 60 * 1000UL); // 15 minutes
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
esp_sntp_setservername(0, "pool.ntp.org");
esp_sntp_init();
setTimezone();
}
void setTimezone() {
setenv("TZ", TIMEZONE, 1);
tzset();
}
void drawTime() {
static char buff[50];
static struct tm t;
getLocalTime(&t);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.setTextSize(1);
sprintf(buff, " %2d:%02d:%02d ", t.tm_hour, t.tm_min, t.tm_sec);
tft.drawString(buff, tft.width() / 2, 130, 7);
sprintf(buff, " %s, %s %d, %d ",
DAYSTR[t.tm_wday], MONTHSTR[t.tm_mon],
t.tm_mday, t.tm_year + 1900);
tft.drawString(buff, tft.width() / 2, 200, 4);
}
bool isOn() {
static struct tm t;
getLocalTime(&t);
return schedule[t.tm_hour][t.tm_wday] && isAlarmOn;
}
void initDisplay() {
tft.begin();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
tft.setTouch(cal);
}
void btnView_pressed(void) {
if (btnView.justPressed()) {
isTimeView = !btnView.getState();
drawView();
tft.setTextFont(4);
tft.setTextSize(1);
btnView.drawSmoothButton(isTimeView, 1, TFT_BLACK, isTimeView ? "alarm" : "time");
}
}
void btnAlarm_pressed(void) {
if (btnAlarm.justPressed()) {
isAlarmOn = !btnAlarm.getState();
tft.setTextFont(4);
tft.setTextSize(1);
btnAlarm.drawSmoothButton(isAlarmOn, 1, TFT_DARKGREY, isAlarmOn ? "on" : "off");
}
}
void initButtons() {
uint16_t w = 100;
uint16_t h = 50;
uint16_t y = tft.height() - h + 14;
uint16_t x = tft.width() / 2;
tft.setTextFont(4);
tft.setTextSize(1);
btnView.initButtonUL(x - w - 10, y, w, h, TFT_DARKGREY, TFT_DARKGREY, TFT_BLACK, "alarm", 1);
btnView.setPressAction(btnView_pressed);
btnView.drawSmoothButton(isTimeView, 1, TFT_BLACK);
btnAlarm.initButtonUL(x + 10, y, w, h, TFT_DARKGREY, TFT_BLACK, TFT_DARKGREY, "off", 1);
btnAlarm.setPressAction(btnAlarm_pressed);
btnAlarm.drawSmoothButton(isAlarmOn, 1, TFT_BLACK);
}
void handleButtons() {
tft.setTextFont(4);
uint8_t nBtns = sizeof(btns) / sizeof(btns[0]);
uint16_t x = 0, y = 0;
bool touched = tft.getTouch(&x, &y);
for (uint8_t b = 0; b < nBtns; b++) {
if (touched) {
if (btns[b]->contains(x, y)) {
btns[b]->press(true);
btns[b]->pressAction();
}
} else {
btns[b]->press(false);
btns[b]->releaseAction();
}
}
}
void drawView() {
tft.fillScreen(TFT_BLACK);
initButtons();
if (isTimeView) {
drawTime();
} else {
drawSchedule();
drawLabels();
}
}
void updateView() {
static uint16_t x = 0, y = 0;
if (isTimeView) {
drawTime();
} else {
if (tft.getTouch(&x, &y)) {
selectSlot(x, y);
}
}
}
void setup() {
initWiFi();
initSNTP();
initDisplay();
pinMode(alarmPin, OUTPUT);
drawView();
}
void loop() {
updateView();
handleButtons();
digitalWrite(alarmPin, isOn() ? HIGH : LOW);
delay(100);
}
Come puoi vedere, è un bel po’ di codice. La sezione seguente ti offre una panoramica rapida delle funzioni e del loro significato.
Panoramica delle funzioni
drawLabels(): Disegna le etichette per le righe e le colonne sul display.drawSlot(int r, int c): Disegna una fascia oraria sulla griglia del programma basandosi sulla riga e colonna fornite.drawSchedule(): Disegna l’intera griglia del programma sul display.selectSlot(int x, int y): Permette agli utenti di selezionare una fascia oraria sulla griglia toccando il display.initWiFi(): Inizializza la connessione WiFi usando SSID e password forniti.initSNTP(): Inizializza il protocollo SNTP (Simple Network Time Protocol) per la sincronizzazione dell’ora.setTimezone(): Imposta il fuso orario del dispositivo basandosi sulla costante fornita.drawTime(): Disegna l’ora e la data correnti sul display.isOn(): Controlla se l’ora corrente corrisponde a una fascia programmata e se l’allarme è abilitato.initDisplay(): Inizializza il display e configura la funzionalità touch.btnView_pressed()ebtnAlarm_pressed(): Gestiscono gli eventi di pressione dei pulsanti per passare dalla visualizzazione dell’ora a quella del programma e per attivare/disattivare l’allarme.initButtons(): Inizializza i pulsanti sul display per interagire con il progetto.handleButtons(): Gestisce le azioni di pressione e rilascio dei pulsanti sul display.drawView(): Disegna la visualizzazione dell’ora o del programma in base alla modalità corrente.updateView(): Aggiorna il display in base alle interazioni dell’utente e alla modalità corrente.
Diamo un’occhiata più da vicino a queste funzioni e ad altre parti del codice.
Inclusione delle librerie
Il codice inizia includendo diverse librerie necessarie per il funzionamento dell’ESP32 e del display. Servono librerie per il display TFT, la connessione Wi-Fi, la sincronizzazione dell’ora con un server SNTP e i nomi di mesi e giorni (datestrsl.h)
#include "tft_setup.h" #include "TFT_eSPI.h" #include "TFT_eWidget.h" #include "stdarg.h" #include "WiFi.h" #include "esp_sntp.h" #include "datestrs.h"
Costanti e variabili
Successivamente definiamo diverse costanti e variabili che saranno usate nel programma. Queste includono credenziali Wi-Fi, dimensioni del display, pin dell’allarme e un array per il programma.
const char* TIMEZONE = "AEST-10AEDT,M10.1.0,M4.1.0/3"; const char* SSID = "SSID"; const char* PWD = "PASSWORD"; const int rs = 24, cs = 7; const int alarmPin = 25; bool schedule[rs][cs]; bool isTimeView = true; bool isAlarmOn = false;
Dovrai impostare il SSID e la PWD per il tuo WiFi e probabilmente dovrai anche cambiare il TIMEZONE. La specifica del fuso orario “AEST-10AEDT,M10.1.0,M4.1.0/3” è per l’Australia, corrispondente all’Australian Eastern Standard Time (AEST) con aggiustamenti per l’ora legale.
Le parti di questa definizione del fuso orario sono le seguenti
- AEST: Australian Eastern Standard Time
- -10: offset UTC di 10 ore avanti rispetto al Tempo Coordinato Universale (UTC)
- AEDT: Australian Eastern Daylight Time
- M10.1.0: il passaggio all’ora legale avviene la prima domenica di ottobre
- M4.1.0/3: il ritorno all’ora standard avviene la prima domenica di aprile, con una differenza di 3 ore da UTC.
Per altre definizioni di fusi orari dai un’occhiata al Posix Timezones Database. Basta copiare e incollare la stringa che trovi lì e modificare la costante TIMEZONE di conseguenza.
Funzioni di conversione coordinate
Le seguenti funzioni convertono tra sistemi di coordinate per il display:
int c2x(int c) { ... }
int r2y(int r) { ... }
int x2c(int x) { ... }
int y2r(int y) { ... }
c2x() e r2x() mappano colonne e righe (c,r) nella griglia del programma alle coordinate dello schermo (x,y), mentre x2c() e y2r() eseguono l’operazione inversa. Sono necessarie per convertire un evento touch in una prenotazione di una fascia oraria, per esempio.
Funzioni di disegno
Il codice include diverse funzioni per disegnare elementi sul display:
drawLabels
Questa funzione disegna le etichette per le righe (ore) e le colonne (giorni della settimana) del programma.
void drawLabels() {
tft.setTextFont(1);
tft.setTextSize(2);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
for (int r = 0; r < rs; r++) {
tft.setCursor(24, r2y(r));
tft.printf("%02d", r);
}
for (int c = 0; c < cs; c++) {
tft.setCursor(c2x(c), 15);
tft.print(DAYSTR[c]);
}
}

drawSlot
Questa funzione disegna una singola fascia oraria nel programma, riempiendola di bianco se l’allarme è impostato per quell’ora.
void drawSlot(int r, int c) {
uint32_t color = schedule[r][c] ? TFT_WHITE : TFT_BLACK;
tft.fillRect(c2x(c) + 1, r2y(r) + 1, cw - 2, ch - 2, color);
}

drawSchedule
Questa funzione disegna l’intera griglia del programma, chiamando drawSlot() per ogni fascia oraria.
void drawSchedule() {
for (int r = 0; r < rs; r++) {
for (int c = 0; c < cs; c++) {
tft.drawRect(c2x(c), r2y(r), cw, ch, TFT_DARKGREY);
drawSlot(r, c);
}
}
}

selectSlot
Questa funzione cambia stato e colore di una fascia oraria quando l’utente la tocca sul display.
void selectSlot(int x, int y) {
int c = x2c(x);
int r = y2r(y);
if (c < 0 || c >= cs || r < 0 || r >= rs) return;
schedule[r][c] = !schedule[r][c];
drawSlot(r, c);
}
Inizializzazione Wi-Fi e SNTP
Le seguenti funzioni gestiscono l’inizializzazione di Wi-Fi e SNTP (Simple Network Time Protocol):
initWiFi
Questa funzione connette l’ESP32 alla rete Wi-Fi specificata.
void initWiFi() {
WiFi.begin(SSID, PWD);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
}
initSNTP
Questa funzione configura il servizio SNTP per sincronizzare l’ora. Si connette a un server internet che fornisce informazioni temporali molto precise e sincronizza di conseguenza l’orologio interno dell’ESP32.
void initSNTP() {
sntp_set_sync_interval(15 * 60 * 1000UL); // 15 minutes
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
esp_sntp_setservername(0, "pool.ntp.org");
esp_sntp_init();
setTimezone();
}
Finché c’è una connessione Wi-Fi e internet, questo garantisce che l’orologio del nostro interruttore timer digitale sia sempre preciso e si regoli automaticamente per l’ora legale. Per maggiori dettagli dai un’occhiata al tutorial How to synchronize ESP32 clock with SNTP server.
setTimezone
Questa funzione imposta il fuso orario per l’applicazione. Devi assicurarti che questa funzione venga eseguita ogni volta che l’ESP32 si avvia, altrimenti l’orologio sarà sfasato.
void setTimezone() {
setenv("TZ", TIMEZONE, 1);
tzset();
}
Funzione di visualizzazione dell’ora
La funzione drawTime() recupera l’ora locale corrente, sincronizzata via SNTP, e mostra ora e data sullo schermo.
void drawTime() {
static char buff[50];
static struct tm t;
getLocalTime(&t);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.setTextSize(1);
sprintf(buff, " %2d:%02d:%02d ", t.tm_hour, t.tm_min, t.tm_sec);
tft.drawString(buff, tft.width() / 2, 130, 7);
sprintf(buff, " %s, %s %d, %d ",
DAYSTR[t.tm_wday], MONTHSTR[t.tm_mon],
t.tm_mday, t.tm_year + 1900);
tft.drawString(buff, tft.width() / 2, 200, 4);
}
La funzione usa le costanti per i nomi di giorni e mesi memorizzate nel file datestrs.h.

Se ti servono più dettagli dai un’occhiata ai tutorial Digital Clock with CrowPanel 3.5″ ESP32 Display e Digital Clock on e-Paper Display dove implementiamo anche un orologio digitale.
Funzione di controllo allarme
La funzione isOn() verifica se l’allarme deve essere attivato in base all’ora corrente e al programma. Se la cella della griglia schedule[t.tm_hour][t.tm_wday] contiene true e l’interruttore generale dell’allarme è acceso (isAlarmOn), la funzione restituisce true, altrimenti false.
bool isOn() {
static struct tm t;
getLocalTime(&t);
return schedule[t.tm_hour][t.tm_wday] && isAlarmOn;
}
Inizializzazione del display
La funzione initDisplay() inizializza il display impostando la rotazione dello schermo, riempiendo lo schermo di nero e impostando i parametri di calibrazione per il touchscreen.
void initDisplay() {
tft.begin();
tft.setRotation(0);
tft.fillScreen(TFT_BLACK);
tft.setTouch(cal);
}
Funzioni dei pulsanti
Il codice include funzioni per gestire la pressione dei pulsanti per passare dalla visualizzazione dell’ora alle impostazioni dell’allarme:
btnView_pressed
Questa funzione alterna la visualizzazione tra l’ora corrente e il programma degli allarmi.
void btnView_pressed(void) {
if (btnView.justPressed()) {
isTimeView = !btnView.getState();
drawView();
tft.setTextFont(4);
tft.setTextSize(1);
btnView.drawSmoothButton(isTimeView, 1, TFT_BLACK, isTimeView ? "alarm" : "time");
}
}

btnAlarm_pressed
Questa funzione attiva o disattiva l’interruttore generale dell’allarme. Se l’interruttore è spento, il programma degli allarmi è disabilitato.
void btnAlarm_pressed(void) {
if (btnAlarm.justPressed()) {
isAlarmOn = !btnAlarm.getState();
tft.setTextFont(4);
tft.setTextSize(1);
btnAlarm.drawSmoothButton(isAlarmOn, 1, TFT_DARKGREY, isAlarmOn ? "on" : "off");
}
}

Inizializzazione dei pulsanti
La funzione initButtons() inizializza i pulsanti sul display.
void initButtons() {
uint16_t w = 100;
uint16_t h = 50;
uint16_t y = tft.height() - h + 14;
uint16_t x = tft.width() / 2;
tft.setTextFont(4);
tft.setTextSize(1);
btnView.initButtonUL(x - w - 10, y, w, h, TFT_DARKGREY, TFT_DARKGREY, TFT_BLACK, "alarm", 1);
btnView.setPressAction(btnView_pressed);
btnView.drawSmoothButton(isTimeView, 1, TFT_BLACK);
btnAlarm.initButtonUL(x + 10, y, w, h, TFT_DARKGREY, TFT_BLACK, TFT_DARKGREY, "off", 1);
btnAlarm.setPressAction(btnAlarm_pressed);
btnAlarm.drawSmoothButton(isAlarmOn, 1, TFT_BLACK);
}

Gestione dei pulsanti
La funzione handleButtons() controlla la pressione dei pulsanti ed esegue le azioni corrispondenti.
void handleButtons() {
tft.setTextFont(4);
uint8_t nBtns = sizeof(btns) / sizeof(btns[0]);
uint16_t x = 0, y = 0;
bool touched = tft.getTouch(&x, &y);
for (uint8_t b = 0; b < nBtns; b++) {
if (touched) {
if (btns[b]->contains(x, y)) {
btns[b]->press(true);
btns[b]->pressAction();
}
} else {
btns[b]->press(false);
btns[b]->releaseAction();
}
}
}
Funzione di disegno della visualizzazione
La funzione drawView() aggiorna il display in base alla modalità di visualizzazione corrente (ora o programma).
void drawView() {
tft.fillScreen(TFT_BLACK);
initButtons();
if (isTimeView) {
drawTime();
} else {
drawSchedule();
drawLabels();
}
}
Funzione di aggiornamento della visualizzazione
La funzione updateView() aggiorna il display in base alle interazioni dell’utente.
void updateView() {
static uint16_t x = 0, y = 0;
if (isTimeView) {
drawTime();
} else {
if (tft.getTouch(&x, &y)) {
selectSlot(x, y);
}
}
}
Funzione di setup
La funzione setup() inizializza Wi-Fi, SNTP, display e imposta GPIO25 in modalità output. Collegheremo un LED o un relè a questo pin e li controlleremo tramite il programma degli allarmi.
void setup() {
initWiFi();
initSNTP();
initDisplay();
pinMode(alarmPin, OUTPUT);
drawView();
}
Funzione loop
Infine, la funzione loop() aggiorna continuamente la visualizzazione, gestisce la pressione dei pulsanti e controlla il pin dell’allarme in base al programma.
void loop() {
updateView();
handleButtons();
digitalWrite(alarmPin, isOn() ? HIGH : LOW);
delay(100);
}
E questo è il codice completo per un interruttore timer digitale. Nelle prossime due sezioni ti mostro come collegare un LED o un relè al CrowPanel Display, che poi potremo controllare tramite il programma degli allarmi.
Collegare un LED all’interruttore timer digitale
Per testare che il codice del nostro interruttore timer digitale funzioni, colleghiamo un LED a GPIO25 come segue:

Assicurati che il pin corto (catodo) del LED sia collegato a massa (filo nero) e il pin lungo tramite una resistenza a GPIO25 (filo giallo). Il CrowPanel viene fornito con un connettore codificato a colori, quindi seguendo i colori non puoi sbagliare. La foto seguente mostra il cablaggio reale con i cavi codificati a colori:

Per testare l’interruttore timer digitale leggi l’ora e la data correnti, ad esempio 12:37:00 di mercoledì, e poi imposta la fascia oraria corrispondente nel programma degli allarmi:

Se il pulsante generale dell’allarme è attivo, il LED dovrebbe accendersi. Puoi spegnerlo disattivando il pulsante generale o modificando il programma.
Collegare un relè all’interruttore timer digitale
Se vuoi controllare le luci di casa o la pompa dell’irrigazione ti servirà un relè. Il diagramma seguente mostra come collegarlo:

VCC (cavo rosso) e GND (cavo nero) sono collegati ai pin corrispondenti su un modulo relè. GPIO25 è collegato al pin di ingresso (cavo giallo) del relè.
Il dispositivo ad alta tensione e alta corrente che vuoi controllare è collegato ai terminali COM e NC (normalmente aperto) del modulo relè.Fai molta attenzione quando lavori con tensioni superiori a 50V! Devi assicurarti che il modulo relè sia adatto per la tensione e la corrente che vuoi commutare. I moduli relè tipici (AITRIP, HiLetgo) possono commutare 220V a 4A fino a 10A.
La foto sotto mostra il cablaggio del modulo relè con il CrowPanel Display:

Conclusione
In questo tutorial hai imparato come costruire un interruttore timer digitale con il CrowPanel 3.5″ ESP32 Display. Rispetto a molti prodotti commerciali, il nostro interruttore timer digitale ha sempre un orologio preciso e, grazie al grande display, è molto facile impostare e modificare rapidamente i programmi.
Ci sono molte possibili estensioni a questo progetto. Potresti voler impostare programmi con risoluzione a minuti o secondi. Oppure programmare un calendario per un intero mese o anno.
Invece di collegare un relè potresti anche usare Bluetooth, MQTT o altri protocolli per controllare dispositivi in modalità wireless. Il codice sopra ti fornisce una base per farlo.
Se hai domande sentiti libero di lasciarle nella sezione commenti.
Buon divertimento con il tinkering 😉
Link
Ecco alcuni link che ho trovato utili per scrivere questo post.
- CrowPanel 3.5″-ESP32 Display
- CrowPanel ESP32 Display Wiki
- CrowPanel ESP32 Display User_Manual
- CrowPanel 3.5″ Schematic Diagram
- CrowPanel ESP32 Display Video Tutorials
- TFT 3.5″ Touch Screen & ESP32 built in – Elecrow review
- Getting Started Tips 3.5″ Elecrow TFT ESP32

