DEV Community

Cover image for Writing your own e-paper panel or controller in Rust with epdsi
Mariusz Jurgielewicz
Mariusz Jurgielewicz

Posted on

Writing your own e-paper panel or controller in Rust with epdsi

I own a Good Display GDEY0266T90H, a 2.66" black and white panel with an SSD1685 controller. GxEPD2 has no class for it.

So I wrote one. GxEPD2 lets you keep a panel class inside your sketch, and GxEPD2_BW takes it like any shipped one. The class and a demo sketch are in my repo: xiao-mg24-arduino-examples/GxEPD2/GDEY0266T90H/Demo.

This post covers where the idea came from and what the same job looks like in epdsi, my Rust driver framework.

Where the idea came from

The header of the class says it:

// Derived from GxEPD2_290_GDEY029T71H (same SSD1685 command set) by Jean-Marc Zingg,
// https://github.com/ZinggJM/GxEPD2 - GxEPD2 has no GDEY0266T90H class.
// Init and update values follow Good Display's AU-GDEY0266T90H-2FP-20230915 sample.
Enter fullscreen mode Exit fullscreen mode

The recipe is short. Find the GxEPD2 panel that uses the same controller (the 2.9" GDEY029T71H), copy its class, change the dimensions, and take the init and update values from Good Display's own demo. GxEPD2 makes that cheap because every panel is a class of the same shape. Credit for that design goes to Jean-Marc Zingg.

I ran it on a XIAO MG24 with the Seeed ePaper Driver Board v2. Two things only showed up on the hardware:

  • Good Display's sample uses data entry mode 0x01 with a reversed y window. With GxEPD2's raster the picture came out mirrored, because the vendor's images are pre-flipped. The 2.9" class uses 0x03, and so does mine.
  • The panel has 184 source outputs and the power-on default is 200. The extra 16 show up as a noise strip until bits 7 and 6 of the second Display Update Control byte are set to 01.

What epdsi needs for the same job

The GxEPD2 class has 22 public methods and 9 private helpers. epdsi splits the work in two. A panel is a zero-sized type with three constants: width, height and color mode. A controller is seven methods. Both are public traits, so they can live in your own crate and go into EpdBuilder like the shipped ones.

That gives three cases:

  1. A new panel on an IC epdsi already supports. Write the panel, reuse the controller, done.
  2. A new IC. Implement EpdController and EpdPanel.
  3. A shipped controller with a different init. Wrap it and forward the methods.

The third case is the wrapper pattern from my post Does Rust Support Inheritance? Yes, No, and Maybe, All in the Same File. Ssd1680Controller and Ssd1681Controller are thin wrappers over a shared Ssd168xController, and the forwarding boilerplate in that post is what you would write for your own variant.

The one catch

EpdController is sync or async depending on epdsi's blocking feature. Your crate has to follow that switch, which means a feature named exactly blocking that forwards to epdsi:

[dependencies]
epdsi = { version = "0.7", default-features = false }
embedded-hal = "1.0"
embedded-hal-async = "1.0"
maybe-async-cfg = "0.2"

[features]
default = ["blocking"]
blocking = ["epdsi/blocking"]   # the name must be `blocking`
Enter fullscreen mode Exit fullscreen mode

With any other name the compiler gives you E0053, "method init_sequence has an incompatible type for trait ... expected Result<..>, found future". The message does not mention the feature, so it is easy to chase in the wrong direction.

Before you power the panel

Every hardware problem I have hit in epdsi sat outside the register values. These are the checks I run first:

  • Reset and BUSY polarity. A wrong wait_busy argument hangs, or returns at once with nothing drawn.
  • Reset pulse width. Use the datasheet minimum, then the vendor demo's value. A pulse right at the minimum has latched intermittently on one of my boards.
  • Pixel packing and row stride.
  • Whether the controller drops its charge pump after a refresh, so a bare refresh command is ignored until PON is sent again.
  • How the IC wakes from deep sleep. Often only a hardware reset does it.

Then write down the vendor demo's bytes in order, compare them with the datasheet, and mark any byte the datasheet doesn't document as vendor-only. Pin the whole stream in a test that records each SPI write with the DC pin level.

Where this leaves the GDEY0266T90H

It isn't in epdsi yet. The SSD1685 is on my list, and GxEPD2's demo suggests it may need only a small variant of the SSD168x controller. I'll revise the guide when I know.

I only add panels I have in hand, and never from a datasheet alone. I buy new panels occasionally, if they are available to buy, and I accept donations only on exception, for panels from known sources that are well documented. The panel support policy has the details, and your own crate keeps working in the meantime.

The full guide, with a controller that compiles in both modes, is in the README. It will be on docs.rs with the next release.

Top comments (0)