What is it?

LilyGo is a Chinese manufacturer who makes a wide variety of embedded microcontrollers. For me, some of the most exciting ones they have are based on the ESP-32 chip. The ESP-32 offers a lot of capabilities in a small package: WiFi, Bluetooth, a dualcore 240Mhz CPU, and way more RAM+flash storage than other cheap boards in this space. What’s interesting about LilyGo’s products is that they make boards with builtin integration of all sorts of peripherals: 18650 batteries, LoRan radios, cameras, and many many more. They also have a range of boards with built in ribbon connectors for use with eink displays. Eink/epaper is great for displaying information because in addition to being easy on the eyes it doesn’t require persistent power. Electricity is only used when the display changes, so it’s great for battery power projects that don’t need high refresh rates.

I purchased this display, which was supposed to be their T5 v2.0 display with a 2.13 inch integrated screen. Instead, my order came with the T5 v2.3. As far as I can tell, the only difference is that the v2.3 has an on/off switch and the battery connector is in a slightly different spot. Also, the 2.3 might be using a different version of the display than the 2.0.

Their documentation is better than nearly all of their competitors, but it still isn’t great. According to their schematics the battery connector is a 2mm JST connector. I think it’s actually a 1.25mm connector similar to the Molex PicoBlade. LilyGo has some sample code for the display that uses a forked version of the GxEPD library. Unfortunately their fork is based off of a really old version of GxEPD and none of the changes have been mainlined into the original library. Even though it isn’t officially supported, you can get the display to work using GxEPD2.

Arduino

Prerequisites

All you need to do is install the GxEPD2 library in the Arduino IDE (Tools -> Manage libraries). GxEPD2 depends on the Adafruit GFX library, which should be installed automatically. Adafruit has a great overview of how their library works. All of that info applies to using GxEPD2.

Hello World

LilyGo’s documentation says that the display revision is 213B72B. That doesn’t exist in the GxEPD2 library, but fortunately there’s a 213_B73 that seems to be compatible.

NOTE: I’ve noticed some semi-permanent ghosting after using this code to do partial refresh with my e-ink screen. I’m not sure if that’s a fault in the hardware or if it’s caused by software abuse so proceed at your own risk

The GxEPD2 sample code is designed to be compatible with a wide range of hardware. Their sample code does a lot of complicated bookkeeping so that it works on low memory devices that can’t store the entire framebuffer. Fortunately, the ESP32 has plenty of memory, so we don’t need to overcomplicate things. I’ve tried to distill their HelloWorld example down to the essential parts:


#include <GxEPD2_BW.h>                // if you're using a 3 color display, use GxEPD_3C instead
#include <Fonts/FreeMonoBold9pt7b.h>  // look into the adafruit GFX library to see all available fonts

// You can verify the pin assignments below with the table here: https://github.com/lewisxhe/TTGO-EPaper-Series#board-pins
GxEPD2_BW<GxEPD2_213_B73, GxEPD2_213_B73::HEIGHT> display(GxEPD2_213_B73(/*CS=5*/ SS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4));

void setup() {
  delay(1000);				// probably unnecessary, but you never know
  
  display.init();
  display.setFont(&FreeMonoBold9pt7b);

  display.fillScreen(GxEPD_WHITE);
  // Any text that gets printed will go ABOVE the cursor's location, so we need to leave room for our 9pt font!
  display.setCursor(0, 10); // Coordinates are (x,y) starting from the top-left corner. 
  display.setTextColor(GxEPD_BLACK);
  display.print("HelloWorld!");
  display.display();		// this is what actually writes out the framebuffer to the display
}

void loop() {  }

Hello World with partial update

One of the things you quickly notice when using e-ink displays is that they can have a substantial refresh time— after you call display.display() the screen will flash black and white for a few seconds before it resolves into the image we want. Fortunately, these displays have a feature called partial refresh that lets you selectively (and quickly) update individual regions of the display. This is much faster than the default method of updating the entire display every time1. Let’s build on our HelloWorld example and show how to quickly update the screen with partial refresh. We’ll erase the “Hello” in “HelloWorld!”, then after a few seconds replace it with “Bye”. We’ll also print some debug info out over the serial port to verify that the display we’re using supports fast partial update. Like the previous example, this is distilled down from the GxEPD example code.


#include <GxEPD2_BW.h>
#include <Fonts/FreeMonoBold9pt7b.h>


GxEPD2_BW<GxEPD2_213_B73, GxEPD2_213_B73::HEIGHT> display(GxEPD2_213_B73(/*CS=5*/ SS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // GDEH0213B73 seems compatible with GDEH0213B72B

const int start_row = 10;

void setup() {
  Serial.begin(115200);
  Serial.println();
  Serial.println("Starting up");

  display.init();

  

  Serial.println("Display initialized!");
  Serial.printf("Partial update: %i\n", display.epd2.hasPartialUpdate);
  Serial.printf("Fast partial update: %i\n", display.epd2.hasFastPartialUpdate);

  // Do our original HelloWorld:
  display.setFont(&FreeMonoBold9pt7b);
  display.setTextColor(GxEPD_BLACK);
  display.setCursor(0, start_row);
  
  display.fillScreen(GxEPD_WHITE);
  display.print("HelloWorld!");
  display.display();

  delay(5000);

  // Cancel out the hello
  int16_t helloX, helloY;
  uint16_t helloWidth, helloHeight;
  // Figure out where the "Hello" is on screen
  display.getTextBounds("Hello", 0, start_row, &helloX, &helloY, &helloWidth, &helloHeight);
  // Then draw over it with a white rectangle
  display.fillRect(helloX, helloY, helloWidth, helloHeight, GxEPD_WHITE);
  display.displayWindow(helloX, helloY, helloWidth, helloHeight);

  delay(5000);

  //Now say goodbye
  display.setCursor(0, start_row);
  // reuse the variables from before to find out where "bye" fits on the screen
  display.getTextBounds("Bye", 0, start_row, &helloX, &helloY, &helloWidth, &helloHeight);
  display.print("Bye");
  display.displayWindow(helloX, helloY, helloWidth, helloHeight);
  
}

void loop() {}

PlatformIO with ESP-IDF

I started writing a tutorial here, but I realized that it was basically turning into a tutorial for PlatformIO and Visual Studio Code. If you’re comfortable using those tools in conjunction with the ESP-IDF, it’s relatively straightforward to adapt the Arduino IDE instructions.


  1. Interestingly, using “partial” refresh to re-draw the entire display is actually faster than a full refresh ↩︎

Explore tags:

canada 1
covid 1
esp32 1