E-Ink Display

I've been interested in colour e-ink displays for a while, but couldn't justify the expense purely for the purpose of tinkering. During the summer last year, I picked up an Inky Impression display while it was on sale at Pimoroni. This gave me a chance to experiment with one, and I was surprised how good it turned out to be.

First impressions

The display arrived well-packaged in a padded bag inside a sturdy cardboard case. One particularly nice touch by the vendor was the use of a decorative image on the display, which showcases the sort of pictures that work well. Given that the palette only contains 7 colours, and photographic images need to be processed to use these effectively, the results can be very impressive. It helps that the resolution of 640 by 400 pixels is high enough to support effective use of dithering.


The display with its default image.

At this point during the year I was doing things with bare metal code on the STM32 MicroMod processor card and I started working on some code to send data to the display. This took advantage of the existing code in Pimoroni's Inky repository, but was rewritten in the language I was using to experiment with embedded hardware.

The display is programmed via a Serial Peripheral Interface (SPI) bus, which the STM32 makes fairly simple once the hardware is set up according to the datasheet. Of course, the target market of the display will be using either Python (MicroPython or CircuitPython) or C++ (in the Arduino environment) so this is almost at the level of plug-and-play for experienced makers. Still, with a bit of experience, even someone writing their own technology stack from the compiler up can eventually send image data to the display.


An image of Trakai Castle.

None of this is very Inferno-related. However, once some level of stability was achieved with the port to the STM32 processor board, something could be done to make the display available to Inferno and Limbo programs.

An e-ink device

Ideally, we would expose the SPI bus as a file system to the Inferno environment and write Limbo programs to interact with it. However, the code to access the display was already written in a low-level language, so it seemed easier to just expose two files and let the device driver handle the SPI bus. The file system provides a data file that accepts pixel data until enough has been received to send to the display, and a status file that can be read to obtain very simple information about the status of the display. The init script binds the device to the /dev/ink directory:

stm32f405$ ls /dev/ink
data
status

Generally, only the data file is useful, and it can be written to using the usual commands, assuming that there is enough memory left to use them if image data is in the root file system taking up RAM. Here, image data for a quarter of the screen is sent to the display four times:

cat /tmp/piece /tmp/piece /tmp/piece /tmp/piece > /dev/ink/data

A more practical way is to use a Limbo program to generate data and use redirection to send it to the e-ink device.

MakeColour

This is what a program to generate a series of coloured stripes looks like. It starts with the usual declarations, includes and definitions:

implement MakeColour;

include "draw.m";
include "sys.m";

BUFSIZE: con 160;

MakeColour: module
{
    init:   fn(nil: ref Draw->Context, args: list of string);
};

The BUFSIZE is set to a reasonably low value so that the program's memory footprint is kept low. Each line of pixels on the screen is 320 bytes because each pixel is encoded in 4 bits, with 1 bit unused, so only half a line of data is sent at once.

The init function starts with some fairly standard checks:

init(nil: ref Draw->Context, args: list of string)
{
    sys := load Sys Sys->PATH;
    stdout := sys->fildes(1);

    if (len args != 1) {
        sys->fprint(sys->fildes(2), "usage: makecol\n");
        return;
    }

We then set up an array of bytes that will be used to prepare data for the display, then we start a loop to paint a stripe of each colour:

    b := array[BUFSIZE] of byte;
    c := 0;

    for (i := 0; i < 8; i++) {
        cb := byte c;
        for (j := 0; j < BUFSIZE; j++)
            b[j] = cb | (cb << 4);

        for (j = 0; j < 100; j++)
            if (sys->write(stdout, b, BUFSIZE) != BUFSIZE) return;

        c = (c + 1) % 7;
    }
}

The first inner loop fills the buffer with data for each colour. The second loop writes the contents of the buffer to stdout enough times to fill an eighth of the display. Since the palette only contains 7 colours, the first colour appears twice.

When the program is run, we use redirection to send the output to the e-ink device:

makecol > /dev/ink/data

Some colourful stripes.

We can use this method to write text to the display, though text output isn't really the best use of its capabilities and it's not suitable for interactive use. I've written something based on the MakeColour program that sends character bitmaps to the display, but it's not very interesting to look at. It's possible to write some text to the display with a command like this:

ls dis | mc -c 40 | ink > /dev/ink/data

It could be used to display information or a calendar, but maybe a cheaper, lower resolution, monochrome e-ink display would be more suitable for those purposes.


David Boddie
4 March 2023