Render your Arduino e-paper firmware without the board or the panel
Category: Tutorial
You build a stock GxEPD2 sketch with PlatformIO. The sketch boots in the LabWired simulator. The sketch draws a page in the simulator. The page is the same as the page on a physical Waveshare 2.9″ panel.
Why e-paper development is slow
If you write e-paper firmware, you know this loop. Move a label two pixels. Rebuild the firmware. Flash the board. Wait for the multi-second refresh of the panel. Look closely at the glass. If the driver class is wrong, the panel reports success. The panel renders a blank page. Each iteration needs the display wired to the pins. You can flash only one board at a time.
The code that draws the page is not the slow part. The work around the code is the slow part. This post sets up a loop. The same binary runs in a window on your laptop. The binary paints the same pixels.
The sketch
The sketch has no porting layer. The sketch has no mock. The sketch is a stock Arduino-ESP32 program. The program drives GxEPD2 on an ESP32-WROOM-32. GxEPD2 is an e-paper display library. The board has 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();
}
The program is the full Arduino framework. FreeRTOS is under the framework. The program talks to the driver IC of the panel through SPI. No code in the sketch knows if the other end of the bus is glass or a model.
Build with PlatformIO
The build uses the standard espressif32 / Arduino setup. The build pulls GxEPD2 from the registry.
[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
lib_deps =
zinggjm/GxEPD2@^1.6.0
pio run produces firmware.elf.
This image is the same image that you would flash to the board.
The runnable example has the full sketch.
The example has platformio.ini.
The example has a one-command headless run.
Run on the digital twin
Give that ELF to the LabWired simulator.
The simulator boots the Arduino-ESP32 image on an instruction-level Xtensa core model.
The simulator starts FreeRTOS.
The simulator runs your setup().
Your firmware clocks out SPI bytes.
The SPI bytes go to a simulated e-paper panel.
The panel decodes the bytes.
The panel shows the page.
The page is the same “LabWired Reader” page that you would 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;
Caption: This is the same ELF that you would flash to the board. Your browser renders the ELF deterministically.
Try it right here
The embed below is the real lab.
The lab is an ESP32-WROOM-32 e-reader.
The e-reader runs the Arduino-ESP32 + GxEPD2 + FreeRTOS stack.
The e-reader runs from the same .elf that flashes to hardware.
Press Run.
The simulated Waveshare 2.9″ tri-color panel paints the page during the cold boot.
The cold boot takes about two minutes.
This procedure involves no board.
This procedure involves no panel.
This procedure involves no flashing.
Do you want a full window? Open the e-reader lab in the Playground →
From the lab, the work is like web development. Change the sketch. Run the firmware. Look at the rendered page. A full cycle takes about a second. A flash-and-refresh round trip on the bench takes a minute or more.
Simulator operation
- The Xtensa LX6 core model of the ESP32 runs the binary instruction by instruction. The run goes through the bootloader. The run goes through the FreeRTOS scheduler. The run goes through your task. The run goes through the GxEPD2 SPI sequence. The model takes the same code path on silicon.
- The display model follows the command/data state machine of the controller. A wrong driver class renders wrong in the simulator too. An incorrect window command renders wrong in the simulator too. Today you need the physical panel to find this type of bug.
- We diffed the boot path against a physical ESP32-WROOM-32 over its UART. The diff is banner for banner. A clean run in the simulator applies to the bench.
- Runs are deterministic. One ELF gives one output. A rendering regression can fail a CI build like any other test. You do not need a board farm. Flaky panel timing does not occur.
See an e-reader render in the Playground →
If you build an embedded UI and you want this loop for your panel, write to contact@labwired.com. Or book 30 minutes.