Sunday, September 24, 2023

Using E-Ink displays with CircuitPython

 Using E-Ink displays with CircuitPython


Adafruit has some very nifty e-ink displays. It's something I've always wanted to try out: I've got an e-book reader that uses e-ink, and of course I've seen retail proce tags that use them. What I found is that although the demo code worked great, there were some major gotcha moments when using them in a project.

Firstly, the update process for the display is both very slow and very obvious. The display will go into a reverse-video mode and clear to white and to black several times before settling in and showing the new display contents. I've got a YouTube video that shows the refresh cycle.

Secondly, the display can only be updated every 3 minutes. This is locked by the code: if you try to refresh the screen more often, the CircuitPython library will just throw an exception.

Lastly, the sample code, as written, doesn't actually let you refresh the display. This may seem weird: the library code (displayio) certainly includes the concept of a display refresh, and when used with OLED displays you can do a refresh. But as it turns out, there's some kind of bug in the CircuitPython library (as of 2023-09-24), and when I do a refresh, my labels simply turn into black rectangles.

All my project code is up on Github. Take a look at the code.py file for the complete code; also look at the GoveeDisplay.py file; that's the file that makes all of the text labels for the device and handles text updates (e.g., it knows how to take the GoveeData object and update the right labels using either degrees Fahrenheit or Celcius).

The ideal way to make a display is to 

  • Set up the display
  • Create  all the text labels, background images, and layout groups
  • Call display.show() (and maybe display.refresh()) to update the display

After that, we can update the label.text fields, and the display will refresh (or, for e-ink displays, we have to call the display.refresh() method to force a refresh). This is simple and clean: updating the display is done just with the labels.

But for the e-ink display, you have to do something else. You can reuse the text labels and whatnot, but you have to rebuild the display all over again. In code.py, this is done in the while True: loop at about line 229. Looking at the code, on every refresh (which happens every 5 minutes), I have to redo pretty much everything display related: release the old display, make a new display_bus, make a new display, call display.show() (with the existing groups, etc.), and then finally I can do a refresh() and have the display update.


    displayio.release_displays()
    display_bus = displayio.FourWire(
        spi, command=epd_dc, chip_select=epd_cs, reset=epd_reset, baudrate=1000000
    )
    time.sleep(1)

    # For issues with display not updating top/bottom rows correctly set colstart to 8
    display = adafruit_ssd1680.SSD1680(
        display_bus,
        colstart=8,
        width=DISPLAY_WIDTH,
        height=DISPLAY_HEIGHT,
        busy_pin=epd_busy,
        highlight_color=0xFF0000,
        rotation=270,
    )
    # We can use the existing group with the new display.
    display.show(mainDisplayGroup)

    #
    # Wait for a time that's evenly divisible by 5. That means that sometimes
    # the scan results will be a little late.
    #
    wait = TimeToWaitForEvenClock(clock.datetime)
    if wait < 4 * 60:  # If it's e.g., 3:44:59
        time.sleep(wait)

    voltage = get_battery_voltage()
    gd.ShowGovee(userprefs, scanResult, clock.datetime, voltage)
    display.refresh()


One more thing -- it's not super reliable

Regretfully, my experience with the e-ink display is that it's also not super reliable. Every now and then, I'd refresh my code, and then the display just wouldn't update. Or maybe it didn't initialize, or it wasn't connected somehow. There's no exception thrown, or useful trace, or debug message.

In at least one case, I was trying to use a different physical microcontroller board of the same type (an NRF52840). And for whatever reason, the display just didn't want to work that the other micro, but it wouldn't say why. Is there a hardware fault? Did I mess up some connection? I have no idea, and no useful path forward.

In the end, I just used the original microcontroller, and eventually everything worked, mostly. And when it doesn't, pressing "reset" will eventually make everything work again.

Links to devices:






No comments: