LabWired Whitepaper
Tutorial

Render your Arduino e-paper firmware — without the board or the panel

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.

Why e-paper development is slow

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.

The sketch

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.

Build it with PlatformIO

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.

Run it on the digital twin

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;
      
The same ELF you would flash to the board, rendered deterministically in your browser.

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.

Under the hood

A note on scope. This demo boots a full framework stack — Arduino, FreeRTOS, the GxEPD2 driver — inside the simulator. That is a newer, more demanding capability than running the small bare-metal test programs from the PlatformIO unit tests guide, and it doesn't work on every board yet: we are enabling boards one at a time. The supported boards page lists which ones can boot framework firmware today.

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.