E-Paper Display Image Conversion: Waveshare 2.13/4.2/7.5
E-paper is the perfect display for low-power dashboards, ESL tags, conference badges, and Kindle-style readers. But Waveshare's e-paper line has a confusing zoo of resolutions, color modes (B/W, 3-color, 4-color, 7-color), and SPI sub-protocols. This post pins down image conversion for the most common Waveshare panels and shows how to drive them with GxEPD2.
The Waveshare e-paper lineup at a glance
| Panel | Resolution | Colors | Refresh |
|---|---|---|---|
| 2.13" V3 | 250x122 | B/W | ~2s full, ~0.3s partial |
| 2.9" | 296x128 | B/W | ~2s |
| 4.2" V2 | 400x300 | B/W | ~4s |
| 4.2" 7-color | 400x300 | 7-color | ~16s |
| 5.83" | 648x480 | B/W or 3-color | ~6s / ~16s |
| 7.5" V2 | 800x480 | B/W | ~5s |
| 7.5" 3-color | 800x480 | B/W/Red | ~16s |
Always check the model revision printed on your panel's flex cable — "V2" and "V3" use different waveform tables and crashing the wrong driver gives you a permanent ghosting nightmare.
The library: GxEPD2
GxEPD2 by ZinggJM is the de-facto Arduino/ESP32 library for Waveshare panels. Install via Library Manager. It supports nearly every Waveshare model with a consistent API.
Wiring (typical)
| EPD Pin | ESP32 | Arduino Uno |
|---|---|---|
| VCC | 3V3 | 3V3 |
| GND | GND | GND |
| DIN (MOSI) | 23 | 11 |
| CLK (SCK) | 18 | 13 |
| CS | 5 | 10 |
| DC | 17 | 9 |
| RST | 16 | 8 |
| BUSY | 4 | 7 |
Image conversion: B/W panels
For black-and-white panels (2.13", 4.2" V2, 7.5" V2), the format is simple: 1-bit horizontal MSB-first, exactly the same as SSD1306 OLED.
- Open image2cpp
- Canvas size = panel resolution (e.g. 400x300 for 4.2")
- Output: "Arduino code, Horizontal, 1 bit per pixel"
- Threshold: 128 (or use Floyd–Steinberg dithering for photos)
#include <GxEPD2_BW.h>
GxEPD2_BW<GxEPD2_420, GxEPD2_420::HEIGHT> display(GxEPD2_420(/*CS*/ 5, /*DC*/ 17, /*RST*/ 16, /*BUSY*/ 4));
const unsigned char myImage [] PROGMEM = {
// 15,000 bytes for 400x300/8
};
void setup() {
display.init();
display.setRotation(1);
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.drawBitmap(0, 0, myImage, 400, 300, GxEPD_BLACK);
} while (display.nextPage());
display.hibernate(); // critical for low power
}
void loop() {}
3-color panels (Black / White / Red)
3-color e-paper has two separate frame buffers: one for black pixels, one for red pixels. White is the absence of both. You convert the image twice in image2cpp:
- Pre-process the source PNG: split black ink and red ink into two separate B/W PNGs in GIMP. Black areas of the original become the black plane; red areas become the red plane.
- Convert each separately in image2cpp as 1-bit horizontal MSB.
- In code, use the 3-color GxEPD2 driver and call
drawBitmaptwice with different colors.
#include <GxEPD2_3C.h>
GxEPD2_3C<GxEPD2_750c_Z08, GxEPD2_750c_Z08::HEIGHT> display(/*...pins...*/);
const unsigned char blackPlane [] PROGMEM = { /* ... */ };
const unsigned char redPlane [] PROGMEM = { /* ... */ };
void setup() {
display.init();
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.drawBitmap(0, 0, blackPlane, 800, 480, GxEPD_BLACK);
display.drawBitmap(0, 0, redPlane, 800, 480, GxEPD_RED);
} while (display.nextPage());
display.hibernate();
}
7-color panels
The 7-color "ACeP" panels (4.01", 5.65", 7.3", 7.3" Spectra) use 4 bits per pixel encoded as a palette: black, white, red, green, blue, yellow, orange. Convert in image2cpp using the 4-bit palette mode and target the 7-color GxEPD2 driver. Refresh time is brutal (15-20 seconds) so reserve them for static art or weekly-updated dashboards.
Memory math
| Panel | B/W bytes | 3-color bytes |
|---|---|---|
| 2.13" 250x122 | 3,800 | 7,600 |
| 4.2" 400x300 | 15,000 | 30,000 |
| 7.5" 800x480 | 48,000 | 96,000 |
The 7.5" 800x480 needs 48KB just for one B/W image. Won't fit on Uno (32KB flash). Use ESP32 or RP2040.
Partial refresh: the killer feature
E-paper's slow full refresh is fine for daily updates, but partial refresh (changing a small region in 0.3s without full ghosting) lets you build clocks and weather dashboards. Convert a small region image (e.g. 80x40 for the time digits) and use:
display.setPartialWindow(x, y, 80, 40);
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.drawBitmap(x, y, timeDigits, 80, 40, GxEPD_BLACK);
} while (display.nextPage());
Power: the whole point of e-paper
An e-paper image stays on screen with zero power. Update once an hour and run for years on coin cells. The trick: call display.hibernate() after every update and put the ESP32 into deep sleep:
display.hibernate();
esp_sleep_enable_timer_wakeup(60ULL * 60 * 1000000); // 1 hour
esp_deep_sleep_start();
Common pitfalls
- Ghost / shadow of previous image. Wrong waveform table or skipped
display.init(115200, true, 50, false)with full-refresh init. Old controllers need a "hard reset" sometimes. - Permanent burn-in pattern. You drove a B/W panel as 3-color or vice versa. Sometimes recoverable with several full refreshes; sometimes not.
- Image upside-down. Set
display.setRotation(). - Red shows up as black. You used a B/W driver on a 3-color panel.
- Display freezes mid-refresh. BUSY pin not connected or wrong GPIO.
Bottom line
Waveshare e-paper + GxEPD2 + image2cpp's 1-bit MSB output gets you to "image on screen" in 50 lines of code. Convert your image now, or browse our Arduino tutorial for fundamentals.
Need a low-power dashboard frame? quotes laser-cut acrylic and wood frames.
Try image2cpp now — free, in browser
Drop in a PNG, JPG or BMP. Get a paste-ready C++ array for Arduino, ESP32 or RP2040. 100% client-side. Learn more about image2cpp or jump straight to the tool.
Open the converter →