A stock GxEPD2 sketch, built with PlatformIO, boots in the LabWired simulator and draws the same page it draws on a physical Waveshare 2.9″ panel.
If you've written e-paper firmware, you know the loop. Move a label two pixels, rebuild, flash, wait through the panel's multi-second refresh, and squint at the glass. When the driver class is wrong, the panel reports success and renders blank anyway. And every iteration needs the display wired to the pins, on a board you can only flash one at a time.
The slow part isn't your drawing code; it's everything around it. This post sets up a loop where the same binary runs in a window on your laptop and paints the same pixels.
There is no porting layer or mock. The sketch is a stock Arduino-ESP32 program driving GxEPD2, e-paper display lib, on an ESP32-WROOM-32 with a Waveshare 2.9″ tri-color panel:
#include <Arduino.h>
#include <GxEPD2_3C.h>
#include <Fonts/FreeSerifBold12pt7b.h>
// Waveshare 2.9" tri-color (C90c driver). CS=5 DC=17 RST=16 BUSY=4.
GxEPD2_3C<GxEPD2_290_C90c, GxEPD2_290_C90c::HEIGHT> display(
GxEPD2_290_C90c(/*CS=*/5, /*DC=*/17, /*RST=*/16, /*BUSY=*/4));
void drawPage() {
display.setFullWindow();
display.firstPage();
do {
display.fillScreen(GxEPD_WHITE);
display.setTextColor(GxEPD_BLACK);
display.setFont(&FreeSerifBold12pt7b);
display.setCursor(8, 24);
display.print("LabWired Reader");
display.setTextColor(GxEPD_RED);
display.setCursor(230, 122);
display.print("Page 1");
} while (display.nextPage());
}
void setup() {
Serial.begin(115200);
display.init(115200);
display.setRotation(1);
drawPage();
display.hibernate();
}
That's the full Arduino framework with FreeRTOS underneath, talking SPI to the panel's driver IC. Nothing in the sketch knows whether the other end of the bus is glass or a model.
The build is the standard espressif32 / Arduino setup, with GxEPD2 pulled from the registry:
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
zinggjm/GxEPD2@^1.6.0
pio run produces firmware.elf, the same image
you'd flash to the board. The full sketch, platformio.ini, and
a one-command headless run are in the
runnable example.
Hand that ELF to the LabWired simulator. It boots the Arduino-ESP32 image
on an instruction-level Xtensa core model, brings up FreeRTOS, runs your
setup(), and the SPI bytes your firmware clocks out land in a
simulated e-paper panel that decodes them and shows the page — the same
"LabWired Reader" you'd see on glass.
flowchart LR
A["GxEPD2 sketch"] -- "pio run" --> B["firmware.elf"]
B --> C["LabWired sim<br/>Xtensa core + FreeRTOS"]
C -- "SPI bytes" --> D["e-paper panel model"]
D --> E(("rendered<br/>page"))
classDef ok fill:#27c93f,stroke:#1a1a1a,stroke-width:2px,color:#0d2b12;
class E ok;
From there, iterating feels like web development: change the sketch, run, look at the rendered page. A full cycle takes about a second, against the minute or more that a flash-and-refresh round trip costs on the bench.
See an e-reader render in the Playground →
If you're building an embedded UI and want this loop for your panel, write to contact@labwired.com or book 30 minutes.