E-ink Display Driver
The display driver handles initialisation, full/partial refresh, and front-light PWM brightness control.
Library: GxEPD2
GxEPD2 is the most widely used e-ink library for Arduino/ESP32. It supports the SSD1683 controller used by the GDEY042T81-FL02.
GxEPD2 was designed for Arduino, but works under ESP-IDF via the arduino-esp32 component or the esp32-arduino-lib-builder.
Simplest approach: use PlatformIO with the Arduino framework for this project, then add ESP-IDF components for I2S and FATFS. PlatformIO handles the toolchain for you.
Alternatively, port to native ESP-IDF using Waveshare's e-Paper ESP-IDF library as a reference.
Initialisation
#include <GxEPD2_BW.h>
#include <Adafruit_GFX.h>
// GDEY042T81-FL02 with SSD1683
GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT>
display(
GxEPD2_420_GDEY042T81(
/*CS=*/ 10, // GPIO10
/*DC=*/ 5, // GPIO5
/*RST=*/ 4, // GPIO4
/*BUSY=*/ 3 // GPIO3
)
);
void display_init(void) {
display.init(115200, true, 15); // serial speed, initial=true, reset duration=15ms
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
} while (display.nextPage());
}
Full vs. Partial Refresh
| Mode | Duration | Ghosting | Use when |
|---|---|---|---|
| Full refresh | ~2 s | None | Time has changed, menu entered, first draw |
| Partial refresh | ~0.3 s | Accumulates | Clock digits update every minute |
Run a full refresh at least every 10 partial refreshes to clear ghosting:
static int partial_count = 0;
void display_update(bool force_full) {
if (force_full || partial_count >= 10) {
display.setFullWindow();
partial_count = 0;
} else {
display.setPartialWindow(0, 0, 200, 80); // update digits area only
partial_count++;
}
display.firstPage();
do {
draw_clock_face();
} while (display.nextPage());
}
Drawing Text
GxEPD2 uses the Adafruit GFX API for drawing:
#include <Fonts/FreeSansBold48pt7b.h> // large font for time
void draw_clock_face(void) {
display.fillScreen(GxEPD_WHITE);
// Time (large, centred)
display.setFont(&FreeSansBold48pt7b);
display.setTextColor(GxEPD_BLACK);
display.setCursor(40, 160);
char time_str[6];
snprintf(time_str, sizeof(time_str), "%02d:%02d",
current_time.tm_hour, current_time.tm_min);
display.print(time_str);
// Date (smaller, below)
display.setFont(NULL); // back to built-in small font
display.setTextSize(2);
display.setCursor(100, 200);
char date_str[16];
strftime(date_str, sizeof(date_str), "%a %d %b", ¤t_time);
display.print(date_str);
}
Front Light PWM
The GDEY042T81-FL02's front light is controlled by a PWM signal on GPIO2. Use the ESP32 LEDC peripheral:
#include "driver/ledc.h"
void frontlight_init(void) {
ledc_timer_config_t timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.timer_num = LEDC_TIMER_0,
.duty_resolution = LEDC_TIMER_8_BIT, // 0–255
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK,
};
ledc_timer_config(&timer);
ledc_channel_config_t channel = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.timer_sel = LEDC_TIMER_0,
.gpio_num = GPIO_NUM_2,
.duty = 128, // 50% brightness
.hpoint = 0,
};
ledc_channel_config(&channel);
}
void frontlight_set(uint8_t brightness) {
ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, brightness);
ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
}
Call frontlight_set(0) to turn the light off, frontlight_set(255) for maximum.
Memory Note
GxEPD2 allocates ~15 KB for the 400×300 1bpp frame buffer. This requires PSRAM on the
ESP32-S3 (DevKitC-1 N16R8 has 8 MB; WROOM-2 N32R16 has 16 MB — both are sufficient).
If you see a heap_caps_malloc failed panic, PSRAM is not enabled — check sdkconfig
under Component config → ESP PSRAM.