Skip to main content

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

ModeDurationGhostingUse when
Full refresh~2 sNoneTime has changed, menu entered, first draw
Partial refresh~0.3 sAccumulatesClock 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", &current_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.