Skip to main content

How e-ink works

An E-inkE-ink: A display made of tiny capsules of black and white pigment, moved by electric fields. It keeps its image without power. pixel is a capsule of pigment that keeps its color without power but takes about half a second to change, so Inkshelf redraws only what changed, gently, and flashes the whole panel clean every few pages.

Your laptop's screen redraws itself 60 times a second whether anything changed or not. The InkPad 3's screen does the opposite: it does nothing at all until it is told to change, and then it takes a visible moment to do it. Every choice Inkshelf makes about drawing comes from that one fact. This chapter shows what is inside the panel, and the three rules that follow from it.

Think of a magnetic drawing board for children: a pen pulls dark filings up to the surface, and the drawing stays there with no battery. Sliding the bar under the board wipes it clean, but everything blinks. An e-ink panel is that board, with 2.6 million tiny cells and a computer holding the pen.

Tiny capsules of pigment​

Each pixel of the panel holds a clear liquid with two kinds of tiny particles: white ones with one electric charge, black ones with the other. Under each pixel sits an electrode; above the whole panel, a transparent one. Put a voltage between them, and one color is pulled up to the glass while the other sinks. What you see is whichever color sits on top.

InteractiveInside five e-ink pixels
what the eye sees, from above ↓transparent electrode·····pixel electrodes: + pulls the white (−) particles down, − pushes them upat rest: no voltage needed to keep the image
Try this: Click a pixel and watch its particles swim. Switch to “Half pulse” and click a white pixel: the particles stop halfway, and the pixel looks gray. Then turn the power off and try again.

Two properties follow, and they shape everything else:

  • It is bistable. Once the particles have moved, they stay, with no power at all. That is why an e-reader lasts weeks on a charge, and why the panel keeps showing the last page when the app is not drawing.
  • It is slow. Particles have to swim through a liquid. Changing a pixel takes hundreds of milliseconds, not the 16 ms of a computer screen. They swim even slower in the cold: Inkshelf asks the driver to adjust to the panel's temperature (tempUseAmbient in internal/device/pocketbook/eink_refresh.go).

Grays are particles stopped part of the way. To land a pixel exactly on one of 16 grays, the panel does not send one long push but a carefully timed sequence of pushes and pulls.

Waveforms: recipes of voltages​

That sequence is a WaveformWaveform: The sequence of voltages the panel applies to change pixels. Fast ones show few grays; slow ones show 16 and clean ghosts.: a recipe of voltages, frame after frame, that takes each pixel from its old gray to its new one. The panel's controller has several recipes, and each trades speed for quality. The firmware numbers them, and Inkshelf names them in its Waveform type:

WaveformNumberGraysWhat it is forTime on an InkPad 3
GC16GC16: The slow, full-quality waveform: 16 grays, and every pixel is driven, so it flashes and wipes ghosts.216The cleanest: drives every pixel. As a full refresh it flashes black, then white600 ms full, 490 ms partial
GL16GL16: The 16-gray waveform for page turns: it drives only the pixels that change, so it does not flash, but ghosts build up.516Drives only the pixels that change; made for text on white, flickers less490 ms
DUDU / A2: Fast waveforms that only know black and white (DU) or are tuned for animation (A2): quick, but crude.12Black and white only, for quick feedback265 ms
A242Black and white, fastest, leaves the most ghostingnot measured
GC4, DU43, 84Four grays, rarely useful for booksnot measured

The times are the ones the EmulatorEmulator: A stand-in for the reader in a web page on your computer: the real event loop and screens, with a fake firmware. uses to imitate the panel (fullRefreshTime, partialRefreshTime and fastRefreshTime in internal/device/pocketbook/emulated_firmware.go), measured on a reader. Half a second is the number to remember: that is how long every page turn spends waiting for the panel, whatever the app does.

A refresh also comes in two modes. A partial update (updateModePartial) only touches the pixels whose value changed. A full update (updateModeFull) drives every pixel of the area, which is what makes the screen blink.

Ghosting, and the flash every few pages​

A partial refresh is not perfect. A pixel that went from black to white is almost white, but a trace of the black stays. Turn a few pages and these traces pile up into a faint mess of old pages behind the new one: GhostingGhosting: A faint trace of the previous image left after a partial refresh. A full (flashing) refresh clears it.. A Full refreshFull refresh: A refresh that drives every pixel of the area, through black, so the panel flashes but is clean afterwards. drives every pixel through black and white before landing on the image, which wipes the traces, but the whole screen blinks.

InteractiveA panel that remembers
Not refreshed yet: the book just opened.
Page 1 · turns since the last flash: 0
 the dashed turn flashes
The refresh times are the measured ones. The ghosts are exaggerated so they show on a computer screen; on the reader they are fainter.
Try this: Turn off auto-flash and turn pages with the partial button: watch the old panels haunt the new ones. Then press “Flash this page”. Turn the fast (DU) button on a comic to see what 2 grays do to pictures.

So Inkshelf mixes the two. A normal page turn is a partial refresh with GL16 (the reader's pageWaveform): no blink, half a second. Every few turns, the turn is a full GC16 refresh instead, and the ghosts are gone:

The app also flashes when it comes back to the ForegroundForeground: The app the reader shows. Only the foreground app may read input and refresh the screen. (another app may have drawn anything meanwhile) and when a screen changes completely, for example when a book opens.

internal/app/openbook/openbook.go
// fullRefreshDue reports whether the next page turn refreshes the panel whole.
func fullRefreshDue(app *state.State) bool {
every := fullEvery
if app.Reader.Text != nil {
every = textFullEvery
}
return app.Reader.Turns%every == every-1
}

Turns counts the turns since the book was opened, starting at 0, so turns 5, 10, 15… of a comic flash.

Refresh only what changed​

A refresh is not only a waveform: it is also a rectangle. The app tells the panel which area to update, and only that area waits half a second. When you open the reader's toolbar, Inkshelf refreshes only the toolbar's strip at the top (refreshAreaLocked in internal/app/screens/reader/reader.go). When a page turns, it refreshes the page's area and the area of the page before, in case the two pictures have different sizes.

Asking for a refresh does not wait for it to end: the request returns at once, and the panel works on its own. That raises a question. What if a second refresh comes while the first is still running? If they touch the same pixels, the new pixels would be changed under a refresh that is still driving them. If they don't, there is no reason to wait.

That is exactly the rule: Inkshelf waits only when the new rectangle overlaps the one still being refreshed.

InteractiveWait only when it overlaps
12025050075010001250milliseconds1 · GL162 · GL16waits 430 msaskeddone

They overlap: refresh 2 waits 430 ms for refresh 1 to end. All done at 980 ms.

Both refreshes are partial ones: 490 ms for GL16, 265 ms for DU.
Try this: Press Play on “Page, then toolbar”: the toolbar sits on the page, so it waits. Switch to “Two corners” and play again: both run at once. Then drag one rectangle onto the other.

How Inkshelf does it​

Two functions do the drawing side of this: pocketbook.Refresh(rect, waveform) for a partial refresh, and pocketbook.FlashRefresh() for a full GC16 refresh of the whole screen. On the reader, both end in flush, which waits if needed, copies the area from the app's CanvasCanvas: Where Inkshelf draws: a 1404×1872 gray image in shared memory, upright. A refresh copies it, rotated, to the framebuffer. into the FramebufferFramebuffer: The block of memory (/dev/fb0) the panel controller reads pixels from. Writing to it changes nothing on screen until a refresh., and sends the request to the panel's driver with an ioctlioctl: A system call that sends a device-specific command to a driver, here to ask the panel for a refresh (MXCFB_SEND_UPDATE).:

internal/device/pocketbook/framebuffer_linux_arm.go
// waitForOverlappingRefresh waits for the last refresh to end if it overlaps
// panelRect. Refreshes elsewhere go on meanwhile.
func (reader *readerFirmware) waitForOverlappingRefresh(panelRect image.Rectangle) {
if reader.lastMarker == 0 || !panelRect.Overlaps(reader.lastRect) {
return
}
marker := reader.lastMarker
// The ioctl fails when the refresh is already over: nothing to wait for.
_ = ioctl(reader.framebufferFD, mxcfbWaitForUpdate, unsafe.Pointer(&marker))
}

Each refresh gets a number, its marker. Waiting means asking the driver "tell me when refresh number N is over".

Going deeper
  • The driver speaks i.MX6. The InkPad 3 has an Allwinner chip, but PocketBook's panel driver (epdc.ko) offers the ioctls of NXP's i.MX6 e-ink driver on /dev/fb0: MXCFB_SEND_UPDATE (0x4040462e) starts a refresh and returns at once, MXCFB_WAIT_FOR_UPDATE_COMPLETE (0x4004462f) waits for a marker. The request is a 64-byte mxcfb_update_data: top, left, width, height, waveform, mode, marker, temperature (mxcfbUpdate in eink_refresh.go).
  • Rotated rectangles. The panel is mounted turned a quarter, so the rectangle sent to the driver is the app's rectangle rotated into framebuffer coordinates (framebufferRect). The overlap test is done on those, which gives the same answer.
  • Only the last refresh is remembered. flush keeps one lastRect and one lastMarker. With three refreshes A, B, C where B does not overlap A but C does, C compares itself only with B and does not wait for A. In practice the app's refreshes come one after the other on one thread, and the case does not arise often.
  • What a page turn costs besides the panel. The copy into the framebuffer, which also rotates the pixels, takes about 22 ms a page turn (copyRotated). flush logs its wait, copy and send times to applications/inkshelf.log: that is how page turns are timed on the reader.
  • Visible or not. Before every refresh, flush checks that the app is in the foreground and that the firmware does not suppress refreshes; otherwise it does nothing.
  • DU and A2 are unused. Inkshelf only uses GL16 (page turns) and GC16 (flashes, other screens, error pages). The fast waveforms are in Waveform because the firmware has them.
  • The emulator is pessimistic. With -eink, the emulated panel sleeps for the refresh time after every refresh, overlapping or not, so it shows the worst case (refreshTime in emulated_firmware.go).
  • Timings drift. The 490 / 600 / 265 ms above come from the emulator's constants and the package documentation. An older comment next to pageWaveform in reader.go says 435 ms for GL16 and 576 ms for GC16, from an earlier measurement. Measure on your own reader with the log if the exact number matters.

Where it lives in the code​

The full story: E-ink and the firmware.

Next: the panel shows only 16 grays, so a comic page must be turned into them. That is From a scan to 16 grays.

Check yourself​

The reader is switched off in the middle of a book. What does the screen show?
Why not use the full, flashing GC16 refresh for every page turn?
The page is refreshing. You open the toolbar, a strip across the top of the page. What happens?
Which waveform does Inkshelf use for an ordinary page turn?