Skip to Content

Interruttore Timer Digitale con Display CrowPanel ESP32

Interruttore Timer Digitale con Display CrowPanel ESP32

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.

Demo dell’interruttore timer digitale

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.

CrowPanel 3.5" ESP32 Display with Housing
CrowPanel 3.5″ ESP32 Display con alloggiamento (source)

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:

Pinout of CrowPanel 3.5" ESP32 Display
Pinout del CrowPanel 3.5″ ESP32 Display

La tabella seguente elenca quali pin GPIO sono assegnati a ciascuna delle tre interfacce IO.

GPIO_DIO25; IO32
UARTRX(IO16); TX(IO17)
I2CSDA(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ì

Struttura della cartella del progetto

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

Arduino IDE with three Tabs
Arduino IDE con tre schede

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.

Calibration of touch screen
Calibrazione del touchscreen

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() e btnAlarm_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]);
  }
}

Hour and Day labels for Schedule
Etichette di ore e giorni per il programma

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);
}
A hour time slot
Una fascia oraria

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);
    }
  }
}
Hourly grid for Schedule
Griglia oraria per il programma

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.


Time and Date shown on Display
Ora e data mostrati sul display

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");
  }
}
Toggling time and schedule view
Alternanza tra visualizzazione ora e programma

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");
  }
}
Attivazione/disattivazione dell’interruttore allarme

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);
}
Buttons for View and Alarm
Pulsanti per visualizzazione e allarme

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:

LED connected to CrowPanel 3.5" ESP32 Display
LED collegato al CrowPanel 3.5″ ESP32 Display

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:

Wiring of LED with Crowpanel Display
Cablaggio del LED con CrowPanel Display

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:

Schedule set for 12:00-13:00 on Wednesday
Programma impostato per 12:00-13:00 di mercoledì

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:

Relè collegato al CrowPanel 3.5″ ESP32 Display

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:

Wiring of Relay with Crowpanel Display
Cablaggio del relè con 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.