↩ Alle projecten en sketches This page in English
ESP32 · TFT_eSPI · T-Display S3

Lilygo T-Display S3 (ESP32)

Lilygo T-Display S3 met 1.9 inch kleurenscherm
Lilygo T-Display S3 — 1.9 inch LCD-kleurenscherm (320×170px), verkrijgbaar met zwart of grijs kastje

Twee bedienbare knoppen, een deep sleep-modus, wisselende tijdzones en zelfs eigen fonts en een weerstation-voorbeeld.

Wat heb je nodig?

Lilygo T-Display S3 (ESP32)

De Lilygo T-Display S3 kan je verkrijgen met een bijbehorend zwart of grijs plastic kastje. Er is een 1.9 inch LCD-kleurenscherm (320×170px) aan boord. De module heeft vooraan 2 knoppen (GPIO0 en GPIO14), en zijkant de resetknop. Voor een stabiele WiFi-verbinding moet je WiFi.setSleep(false); als opdracht geven, vóór de eigenlijke verbinding. Zie sketch hieronder.

Omdat we twee knoppen hebben die we kunnen gebruiken — afgezien van de resetknop — werd in deze code de bovenste knop gebruikt voor de deep sleep modus, en de onderste knop om verschillende tijdzones te selecteren. We slaan de ingestelde tijdzone op in het flashgeheugen, zodat die bij een herstart behouden blijft. We gebruiken hiervoor de bibliotheek preferences.h.

We hebben ook wat speeltjes toegevoegd voor een mooiere presentatie. Dit zowel bij het opstarten, het in deep sleep modus gaan, als het wijzigen van tijdzone.

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

Eigen fonts Je kan ook eigen fonts gebruiken op de ESP. Klik hier voor de voorbeeldschets van het beeld hieronder (pagina in het Engels).
Aangepast lettertype weergegeven op de Lilygo T-Display S3
Weerstation Een eenvoudig weerstation is uiteraard ook mogelijk. Klik hier voor de voorbeeldschets van het beeld hieronder (pagina in het Engels).
Eenvoudig weerstation op de Lilygo T-Display S3

Klokje met deep sleep en wisselende tijdzones

De sketch verbindt met WiFi (of start een configuratieportaal als er nog geen netwerkgegevens zijn opgeslagen), toont de tijd met een startanimatie, en slaat zowel de WiFi-gegevens als de laatst gekozen tijdzone op in flashgeheugen.

#include <WiFi.h>  // Arduino IDE - board: Lilygo T_Display-S3
#include <WebServer.h>
#include <TFT_eSPI.h>     // https://github.com/Bodmer/TFT_eSPI
#include <Preferences.h>  // to save the selected time zone

WebServer server(80);
Preferences flash;
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite sprite = TFT_eSprite(&tft);
TFT_eSprite leftSp = TFT_eSprite(&tft);
TFT_eSprite rightS = TFT_eSprite(&tft);

uint8_t SCREEN_ORIENTATION = 3;  // USB connection: 3 = left side - 1 = right side.
const char* location[] = { "Brussels", "Shanghai", "Auckland", "New York" };
const char* timeZone[] = {  // avoid memory leaks by giving all elements in the array the same length (trailing spaces)
  "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* weekDays[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
const char* startTxt[] = { "Connecting to WiFi", "WiFi OK: Time sync" };
const char* noConnec[] = { " WiFi: no connection.", " Connect to hotspot 'TD-S3'", " and open a browser",
                           " at adres 192.168.4.1", " to enter network name", " and password." };

bool noCon = false, freshStart = true;
String webText, buttons, ssid, pasw;
uint8_t count;

void setup() {
  pinMode(15, OUTPUT);  // PWD
  digitalWrite(15, HIGH);
  pinMode(14, INPUT_PULLUP);  // button for changing time zone
  pinMode(0, INPUT_PULLUP);   // flash button
  initDisplay();
  showStartUpLogo();
  connect_to_WiFi();  // Will start web server (for setup WiFi data) if connection fails
  showConnected();
  setTimeZoneFromFlash();
}

void loop() {
  display_Time();
  if (!digitalRead(14)) switchTimeZone();
  if (!digitalRead(0)) displayOnOff();  // prevent burn-in
}

void initDisplay() {
  tft.init(), tft.setRotation(SCREEN_ORIENTATION);
  sprite.createSprite(320, 170);  // sprite for faster rendering
}

void showStartUpLogo() {  // use functions of library to draw = smaller than a graphic file
  tft.fillScreen(TFT_BLACK);
  sprite.fillSprite(sprite.color565(100, 100, 100));
  for (uint16_t i = 0; i < 12000; i++) {  // create surface with fine texture
    uint8_t j = random(100) + 50;
    sprite.drawPixel(random(320), random(170), sprite.color565(j, j, j));
  }
  sprite.setTextColor(tft.color565(48, 48, 48));
  for (uint8_t i = 0; i < 8; i++) {  // draw IC legs
    sprite.drawRect(131 + (i * 10), 10, 2, 115, sprite.color565(240, 240, 240));
    sprite.fillRoundRect(129 + (i * 10), 18, 6, 99, 2, sprite.color565(240, 240, 240));
    sprite.drawRect(110, 32 + (i * 10), 115, 2, sprite.color565(240, 240, 240));
    sprite.fillRoundRect(118, 30 + (i * 10), 99, 6, 2, sprite.color565(240, 240, 240));
  }
  sprite.fillRoundRect(122, 22, 91, 91, 3, TFT_BLACK);  // draw IC
  sprite.drawRoundRect(122, 22, 91, 91, 3, TFT_DARKGREY);
  sprite.setTextFont(1);
  sprite.drawCentreString("ESP32", 157, 74, 1);
  sprite.drawCentreString("1732S019", 164, 86, 1);
  sprite.fillCircle(200, 34, 3, sprite.color565(16, 16, 16));
  sprite.setFreeFont(&FreeSans18pt7b);
  sprite.setTextColor(TFT_BLACK);  // embossed text below IC
  sprite.drawCentreString(startTxt[0], 161, 132, 1);
  sprite.setTextColor(TFT_WHITE);
  sprite.drawCentreString(startTxt[0], 159, 130, 1);
  sprite.setTextColor(sprite.color565(100, 100, 100));
  sprite.drawCentreString(startTxt[0], 160, 131, 1);
  sprite.pushSprite(0, 0);
}

void show_Message_No_Connection() {
  tft.fillScreen(TFT_NAVY);
  tft.setTextColor(TFT_YELLOW), tft.setTextFont(4), tft.setCursor(0, 0, 4);
  for (uint8_t count = 0; count < 6; count++) tft.println(noConnec[count]);
  noCon = true;
}

void showConnected() {
  leftSp.createSprite(320, 58);
  leftSp.fillSprite(sprite.color565(100, 100, 100));
  leftSp.setFreeFont(&FreeSans18pt7b);
  for (uint16_t i = 0; i < 4000; i++) {
    uint8_t j = random(100) + 50;
    leftSp.drawPixel(random(320), random(58), sprite.color565(j, j, j));  // random greyscale
  }
  leftSp.setTextColor(TFT_BLACK);  // embossed text "connected"
  leftSp.drawCentreString(startTxt[1], 161, 6, 1);
  leftSp.setTextColor(TFT_WHITE);
  leftSp.drawCentreString(startTxt[1], 159, 4, 1);
  leftSp.setTextColor(sprite.color565(100, 100, 100));
  leftSp.drawCentreString(startTxt[1], 160, 5, 1);
  for (uint16_t i = 850; i > 650; i--) leftSp.pushSprite(0, i / 5);  // slide text upwards
  leftSp.pushToSprite(&sprite, 0, 131);                              // add new text to existing sprite
  leftSp.deleteSprite();
}

void setTimeZoneFromFlash() {
  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
  configTzTime(timeZone[count], "pool.ntp.org");             // clock will automatically adjust to daylight saving time
}

void display_Time() {
  struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
  getLocalTime(&tInfo);
  if (freshStart) splitScreen(true);
  sprite.fillSprite(TFT_BLACK);
  uint16_t myColor = (WiFi.status() == WL_CONNECTED) ? TFT_GREEN : TFT_RED;
  sprite.fillCircle(294, 30, 6, myColor);  // logo WiFi
  for (uint8_t tel = 0; tel < 3; tel++) {
    sprite.drawSmoothArc(294, 32, 30 - tel * 7, 28 - tel * 7, 135, 225, myColor, myColor, true);
  }
  sprite.setTextFont(7), sprite.setTextColor(TFT_CYAN, TFT_BLACK), sprite.setTextSize(2);
  sprite.setCursor(-2, 0), sprite.printf("%02d", tInfo.tm_hour);
  sprite.fillRect(126, 28, 5, 10, TFT_CYAN), sprite.fillRect(126, 56, 5, 10, TFT_CYAN);  // colon between hour & minutes
  sprite.setCursor(131, 0), sprite.printf("%02d", tInfo.tm_min);
  sprite.setCursor(256, 45), sprite.setTextSize(1), sprite.printf("%02d", tInfo.tm_sec);
  sprite.setTextColor(TFT_YELLOW), sprite.setFreeFont(&FreeSans12pt7b);
  sprite.setCursor(9, 157), sprite.print(weekDays[tInfo.tm_wday]);
  sprite.drawRoundRect(0, 134, 320, 34, 5, TFT_YELLOW);
  sprite.setCursor(188, 159), sprite.setTextColor(TFT_CYAN);
  sprite.printf("%02d-%02d-%04d", tInfo.tm_mday, 1 + tInfo.tm_mon, 1900 + tInfo.tm_year);
  sprite.setTextColor(TFT_RED), sprite.drawCentreString(location[count], 160, 108, 1);
  if (freshStart) splitScreen(false), freshStart = false;  // before "pushSprite" because the background must be black
  sprite.pushSprite(0, 0);
}

void splitScreen(bool split) {  // split (true) or merge (false) sprite horizontally
  leftSp.createSprite(160, 170), rightS.createSprite(160, 170);
  for (uint8_t ver = 0; ver < 170; ver++) {  // divide the sprite into 2 pieces & write data to two smaller sprites
    for (uint16_t hor = 0; hor < 160; hor++) leftSp.drawPixel(hor, ver, sprite.readPixel(hor, ver));
    for (uint16_t hor = 160; hor < 320; hor++) rightS.drawPixel(hor - 160, ver, sprite.readPixel(hor, ver));
  }
  if (split) {  // avoid sloppy lines: make sure the remaining part of the screen is black
    leftSp.drawFastVLine(159, 0, 170, TFT_BLACK), leftSp.drawFastVLine(158, 0, 170, TFT_BLACK);
    rightS.drawFastVLine(0, 0, 170, TFT_BLACK), rightS.drawFastVLine(1, 0, 170, TFT_BLACK);
  }
  for (uint16_t hor = 0; hor < 160; hor += 2) {
    if (split) leftSp.pushSprite(0 - hor, 0), rightS.pushSprite(hor + 160, 0);  // move both sprites to the outer edge
    else leftSp.pushSprite(hor - 160, 0), rightS.pushSprite(320 - hor, 0);      // merge both sprites
  }
  leftSp.deleteSprite(), rightS.deleteSprite();
}

void switchTimeZone() {
  leftSp.createSprite(320, 28), leftSp.setFreeFont(&FreeSans12pt7b);
  leftSp.fillSprite(TFT_BLACK);
  leftSp.setTextColor(TFT_RED), leftSp.drawCentreString(location[count], 160, 3, 1);
  for (uint16_t tel = 0; tel < 320; tel++) leftSp.pushSprite(tel, 105);  // UI debouncing
  count = (count + 1) % (sizeof(timeZone) / sizeof(timeZone[0]));        // increase modulo (number of elements in char array)
  configTzTime(timeZone[count], "pool.ntp.org");
  leftSp.fillSprite(TFT_BLACK);
  leftSp.drawCentreString(location[count], 160, 3, 1);
  for (int16_t tel = -320; tel < 1; tel++) leftSp.pushSprite(tel, 105);
  leftSp.deleteSprite();
}

void displayOnOff() {  // flash button pressed: display on - off
  uint8_t savedZone;
  flash.begin("my-clock");                                 // flash memory (also write since 2nd param = not set)
  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.setSleep(false);
  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("TD-S3", "", 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, de Lilygo TTGO T-Display en de Waveshare ESP32-S3.