Skip to Content

Digitaler Zeitschalter mit CrowPanel ESP32 Display

Digitaler Zeitschalter mit CrowPanel ESP32 Display

In diesem Tutorial lernst du, wie man einen digitalen Zeitschalter mit dem CrowPanel 3.5″ ESP32 Display baut. Wir werden die interne Uhr mit einem Internet-Zeitserver synchronisieren, um sie genau zu halten, und die TFT_eSPI-Bibliothek verwenden, um eine ansprechende Benutzeroberfläche zu erstellen, die das Einstellen der Zeiten erleichtert.

Ein digitaler Zeitschalter ist ein elektronisches Gerät, das andere Geräte automatisch zu vom Benutzer festgelegten Zeiten ein- oder ausschaltet. Er verfügt typischerweise über ein digitales Display zur Programmierung der Ein-/Aus-Zeiten. Er kann verwendet werden, um Lichter, Haushaltsgeräte und andere elektrische Geräte zu vorgegebenen Zeiten zu steuern. Zum Beispiel kannst du damit Lichter in deinem Zuhause bei Sonnenuntergang einschalten und sie zur Schlafenszeit ausschalten oder deine Sprinkleranlage so programmieren, dass sie den Rasen bewässert und nach einer bestimmten Dauer wieder abschaltet, um Überwässerung zu vermeiden.

Demo des digitalen Zeitschalters

Der digitale Zeitschalter, den du in diesem Tutorial baust, ist besser als die meisten handelsüblichen digitalen Zeitschalter. Seine Zeit ist immer genau und es ist keine Umstellung auf Sommerzeit nötig. Außerdem macht das große Display das Einstellen oder Ändern der Zeitpläne sehr einfach.

Benötigte Teile

Für dieses Projekt benötigst du das CrowPanel 3,5″ ESP32 Display von ELECROW und die Arduino IDE. Das Panel wird mit einem USB-Kabel und einem 4-poligen DuPont-Kabel geliefert, sodass keine zusätzlichen Kabel erforderlich sind.

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.

Eigenschaften des CrowPanel 3,5″ ESP32 Displays

Das CrowPanel 3.5″ ESP32 Display von ELECROW ist ein resistiver Touchscreen mit einem TFT-Display mit 480*320 Pixeln und einem integrierten ESP32-WROVER-B als Steuerprozessor. Du kannst das Display mit einem schönen Acrylgehäuse (siehe Bild unten) erhalten, was bedeutet, dass du kein eigenes Gehäuse bauen musst.

CrowPanel 3.5" ESP32 Display with Housing
CrowPanel 3,5″ ESP32 Display mit Gehäuse (source)

Darüber hinaus verfügt das Board über einen TF-Kartensteckplatz, eine UART-Schnittstelle, eine I2C-Schnittstelle, eine Lautsprecherschnittstelle, einen Batterieanschluss mit Ladefunktion und einen kleinen GPIO-Port mit zwei GPIO-Pins. Siehe das Pinout unten:

Pinout of CrowPanel 3.5" ESP32 Display
Pinbelegung des CrowPanel 3,5″ ESP32 Displays

Die folgende Tabelle zeigt, welche GPIO-Pins welchen der drei IO-Schnittstellen zugeordnet sind.

GPIO_DIO25; IO32
UARTRX(IO16); TX(IO17)
I2CSDA(IO22); SCL(IO21)

Das Board kann über den USB-Anschluss (5V, 2A) oder durch Anschluss einer Standard-3,7V-LiPo-Batterie an den BAT-Anschluss mit Strom versorgt werden. Achte dabei auf die Polarität des Steckers und der Batterie. Wenn USB-Kabel und Batterie gleichzeitig angeschlossen sind, lädt das Board die Batterie (maximaler Ladestrom 500mA).

Du kannst das Board mit verschiedenen Entwicklungsumgebungen programmieren, wie Arduino IDE, Espressif IDF, Lua RTOS und Micro Python, und es ist kompatibel mit dem LVGL graphics library. In diesem Tutorial verwenden wir jedoch die Arduino IDE und die TFT_eSPI graphics library.

Projektstruktur erstellen

Bevor wir ins Detail gehen, erstellen wir die Projektstruktur und das Setup für das TFT_eSPI library.

Öffne deine Arduino IDE und erstelle ein Projekt „digital_timer_switch“ und speichere es (Speichern unter …). Dadurch wird ein Ordner „digital_timer_switch“ mit der Datei „digital_timer_switch.ino“ darin erstellt. In diesem Ordner erstelle eine weitere Datei namens „datestrs.h“ und eine dritte namens „tft_setup.h„. Dein Projektordner sollte dann so aussehen

Projektordnerstruktur

Und in deiner Arduino IDE solltest du jetzt drei Tabs mit den Namen „digital_timer_switch.ino„, „datestrs.h“ und „tft_setup.h“ sehen.

Arduino IDE with three Tabs
Arduino IDE mit drei Tabs

Klicke auf den Tab „tft_setup.h„, um die Datei zu öffnen, und kopiere den folgenden Code hinein:

// 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

Dieser Code teilt der TFT_eSPI library mit, welches Display wir verwenden. Genauer gesagt, informieren wir die Bibliothek über die Abmessungen des Displays (TFT_WIDTH, TFT_WIDTH), den Display-Treiber (ILI9488_DRIVER), welche SPI-Pins zur Steuerung verwendet werden und welche Schriftarten geladen werden sollen. Ohne diese Einstellungen kannst du nichts auf dem Display anzeigen.

Für detailliertere Informationen sieh dir das Tutorial CrowPanel 2.8″ ESP32 Display : Easy Setup Guide an. Dort erklären wir, wie man das 2,8″ Display einrichtet. Die Schritte und Beschreibungen gelten jedoch genauso für das 3,5″ Display, nur die Einstellungen für Bildschirmgröße und Treiber sind unterschiedlich.

Touchscreen kalibrieren

Das CrowPanel 3,5″ Display verfügt über einen resistiven Touchscreen, den du zuerst kalibrieren musst, bevor du ihn mit der TFT_eSPI-Bibliothek verwenden kannst. Kopiere den folgenden Code in die Datei „digital_timer_switch.ino„, kompiliere ihn und lade ihn auf das CrowPanel 3,5“ Display hoch.

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

Wenn der Code läuft, zeigt das Display einen Pfeil und fordert dich auf, die Ecke zu berühren, auf die er zeigt. Dies wird für alle vier Ecken wiederholt. Verwende den kleinen Stift, der mit dem Display geliefert wird, und versuche, die Ecken so genau wie möglich zu treffen.

Calibration of touch screen
Kalibrierung des Touchscreens

Am Ende des Kalibrierungsprozesses gibt der Code die 5 Kalibrierungsparameter (Eckkoordinaten und Bildschirmausrichtung) im Serial Monitor aus. Du solltest etwas Ähnliches sehen:

cal: { 260, 3518, 270, 3629, 4 }

Speichere die Parameter irgendwo, da du sie im Code des digitalen Zeitschalters benötigen wirst. Die Kalibrierung wiederholt sich alle 10 Sekunden, sodass du mehrere Versuche hast, die genauesten Parameter zu erhalten.

Für detailliertere Informationen zum Kalibrierungsprozess siehe das CrowPanel 2.8″ ESP32 Display : Easy Setup Guide Tutorial.

Code für den digitalen Zeitschalter

In diesem Abschnitt schreiben wir den kompletten Code für den digitalen Zeitschalter. Beginne damit, den folgenden Code in die Datei „datestrs.h“ in deinem Projektordner digital_timer_switch zu kopieren.

// 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"
};

Kopiere anschließend den gesamten untenstehenden Code in die Datei „digital_timer_switch.ino“ und ersetze dort den Kalibrierungscode.

Dies ist der Hauptcode. Er ermöglicht es dem Benutzer, Alarme basierend auf einem Wochenplan einzustellen, die aktuelle Zeit anzuzeigen und Alarme ein- oder auszuschalten. Das Display wird in Echtzeit aktualisiert, und der Benutzer kann über Touch-Buttons interagieren. Lies ihn dir kurz durch, dann besprechen wir die Details.

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

Wie du siehst, ist das eine ganze Menge Code. Der folgende Abschnitt gibt dir einen schnellen Überblick über die Funktionen und ihre Bedeutung.

Funktionsübersicht

  • drawLabels(): Zeichnet Beschriftungen für die Zeilen und Spalten auf dem Display.
  • drawSlot(int r, int c): Zeichnet ein Stundenfeld im Zeitplanraster basierend auf der angegebenen Zeile und Spalte.
  • drawSchedule(): Zeichnet das gesamte Zeitplanraster auf dem Display.
  • selectSlot(int x, int y): Ermöglicht es dem Benutzer, ein Stundenfeld im Zeitplanraster durch Berührung auszuwählen.
  • initWiFi(): Initialisiert die WiFi-Verbindung mit dem angegebenen SSID und Passwort.
  • initSNTP(): Initialisiert das SNTP (Simple Network Time Protocol) zur Zeitsynchronisation.
  • setTimezone(): Setzt die Zeitzone für das Gerät basierend auf der angegebenen Konstante.
  • drawTime(): Zeichnet die aktuelle Zeit und das Datum auf dem Display.
  • isOn(): Prüft, ob die aktuelle Zeit einem geplanten Slot entspricht und der Alarm aktiviert ist.
  • initDisplay(): Initialisiert das Display und richtet die Touch-Funktionalität ein.
  • btnView_pressed() und btnAlarm_pressed(): Verarbeiten Button-Events zum Umschalten zwischen Zeit- und Zeitplanansicht sowie zum Ein-/Ausschalten des Alarms.
  • initButtons(): Initialisiert die Buttons auf dem Display zur Interaktion mit dem Projekt.
  • handleButtons(): Behandelt Button-Drücken und -Loslassen auf dem Display.
  • drawView(): Zeichnet entweder die Zeit- oder die Zeitplanansicht basierend auf dem aktuellen Modus.
  • updateView(): Aktualisiert das Display basierend auf Benutzerinteraktionen und aktuellem Modus.

Werfen wir einen genaueren Blick auf diese Funktionen und andere Teile des Codes.

Bibliothekseinbindungen

Der Code beginnt mit dem Einbinden mehrerer Bibliotheken, die für die Funktionalität des ESP32 und des Displays notwendig sind. Wir benötigen Bibliotheken für das TFT-Display, die Wi-Fi-Verbindung, die Zeitsynchronisation mit einem SNTP-Zeitserver sowie die Namen der Monate und Wochentage (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"

Konstanten und Variablen

Als Nächstes definieren wir mehrere Konstanten und Variablen, die im Programm verwendet werden. Dazu gehören Wi-Fi-Zugangsdaten, Display-Abmessungen, Alarm-Pin und ein Zeitplan-Array.

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;

Du musst die SSID und PWD für dein WiFi einstellen und wahrscheinlich auch die TIMEZONE ändern. Die oben angegebene Zeitzonenspezifikation „AEST-10AEDT,M10.1.0,M4.1.0/3“ gilt für Australien und entspricht der Australian Eastern Standard Time (AEST) mit Sommerzeitumstellung.

Die Bestandteile dieser Zeitzonendefinition sind wie folgt

  • AEST: Australian Eastern Standard Time
  • -10: UTC-Versatz von 10 Stunden vor der koordinierten Weltzeit (UTC)
  • AEDT: Australian Eastern Daylight Time
  • M10.1.0: Übergang zur Sommerzeit am ersten Sonntag im Oktober
  • M4.1.0/3: Rückkehr zur Normalzeit am ersten Sonntag im April, mit einem 3-stündigen Unterschied zur UTC.

Für andere Zeitzonendefinitionen siehe das Posix Timezones Database. Kopiere einfach den dort gefundenen String und passe die TIMEZONE-Konstante entsprechend an.

Koordinaten-Umrechnungsfunktionen

Die folgenden Funktionen wandeln zwischen Koordinatensystemen für das Display um:

int c2x(int c) { ... }
int r2y(int r) { ... }
int x2c(int x) { ... }
int y2r(int y) { ... }

c2x() und r2x() ordnen Spalten und Zeilen (c,r) im Zeitplanraster Bildschirmkoordinaten (x,y) zu, während x2c() und y2r() die umgekehrte Operation ausführen. Sie werden benötigt, um beispielsweise eine Berührungsaktion in eine Buchung eines Zeitfensters umzuwandeln.

Zeichenfunktionen

Der Code enthält mehrere Funktionen zum Zeichnen von Elementen auf dem Display:

drawLabels

Diese Funktion zeichnet die Beschriftungen für die Zeilen (Stunden) und Spalten (Wochentage) des Zeitplans.

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
Stunden- und Tagesbeschriftungen für den Zeitplan

drawSlot

Diese Funktion zeichnet ein einzelnes Stundenfeld im Zeitplan und füllt es weiß, wenn der Alarm für diese Zeit aktiviert ist.

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
Ein Stundenfeld

drawSchedule

Diese Funktion zeichnet das gesamte Zeitplanraster, indem sie für jedes Zeitfenster drawSlot() aufruft.

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
Stundenraster für den Zeitplan

selectSlot

Diese Funktion schaltet den Zustand und die Farbe eines Zeitfensters um, wenn der Benutzer es auf dem Display berührt.

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

Wi-Fi- und SNTP-Initialisierung

Die folgenden Funktionen kümmern sich um die Initialisierung von Wi-Fi und SNTP (Simple Network Time Protocol):

initWiFi

Diese Funktion verbindet den ESP32 mit dem angegebenen Wi-Fi-Netzwerk.

void initWiFi() {
  WiFi.begin(SSID, PWD);
  while (WiFi.status() != WL_CONNECTED) {
    delay(100);
  }
}

initSNTP

Diese Funktion richtet den SNTP-Dienst zur Zeitsynchronisation ein. Sie verbindet sich im Wesentlichen mit einem Server im Internet, der sehr genaue Zeitinformationen bereitstellt, und synchronisiert die interne Uhr des ESP32 entsprechend.

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

Solange eine Wi-Fi- und Internetverbindung besteht, sorgt dies dafür, dass die Uhr unseres digitalen Zeitschalters immer genau ist und sich automatisch an die Sommerzeit anpasst. Für mehr Details siehe das How to synchronize ESP32 clock with SNTP server Tutorial.

setTimezone

Diese Funktion setzt die Zeitzone für die Anwendung. Du musst sicherstellen, dass diese Funktion bei jedem Start des ESP32 ausgeführt wird, sonst läuft deine Uhr falsch.

void setTimezone() {
  setenv("TZ", TIMEZONE, 1);
  tzset();
}

Zeit-Anzeige-Funktion

Die drawTime() Funktion ruft die aktuelle Ortszeit ab, die über SNTP synchronisiert wird, und zeigt die Zeit und das Datum auf dem Bildschirm an.

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

Die Funktion verwendet die Konstanten für die Namen der Wochentage und Monate, die in der datestrs.h Datei gespeichert sind.


Time and Date shown on Display
Zeit und Datum auf dem Display

Wenn du mehr Details brauchst, sieh dir das Digital Clock with CrowPanel 3.5″ ESP32 Display und das Digital Clock on e-Paper Display Tutorial an, in denen wir auch eine digitale Uhr implementieren.

Alarm-Prüffunktion

Die isOn() Funktion prüft, ob der Alarm basierend auf der aktuellen Zeit und dem Zeitplan ausgelöst werden soll. Wenn die Rasterzelle schedule[t.tm_hour][t.tm_wday] wahr ist und der Gesamtschalter für den Alarm eingeschaltet ist (isAlarmOn), gibt die Funktion true zurück, andernfalls false.

bool isOn() {
  static struct tm t;
  getLocalTime(&t);
  return schedule[t.tm_hour][t.tm_wday] && isAlarmOn;
}

Display-Initialisierung

Die initDisplay() Funktion initialisiert das Display, indem sie die Bildschirmausrichtung setzt, den Bildschirm schwarz füllt und die Kalibrierungsparameter für den Touchscreen einstellt.

void initDisplay() {
  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(TFT_BLACK);
  tft.setTouch(cal);
}

Button-Funktionen

Der Code enthält Funktionen zur Behandlung von Button-Drücken zum Umschalten zwischen Zeitansicht und Alarmeinstellungen:

btnView_pressed

Diese Funktion wechselt die Ansicht zwischen der aktuellen Zeit und dem Alarmzeitplan.

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
Umschalten zwischen Zeit- und Zeitplanansicht

btnAlarm_pressed

Diese Funktion schaltet den Gesamtschalter für den Alarm ein und aus. Wenn der Schalter aus ist, ist der Alarmzeitplan deaktiviert.

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");
  }
}
Umschalten des Alarmschalters

Button-Initialisierung

Die initButtons() Funktion initialisiert die Buttons auf dem 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
Buttons für Ansicht und Alarm

Button-Verarbeitung

Die handleButtons() Funktion prüft Button-Drücke und führt die entsprechenden Aktionen aus.

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

Ansichts-Zeichenfunktion

Die drawView() Funktion aktualisiert das Display basierend auf dem aktuellen Ansichtsmodus (Zeit oder Zeitplan).

void drawView() {
  tft.fillScreen(TFT_BLACK);
  initButtons();
  if (isTimeView) {
    drawTime();
  } else {
    drawSchedule();
    drawLabels();
  }
}

Ansichts-Aktualisierungsfunktion

Die updateView() Funktion aktualisiert das Display basierend auf Benutzerinteraktionen.

void updateView() {
  static uint16_t x = 0, y = 0;
  if (isTimeView) {
    drawTime();
  } else {
    if (tft.getTouch(&x, &y)) {
      selectSlot(x, y);
    }
  }
}

Setup-Funktion

Die setup() Funktion initialisiert Wi-Fi, SNTP, Display und setzt GPIO25 in den Ausgangsmodus. Wir werden eine LED oder ein Relais an diesen Pin anschließen und sie über den Alarmzeitplan steuern.

void setup() {
  initWiFi();
  initSNTP();
  initDisplay();
  pinMode(alarmPin, OUTPUT);
  drawView();
}

Loop-Funktion

Schließlich aktualisiert die loop() Funktion kontinuierlich die Ansicht, verarbeitet Button-Drücke und steuert den Alarm-Pin basierend auf dem Zeitplan.

void loop() {
  updateView();
  handleButtons();
  digitalWrite(alarmPin, isOn() ? HIGH : LOW);
  delay(100);
}

Und das ist der komplette Code für einen digitalen Zeitschalter. In den nächsten beiden Abschnitten zeige ich dir, wie du eine LED oder ein Relais an das CrowPanel Display anschließt, das wir dann über den Alarmzeitplan steuern können.

Anschluss einer LED an den digitalen Zeitschalter

Um zu testen, ob der Code für unseren digitalen Zeitschalter funktioniert, schließen wir eine LED wie folgt an GPIO25 an:

LED connected to CrowPanel 3.5" ESP32 Display
LED angeschlossen am CrowPanel 3,5″ ESP32 Display

Achte darauf, dass der kurze Pin (Kathode) der LED mit Masse (schwarzes Kabel) verbunden ist und der lange Pin über einen Widerstand mit GPIO25 (gelbes Kabel). Das CrowPanel wird mit einem farbcodierten Stecker geliefert, sodass du dich an den Farben orientieren kannst. Das folgende Foto zeigt die tatsächliche Verkabelung mit den farbcodierten Kabeln:

Wiring of LED with Crowpanel Display
Verkabelung der LED mit CrowPanel Display

Um den digitalen Zeitschalter zu testen, lies die aktuelle Zeit und das Datum, z.B. 12:37:00 am Mittwoch, und stelle dann das entsprechende Zeitfenster im Alarmzeitplan ein:

Schedule set for 12:00-13:00 on Wednesday
Zeitplan eingestellt für 12:00-13:00 am Mittwoch

Wenn der Gesamtschalter für den Alarm eingeschaltet ist, sollte die LED leuchten. Du kannst sie dann durch Umschalten des Gesamtschalters oder Ändern des Zeitplans ausschalten.

Anschluss eines Relais an den digitalen Zeitschalter

Wenn du Lichter in deinem Zuhause oder die Pumpe deiner Sprinkleranlage steuern möchtest, benötigst du ein Relais. Das folgende Diagramm zeigt dir, wie du es anschließt:

Relais angeschlossen am CrowPanel 3,5″ ESP32 Display

VCC (rotes Kabel) und GND (schwarzes Kabel) sind mit den entsprechenden Pins eines Relaismoduls verbunden. GPIO25 ist mit dem Eingangspin (gelbes Kabel) des Relais verbunden.

Das Hochspannungs- und Hochstromgerät, das du steuern möchtest, wird an die COM- und NC (normalerweise geschlossen) Anschlüsse des Relaismoduls angeschlossen.Sei sehr vorsichtig beim Umgang mit Spannungen über 50V!Du musst sicherstellen, dass das Relaismodul für die Spannung und den Strom ausgelegt ist, die du schalten möchtest. Typische Relaismodule (AITRIP, HiLetgo) können 220V bei 4A bis 10A schalten.

Das Foto unten zeigt die Verkabelung des Relaismoduls mit dem CrowPanel Display:

Wiring of Relay with Crowpanel Display
Verkabelung des Relais mit CrowPanel Display

Fazit

In diesem Tutorial hast du gelernt, wie man einen digitalen Zeitschalter mit dem CrowPanel 3.5″ ESP32 Display baut. Im Vergleich zu vielen handelsüblichen Produkten hat unser digitaler Zeitschalter immer eine genaue Zeit und dank des großen Displays ist es sehr einfach, Zeitpläne schnell einzustellen und zu ändern.

Es gibt viele mögliche Erweiterungen für dieses Projekt. Du möchtest vielleicht Zeitpläne mit Minuten- oder Sekundenauflösung einstellen oder einen Zeitplan für einen ganzen Monat oder ein Jahr programmieren.

Statt ein Relais anzuschließen, könntest du auch Bluetooth, MQTT oder andere Protokolle verwenden, um Geräte drahtlos zu steuern. Der obige Code bietet dir dafür eine Grundlage.

Wenn du Fragen hast, kannst du sie gerne im Kommentarbereich stellen.

Viel Spaß beim Tüfteln 😉

Links

Hier einige Links, die ich beim Schreiben dieses Beitrags hilfreich fand.