↩ All projects and sketches Deze pagina in het Nederlands
ESP32 · e-paper · SPI

2.13 inch e-paper display: NTP clock with ESP32

2.13 inch e-paper display connected to an ESP32
2.13 inch e-paper on Amazon — the code also works unchanged on the LILYGO TTGO T5

An e-paper display needs no power to keep its image — ideal for a clock that just stays "on screen", even without power.

What do you need for this?

2.13 inch e-paper display + ESP32

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", so 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
}
Partial refresh = no flicker display.updateWindow() only refreshes the changed area of the screen instead of the whole display — so there's no visible flicker on every minute change. A full display.update(), like at startup, does cause a brief ~2 second flicker.

More displays with the ESP8266 / ESP32

Curious about other displays? Check out the full overview, including TM1637, SSD1306, SH1106, ST7735, ST7789 and integrated displays like the Lilygo T-Display and the Waveshare ESP32-S3.