Das Projekt ist sehr interessant, da es sehr verschiedene Bereiche abdeckt.

  • NTP Zeitabfrage
  • Display Ansteuerung mit touch
  • Helligkeitsregulierung der Anzeige
  • Little FS Speichern der Einstellungen /Laden beim Starten mit JSON
  • OTA update
  • Webserver
  • Tochterboard Platine erstellen mit KICAD9
  • Gehäuse entwerfen mit FreeCAD für den 3D Druck

Was brauchen wir für dieses Projekt:

  • Wir benötigen einen ESP32Dev Kit 1 38 Polig
  • ILI9341 Display 2.8""

  • Tochterplatine

  • Helligkeitssensor

Für die Grafikansteuerung verwende ich die Library TFT_eSPI von Bodmer.
Die Library ist für viele Displays gemacht, daher muss diese zuerst für mein ILI9341 konfiguriert werden.
Die folgenden Dateien befinden sich im Arduino Ordner:   C:\Users\DeinBenutzerName\Documents\Arduino\libraries\TFT_eSPI

Da ich die User_Setup.h nicht verwende, kommentiere ich diese aus und aktiviere weiter unten den die entsprechende Datei <User_Setups/Setup42_ILI9341_ESP32.h> für mein Display.

 

   

 

Custom Fonts

Da ich gerne eine 7Segment Anzeige möchte, brauche ich einen Custom Font, welchen ich für meine Bedürfnisse konvertieren Muss.
Hierzu gibt es ein gutes Video auf Youtube und auch das entsprechende Programm zum Download. Das Video zeigt zwar dies mit der Adafruit Library, gilt aber auch für
unsere Library von Bodmer.

Die fertigen konvertierten Fonts werden in folgenden Ordner gespeichert. Hier mein Font in verschiedenen Grössen.

Danach müssen die Fonts in der Datei User_Custom_Fonts.h eingetragen werden.

Somit sind die Vorbereitungen abgeschlossen und wir können uns dem Code widmen.
/*
  Beispiel für TFT_Uhr
*/

// Benötigte Librarys
#include <FS.h>
#include "SPI.h"

#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <ElegantOTA.h>

#include "TFT_eSPI.h"
#include <TFT_eWidget.h> 
#include <WiFi.h>
#include "NTP.h"
#include <WiFiUdp.h>
#include "Free_Fonts.h"
#include <ArduinoJson.h>


// File path in LittleFS
const char *filename = "/config.json";

const int potPin = 34;
int potValue = 0;
uint16_t Farbe = TFT_DARKGREEN;

// Backlight pin (check your wiring)
#define LEDC_PIN 21
#define LEDC_RESOLUTION 10 // Resolution: 10 bits

// Stock font and GFXFF reference handle
#define GFXFF 1
#define FF18 &FreeSans12pt7b
#define CF_LED44 &digital_744pt7b
#define CF_LED18 &digital_718pt7b

//Wifi Credentials
char ssid[] = "mousy";
char password[] = "emf2ytPpdzae";

AsyncWebServer server(80);

//Starting Wifi and UDP
WiFiUDP wifiUdp;
NTP ntp(wifiUdp);

// Use hardware SPI
TFT_eSPI tft = TFT_eSPI();

//Buttons definieren
#define CALIBRATION_FILE "/TouchCalData1"
#define REPEAT_CAL false

ButtonWidget btnMenu = ButtonWidget(&tft);
ButtonWidget btnWhite = ButtonWidget(&tft);
ButtonWidget btnGreen = ButtonWidget(&tft);
ButtonWidget btnRed = ButtonWidget(&tft);
ButtonWidget btnBlue = ButtonWidget(&tft);
ButtonWidget btnGelb = ButtonWidget(&tft);
ButtonWidget btnGrau = ButtonWidget(&tft);

//Button Grösse definieren
#define BUTTON_W 80
#define BUTTON_H 50

// Create an array of button instances to use in for() loops
// This is more useful where large numbers of buttons are employed
ButtonWidget* btn[] = {&btnMenu , &btnWhite , &btnGreen , &btnRed , &btnBlue , &btnGelb , &btnGrau};;
uint8_t buttonCount = sizeof(btn) / sizeof(btn[0]);

//Action der Menu Taste
void btnMenu_pressAction(void){
  if (btnMenu.justPressed()) {
    //Rahmen Zeichnen
    tft.fillScreen(TFT_BLACK);
    tft.drawRect(2, 2, 318, 25, 0xFAAA);
    tft.drawRect(2, 28, 318, 208, 0x540);

    //Titel
    tft.setFreeFont(FF17);
    tft.drawString("Farbe auswaehlen", 70, 6);  
    
    //Button Weiss
    btnWhite.initButtonUL(20, 80, BUTTON_W, BUTTON_H, 0xAD55 , TFT_WHITE, TFT_BLACK, "Weiss", 1);
    btnWhite.setPressAction(btnWhite_pressAction);
    //btnWhite.setReleaseAction(btnL_releaseAction);
    btnWhite.drawSmoothButton(false, 3, TFT_BLACK); // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
    
    //Button Grün
    btnGreen.initButtonUL(120, 80, BUTTON_W, BUTTON_H, TFT_WHITE, TFT_DARKGREEN, TFT_BLACK, "Gruen", 1);
    btnGreen.setPressAction(btnGreen_pressAction);
    btnGreen.drawSmoothButton(false, 3, TFT_BLACK); // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
    
    //Button Rot
    btnRed.initButtonUL(220, 80, BUTTON_W, BUTTON_H, TFT_WHITE, TFT_RED, TFT_BLACK, "Rot", 1);
    btnRed.setPressAction(btnRed_pressAction);
    btnRed.drawSmoothButton(false, 3, TFT_BLACK); // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing

    //Button Blau
    btnBlue.initButtonUL(20, 150, BUTTON_W, BUTTON_H, TFT_WHITE , TFT_BLUE, TFT_WHITE, "Blau", 1);
    btnBlue.setPressAction(btnBlue_pressAction);
    btnBlue.drawSmoothButton(false, 3, TFT_BLACK); // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
    
    //Button Gelb
    btnGelb.initButtonUL(120, 150, BUTTON_W, BUTTON_H, TFT_WHITE, 0xFFEA, TFT_BLACK, "Gelb", 1);
    btnGelb.setPressAction(btnGelb_pressAction);
    btnGelb.drawSmoothButton(false, 3, TFT_BLACK); // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing
    
    //Button Grau
    btnGrau.initButtonUL(220, 150, BUTTON_W, BUTTON_H, TFT_WHITE, 0xAD55, TFT_BLACK, "Grau", 1);
    btnGrau.setPressAction(btnGrau_pressAction);
    btnGrau.drawSmoothButton(false, 3, TFT_BLACK); // 3 is outline width, TFT_BLACK is the surrounding background colour for anti-aliasing

      // Serial.print("Button toggled: ");
      // if (btnMenu.getState()) Serial.println("ON");
      // else  Serial.println("OFF");
      // btnMenu.setPressTime(millis());
  }

  // if button pressed for more than 1 sec...
  // if (millis() - btnMenu.getPressTime() >= 1000) {
  //   Serial.println("Stop pressing my buttton.......");
  // }
  // else Serial.println("Button 3 is being pressed");
}

//Menu Button initialisieren
void initButtons() {
  //Menue Button
  btnMenu.initButtonUL(10, 10, BUTTON_W, BUTTON_H, TFT_BLACK, TFT_BLACK, TFT_BLUE, "Menu", 1);
  btnMenu.setPressAction(btnMenu_pressAction);
  btnMenu.drawSmoothButton(false, 3, TFT_BLACK);
}

// oldm wird für die aktualisierung der Uhr verwendet jede Minute
int oldm = 70; //Wert 70 kann nie ereicht werden und zwingt daher zur 1. Aktualisierung
int x = 200; // Wert für die Helligkeit des Displays

//Action der weissen Taste
void btnWhite_pressAction(void){
  //Serial.println("White Button is being pressed");
  Farbe = TFT_WHITE;
  writeJSON();
  oldm = 0;
}

//Action der grünen Taste
void btnGreen_pressAction(void){
  //Serial.println("Darkgreen Button is being pressed");
  Farbe = TFT_DARKGREEN;
  writeJSON();
  oldm = 0; 
}

//Action der roten Taste
void btnRed_pressAction(void){
  //Serial.println("Red Button is being pressed");
  Farbe = TFT_RED;
  writeJSON();
  oldm = 0; 
}

//Action der blauen Taste
void btnBlue_pressAction(void){
  //Serial.println("Blue Button is being pressed");
  Farbe = TFT_BLUE;
  writeJSON();
  oldm = 0; 
}

//Action der gelben Taste
void btnGelb_pressAction(void){
  //Serial.println("Yellow Button is being pressed");
  Farbe = 0xFFEA;
  writeJSON();
  oldm = 0; 
}

//Action der grauen Taste
void btnGrau_pressAction(void){
  //Serial.println("Grey Button is being pressed");
  Farbe = 0xAD55;
  writeJSON();
  oldm = 0; 
}

//Helligkeitsregelung
void brightnes(){
    if (potValue < 2000){
      x=3000;}
      else if (potValue > 3000){
      x=1;
    }
  int dutyCycle = (pow(2, LEDC_RESOLUTION) - 1) * 0. + x; // Set duty cycle to 50%
  ledcWrite(LEDC_PIN, dutyCycle); 
}

// Function to write JSON to LittleFS
bool writeJSON() {
    // Create a JSON document
    StaticJsonDocument<256> doc;
    doc["farbe"] = Farbe;
    doc["wifi"]["ssid"] = ssid;
    doc["wifi"]["password"] = password;

    // Open file for writing
    File file = LittleFS.open(filename, FILE_WRITE);
    if (!file) {
        Serial.println("Failed to open file for writing");
        return false;
    }

    // Serialize JSON to file
    if (serializeJson(doc, file) == 0) {
        Serial.println("Failed to write JSON to file");
        file.close();
        return false;
    }

    file.close();
    Serial.println("JSON written successfully");
    return true;
}

// Function to read JSON from LittleFS
bool readJSON() {
    // Open file for reading
    File file = LittleFS.open(filename, FILE_READ);
    if (!file) {
        Serial.println("Failed to open file for reading");
        return false;
    }

    // Allocate a JSON document
    StaticJsonDocument<256> doc;

    // Deserialize JSON from file
    DeserializationError error = deserializeJson(doc, file);
    if (error) {
        Serial.print("Failed to read file: ");
        Serial.println(error.c_str());
        file.close();
        return false;
    }

    file.close();

    Farbe = doc["farbe"];

    // Print values
    // Serial.println("JSON read successfully:");
    // Serial.print("Device: ");
    // Serial.println(doc["device"].as<const char*>());
    // Serial.print("Version: ");
    // Serial.println(doc["version"].as<float>());
     Serial.print("WiFi SSID: ");
     Serial.println(doc["wifi"]["ssid"].as<const char*>());
     Serial.print("WiFi Password: ");
     Serial.println(doc["wifi"]["password"].as<const char*>());

    return true;
}

void touch_calibrate() {
  uint16_t calData[5];
  uint8_t calDataOK = 0;

  // check file system exists
  if (!LittleFS.begin()) {
    Serial.println("formatting file system");
    LittleFS.format();
    LittleFS.begin();
    
  }

  // check if calibration file exists and size is correct
  if (LittleFS.exists(CALIBRATION_FILE)) {
    if (REPEAT_CAL)
    {
      // Delete if we want to re-calibrate
      LittleFS.remove(CALIBRATION_FILE);
    }
    else
    {
      File f = LittleFS.open(CALIBRATION_FILE, "r");
      if (f) {
        if (f.readBytes((char *)calData, 14) == 14)
          calDataOK = 1;
        f.close();
      }
    }
  }

  // check if config file exists
  if (!LittleFS.exists(filename)){
      writeJSON();
  }
  else
    {
      readJSON();
    }
  

  if (calDataOK && !REPEAT_CAL) {
    // calibration data valid
    tft.setTouch(calData);
  } else {
    // data not valid so recalibrate
    tft.fillScreen(TFT_BLACK);
    tft.setCursor(20, 0);
    tft.setTextFont(2);
    tft.setTextSize(1);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);

    tft.println("Touch corners as indicated");

    tft.setTextFont(1);
    tft.println();

    if (REPEAT_CAL) {
      tft.setTextColor(TFT_RED, TFT_BLACK);
      tft.println("Set REPEAT_CAL to false to stop this running again!");
    }

    tft.calibrateTouch(calData, TFT_MAGENTA, TFT_BLACK, 15);

    tft.setTextColor(TFT_GREEN, TFT_BLACK);
    tft.println("Calibration complete!");

    // store data
    File f = LittleFS.open(CALIBRATION_FILE, "w");
    if (f) {
      f.write((const unsigned char *)calData, 14);
      f.close();
    }
  }
}




void setup(void) {

  Serial.begin(115200);
  WiFi.begin(ssid, password);
  //TFT Start
  tft.begin();
  tft.setRotation(1); // Querformat Pins rechts
  tft.setFreeFont(FF18);
  touch_calibrate();

  
  // Setup PWM channel for backlight
  ledcAttach(LEDC_PIN, 5000, LEDC_RESOLUTION); // Set frequency to 5000 Hz and resolution to 10 bits

  int dutyCycle = (pow(2, LEDC_RESOLUTION) - 1) * 0. + x; // Set duty cycle to 50%
  ledcWrite(LEDC_PIN, dutyCycle);

  while (WiFi.status() != WL_CONNECTED) {
    tft.fillScreen(TFT_BLACK);
    tft.setTextColor(TFT_WHITE, TFT_BLACK);
    tft.drawString("Connecting ...", 30, 80);
    //Serial.print("Connecting ...");
    delay (500);
    tft.drawString("HTTP server started", 30, 110);
    tft.drawString("IP address: ", 30, 140);
    
    }
  
  server.begin();
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", "Uhren Webserver only for update.");
  });
  

  ElegantOTA.begin(&server);    // Start ElegantOTA

  //String ip = String(WiFi.localIP());
    tft.drawString(WiFi.localIP().toString(), 160, 140);
    //Serial.println(ip);
    Serial.println(WiFi.localIP());
    delay(5000);

  //NTP Settings  
  ntp.ruleDST("CEST", Last, Sun, Mar, 2, 120); // last sunday in march 2:00, timetone +120min (+1 GMT + 1h summertime offset)
  ntp.ruleSTD("CET", Last, Sun, Oct, 3, 60); // last sunday in october 3:00, timezone +60min (+1 GMT)
  void begin(const char* server = "0.ch.pool.ntp.org");
  ntp.begin();
  
}

void loop() {
  ElegantOTA.loop();
  // Internetzeit update
  ntp.update();
  //Helligkeit prüfen
  potValue = analogRead(potPin);
  brightnes();
  //Serial.println(potValue);

  static uint32_t scanTime = millis();
  uint16_t t_x = 9999, t_y = 9999; // To store the touch coordinates

  // Scan keys every 50ms at most
  if (millis() - scanTime >= 50) {
    // Pressed will be set true if there is a valid touch on the screen
    bool pressed = tft.getTouch(&t_x, &t_y);
    scanTime = millis();
    for (uint8_t b = 0; b < buttonCount; b++) {
      if (pressed) {
        if (btn[b]->contains(t_x, t_y)) {
          btn[b]->press(true);
          btn[b]->pressAction();
        }
      }
      else {
        btn[b]->press(false);
        btn[b]->releaseAction();
      }
    }
  }


  
    if (oldm != ntp.minutes()){
        tft.fillScreen(TFT_BLACK);// Clear screen
        
        //tft.setFreeFont(FF18);// Select the font
        initButtons();
        //tft.setTextColor(TFT_DARKGREEN, TFT_BLACK);
        tft.setTextColor(Farbe, TFT_BLACK);
        tft.setFreeFont(CF_LED18);
        tft.drawString(ntp.formattedTime("%d. %B %Y"), 50, 200, GFXFF);// Print the string name of the font
        tft.setFreeFont(CF_LED44);                 // Select the font
        tft.drawString(ntp.formattedTime("%R"), 90, 80, GFXFF);// Print the test text in the custom font
        oldm = ntp.minutes();
        //Serial.print(ntp.minutes());
        //delay(1000);
    } 
}


 

Download: