Deze pagina in het Nederlands

ESP8266 / ESP32 with displays



Introduction

Introduction

The ESP8266 and the ESP32 are microcontrollers with WiFi on board. They can be programmed via a USB cable, connected to a PC. Cheap and easy.

After uploading the code, the microcontroller will work on its own, provided we supply power via battery or adaptor with a USB power cable. These microcontrollers are so cheap and versatile that they have largely succeeded their predecessor - the Arduino. Meanwhile, they are commonplace among thousands of hobbyists worldwide.

Thanks to the many connection possibilities (WiFi, bluetooth, IR remote control, ...) and the possible connections (relay, LED, display, sensor for temperature, etc.), it can be used to create numerous projects to control remotely (drones, thermostat, control of a lock or lamp or motor), or projects that can send alerts to a cell phone. Control via an app on smartphone (even from hundreds of kilometers away) is also easy.

The examples here are clocks with different displays. Why clocks? They are relatively simple, both in terms of hardware and programming. And they are excellent examples if you want to work with displays. An NTP clock (which synchronizes time over the Internet) must take into account DST or winter time, and must switch automatically. Otherwise, the code is incomplete. Displaying the hour as "14:7" is sloppy. There are numerous methods and functions for using a leading "0".

On this page, the code was kept as compact as possible.



What do you need for this?

What do you need for this?

Download the Arduino IDE (free)

This is the compiler, which you use to write sketches and upload the code to the microcontroller.

How to configure the Arduino IDE for the ESP32

How to configure the Arduino IDE for the ESP8266


How to install libraries in the Arduino IDE

Starter package hardware:

An inexpensive starter pack for the ESP8266
A more expensive (more complete) starter pack for the ESP8266
Solderless connecting wires
An ESP32 microcontroller



TM1637-display + ESP8266

TM1637-display + ESP8266

Amazon: TM1637


Connections:

ESP8266	-> TM1637
=================
GND --------> GND
3V3 --------> VCC
D2  --------> DIO
D1  --------> CLK


Sketch for a simple clock:

#include <WiFiManager.h>    // https://github.com/tzapu/WiFiManager/
#include <TM1637Display.h>  // https://github.com/avishorp/TM1637/

struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
WiFiManager myWiFi;
TM1637Display ledSeg(5, 4);  // scl (clk) -> GPIO5 (D1) / sda (dio) -> GPIO4 (D2).

void setup() {
  Serial.begin(115200);                                           // watch serial monitor in case of new WiFi-connection
  myWiFi.autoConnect("TM1637_ESP");                               // connect ESP to new wifi via GSM or PC in that case
  ledSeg.setBrightness(2);                                        // 0 .. 7
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "be.pool.ntp.org");  // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
}

void loop() {
  getLocalTime(&tInfo);                                                                           // sntp sync at startup & every hour from then on
  ledSeg.showNumberDecEx(tInfo.tm_hour * 100 + tInfo.tm_min, 0x40 * (millis() % 1000 < 500), 1);  // 0x40 = ":", 1 = leading zeros
}
  • adjusts for daylight saving time automatically
  • synchronizes once an hour with SNTP server
  • can connect to any WiFi without changing the code (adjustable with e.g. tablet or smartphone)
  • no "hard coded" password.
  • only 18 lines of code
  • no delay() commands

How does it work?

  • #include <WiFiManager.h> library to connect the ESP to the WiFi, including Web server if the WiFi data was not previously entered. If the connection fails, you will see on the serial monitor:
    • *wm:StartAP with SSID: TM1637_ESP
    • *wm:AP IP address: 192.168.4.1
    • *wm:Starting Web Portal

  • You need to connect to this access point with your cell phone or tablet or PC, launch a browser and enter 192.168.4.1 as the address, and you can enter the WiFi data. The advantage is that you can move the ESP elsewhere (or change a WiFi access point) without recompiling the code. Even without a serial monitor, you will notice if the clock does not work: then you also have to connect via the access point and enter the WiFi data. Once the clock has started you can disconnect the WiFi connection, the clock will just keep running (obviously without synchronization in that case).

  • The last lines in the functions "setup" and "loop" are "powerful", they execute a lot on 1 line.
    • configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "be.pool.ntp.org"); this is the line that ensures that the correct time zone is used, the clock automatically adjusts to summer or winter time, and then indicates which NTP servers you wish to synchronize with.
    • ledSeg.showNumberDecEx(ti.tm_hour * 100 + ti.tm_min, 0x40 * (millis() % 1000 < 500), 1); the time is shown on the display with a colon (hidden or shown every half second), and with leading zeros.

  • getLocalTime(&timeinfo); requests the time from the ESP8266, adjusted according to time zone and daylight saving time / standard time. On startup the internal clock is synchronized with the NTP server, thereafter the synchronization occurs every hour. This was extensively tested with callback functions.

  • There is no interval set for synchronization with SNTP. By default - so in this case (ESP8266) - that interval is set to 1 hour, which is more than enough to get the correct time. If you would like to set a different interval, in the example below (SSD1306 display) you can see on the last lines how to do that. Update to standard time / DST is also done without synchronization. The update via NTP always happens in UTC time, never in local time. And it is local time that controls daylight saving time or standard time.

Where are the libraries?

In the many examples of clocks you find on the Internet, you see "NTPClient.h", "Time.h", "TimeLib.h" and "WiFiUDP.h". Where are those in this sketch?

Short answer: you don't need those. Time.h is built into the ESP core. So we should not add these libraries to the list of the Arduino IDE, and we should not declare them in these sketches.





TM1637-display + ESP32

TM1637-display + ESP32

The following code for our "minimum clock" is adapted for both the ESP8266 and the ESP32. The default time synchronization interval for the ESP32 is 3 hours, for the ESP8266 it is 1 hour. The only modifications are the display GPIO connections:

Connections:

ESP32 ---> TM1637
=================
GND --------> GND
3V3 --------> VCC
G21 --------> DIO
G22 --------> CLK


Sketch:

#include <WiFiManager.h>    // https://github.com/tzapu/WiFiManager/
#include <TM1637Display.h>  // https://github.com/avishorp/TM1637/

struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
WiFiManager myWiFi;
#if defined(ESP32)  
TM1637Display ledSeg(22, 21);  // scl (clk) -> G22 / sda (dio) -> G21.
#elif defined(ESP8266)
TM1637Display ledSeg(5, 4);  // scl (clk) -> GPIO5 (D1) / sda (dio) -> GPIO4 (D2).
#endif

void setup() {
  Serial.begin(115200);                                           // watch serial monitor in case of new WiFi-connection
  myWiFi.autoConnect("TM1637_ESP");                               // connect ESP to new wifi via GSM or PC in that case
  ledSeg.setBrightness(2);                                        // 0 .. 7
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "be.pool.ntp.org");  // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
}

void loop() {
  getLocalTime(&tInfo);                                                                           // sntp sync at startup & every hour from then on
  ledSeg.showNumberDecEx(tInfo.tm_hour * 100 + tInfo.tm_min, 0x40 * (millis() % 1000 < 500), 1);  // 0x40 = ":", 1 = leading zeros
}

We could extend the sketch so that when the clock establishes a WiFi connection during startup, it looks up its own location via an API. Then the time zone could be assigned automatically. Thus, you could create an NTP clock with the ESP that you could use anywhere in the world. After connecting to an available WiFi, it displays the correct local time.



SSD1306-display + ESP8266 | ESP32

SSD1306-display + ESP8266 | ESP32

This is a small and inexpensive display (0.96"), with easy connection via the same pins as I2C. This display can show much more information than an LED segment display. This example code can be put on both the ESP8266 and the ESP32 without changes. Of course, SCL and SDA must be connected to D1 (GPIO5) and D2 (GPIO4)[ESP8266] or to 22 and 21 [ESP32] respectively.

In the code below, we have set a 4-hour interval to synchronize with SNTP.

Amazon: SSD1306
Amazon: SSD1306 (blue text)

   



Connections:

ESP32 ---> SDD1306 / SH1106
===========================
GND  --------> GND
3V3  --------> VCC
G21  --------> SDA
G22  --------> SCL
===========================
ESP8266 -> SDD1306 / SH1106
===========================
GND  --------> GND
3V3  --------> VCC
D2   --------> SDA
D1   --------> SCL


Sketch:

#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager/
#if defined(ESP32) 
#include <esp_sntp.h>  // ESP32 core
#endif
#include <Adafruit_SSD1306.h>
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>

WiFiManager myWiFi;
Adafruit_SSD1306 display(128, 64, &Wire, -1); // GND -> GND, 3V3 -> VCC, esp32: G21 -> SDA, G22 -> SCL
struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/          esp8266: D1 -> SCL,  D2 -> SDA

void setup() {
  Serial.begin(115200);  // watch serial monitor in case of new WiFi-connection
  display.begin(2, 0x3C);
  display.setTextColor(1);  // white = 1, black = 0
  display.setFont(&FreeSansBold12pt7b);
  myWiFi.autoConnect("SSD1306_ESP");  // connect ESP to new wifi via GSM or PC if new WiFi-connection
#if defined(ESP32)
  sntp_set_sync_interval(4 * 60 * 60 * 1000UL);
#endif
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "be.pool.ntp.org");
}

void loop() {
  getLocalTime(&tInfo);  // sntp sync at startup & every 4 hours from then on
  display.clearDisplay();
  display.drawRect(0, 0, 128, 40, 1);
  display.setFont(&FreeSansBold18pt7b);
  display.setCursor(4, 31);
  display.printf("%02d:%02d", tInfo.tm_hour, tInfo.tm_min);
  display.setFont(&FreeSansBold12pt7b);
  display.printf(":%02d", tInfo.tm_sec);
  display.setCursor(4, 63);
  display.printf("%02d-%02d-%04d", tInfo.tm_mday, 1 + tInfo.tm_mon, 1900 + tInfo.tm_year);
  display.display();
}
#if defined(ESP8266)
uint32_t sntp_update_delay_MS_rfc_not_less_than_15000() {  // declare only, unnecessary to call this function.
  return 4 * 60 * 60 * 1000UL;                             // NTP sync every 4 hr
}
#endif



SH1106-display + ESP8266 | ESP32

SH1106-display + ESP8266 | ESP32

This is a slightly larger display (1.3 inches), also easy to connect via the same pins as I2C. Compared to the previous code, he library was modified and the time synchronization interval omitted. This code can also be used on both the ESP8266 and ESP32 without changes. Of course, SCL and SDA must go to D1 (GPIO5) and D2 (GPIO4)[ESP8266] or to 22 and 21 [ESP32] respectively.

Aliexpress: SH1106 (blue text)



Connections:

ESP32 ---> SDD1306 / SH1106
===========================
GND  --------> GND
3V3  --------> VCC
G21  --------> SDA
G22  --------> SCL
===========================
ESP8266 -> SDD1306 / SH1106
===========================
GND  --------> GND
3V3  --------> VCC
D2   --------> SDA
D1   --------> SCL


Sketch:

#include <WiFiManager.h>      // https://github.com/tzapu/WiFiManager/
#include <Adafruit_SH110X.h>  // https://github.com/adafruit/Adafruit_SH110X
#include <Fonts/FreeSansBold12pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>

WiFiManager myWiFi;  
Adafruit_SH1106G display = Adafruit_SH1106G(128, 64, &Wire, -1);  // GND -> GND, 3V3 -> VCC, esp32: G21 -> SDA, G22 -> SCL
struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/          esp8266: D1 -> SCL,  D2 -> SDA

void setup() {
  Serial.begin(115200);       // watch serial monitor in case of new WiFi-connection
  display.begin(0x3C, true);  // Address 0x3C default
  display.setTextColor(1);    // white = 1, black = 0
  display.setFont(&FreeSansBold12pt7b);
  myWiFi.autoConnect("SSD1306_ESP");  // connect ESP to new wifi via GSM or PC if new WiFi-connection
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "be.pool.ntp.org");
}

void loop() {
  getLocalTime(&tInfo);  // sntp sync at startup & every hour from then on (esp8266) - every 3 hours (esp32)
  display.clearDisplay();
  display.drawRect(0, 0, 128, 40, 1);
  display.setFont(&FreeSansBold18pt7b);
  display.setCursor(4, 31);
  display.printf("%02d:%02d", tInfo.tm_hour, tInfo.tm_min);
  display.setFont(&FreeSansBold12pt7b);
  display.printf(":%02d", tInfo.tm_sec);
  display.setCursor(4, 63);
  display.printf("%02d-%02d-%04d", tInfo.tm_mday, 1 + tInfo.tm_mon, 1900 + tInfo.tm_year);
  display.display();
}



ESP8266 + integrated display SSD1306

ESP8266 + integrated display SSD1306

AliExpress: ESP8266 V3 (Blue/yellow - connected: SDA->D6, SCL->D5)
AliExpress: ESP8266 V2 - without pins (White - connected: SDA->D1, SCL->D2)

This ESP8266 is very convenient. You don't need a breadboard or wires to connect the display, the display is "on board". Just plug it into your PC's USB and you can program a WiFi network scanner or a clock. The SCL (serial clock) of the display is already connected to D5 (GPIO14) of the ESP8266, and the SDA (serial data) to D6 (GPIO12).

Here a different library is used for the display, "SSD1306.h". In the sketch shown, this library is implicitly started by the second line #include <SSD1306Wire.h>.
This library has some useful features, such as automatic centering. There are 3 fonts integrated into the library "SSD1306.h".

Sketch:

#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager/
#include <SSD1306Wire.h>  // https://github.com/ThingPulse/esp8266-oled-ssd1306

WiFiManager myWiFi;
SSD1306Wire display(0x3c, D6, D5);  // ADDRESS, SDA, SCL
struct tm tInfo;                    // https://cplusplus.com/reference/ctime/tm/
char days[7][10] = { "zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag" };

void setup() {
  Serial.begin(115200);               // watch serial monitor in case of new WiFi-connection
  myWiFi.autoConnect("SSD1306_ESP");  // connect ESP to new wifi via GSM or PC if new WiFi-connection
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org");
  display.init();
  display.setContrast(255);
  display.setTextAlignment(TEXT_ALIGN_CENTER);
}

void loop() {
  char hourMin[6], second[3], theDate[11];
  getLocalTime(&tInfo);                              // sntp sync at startup & every hour from then on
  strftime(hourMin, sizeof(hourMin), "%R", &tInfo);  // https://cplusplus.com/reference/ctime/strftime
  strftime(second, sizeof(second), "%S", &tInfo);
  strftime(theDate, sizeof(theDate), "%d-%m-%G", &tInfo);
  display.clear();
  display.setFont(ArialMT_Plain_24);  // display hour:minute in a larger font
  display.drawString(54, 0, hourMin);
  display.setFont(ArialMT_Plain_16);
  display.drawString(95, 7, second);
  display.drawString(64, 26, theDate);
  display.drawString(64, 45, days[tInfo.tm_wday]);
  display.display();
}

A second version, this time for the black and white version from AliExpress.


We added an extra option to the code: if the "flash" button (next to the USB connector) is pressed for three seconds, the WiFi data should be erased. Of course, we do not use "delay()". The serial monitor was omitted, and a function was added to show the user if no WiFi connection is set up.

Sketch:

#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager/
#include <SSD1306Wire.h>  // https://github.com/ThingPulse/esp8266-oled-ssd1306

WiFiManager myWiFi;
SSD1306Wire display(0x3c, D1, D2);  // ADDRESS, SDA, SCL (D1, D2)

struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
char days[7][10] = { "zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag" };
unsigned long startC;
bool pressed = false;

void setup() {
  display.init();
  display.setContrast(255);
  display.setTextAlignment(TEXT_ALIGN_CENTER);
  myWiFi.setAPCallback(messageNoConnection);  // is called when no wifi was set up yet
  myWiFi.autoConnect("DUINO");                // connect ESP to new wifi via GSM or PC if new WiFi-connection
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org");
}

void loop() {
  char hourMin[6], second[3], theDate[11];
  getLocalTime(&tInfo);                              // sntp sync at startup & every hour from then on
  strftime(hourMin, sizeof(hourMin), "%R", &tInfo);  // https://cplusplus.com/reference/ctime/strftime
  strftime(second, sizeof(second), "%S", &tInfo);
  strftime(theDate, sizeof(theDate), "%d-%m-%G", &tInfo);
  display.clear();
  display.setFont(ArialMT_Plain_24);  // display hour:minute in a larger font
  display.drawString(54, 0, hourMin);
  display.setFont(ArialMT_Plain_16);
  display.drawString(95, 7, second);
  display.drawString(64, 26, theDate);
  display.drawString(64, 45, days[tInfo.tm_wday]);
  display.display();
  buttonFlashPressed_3_Sec();
}

void buttonFlashPressed_3_Sec() {  // flash button pressed during 3 seconds: erase WiFi credentials
  if (!digitalRead(0) && (pressed) && (millis() - startC > 3000)) myWiFi.resetSettings(), ESP.restart();
  if (!digitalRead(0)) {
    if (!pressed) startC = millis();  // first time the loop encounters the pressed button: start counter
    pressed = true;                   // Indicate that the button was previously pressed
  } else pressed = false;             // button released before the 3 seconds expired.
}

void messageNoConnection(WiFiManager* myWiFi) {
  display.setFont(ArialMT_Plain_10);
  display.clear();
  display.drawString(64, 0, "WiFi: no connection.");
  display.drawString(64, 10, "Connect to hotspot DUINO");
  display.drawString(64, 20, "and open a browser at");
  display.drawString(64, 30, "address 192.168.4.1");
  display.drawString(64, 40, "to enter network name");
  display.drawString(64, 50, "and password");
  display.display();
}



SPI-display ST7735 (160*124px) + ESP8266

SPI-display ST7735 (160*124px) + ESP8266

AliExpress: ST7735

With Adafruit's library, this code causes flickering on the screen. Therefore, the (much faster) library TFT_eSPI was chosen here. You have to put a file "User_Setup.h".in the folder of the library "TFT_eSPI" (see tip in red text below). You can read the circuit schematics between the display and the microcontroller from this file "User_Setup.h".

Connections:

ESP8266   ======   128*160 display
==================================
  3V3   --------------- VCC
  GND   --------------- GND
  D8    --------------- CS
  D4    --------------- RESET
  D3    --------------- A0
  D7    --------------- SDA
  D5    --------------- SCK
  3V3   --------------- LED


Sketch:

#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager/
#include <TFT_eSPI.h>     // https://github.com/Bodmer/TFT_eSPI

WiFiManager myWiFi;
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite sprite = TFT_eSprite(&tft);
struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
char weekDay[7][10] = { "zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag" };

void setup() {
  tft.init(), tft.setRotation(1);
  myWiFi.setAPCallback(showMessageNoConnection);  // is called when no wifi was found
  myWiFi.autoConnect("ST7735_ESP");
  sprite.createSprite(160, 128);  // sprite for faster rendering
  configTzTime(PSTR("CET-1CEST,M3.5.0,M10.5.0/3"), "be.pool.ntp.org");
  sprite.setTextSize(1);  // time zones: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
}

void loop() {
  getLocalTime(&tInfo);  // sntp sync at startup & every hour from then on (esp8266)
  sprite.fillScreen(TFT_BLACK);
  sprite.setTextColor(TFT_CYAN, TFT_BLACK);
  sprite.setTextFont(7), sprite.setCursor(0, 0, 7);
  sprite.printf("%02d:%02d", tInfo.tm_hour, tInfo.tm_min);
  sprite.setCursor(142, 34, 2);
  sprite.printf("%02d", tInfo.tm_sec);
  sprite.setTextColor(TFT_YELLOW, TFT_BLACK), sprite.setFreeFont(&FreeSansBold12pt7b);  // custom font
  sprite.drawCentreString(weekDay[tInfo.tm_wday], 80, 66, 1);
  sprite.setTextColor(TFT_GREEN, TFT_BLACK), sprite.setCursor(20, 120, 4);
  sprite.printf("%02d-%02d-%04d", tInfo.tm_mday, 1 + tInfo.tm_mon, 1900 + tInfo.tm_year);  // dd-mm-yyyy
  sprite.drawRect(-1, 56, 162, 40, TFT_WHITE);
  sprite.pushSprite(0, 0);
}

void showMessageNoConnection(WiFiManager* myWiFi) {
  tft.fillScreen(TFT_NAVY);
  tft.setTextColor(TFT_YELLOW), tft.setCursor(0, 0, 2);
  tft.print(F("WiFi: no connection\nConnect to hotspot\nST7735_ESP and open\n"));
  tft.print(F("a browser at address\n192.168.4.1\nto enter network name\nand password"));
}

Contents of the "User_Setup.h" file in the case of an ST7735 display:

https://github.com/Bodmer/TFT_eSPI/blob/master/User_Setups/Setup2_ST7735.h

TFT_eSPI - Attention:

For easy overview, we modify the content of the file "User_Setup.h". This is a quick and dirty solution if you want to get 1 display working and see quick results.

If you use multiple displays and/or don't want to lose data when installing a new copy of this library, you'd better follow the tip from the designer of the library:

https://github.com/Bodmer/TFT_eSPI/#tips

You can comment out the entire contents of "User_Setup.h" if you work this way.




2.13 inch e-paper-display + ESP32

2.13 inch e-paper-display + ESP32

Amazon: 2.13inch e-paper

You can use the code from the sketch below unchanged for the LILYGO TTGO T5: AliExpress

The advantage of an e-paper display is that no power is needed to display information on the screen. Only a little bit to change the data on the screen. What you put on it stays there forever, even without power. The disadvantage is a slower update than the tft-spi displays (which is why the seconds are omitted), and you have to pay attention to how you "refresh" the screen so as not to get a violent "flicker". The library used does not have a function "drawCentreString" we have to create our own function for a string we want to display centered.

Connections:

ESP32 ---> 2.13 inch e-paper
============================
G4   --------> BUSY
G16  --------> RST
G17  --------> DC
G5   --------> CS
G18  --------> CLK
G23  --------> DIN
GND  --------> GND
3V3  --------> VCC


Sketch:

#include <GxEPD.h>
#include <GxDEPG0213BN/GxDEPG0213BN.h>  // 2.13" black/white 128x250 px
#include <WiFiManager.h>                // https://github.com/tzapu/WiFiManager/
#include <esp_sntp.h>                   // ESP32 core
#include <Fonts/FreeSansBold24pt7b.h>
#include <Fonts/FreeSansBold18pt7b.h>
#include <Fonts/FreeSansBold9pt7b.h>
#include <GxIO/GxIO_SPI/GxIO_SPI.h>
#include <GxIO/GxIO.h>

char days[7][10] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
int previousMinute;

GxIO_Class io(SPI, /*CS=5*/ SS, /*DC=*/17, /*RST=*/16);  // 17 & 16 = arbitrary choice
GxEPD_Class display(io, /*RST=*/16, /*BUSY=*/4);         // 16 & 4 = arbitrary choice
struct tm tInfo;                                         // https://cplusplus.com/reference/ctime/tm/
WiFiManager myWiFi;  

void setup() {
  display.init();
  display.setRotation(1);                          // landscape
  display.setTextColor(GxEPD_BLACK, GxEPD_WHITE);  // GxEPD_BLACK = 0x0000, GxEPD_WHITE = 0xFFFF
  myWiFi.setAPCallback(noConnection);              // on screen instructions for wifi connection
  myWiFi.autoConnect("E-PAPER-ESP32");             // will also reconnect after connection was lost
  sntp_set_sync_interval(4 * 60 * 60 * 1000UL);    // sntp sync every 4 hr
  configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "be.pool.ntp.org");
  display.fillScreen(GxEPD_WHITE);
  display.update();  // fully updating the display causes flicker for 2 seconds
}

void loop() {
  char hourMin[6], theDate[11];
  getLocalTime(&tInfo);
  if (previousMinute != tInfo.tm_min) {                      // update display only when minute changes
    strftime(hourMin, sizeof(hourMin), "%R", &tInfo);        // https://cplusplus.com/reference/ctime/strftime
    strftime(theDate, sizeof(theDate), "%d-%m-%G", &tInfo);  // this display is too slow to show the seconds
    display.fillScreen(GxEPD_WHITE);
    showCentered(39, hourMin);
    showCentered(84, days[tInfo.tm_wday]);
    showCentered(126, theDate);
    previousMinute = tInfo.tm_min;
    display.updateWindow(66, 0, 116, 41, true);  // partial update time (= no flicker)
    display.updateWindow(0, 50, 250, 78, true);  // partial update date
    display.powerDown();
  }
}

void showCentered(byte myHeight, const char* myText) {
  display.setTextColor(GxEPD_WHITE);
  display.setFont(strlen(myText) > 9 ? &FreeSansBold18pt7b : &FreeSansBold24pt7b);  // date = smaller font
  display.setCursor(0, myHeight);
  display.print(myText);
  display.setTextColor(GxEPD_BLACK);
  display.setCursor((display.width() - display.getCursorX()) / 2, myHeight);
  display.print(myText);
}

void noConnection(WiFiManager* myWiFi) {
  display.fillScreen(GxEPD_WHITE);
  display.setFont(&FreeSansBold9pt7b);
  display.setTextSize(1);
  showNoConnection(18, F("WiFi: no connection."));
  showNoConnection(35, F("Connect to hotspot:"));
  showNoConnection(52, F("E-PAPER-ESP32 and open"));
  showNoConnection(69, F("a browser at address"));
  showNoConnection(86, F("192.168.4.1"));
  showNoConnection(103, F("to enter network name"));
  showNoConnection(120, F("and password"));
  display.update();
}

void showNoConnection(byte top, const __FlashStringHelper* tekst) {
  display.setCursor(0, top);  // allows for 7 lines on a 128 px height display
  display.print(tekst);       // println without setCursor = interline of text is too large
}



SPI-display ST7789 (320*240px) + ESP8266

SPI-display ST7789 (320*240px) + ESP8266

Amazon: 2 inch display

This display has a resolution of 320 * 240 pixels, providing a very sharp image.
The display has a high refresh rate, you could display - if desired - hundredths of a second. The fastest and most versatile library for such displays is TFT-eSPI (Bodmer). It includes numerous examples, analog clocks, fonts, sprites, various gauges, the game "pong", graphs and fractals.

  • it can work with colors
  • with this library we can center with a function "drawCentreString"
  • the display has the ST7789 driver (supported by several libraries)
  • on this clock we're also showing the WiFi status (green or red = connected or not respectively)

The code contains about 10 lines more than that of the e-paper above, because it provides functions for the synchronization interval and the callback of the synchronization.
Because the TFT_eSPI library supports multiple displays, you need to set up a file "User_Setup.h" (or choose a predefined setup from the "User_Setups" folder of the TFT_eSPI library). Below the code I'll go into more detail on that. You will also find the schematics (ESP8266 - display) below.

Connections:

ESP8266	|   2 inch Waveshare (ST7739V driver) | 2.4 inch Waveshare (ILI9341 driver)
===============================
GND	------- GND
3V3	------- VCC
D3	------- DC
RST	------- RST
D5	------- CLK
D7	------- DIN
D8	------- CS
        ------- BL (not connected - niet verbonden)

Sketch:

#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager/
#include <TFT_eSPI.h>     // https://github.com/Bodmer/TFT_eSPI
#include <coredecls.h>    // ESP8266 core

struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
char days[7][10] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
byte previousHour = 24, previousMinute = 61, previousSecond;
String previousWeekDay;

TFT_eSPI tft = TFT_eSPI();
WiFiManager myWiFi;

void setup() {
  Serial.begin(115200);
  tft.init();
  tft.setRotation(1);
  tft.fillScreen(TFT_BLACK);  // clear screen from any previous session
  myWiFi.setAPCallback(showMessageNoConnection);
  myWiFi.autoConnect("ST7789_ESP");
  settimeofday_cb(esp8266NtpCallback);  // we need "#include <coredecls.h>" for this
  tft.fillScreen(TFT_BLACK);            // clear screen from "showMessageNoConnection" messages
  configTzTime(PSTR("CET-1CEST,M3.5.0,M10.5.0/3"), "be.pool.ntp.org");
  tft.setTextColor(TFT_CYAN);
  tft.setTextSize(2);
  tft.setCursor(117, 14, 7);
  tft.print(":");
}

void loop() {
  showChangesOnDisplay();
  WiFi.isConnected() ? showLogoWiFi(TFT_GREEN) : showLogoWiFi(TFT_RED);
}

void showTime(char timeChar[2], int leftS, int top, bool large) {
  tft.setCursor(leftS, top, 7);  // 7 is a built in font
  tft.setTextColor(TFT_CYAN, TFT_BLACK);
  tft.setTextSize(large + 1);
  tft.print(timeChar);
  tft.setTextSize(1);
}

void showChangesOnDisplay() {                             // this function divides the screen changes
  char hourChar[3], minChar[3], secChar[3], theDate[11];  // into chunks to prevent flicker
  getLocalTime(&tInfo);
  if (tInfo.tm_sec != previousSecond) {
    sprintf(secChar, "%02d", tInfo.tm_sec);  // format 2 digits decimal
    showTime(secChar, 256, 61, false);
    previousSecond = tInfo.tm_sec;
  }
  if (tInfo.tm_min != previousMinute) {
    sprintf(minChar, "%02d", tInfo.tm_min);
    showTime(minChar, 131, 14, true);
    previousMinute = tInfo.tm_min;
  }
  if (previousHour != tInfo.tm_hour) {
    sprintf(hourChar, "%02d", tInfo.tm_hour);
    showTime(hourChar, -2, 14, true);
    previousHour = tInfo.tm_hour;
     if (previousWeekDay != days[tInfo.tm_wday]) {
      tft.fillRect(0, 134, 320, 106, TFT_BLACK);
      tft.setTextColor(TFT_YELLOW);
      tft.setFreeFont(&FreeSansBold24pt7b);  // custom font. &FreeMonoBold24pt7b is possible too
      tft.drawCentreString(days[tInfo.tm_wday], 160, 136, 1);
      tft.drawRoundRect(0, 124, 320, 70, 3, TFT_WHITE);
      tft.setTextColor(TFT_GREEN);
      strftime(theDate, sizeof(theDate), "%d-%m-%G", &tInfo);
      tft.drawCentreString(theDate, 160, 202, 1);
      tft.setTextFont(7);
      previousWeekDay = days[tInfo.tm_wday];
    }
  }
}

void showLogoWiFi(int myColor) {
  tft.fillCircle(294, 30, 6, myColor);
  for (byte tel = 0; tel < 3; tel++) {
    tft.drawSmoothArc(294, 32, 30 - tel * 7, 28 - tel * 7, 135, 225, myColor, myColor, true);
  }
}

void showMessageNoConnection(WiFiManager* myWiFi) {
  tft.setCursor(0, 0, 4);
  tft.setTextColor(TFT_GREEN, TFT_BLACK);
  tft.print(F("\nWiFi: no connection\nConnect to hotspot:\n"));
  tft.print(F("ST7789_ESP and open\na browser at address \n192.168.4.1\n"));
  tft.print(F("to enter network name\nand password"));
}

void esp8266NtpCallback(bool from_sntp) {  // in setup: "settimeofday_cb(esp8266NtpCallback)";
  if (from_sntp) Serial.printf("\n*** NTP Sync ***\n%s \n", getenv("TZ"));
}

uint32_t sntp_update_delay_MS_rfc_not_less_than_15000() {  // declare only, unnecessary to call this function
  return 4 * 60 * 60 * 1000UL;                             // SNTP sync every 4 hr
}

You'll find among the predefined "User_Setups" the most common configurations. For example the "Setup18_ST7789.h" in this folder can be used for this project. Should the displayed colors not be correct (red and green swapped): you can set the 3rd line active and uncomment the 4th line. See tip in red text below. Contents of the User_Setup.h file (for this project/display; in the TFT_eSPI folder - libraries):

//            ST7789
#define USER_SETUP_INFO "User_Setup"
#define ST7789_DRIVER 
// #define TFT_RGB_ORDER TFT_RGB  // Colour order Red-Green-Blue
#define TFT_RGB_ORDER TFT_BGR  // Colour order Blue-Green-Red
#define TFT_HEIGHT 320 // ST7789 240 x 320
#define TFT_CS   PIN_D8  // Chip select control pin D8
#define TFT_DC   PIN_D3  // Data Command control pin
#define TFT_RST  -1    // Set TFT_RST to -1 if the display RESET is connected to NodeMCU RST or 3.3V
// fonts die je niet gebruikt best uitcommenten om FLASH te besparen
#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_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
#define LOAD_GFXFF  // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts

#define SPI_FREQUENCY  27000000
#define SPI_READ_FREQUENCY  20000000
#define SPI_TOUCH_FREQUENCY  2500000

TFT_eSPI - Attention:

For easy overview, we modify the content of the file "User_Setup.h". This is a quick and dirty solution if you want to get 1 display working and see quick results.

If you use multiple displays and/or don't want to lose data when installing a new copy of this library, you'd better follow the tip from the designer of the library:

https://github.com/Bodmer/TFT_eSPI/#tips

You can comment out the entire contents of "User_Setup.h" if you work this way.



ESP32-3248S035 integrated display (480 * 320px)

ESP32-3248S035 integrated display (480 * 320px)

Aliexpress: 3.5" capacitive

This is a convenient combination of an ESP32 and a relatively large touch screen. You won't find many examples of this combination for the Arduino IDE.

We do not use the touch part in this example (but it works perfectly), you can just load (and test) that from the examples of the library "TFT_eSPI".

In this sketch, we retrieve dynamic electricity prices (Europe) via an API to show them on the display. We can change the view to numeric data or graph (with the flash button).

Obviously there is no circuit schematics to display, but it is quite a search if you want to configure this system for the Arduino IDE. We will again use the library TFT-eSPI (Bodmer). You need to place a file "User_Setup.h" in the folder of the library "TFT_eSPI".

Contents of the file "User_Setup.h" for the ESP32-3248S035:

#define ST7796_DRIVER
#define TFT_WIDTH  320
#define TFT_HEIGHT 480 // 
#define TFT_BL   27            // LED back-light control pin
#define TFT_BACKLIGHT_ON HIGH  // Level to turn ON back-light (HIGH or LOW)

#define TFT_MISO 12
#define TFT_MOSI 13  // In some display driver board, it might be written as "SDA" and so on.
#define TFT_SCLK 14
#define TFT_CS   15  // Chip select control pin
#define TFT_DC   2   // Data Command control pin
#define TFT_RST  -1  // Reset pin (could connect to Arduino RESET pin)

#define TOUCH_CS 33     // Chip select pin (T_CS) of touch screen

#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_FONT8N // Font 8. Alternative to Font 8 above, slightly narrower, so 3 digits fit a 160 pixel TFT
#define LOAD_GFXFF  // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts

// Comment out the #define below to stop the SPIFFS filing system and smooth font code being loaded
// this will save ~20kbytes of FLASH
#define SMOOTH_FONT

#define SPI_FREQUENCY  65000000
#define SPI_READ_FREQUENCY  20000000
#define SPI_TOUCH_FREQUENCY  2500000  //2500000

TFT_eSPI - Attention:

For easy overview, we modify the content of the file "User_Setup.h". This is a quick and dirty solution if you want to get 1 display working and see quick results.

If you use multiple displays and/or don't want to lose data when installing a new copy of this library, you'd better follow the tip from the designer of the library:

https://github.com/Bodmer/TFT_eSPI/#tips

You can comment out the entire contents of "User_Setup.h" if you work this way.



Sketch:



/*==========================================================================================
This sketch was written for the ESP32-3248S035 with integrated display (480 * 320 pixels). 
If a WiFi connection cannot be made, a web server is started to store the WiFi credentials.
Dynamic electricity prices (Europe) are retrieved via an API and displayed on the screen. 
The flash button switches the display between figures in two columns or graph. 
Prices for the next day are available between 1200 hours and 2100 hours (UTC). 
The script will check (starting at 1200 UTC) every hour if new API data is available. 
The script keeps repeating this every hour until it succeeds. If the first bidding zone  
returns no results any more, the script tries the second zone. If only the second bidding 
zone produces results, every hour an attempt is made to obtain the data for the first zone. 
=============================================================================================
Arduino IDE - board: ESP32 Dev Module or DOIT ESP32 DEVKIT V1
https://nl.aliexpress.com/item/1005006398547061.html
https://nl.aliexpress.com/item/1005005900820162.html
==========================================================================================*/

#include <WiFiManager.h>  // https://github.com/tzapu/WiFiManager/
#include <TFT_eSPI.h>     // https://github.com/Bodmer/TFT_eSPI
#include <ArduinoJson.h>  // https://github.com/bblanchon/ArduinoJson
#include <HTTPClient.h>   // for API connection
#include <Preferences.h>  // no need to install this library

TFT_eSPI tft = TFT_eSPI();  // User_Setup.h settings available on: https://espgo.be/index-en.html#2035
WiFiManager myWiFi;
Preferences flash;

#define SHOW_ITEMS 34  // max number of records you want to process
#define this_Ssid "S035_ESP"

struct tm tInfo;  // https://cplusplus.com/reference/ctime/tm/
bool chart = true, bidLU = false, freshStart = true, cursorUpdated = false;
unsigned long lastAction;
constexpr uint8_t VALUE_RED = 100, FLASH_BUTTON = 0;  // electricity price in red color if higher than VALUE_RED
const char* biddingZone[] = { "BE", "DE-LU" };        // default & backup bidding zone
double prijzen[SHOW_ITEMS];                           // most recent prices
double prices[SHOW_ITEMS * 2];                        // all prices returned by the API
double priceNow;                                      // price of current hour
time_t epo[SHOW_ITEMS];                               // most recent epoch times
time_t epoc[SHOW_ITEMS * 2];                          // all epoch times returned by the API

void setup() {
  const char* time_Zone = "CET-1CEST,M3.5.0,M10.5.0/3";  // https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
  pinMode(FLASH_BUTTON, INPUT_PULLUP);
  for (const uint8_t &pin : {4, 16, 17}) pinMode(pin, OUTPUT), digitalWrite(pin, HIGH); // red, green & blue LEDs off
  tft_Setup();
  getBidZoneFromFlash();
  myWiFi.setAPCallback(messageNoConnection);
  myWiFi.autoConnect(this_Ssid);
  configTzTime(time_Zone, "be.pool.ntp.org");
  showStartupData();
}

void loop() {
  display_Time();
  if (!digitalRead(FLASH_BUTTON)) changeView();  // flash button changes between chart or list of figures
  // if (priceNow < 0) start_Energy-Guzzling_device();  // priceNow = current electricity price
  // else stop_Energy-Guzzling_device();
}

void tft_Setup() {  // no sprites because the data on the screen is rather static
  tft.init();
  tft.setRotation(3);
  tft.setTextWrap(false);
  tft.fillScreen(TFT_BLACK);  // delete output from previous sessions
  tft.drawCentreString("Connecting to WIFi", 240, 160, 4);
}

void getBidZoneFromFlash() {
  flash.begin("bid_zone", true);         // read from flash, true = read only
  bidLU = flash.getBool("bid_zone", 0);  // retrieve the last set bidding zone - default to first in the array [0]
  flash.end();
}

bool timeElapsed(unsigned long myMillis) {  // avoid blocking task "delay(millisec)"
  return millis() - lastAction >= myMillis;
}

void showStartupData() {
  tft.fillScreen(TFT_BLACK);
  tft.drawCentreString("Retrieving SNTP time", 240, 160, 4);
  getLocalTime(&tInfo);
  tft.fillScreen(TFT_BLACK);
  tft.setTextColor(TFT_ORANGE);
  tft.drawCentreString("Retrieving Json Data", 240, 160, 4);
}

void changeView() {
  if (timeElapsed(800)) {  // UI debouncing (flash button)
    chart = !chart;
    chart ? show_Chart() : show_Data();
    showCurrentPrice();
    lastAction = millis();
  }
}

void display_Time() {
  char theDate[11], theTime[9];
  getLocalTime(&tInfo);  // SNTP sync at startup and every 3 hours thereafter (ESP32)
  time_t now;
  time(&now);  // assign current epoch time (UTC) to "now"
  tft.drawRect(-1, 26, 482, 2, WiFi.isConnected() ? TFT_GREEN : TFT_RED);
  strftime(theDate, sizeof(theDate), "%d-%m-%G", &tInfo);  // https://cplusplus.com/reference/ctime/strftime/
  strftime(theTime, sizeof(theTime), "%T", &tInfo);
  tft.setTextColor(TFT_GREEN, TFT_BLACK), tft.drawString(theDate, 2, 0, 4);
  tft.setTextColor(TFT_WHITE, TFT_BLACK), tft.drawString(theTime, 139, 0, 4);
  tft.drawRightString(bidLU ? "Lux" : "Bel", 480, 8, 2);
  if ((freshStart) || ((((epo[SHOW_ITEMS - 1] - now) / 3600) < 9) && (tInfo.tm_min == 2) && (tInfo.tm_sec == 1))) {
    if (WiFi.isConnected()) get_JsonData();  // ^^from 1200 hrs UTC: try to retrieve new data every hour until it succeeds
    chart ? show_Chart() : show_Data(), showCurrentPrice();
    freshStart = false;
  }
  if (tInfo.tm_min == 0) {
    if (tInfo.tm_sec == 0) tryDefaultZone();       // if not default bidding zone, try default zone every new hour
    if (tInfo.tm_sec == 1) cursorUpdated = false;  // reset update flag
  }
}

void tryDefaultZone() {  // if current bidding zone isn't the default bidding zone, try default zone
  if (!cursorUpdated) {  // prevent updating several times per cycle
    cursorUpdated = true;
    if (bidLU) {                         // if the current bidding zone isn't the default zone
      flash.begin("bid_zone", false);    // flash memory (false = also write)
      flash.putBool("bid_zone", false);  // store default bidding zone to flash memory
      flash.end();
      ESP.restart();  // restart because want to retrieve the data of the default bidding zone.
    }
    chart ? show_Chart() : show_Data();  // refresh local data every hour
    showCurrentPrice();
  }
}

void show_Chart() {
  tft.fillRect(0, 28, 480, 292, TFT_BLACK);       // erase any previous graphic data
  tft.setTextColor(tft.color565(160, 160, 160));  // set color once and adjust only when needed
  for (uint8_t i = 0; i < 6; i++) {               // draw horizontal grid lines and value labels
    tft.drawFastHLine(0, 40 + i * 50, 480, tft.color565(48, 48, 48));
    for (uint8_t j = 0; j < 4; j++) {
      tft.drawRightString(String(i * 100 - 100), j * 150 + 23, 286 - i * 50, 1);
    }
  }
  for (uint8_t i = 0; i < SHOW_ITEMS; i++) {
    if (epo[i]) {                           // only process if there is valid data
      struct tm* cor = localtime(&epo[i]);  // convert epoch to local time
      bool isCurrentHour = (cor->tm_hour == tInfo.tm_hour) && (cor->tm_mday == tInfo.tm_mday);
      if (isCurrentHour) {
        priceNow = prijzen[i];
        tft.drawFastVLine(6 + i * 14, 26, 292, TFT_MAGENTA);
        tft.fillTriangle((i * 14) - 4, 300, (i * 14) - 4, 310, 6 + i * 14, 305, TFT_MAGENTA);
        tft.fillTriangle((i * 14) - 4, 55, (i * 14) - 4, 65, 6 + i * 14, 60, TFT_MAGENTA);
      }
      uint16_t myColor;  // determine the color based on the value of prijzen[i]
      if (prijzen[i] < 0) myColor = TFT_YELLOW;
      else if (prijzen[i] < 50) myColor = TFT_GREEN;
      else if (prijzen[i] < VALUE_RED) myColor = TFT_CYAN;
      else myColor = TFT_RED;
      int price_scaled = prijzen[i] / 2;           // scale prices for display
      int height_offset = (480 - prijzen[i]) / 2;  // calculate offset for positive values
      if (prijzen[i] < 0) {                        // draw the price bar
        tft.fillRect(i * 14, 240, 13, 0 - price_scaled, myColor);
        tft.drawFastVLine(i * 14 + 13, 240, (0 - prijzen[i]) / 2, TFT_BLACK);
      } else {
        tft.fillRect(i * 14, height_offset, 13, price_scaled, myColor);
        tft.drawFastVLine(i * 14 + 13, height_offset, price_scaled, TFT_BLACK);
      }
      tft.setCursor(1 + i * 14, 230, 1);  // draw the hour label at the bottom of the chart
      tft.printf("%02d", cor->tm_hour);
      tft.setTextColor(TFT_RED);  // draw the hour label again (red)
      tft.setCursor(1 + i * 14, 243, 1);
      tft.printf("%02d", cor->tm_hour);
      tft.setTextColor(prijzen[i] > 0 ? TFT_BLACK : TFT_YELLOW);  // Reset text color to default
    }
  }
}

void show_Data() {  // display Json data in 2 columns (time / price)
  tft.fillRect(0, 28, 480, 292, TFT_BLACK);
  tft.drawRect(240, 26, 2, 292, WiFi.isConnected() ? TFT_GREEN : TFT_RED);
  struct tm* cor;
  uint16_t xPos, yPos;
  for (uint8_t i = 0; i < SHOW_ITEMS; i++) {
    if (epo[i]) {                 // check for valid data
      cor = localtime(&epo[i]);   // convert epoch to local time
      xPos = (i < 18) ? 0 : 260;  // calculate position for cursor
      yPos = 33 + (i % 18) * 16;
      tft.setTextColor(TFT_LIGHTGREY, TFT_BLACK);  // display index number
      tft.setCursor(xPos, yPos, 2);
      tft.printf(" %02d. ", i + 1);
      if ((cor->tm_hour == tInfo.tm_hour) && (cor->tm_mday == tInfo.tm_mday)) {  // check if current hour/day matches
        priceNow = prijzen[i];
        tft.setTextColor(TFT_MAGENTA, TFT_BLACK);
      } else tft.setTextColor(TFT_WHITE, TFT_BLACK);
      tft.printf("%02d-%02d-%02d %02d:%02d", cor->tm_mday, cor->tm_mon + 1, cor->tm_year + 1900, cor->tm_hour, cor->tm_min);
      if (prijzen[i] < 0) tft.setTextColor(TFT_YELLOW, TFT_BLACK);  // set color based on price value
      else if (prijzen[i] < 50) tft.setTextColor(TFT_GREEN, TFT_BLACK);
      else if (prijzen[i] < VALUE_RED) tft.setTextColor(TFT_CYAN, TFT_BLACK);
      else tft.setTextColor(TFT_RED, TFT_BLACK);
      tft.drawRightString(String(prijzen[i]), (i < 18 ? 220 : 480), yPos, 2);  // display the price on the right
    }
  }
}

void showCurrentPrice() {  // price (upper right corner of display)
  tft.setCursor(264, 0, 4);
  tft.setTextColor(TFT_SILVER, TFT_BLACK);
  tft.print("/MWh:");
  tft.fillCircle(255, 10, 9, TFT_SILVER);  // euro symbol
  tft.fillCircle(255, 10, 7, TFT_BLACK), tft.fillRect(258, 5, 8, 12, TFT_BLACK);
  tft.drawRect(247, 8, 9, 2, TFT_SILVER), tft.drawRect(249, 11, 6, 2, TFT_SILVER);
  tft.setTextColor(priceNow < VALUE_RED ? TFT_CYAN : TFT_RED);
  if (priceNow < 50) tft.setTextColor(TFT_GREEN);
  if (priceNow < 0) tft.setTextColor(TFT_YELLOW);
  tft.fillRect(344, 0, 100, 22, TFT_BLACK);
  tft.setCursor(344, 0, 4);
  tft.printf("%3.2F", priceNow);
}

void get_JsonData() {
  String my_link;
  uint8_t API_ITEMS;
  memset(prijzen, 0, sizeof(prijzen));
  memset(prices, 0, sizeof(prices));
  memset(epo, 0, sizeof(epo));
  memset(epoc, 0, sizeof(epoc));
  time_t now = mktime(&tInfo) - (mktime(&tInfo) % 3600);  // convert "struct tm" to time_t and round down to the whole hour
  my_link = String("https://api.energy-charts.info/price?bzn=") + biddingZone[bidLU] + "&start="
            + String(now - (24 * 3600)) + "&end="
            + String(now + (SHOW_ITEMS * 3600));
  HTTPClient http;
  http.begin(my_link);
  if (timeElapsed(3000)) {  // allows the http connection to be complete
    lastAction = millis();
    if (http.GET() >= 200 && http.GET() < 300) {
      JsonDocument doc;
      String json = http.getString();
      auto error = deserializeJson(doc, json.c_str());
      if (error) {
        tft.drawCentreString("deserializeJson() failed", 240, 200, 4);
        tft.drawCentreString(error.c_str(), 240, 250, 4);
      }
      for (uint8_t i = 0; i < SHOW_ITEMS * 2; i++) {  // actually more data than we can show on display
        prices[i] = doc["price"][i];
        epoc[i] = doc["unix_seconds"][i];
        API_ITEMS = i;  // count number of returned items
      }
    } else {  // http.get has failed
      tft.fillScreen(TFT_BLACK);
      tft.drawCentreString("Error in API response", 240, 160, 4);
      tft.drawCentreString("Restarting device", 240, 190, 4);
      flash.begin("bid_zone", false);     // true = read only
      flash.putBool("bid_zone", !bidLU);  // try other bidding zone
      flash.end();
      delay(1000);
      ESP.restart();
    }
  }
  http.end();
  uint8_t j = SHOW_ITEMS + 1;
  for (uint8_t i = API_ITEMS; i >= 0; i--) {  // select the most recent Json data
    if (epoc[i] != 0) {                       // ignore empty data
      j--;                                    // counter for results
      prijzen[j - 1] = prices[i];             // transferring valid prices from API results
      epo[j - 1] = epoc[i];                   // also hours to "local" array
      if (j == 0) break;                      // stop when we have the [SHOW_ITEMS] most recent items
    }
  }
}

void messageNoConnection(WiFiManager* myWiFi) {
  const char* connect[] = { "WiFi: no connection", "Connect to hotspot", this_Ssid, "Start a browser - address",
                      "192.168.4.1", "to save network", "and password" };
  tft.fillScreen(TFT_NAVY), tft.setTextColor(TFT_YELLOW);
  for (uint8_t i = 0; i < 7; i++) tft.drawCentreString(connect[i], 240, 40 + i * 30, 4);
}



Lilygo TTGO T-Display (ESP32)


Lilygo TTGO T-Display (ESP32)

LILYGO TTGO T-Display (Aliexpress)
LILYGO TTGO T-Display (Lilygo)

This is an ESP32 with an onboard SPI display (240*135px). On the front, there are two buttons that we can operate while the software is running, and a reset button on the side. Because the display is relatively small, it works quickly. You can notice that in e.g. the sprites. We can order this combination with a matching plastic box. It is a pity that there is no magnet in the back of the box (in the two following combinations there is). When using the plastic case, you have to push the buttons fairly hard. For operating a game, it is not really suitable. That's much better designed in the next Lilygo product we review here.

For clock operation (deep sleep, change time zones) the buttons do not hamper. As you notice on the video below, we save the selected time zone, so that the set time zone is retained on restart or after deep sleep mode.

We also used our own fonts and multiple animations on this development board. Click here for the sample sketch of the image below.



In the TFT_eSPI library used, a file is provided in the User_Setups folder for this board: Setup25_TTGO_T_Display.h

Sketch:

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



Lilygo T-Display S3 (ESP32)


Lilygo T-Display S3 (ESP32)

LILYGO T-Display S3

You can acquire the Lilygo T-Display S3 with a matching black or gray plastic case. There is a 1.9-inch color LCD screen (320 * 170px) on board. The module has 2 buttons at the front (GPIO0 and GPIO14), and there is also a reset button on the side.

Since we have two buttons - apart from the reset button - we can use both of them in this code. The top button was used for deep sleep mode, and the bottom button to select different time zones. We store the set time zone in flash memory, so that it is retained when we restart. We use the library preferences.h.

We also added some toys for a nicer presentation. A small animation at startup, when going into deep sleep mode, and when changing time zone.

In the TFT_eSPI library used, a file is provided in the User_Setups folder for this board: Setup206_LilyGo_T_Display_S3.h

You can also use your own fonts on a ESP. Click here for the sample sketch of the image below.



Of course, a simple weather station is also possible. Click here for the sample sketch of the image below.



Sketch:

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




Connecting to WiFi

Connecting to WiFi

The ESP8266 / ESP32 is basically a programmable switch of GPIO pins, with WiFi and Bluetooth / BLE. WiFi is almost always used with the ESP: most programmable boards without WiFi are much more energy efficient than the ESP. So we can say that we always use the ESP for WiFi.

So how do we best program the connection?

Hard-coding the login information in the sketch? That's a bad idea. Add a separate file “credentials.h” with SSID and password to the sketch? Neither can developing a professional application that way. A customer will not recompile a sketch, he wants his IOT gadget to just work after setup.

On startup, the ESP should try to connect to WiFi, using the login credentials from the EEPROM. If that fails, launch an access point and a web server to store the login credentials, then reboot the ESP.

That is exactly what the library WiFiManager.h does, with only 2 lines in your sketch. You'll find plenty of sketches on this page to see how that works. However, since the release of core 3.0.x (Arduino IDE, boards manager), WiFiManager.h causes errors with some boards.

If you want to avoid these errors, and don't like to depend on a library, you can manually add the code in your sketch. Here is a simple example (ESP32) connecting to WiFi, and if it fails, starting a WiFi access point and web server to store the data.

#include <WiFi.h>
#include <WebServer.h>
#include <Preferences.h>  // no need to install or download this library
#include <vector>         // C++ Standard Library: dynamic array container

WebServer server(80);
Preferences flash;

#define this_Ssid "ESP32-Conn"
String ssid, pasw;
std::vector<String> availableSSIDs;

void setup() {
  Serial.begin(115200);
  connect_to_WiFi();
  Serial.println(" Connected to " + WiFi.SSID());  // script lands on this line only if the connection succeeded
}

void loop() {}  // no command lines in this design

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();
  Serial.print("Connecting");
  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);
    Serial.print(".");
  }
  Serial.print("\nConnection failed. Connect to hotspot\n");  // script lands on this line only when the connection fails
  Serial.print(this_Ssid);
  Serial.print("\nand open a browser @ 192.168.4.1 to save the WiFi credentials");
  int numNetworks = WiFi.scanNetworks();
  availableSSIDs.clear();
  for (int i = 0; i < numNetworks; i++) availableSSIDs.push_back(WiFi.SSID(i));  // https://favtutor.com/blogs/push-back-vector-cpp
  WiFi.mode(WIFI_MODE_AP);
  WiFi.softAP(this_Ssid, "", 1);
  server.on("/", serveWiFiSetupPage);
  server.on("/setting", processWiFiSettingsSubmission);
  server.begin();
  for (;;) server.handleClient();  // infinite loop until the WiFi credentials are inserted
}

void serveWiFiSetupPage() {  // html to insert WiFi credentials and P1 meter data
  String webText = F("<!DOCTYPE HTML><html lang='en'><head><title>WiFi Setup</title>");
  webText += F("<meta name='viewport' content='width=device-width, initial-scale=1.0'><style>");
  webText += F("body { font-family: Arial, sans-serif; background: #f4f4f4; margin: 0; padding: 0; text-align: center; }");
  webText += F(".container { max-width: 440px; background: white; padding: 20px; margin: 20px auto; border-radius: 10px;");
  webText += F("box-shadow: 0 0 10px rgba(0,0,0,0.1); } h1 { font-size: 24px; color: #333; margin-bottom: 10px; }");
  webText += F("p { font-size: 16px; color: #555; } label { display: block; text-align: left; margin-top: 12px; color: #333; }");
  webText += F("input, select, button { width: 100%; padding: 10px; margin-top: 4px; border-radius: 5px;");
  webText += F("border: 1px solid #ccc; font-size: 16px; box-sizing: border-box; }");
  webText += F("input[type=submit], button { background: #007BFF; color: white; border: none; cursor: pointer; }");
  webText += F("input[type=submit]:hover, button:hover { background: #0056b3; }");
  webText += F("fieldset { border: 2px solid #007BFF; border-radius: 10px; padding: 16px; margin-top: 20px; background: #f0f8ff;");
  webText += F("box-shadow: inset 0 0 5px rgba(0,123,255,0.1); }legend { font-weight: bold; color: white; background: ");
  webText += F("#006400; padding: 4px 10px; border-radius: 5px; font-size: 16px; }");
  webText += F("#ssid-buttons { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; margin-top: 16px; }");
  webText += F("#ssid-buttons button { width: auto; padding: 8px 14px; font-size: 14px; }</style></head><body>");
  webText += F("<div class='container'><h1>WiFi Setup</h1><p>Select a network or enter details below.</p>");
  webText += F("<div id='ssid-buttons'>");
  for (const String& ssid : availableSSIDs) {
    webText += "<button onclick=\"document.getElementsByName('ssid')[0].value='" + ssid + "'\">" + ssid + "</button>";
  }
  webText += F("</div><form method='get' action='setting'><fieldset><legend>WiFi Settings</legend>");
  webText += F("<label><b>SSID:</b></label><input name='ssid' required><label><b>Password:</b></label><input type='password'");
  webText += F(" id='pass' name='pass' required></fieldset>");
  webText += F("<br><input type='submit' value='Save'></form></div></body></html>");  // Submit
  server.send(200, "text/html", webText);
}

void processWiFiSettingsSubmission() {
  String newSsid = server.arg("ssid");  // collect data from web page
  String newPassword = server.arg("pass");
  uint16_t newP1Interval = server.arg("p1Upd").toInt();
  if (newSsid.length() > 0) {  // save data to FLASH memory
    flash.begin("login_data", false);
    flash.putString("ssid", newSsid);
    flash.putString("pasw", newPassword);
    flash.end();
  }
  String webText = F("<!DOCTYPE HTML>\n<html lang='en'>\n<head><title>Setup</title>\n<meta name='viewport' ");  // user feedback
  webText += F("content='width=device-width, initial-scale=1.0'>\n<style>\n* {\n  font-family: Arial, Helvetica");
  webText += F(", sans-serif;\n  font-size: 45px;\n  font-weight: 600;\n  margin: 0;\n  text-align: center;\n}");
  webText += F("\n\n@keyframes op_en_neer {\n  0%   {height:  0px;}\n  50%  {height:  40px;}\n  100% {height:  0px;}\n}");
  webText += F("\n\n.opneer {\n  margin: auto;\n  text-align: center;\n  animation: op_en_neer 2s infinite;\n}");
  webText += F("\n</style>\n</head>\n<body>\n<div class=\"opneer\"></div>\nESP will reboot<br>Close this window");
  webText += F("\n</body>\n</html>");
  server.send(200, "text/html", webText);
  delay(500);
  ESP.restart();
}



ESP32 + display: read digital electricity meter data
ESP32 + display: read digital electricity meter data