GxEPD2: drawing on an e-paper display
GxEPD2, by Jean-Marc Zingg, drives the SPI e-paper panels from Waveshare, Good Display and the others: the kind of screen that keeps its picture with the power off and is read like paper. It uses Adafruit GFX for drawing, so text and shapes work the way they do on an OLED.
Built in on Mokxi: #include <GxEPD2_BW.h> and it builds, with nothing to install. Free, and no account needed.
What GxEPD2 does
Two things are different from any other display. First, you pick a class for the exact panel, such as GxEPD2_213_BN or GxEPD2_290_T94, because each controller and glass has its own refresh sequence; the name is on the back of the panel or its listing. Second, a refresh takes a second or more and flashes the screen, and the panel signals that it is busy on a BUSY pin the library waits on.
Drawing uses a page loop like U8g2鈥檚. The sketch draws inside firstPage() and nextPage(), and the library sends the picture in bands small enough to fit an Uno鈥檚 RAM. After the final page, hibernate() powers the panel down, and the picture stays.
| Library Manager name | GxEPD2 |
|---|---|
| Author | Jean-Marc Zingg |
| Include | #include <GxEPD2_BW.h> |
| Boards | every board Mokxi compiles for but ATtiny85 |
| In the simulator | Runs, with a gap the note below names. |
| Parts it drives | E-paper display, SSD1680 2.13 inch, E-paper display, 2.9 inch |
Install it
It builds on every board Mokxi compiles for but ATtiny85.
In Mokxi it is built in: #include <GxEPD2_BW.h> and the sketch compiles, with no download, no zip and no Library Manager.
On a real board, install "GxEPD2" by Jean-Marc Zingg from the Arduino IDE鈥檚 Library Manager (Sketch, Include Library, Manage Libraries), or add GxEPD2 to lib_deps in PlatformIO. Mokxi follows the calls of release 1.6.2, so a sketch written here builds there too.
What to know in the simulator: The SSD1680 panels only (GxEPD2_213_BN, GxEPD2_290_BS, GxEPD2_290_T94, GxEPD2_290_T94_V2), without GFX fonts.
ESP32-C3 路 Arduino Uno 路 Arduino Nano 路 Arduino Mega 2560 路 Arduino Leonardo 路 SparkFun Pro Micro 路 Arduino Micro 路 ESP32-C6 路 Raspberry Pi Pico 路 Raspberry Pi Pico W 路 Seeed XIAO SAMD21 路 STM32 路 STM32 Nucleo-F411RE 路 STM32 Blue Pill 路 micro:bit V2 路 Arduino Uno R4 路 ESP32 DevKit 路 ESP32-S3-DevKitC-1 路 ESP8266 NodeMCU 路 Arduino Pro Mini 3.3 V 路 Seeed XIAO ESP32-C3 路 ESP32-C3 SuperMini 路 Seeed XIAO ESP32-C6 路 ESP32-DevKitC V4 路 LOLIN32 Lite 路 Wemos D1 R32 路 Cheap Yellow Display 路 TTGO T-Display 路 Seeed XIAO ESP32-S3 路 Arduino Nano ESP32 路 Arduino Nano RP2040 Connect 路 Seeed XIAO RP2040 路 Waveshare RP2040-Zero 路 Arduino Uno R4 WiFi
The calls you will use
In the order a sketch meets them.
GxEPD2_BW<GxEPD2_213_BN, GxEPD2_213_BN::HEIGHT> display(GxEPD2_213_BN(10, 9, 8, 7))
A black and white 2.13 inch panel with its CS, DC, RST and BUSY pins. On an Uno, use a smaller page height to fit the RAM, as the library鈥檚 examples do.
display.init(115200)
Resets the panel and starts SPI. The number is the bit rate for the library鈥檚 diagnostic messages on Serial.
display.setFullWindow()
The next page loop redraws the whole panel with a full refresh, which clears any ghosting.
display.setPartialWindow(0, 0, 128, 32)
Limits the next page loop to one area and refreshes it without the full flash, for a clock or a value that changes.
display.firstPage()
Starts the page loop. Put every drawing call inside the do-while that ends on nextPage().
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.setCursor(10, 30);
display.print("Hello");
} while (display.nextPage());display.setTextColor(GxEPD_BLACK)
Black text on the white background. GxEPD_WHITE and GxEPD_BLACK are the two colors a black and white panel has.
display.hibernate()
Powers the panel鈥檚 driver down after drawing. The image stays on the glass with no power at all.
Example: E-paper name badge
On the Arduino Uno R3. It runs in the browser exactly as printed.
// E-paper name badge: a 2.13 inch black and white e-paper panel on an
// SSD1680, driven with GxEPD2, as a conference or club badge.
//
// e-paper SCL -> 13 SDA -> 11 CS -> 10 DC -> 9 RES -> 8 BUSY -> 7
// VCC -> 3.3 V GND -> GND
// button pin 2 to GND (the pin's pull-up holds it high)
//
// The badge draws its name once with a full refresh, which takes the panel
// about two seconds while BUSY is high. The button swaps the bottom line
// between the role and a contact line with a partial refresh, a fraction of
// a second, and then the controller goes back to deep sleep. E-paper keeps
// its picture with no power at all, so a real badge draws and then sleeps
// until the button is pressed again.
//
// The panel is 250 x 122 pixels, bigger than this board's 2 KB of RAM, so
// GxEPD2 draws it in pages of 50 rows: the do { } while (nextPage()) loop
// runs once for each page.
#include <GxEPD2_BW.h>
#define MAX_DISPLAY_BUFFER_SIZE 800
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? \
EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
GxEPD2_BW<GxEPD2_213_BN, MAX_HEIGHT(GxEPD2_213_BN)>
display(GxEPD2_213_BN(10, 9, 8, 7)); // CS, DC, RES, BUSY
const int BUTTON = 2;
bool contact = false;
void bottomLine() {
display.fillRect(0, 96, 250, 26, GxEPD_WHITE);
display.setTextColor(GxEPD_BLACK);
display.setTextSize(2);
display.setCursor(12, 102);
display.print(contact ? "ADA@CLUB.ORG" : "ELECTRONICS CLUB");
}
void drawBadge() {
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
// The band across the top, white letters on black.
display.fillRect(0, 0, 250, 24, GxEPD_BLACK);
display.setTextColor(GxEPD_WHITE);
display.setTextSize(2);
display.setCursor(18, 5);
display.print("HELLO, MY NAME IS");
// The name, as big as it fits.
display.setTextColor(GxEPD_BLACK);
display.setTextSize(4);
display.setCursor(30, 44);
display.print("ADA");
display.drawFastHLine(10, 88, 230, GxEPD_BLACK);
bottomLine();
} while (display.nextPage());
}
void drawBottom() {
display.setPartialWindow(0, 96, 250, 26);
display.firstPage();
do {
bottomLine();
} while (display.nextPage());
}
void setup() {
pinMode(BUTTON, INPUT_PULLUP);
display.init(115200);
display.setRotation(1);
drawBadge();
display.hibernate();
}
void loop() {
if (digitalRead(BUTTON) == LOW) {
contact = !contact;
drawBottom();
display.hibernate();
while (digitalRead(BUTTON) == LOW) delay(10);
}
delay(20);
}Open it in the editor, wired and ready to run 路 E-paper name badge: the project page
Common errors and how to fix them
Busy Timeout! on the Serial monitor
The library waited for the BUSY pin and it never changed. Check the BUSY wire and its pin in the constructor, and that the panel class matches your panel, since a wrong class sends commands it ignores.
A faint copy of the old picture stays behind
That is ghosting from many partial refreshes. Do a full refresh with setFullWindow() every few minutes or every few dozen updates.
The sketch resets or the picture is cut short on an Uno
A full frame buffer does not fit in 2 KB. Use the page height trick from the GxEPD2 examples (MAX_DISPLAY_BUFFER_SIZE) so the library draws the panel in several bands.
Projects that use GxEPD2
Built-in projects that include it. Each page has the full sketch and opens in the editor, already wired.
Try GxEPD2 without installing anything
Open the example, change the sketch, and keep your version in a free account.