drivers.Epaper.EPD.EPD

class drivers.Epaper.EPD.EPD

E-paper display driver for a 128x296 tri-color (black/white/red) panel.

Wraps the SPI command set of a Waveshare-compatible EPD controller and exposes a high-level interface to initialize the panel, push framebuffers derived from PIL images, clear the screen, and enter low-power sleep.

Variables:
  • epdconfig (Epdconfig) – Shared hardware abstraction handle providing GPIO toggling, SPI byte transfers, and module init/exit helpers.

  • reset_pin (int) – BCM GPIO number for the panel RST line.

  • dc_pin (int) – BCM GPIO number for the data/command DC select line.

  • busy_pin (int) – BCM GPIO number for the controller BUSY flag.

  • cs_pin (int) – BCM GPIO number for the SPI CS chip-select line.

  • width (int) – Display width in pixels (EPD_WIDTH = 128).

  • height (int) – Display height in pixels (EPD_HEIGHT = 296).

  • TIMEOUT (int) – Maximum time, in milliseconds, that ReadBusy() waits for the panel before aborting (5000 ms).

Example

Render a PIL image to the panel:

>>> from PIL import Image
>>> from drivers.Epaper.EPD import EPD
>>> epd = EPD()
>>> epd.init()
>>> img = Image.new('1', (epd.width, epd.height), 255)
>>> epd.display(epd.getbuffer(img))
>>> epd.sleep()

Bind panel dimensions and copy GPIO pin numbers from Epdconfig.

Reads RST_PIN, DC_PIN, BUSY_PIN, and CS_PIN from the class-level Epdconfig instance and stores EPD_WIDTH / EPD_HEIGHT on the instance for use by buffer-sizing helpers.

Parameters:

None.

Returns:

None.

Raises:

AttributeError – If Epdconfig does not expose one of the expected pin constants.

Example

>>> from drivers.Epaper.EPD import EPD
>>> epd = EPD()
>>> epd.width, epd.height
(128, 296)

Note

This constructor performs no I/O. Call init() to bring the SPI bus and the panel itself online before any draw operation.

Clear()

Blank the panel by writing 0xFF to both data channels and refreshing.

Writes width * height / 8 bytes of 0xFF on channel 0x10 and channel 0x13, then issues 0x12 (DISPLAY_REFRESH) and blocks on ReadBusy() until the panel finishes.

Parameters:

None.

Returns:

None.

Raises:

OSError – If any SPI transfer fails mid-transmission.

Example

>>> epd = EPD()
>>> epd.init()
>>> epd.Clear()  # blank the screen

Note

Recommended between successive frame updates to scrub any latent ghosting from the previous image. Same timing characteristics as display().

Dev_exit()

Release SPI and GPIO resources held by the panel module.

Delegates to Epdconfig.module_exit(), which closes the spidev handle, drives RST and DC low, and runs GPIO.cleanup().

Parameters:

None.

Returns:

None.

Raises:

OSError – Propagated from spidev/GPIO cleanup if the handles are already closed.

Example

>>> epd = EPD()
>>> epd.init()
>>> epd.sleep()
>>> epd.Dev_exit()

Note

Call after sleep() to free the peripherals for other consumers (or to allow a clean process exit).

ReadBusy()

Poll the panel BUSY line until it is released or timeout expires.

Waits in a busy-loop, sleeping 100 ms between samples, until Epdconfig.digital_read() reports a non-zero level on busy_pin. Logs [ERR EPD] Timeout BUSY and returns early if TIMEOUT milliseconds elapse.

Parameters:

None.

Returns:

None.

Raises:

None – Timeout is reported via stdout, not exceptions.

Example

>>> epd = EPD()
>>> epd.send_command(0x04)  # POWER_ON
>>> epd.ReadBusy()  # block until controller finishes

Note

On this controller, BUSY = 0 means the panel is processing a command. The timeout (TIMEOUT, 5000 ms) guards against hung peripherals so the host application can continue.

display(blackimage)

Push a framebuffer to the panel and trigger a full refresh.

Streams an all-white pattern on channel 0x10 (DATA_START_TRANSMISSION_1) and the caller-supplied buffer on channel 0x13 (DATA_START_TRANSMISSION_2), then issues 0x12 (DISPLAY_REFRESH) and blocks on ReadBusy() until the panel reports completion.

Parameters:

blackimage (list[int] | None) – Byte buffer returned by getbuffer(). If None, no data is transmitted and only the refresh command is issued.

Returns:

None.

Raises:

OSError – If any SPI transfer fails mid-transmission.

Example

>>> from PIL import Image
>>> epd = EPD()
>>> epd.init()
>>> img = Image.new('1', (epd.width, epd.height), 255)
>>> epd.display(epd.getbuffer(img))

Note

This is a full refresh; expect roughly 12-15 s for the panel to settle. Only the parameter blackimage is honoured: the red data path is written as all-white (0xFF) so red ink is never produced.

See also

getbuffer() for image-to-buffer conversion. Clear() for blanking the panel.

getbuffer(image)

Pack a PIL image into the 1-bit framebuffer expected by the panel.

Converts image to mode "1" (1-bit black/white), then walks each pixel and clears the corresponding bit in the output buffer when the pixel is black. The buffer is MSB-first within each byte, with 0xFF (all white) used as the initial fill.

Parameters:

image (PIL.Image.Image) – Source image whose dimensions exactly match width x height (128 x 296).

Returns:

width * height / 8 bytes (4736 bytes) of packed pixel data, ready to pass to display().

Return type:

list[int]

Raises:
  • ValueError – If image width or height does not match the display geometry.

  • AttributeError – If image does not implement the PIL convert/load interface.

Example

>>> from PIL import Image
>>> epd = EPD()
>>> img = Image.new('1', (epd.width, epd.height), 0)
>>> buf = epd.getbuffer(img)
>>> len(buf)
4736

Note

This driver currently exposes only a single (black) channel; the red channel is filled with 0xFF directly inside display(). Pixel value 0 denotes black ink, 1 denotes white background.

init()

Initialize the SPI bus and program the EPD panel registers.

Calls Epdconfig.module_init() to configure GPIO and SPI, then issues the booster, power-on, panel-setting, resolution, and VCOM commands needed to bring the panel into the operational state.

Parameters:

None.

Returns:

0 on success, -1 if Epdconfig.module_init() fails to configure the SPI bus or GPIO pins.

Return type:

int

Raises:

OSError – Propagated from Epdconfig if SPI or GPIO open fails.

Example

>>> epd = EPD()
>>> if epd.init() == 0:
...     epd.Clear()

Note

Command sequence used during init:

  • 0x06 BOOSTER_SOFT_START with payload 0x17 0x17 0x17

  • 0x04 POWER_ON

  • 0x00 PANEL_SETTING with payload 0x9F

  • 0x61 TCON_RESOLUTION with payload 0x80 0x01 0x28 (128 x 296)

  • 0x50 VCOM_AND_DATA_INTERVAL_SETTING with payload 0x97

A 3-second blocking sleep follows reset() to allow the controller’s internal supply to settle.

millis()

Return the current wall-clock time in milliseconds since the epoch.

Parameters:

None.

Returns:

int(time.time() * 1000).

Return type:

int

Raises:

None.

Example

>>> epd = EPD()
>>> ts = epd.millis()
>>> isinstance(ts, int)
True

Note

Used internally by ReadBusy() to enforce TIMEOUT. Accuracy depends on the host clock; on Raspberry Pi this is typically millisecond-grade once NTP has synchronized.

reset()

Drive the panel RST line through three low/high pulses.

Performs the hardware-reset sequence required by the EPD controller after power-up or before re-initialization. Each pulse holds the line at the target level for 100 ms via Epdconfig.delay_ms().

Parameters:

None.

Returns:

None.

Raises:

RuntimeError – Propagated from the underlying GPIO layer if the RST pin has not been configured as an output.

Example

>>> epd = EPD()
>>> epd.epdconfig.module_init()
>>> epd.reset()

Note

The triple-pulse pattern matches the Waveshare reference driver and is required for reliable wake from deep sleep. Total duration is roughly 600 ms.

send_command(command)

Transmit a single command byte to the EPD controller over SPI.

Pulls DC low to mark the byte as a command, asserts CS, sends the byte via Epdconfig.spi_writebyte(), and deasserts CS.

Parameters:

command (int) – 8-bit command code from the panel’s command set (e.g. 0x06 for BOOSTER_SOFT_START).

Returns:

None.

Raises:

OSError – If the underlying SPI transfer fails.

Example

>>> epd = EPD()
>>> epd.send_command(0x12)  # trigger DISPLAY_REFRESH

Note

Commands and data must alternate via send_command() and send_data() calls; the DC line is what distinguishes the two on the wire.

send_data(data)

Transmit a single data byte to the EPD controller over SPI.

Pulls DC high to mark the byte as data, asserts CS, sends the byte via Epdconfig.spi_writebyte(), and deasserts CS.

Parameters:

data (int) – 8-bit payload byte associated with the most recently issued command.

Returns:

None.

Raises:

OSError – If the underlying SPI transfer fails.

Example

>>> epd = EPD()
>>> epd.send_command(0x00)  # PANEL_SETTING
>>> epd.send_data(0x9F)

Note

Each call writes exactly one byte; long buffers must be sent byte-by-byte (see display() and Clear()).

sleep()

Put the panel into deep sleep to minimize quiescent current.

Programs VCOM_AND_DATA_INTERVAL_SETTING (0x50) with payload 0xF7, issues POWER_OFF (0x02) and waits via ReadBusy(), then issues POWER_SETTING (0x07) with payload 0xA5 to switch the gate driver to its external source.

Parameters:

None.

Returns:

None.

Raises:

OSError – If any SPI transfer fails.

Example

>>> epd = EPD()
>>> epd.init()
>>> epd.Clear()
>>> epd.sleep()

Note

The panel will not respond to display commands until reset() and init() are called again. Always sleep the panel before powering down the host to preserve the last shown frame.

TIMEOUT = 5000
_timeout_ = 5
busy_pin = 24
cs_pin = 8
dc_pin = 25
epdconfig
height = 296
lut_b = [14, 20, 1, 138, 6, 4, 138, 74, 15, 131, 67, 12, 6, 74, 4]
lut_g1 = [142, 148, 1, 138, 6, 4, 138, 74, 15, 131, 67, 12, 6, 10, 4]
lut_g2 = [142, 148, 1, 138, 6, 4, 138, 74, 15, 131, 67, 12, 6, 10, 4]
lut_red0 = [131, 93, 1, 129, 72, 35, 119, 119, 1, 0, 0, 0, 0, 0, 0]
lut_red1 = [3, 29, 1, 1, 8, 35, 55, 55, 1, 0, 0, 0, 0, 0, 0]
lut_vcom0 = [14, 20, 1, 10, 6, 4, 10, 10, 15, 3, 3, 12, 6, 10, 0]
lut_vcom1 = [3, 29, 1, 1, 8, 35, 55, 55, 1, 0, 0, 0, 0, 0, 0]
lut_w = [14, 20, 1, 10, 70, 4, 138, 74, 15, 131, 67, 12, 134, 10, 4]
reset_pin = 17
width = 128