↩ Alle projecten en sketches This page in English
ESP32 · TFT_eSPI · TTGO

Lilygo TTGO T-Display (ESP32)

Lilygo TTGO T-Display met ingebouwd SPI-display
LILYGO TTGO T-Display op AliExpress · bij Lilygo zelf — 240×135px SPI-scherm

Een ESP32 met SPI-scherm aan boord en twee bedienbare knoppen — ideaal voor een klokje met deep sleep en meerdere tijdzones.

Wat heb je nodig?

Lilygo TTGO T-Display (ESP32)

Dit is een ESP32 met een SPI-display aan boord (240×135px). Aan de voorzijde zijn er twee knoppen die we kunnen bedienen terwijl de software loopt, en een resetknop aan de zijkant. Omdat het display relatief klein is, werkt het snel. Dat merk je aan bvb. de sprites. We kunnen deze combinatie bestellen met een bijbehorend plastic doosje. Alleen spijtig dat er geen magneet in de achterzijde van het doosje verwerkt is (bij andere Lilygo-combinaties is dat wel het geval). Als je het plastic kastje gebruikt, moet je redelijk hard op de knoppen duwen — voor de bediening van een spel is het niet echt geschikt.

Voor de bediening van een klokje (deep sleep, wijzigen tijdzones) hinderen de knoppen niet. Zoals je merkt op het filmpje hieronder, we slaan de gekozen tijdzone op, zodat de ingestelde tijdzone bij een herstart of na deep sleep modus behouden blijft.

Eigen fonts en animaties We hebben ook eigen fonts en meerdere animaties gebruikt op dit bord. Klik hier voor de voorbeeldschets van het beeld hieronder (pagina in het Engels).

Klokje met wisselende tijdzones

In de gebruikte bibliotheek TFT_eSPI is in de folder User_Setups een bestand voorzien voor dit bord: Setup25_TTGO_T_Display.h.

De sketch verbindt met WiFi (of start een configuratieportaal als er nog geen netwerkgegevens zijn opgeslagen), toont de tijd voor vier vooraf ingestelde locaties, en slaat de laatst gekozen tijdzone op in flashgeheugen zodat die bewaard blijft na een herstart.

#include <WiFi.h>  // Arduino IDE - board: ESP32 DEV Module
#include <WebServer.h>
#include <TFT_eSPI.h>     // https://github.com/Bodmer/TFT_eSPI
#include <Preferences.h>  // no need to install this library

WebServer server(80);
Preferences flash;
TFT_eSPI tft = TFT_eSPI();  // User_Setup: Setup25_TTGO_T_Display.h
TFT_eSprite sprite = TFT_eSprite(&tft);
TFT_eSprite leftSp = TFT_eSprite(&tft);
TFT_eSprite rightS = TFT_eSprite(&tft);

const char* weekdays[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
const char* location[] = { "Brussels", "Shanghai", "Auckland", "New York" };  // # of items must match # of time zones (line below)
const char* timeZone[] = { "CET-1CEST,M3.5.0,M10.5.0/3", "CST-8", "NZST-12NZDT,M9.5.0,M4.1.0/3", "EST5EDT,M3.2.0,M11.1.0" };
const char* startTxt[] = { "connecting", "to WiFi", "WiFi  OK", "Time sync" };
const char* noConnec[] = { "WiFi: no connection.", "Connect to hotspot", "'Lily', open browser", "address 192.168.4.1",
                           "for login + password." };
bool freshStart = true;
String webText, buttons, ssid, pasw;
uint8_t count;

void setup() {
  flash.begin("my-clock", true);       // read from flash, true = read only
  count = flash.getInt("counter", 0);  // retrieve the last set time zone - default to first in the array [0]
  flash.end();
  count = count % (sizeof(timeZone) / sizeof(timeZone[0]));  // modulo (# of elements in array) = prevent errors
  pinMode(35, INPUT_PULLUP);                                 // button "switch time zones"
  pinMode(0, INPUT_PULLUP);
  tft.init(), tft.setRotation(3);
  tft.fillScreen(TFT_BLACK);
  sprite.createSprite(240, 135);  // sprite for faster rendering
  show_Logo_WiFi();               // this is not really necessary but it's nicer.
  connect_to_WiFi();
  showConnected();
  configTzTime(timeZone[count], "pool.ntp.org");  // set the time zone
}

void loop() {
  displayTime();
  if (!digitalRead(0)) displayOnOff();     // flash button
  if (!digitalRead(35)) switchTimeZone();  // button opposite flash button
}

void show_Logo_WiFi() {
  sprite.fillSprite(sprite.color565(100, 100, 100));
  for (int i = 0; i < 8192; i++) {  // create surface with fine texture
    byte j = random(100) + 50;
    sprite.drawPixel(random(240), random(135), sprite.color565(j, j, j));  // random grayscale
  }
  sprite.setTextColor(TFT_YELLOW);
  sprite.drawCentreString(startTxt[0], 120, 76, 4), sprite.drawCentreString(startTxt[1], 120, 100, 4);
  sprite.fillCircle(120, 56, 6, TFT_BLUE);
  for (byte i = 0; i < 3; i++) sprite.drawSmoothArc(120, 54, 40 - i * 11, 35 - i * 11, 128, 232, TFT_BLUE, TFT_BLUE, 1);
  sprite.pushSprite(0, 0);
}

void showConnected() {
  for (byte i = 0; i < 3; i++) sprite.drawSmoothArc(120, 54, 40 - i * 11, 35 - i * 11, 128, 232, TFT_GREEN, TFT_GREEN, 1);
  sprite.fillCircle(120, 56, 6, TFT_GREEN);
  sprite.pushSprite(0, 0);
  leftSp.createSprite(128, 66);  // sprite for new text to be scrolled up
  leftSp.fillSprite(sprite.color565(100, 100, 100));
  for (int i = 0; i < 3072; i++) {
    byte j = random(100) + 50;
    leftSp.drawPixel(random(128), random(66), sprite.color565(j, j, j));  // random grayscale
  }
  leftSp.setTextColor(TFT_GREEN);
  leftSp.drawCentreString(startTxt[2], 64, 5, 4);
  leftSp.setTextColor(TFT_YELLOW);
  leftSp.drawCentreString(startTxt[3], 64, 30, 4);
  for (int i = 1024; i > 560; i--) leftSp.pushSprite(56, i / 8);  // high values to slow down the animation
  leftSp.pushToSprite(&sprite, 56, 70);
  leftSp.deleteSprite();
}

void show_Message_No_Connection() {  // message on display when there is no WiFi connection
  tft.fillScreen(TFT_NAVY);          // also blue color for the leftmost 9 pixels - sprite.pushSprite(9, 0) below
  tft.setTextColor(TFT_YELLOW);
  for (uint8_t i = 0; i < 5; i++) tft.drawCentreString(noConnec[i], 120, i * 27, 4);
}

void displayTime() {
  struct tm tInfo;       // https://cplusplus.com/reference/ctime/tm/
  getLocalTime(&tInfo);  // SNTP update every 3 hours (default ESP32) since we did not set an interval
  if (freshStart) splitScreen(true);
  sprite.setTextColor(TFT_CYAN, TFT_BLACK);
  sprite.fillSprite(TFT_BLACK);
  sprite.setFreeFont(&FreeSansBold18pt7b);
  sprite.setTextSize(2);
  sprite.setCursor(13, 56);
  sprite.printf("%02d:%02d", tInfo.tm_hour, tInfo.tm_min);
  sprite.setTextSize(1);
  sprite.drawFastHLine(0, 72, 240, WiFi.isConnected() ? TFT_GREEN : TFT_RED);
  sprite.setCursor(187, 58);
  sprite.setFreeFont(&FreeSansBold18pt7b);
  sprite.printf("%02d", tInfo.tm_sec);
  sprite.setTextColor(TFT_YELLOW);
  sprite.drawCentreString(weekdays[tInfo.tm_wday], 120, 80, 1);  // weekday
  sprite.setTextColor(TFT_RED);
  sprite.setFreeFont(&FreeSans12pt7b);
  sprite.setCursor(62, 134);
  sprite.printf("%02d-%02d-%04d", tInfo.tm_mday, 1 + tInfo.tm_mon, 1900 + tInfo.tm_year);  // date
  if (freshStart) splitScreen(false);
  sprite.pushSprite(0, 0);
}

void splitScreen(bool split) {  // split (true) or merge (false) sprite horizontally
  leftSp.createSprite(120, 135), rightS.createSprite(120, 135);
  if (!split) freshStart = false;
  for (byte ver = 0; ver < 135; ver++) {  // divide the sprite into 2 pieces & write data to two smaller sprites
    for (byte hor = 0; hor < 120; hor++) leftSp.drawPixel(hor, ver, sprite.readPixel(hor, ver));
    for (byte hor = 120; hor < 240; hor++) rightS.drawPixel(hor - 120, ver, sprite.readPixel(hor, ver));
  }
  if (split) leftSp.drawFastVLine(119, 0, 135, TFT_BLACK), rightS.drawFastVLine(0, 0, 135, TFT_BLACK);
  for (byte hor = 0; hor < 120; hor++) {  // move both sprites to the left & right outer edges
    if (split) leftSp.pushSprite(0 - hor, 0), rightS.pushSprite(hor + 120, 0);
    else leftSp.pushSprite(hor - 120, 0), rightS.pushSprite(240 - hor, 0);
  }
  leftSp.deleteSprite(), rightS.deleteSprite();
}

void switchTimeZone() {
  leftSp.createSprite(240, 28), leftSp.setFreeFont(&FreeSans12pt7b);
  leftSp.fillSprite(TFT_BLACK);
  leftSp.setTextColor(TFT_RED), leftSp.drawCentreString(location[count], 120, 0, 1);
  for (int tel = 0; tel < 240; tel++) leftSp.pushSprite(tel, 110);  // we need an animation that uses up time = UI debouncing
  count = (count + 1) % (sizeof(timeZone) / sizeof(timeZone[0]));   // increase counter modulo (number of elements in string array)
  configTzTime(timeZone[count], "pool.ntp.org");
  leftSp.fillSprite(TFT_BLACK);
  leftSp.drawCentreString(location[count], 120, 0, 1);
  for (int tel = -960; tel < 1; tel++) leftSp.pushSprite(tel / 4, 114);
  leftSp.deleteSprite();
}

void displayOnOff() {
  byte savedZone;
  flash.begin("my-clock");                                 // read from flash memory
  savedZone = flash.getInt("counter", 0);                  // retrieve the last set time zone - default to first in the array [0]
  if (savedZone != count) flash.putInt("counter", count);  // only write the time zone to flash memory when it was changed
  flash.end();                                             // to prevent chip wear from excessive writing
  digitalWrite(TFT_BL, !digitalRead(TFT_BL)), delay(300);
}

void connect_to_WiFi() {  // connect to WiFi, if not successful: start web server
  WiFi.mode(WIFI_MODE_STA);
  flash.begin("login_data", true);  // true = read only
  ssid = flash.getString("ssid", "");
  pasw = flash.getString("pasw", "");
  flash.end();
  WiFi.begin(ssid.c_str(), pasw.c_str());
  for (uint8_t i = 0; i < 50; ++i) {  // we try for about 8 seconds to connect
    if (WiFi.isConnected()) {
      WiFi.setAutoReconnect(true);
      WiFi.persistent(true);
      return;  // jumps out of this function when WiFi connection succeeds
    }
    delay(160);
  }
  show_Message_No_Connection();  // script lands on this line only when the connection fails
  int n = WiFi.scanNetworks();
  for (int i = 0; i < n; ++i) {  // html to put found networks on buttons on web page
    buttons += "\n<button onclick='scrollNaar(this.id)' id='" + WiFi.SSID(i) + "'>" + WiFi.SSID(i) + "</button><br>";
  }
  WiFi.mode(WIFI_MODE_AP);
  delay(500);
  WiFi.softAP("Lily", "", 1);
  server.on("/", server_Root);
  server.on("/setting", server_Setting);
  server.begin();
  for (;;) server.handleClient();  // infinite loop until the WiFi credentials are inserted
}

void server_Root() {
  webText = "<!DOCTYPE HTML>\n<html lang='en'>\n<head><title>Setup</title>\n<meta name='viewport' ";
  webText += "content='width=device-width, initial-scale=1.0'>";
  webText += "\n<style>\np {\n  font-family: Tahoma, Verdana, Arial, Helvetica, sans-serif;\n  font-size: 18px;\n  margin: 0;\n  text-align: ";
  webText += "center;\n}\n\nbutton, input[type=submit] {\n  width: 250px;\n  border-radius: 5px;\n  color: White;\n  padding:";
  webText += " 4px 4px;\n  margin-top: 16px;\n  margin: 0 auto;\n  display:block;\n  font-size: 18px;\n  font-weight: 600;";
  webText += "\n  background: DodgerBlue;\n}\n\ninput {\n  width: 250px;\n  font-size: 18px;\n  font-weight: 600;\n}";
  webText += "\n</style>\n</head>\n<body><p style='font-family:arial; ";
  webText += "font-size:240%;'>WiFi setup\n</p><p style='font-family:arial; font-size:160%;'>\n<br>";
  webText += "Networks found:<br> Click on item to select or<br>Enter your network data<br> in the boxes below:</p><br>";
  webText += buttons;
  webText += "\n<form method='get' action='setting'>\n<p><b>\nSSID: <br>\n<input id='ssid' name='ssid'>";
  webText += "<br>PASW: </b><br>\n<input type='password' name='pass'><br><br>\n<input type='submit' value='Save'>";
  webText += "\n</p>\n</form>\n<script>\nfunction scrollNaar(tekst) {\n  document.getElementById('ssid')";
  webText += ".value = tekst;\n  window.scrollTo(0, document.body.scrollHeight);\n}\n</script>\n</body>\n</html>";
  server.send(200, "\ntext/html", webText);
}

void server_Setting() {
  webText = "<!DOCTYPE HTML>\n<html lang='en'>\n<head><title>Setup</title>\n<meta name='viewport' ";
  webText += "content='width=device-width, initial-scale=1.0'>\n<style>\n* {\n  font-family: Arial, Helvetica";
  webText += ", sans-serif;\n  font-size: 45px;\n  font-weight: 600;\n  margin: 0;\n  text-align: center;\n}";
  webText += "\n\n@keyframes op_en_neer {\n  0%   {height:  0px;}\n  50%  {height:  40px;}\n  100% {height:  0px;}\n}";
  webText += "\n\n.opneer {\n  margin: auto;\n  text-align: center;\n  animation: op_en_neer 2s infinite;\n}";
  webText += "\n</style>\n</head>\n<body>\n<div class=\"opneer\"></div>\nESP will reboot<br>Close this window";
  webText += "\n</body>\n</html>";
  String myssid = server.arg("ssid");  // we want to store this in flash memory
  String mypasw = server.arg("pass");
  server.send(200, "\ntext/html", webText);
  delay(500);
  if (myssid.length() > 0 && mypasw.length() > 0) {
    flash.begin("login_data", false);
    flash.putString("ssid", myssid);
    flash.putString("pasw", mypasw);
    flash.end();
    ESP.restart();
  }
}

Meer displays met de ESP8266 / ESP32

Benieuwd naar andere displays? Bekijk het volledige overzicht met o.a. TM1637, SSD1306, SH1106, ST7735, ST7789, e-paper en de Lilygo T-Display S3 en Waveshare ESP32-S3.