"""Inky e-Ink Display Driver."""
import sys
import time
import struct
import spidev
try:
import RPi.GPIO as GPIO
except ImportError:
sys.exit('This library requires the RPi.GPIO module\nInstall with: sudo apt install python-rpi.gpio')
try:
import numpy
except ImportError:
sys.exit('This library requires the numpy module\nInstall with: sudo apt install python-numpy')
WHITE = 0
BLACK = 1
RED = YELLOW = 2
RESET_PIN = 27
BUSY_PIN = 17
DC_PIN = 22
MOSI_PIN = 10
SCLK_PIN = 11
CS0_PIN = 0
_SPI_CHUNK_SIZE = 4096
_SPI_COMMAND = GPIO.LOW
_SPI_DATA = GPIO.HIGH
_RESOLUTION = {
(400, 300): (400, 300, 0),
(212, 104): (104, 212, -90),
}
class Inky:
"""Inky e-Ink Display Driver."""
WHITE = 0
BLACK = 1
RED = 2
YELLOW = 2
[docs] def __init__(self, resolution=(400, 300), colour='black', cs_pin=CS0_PIN, dc_pin=DC_PIN, reset_pin=RESET_PIN, busy_pin=BUSY_PIN, h_flip=False, v_flip=False):
"""Initialise an Inky Display.
:param resolution: (width, height) in pixels, default: (400, 300)
:param colour: one of red, black or yellow, default: black
:param cs_pin: chip-select pin for SPI communication
:param dc_pin: data/command pin for SPI communication
:param reset_pin: device reset pin
:param busy_pin: device busy/wait pin
:param h_flip: enable horizontal display flip, default: False
:param v_flip: enable vertical display flip, default: False
"""
if resolution not in _RESOLUTION.keys():
raise ValueError('Resolution {}x{} not supported!'.format(*resolution))
self.resolution = resolution
self.width, self.height = resolution
self.cols, self.rows, self.rotation = _RESOLUTION[resolution]
if colour not in ('red', 'black', 'yellow'):
raise ValueError('Colour {} is not supported!'.format(colour))
self.colour = colour
self.buf = numpy.zeros((self.height, self.width), dtype=numpy.uint8)
self.border_colour = 0
self.dc_pin = dc_pin
self.reset_pin = reset_pin
self.busy_pin = busy_pin
self.cs_pin = cs_pin
self.h_flip = h_flip
self.v_flip = v_flip
self._gpio_setup = False
self._luts = {
'black': [
# Phase 0 Phase 1 Phase 2 Phase 3 Phase 4 Phase 5 Phase 6
# A B C D A B C D A B C D A B C D A B C D A B C D A B C D
0b01001000, 0b10100000, 0b00010000, 0b00010000, 0b00010011, 0b00000000, 0b00000000, # LUT0 - Black
0b01001000, 0b10100000, 0b10000000, 0b00000000, 0b00000011, 0b00000000, 0b00000000, # LUTT1 - White
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # IGNORE
0b01001000, 0b10100101, 0b00000000, 0b10111011, 0b00000000, 0b00000000, 0b00000000, # LUT3 - Red
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # LUT4 - VCOM
# Duration | Repeat
# A B C D |
16, 4, 4, 4, 4, # 0 Flash
16, 4, 4, 4, 4, # 1 clear
4, 8, 8, 16, 16, # 2 bring in the black
0, 0, 0, 0, 0, # 3 time for red
0, 0, 0, 0, 0, # 4 final black sharpen phase
0, 0, 0, 0, 0, # 5
0, 0, 0, 0, 0, # 6
],
'red': [
# Phase 0 Phase 1 Phase 2 Phase 3 Phase 4 Phase 5 Phase 6
# A B C D A B C D A B C D A B C D A B C D A B C D A B C D
0b01001000, 0b10100000, 0b00010000, 0b00010000, 0b00010011, 0b00000000, 0b00000000, # LUT0 - Black
0b01001000, 0b10100000, 0b10000000, 0b00000000, 0b00000011, 0b00000000, 0b00000000, # LUTT1 - White
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # IGNORE
0b01001000, 0b10100101, 0b00000000, 0b10111011, 0b00000000, 0b00000000, 0b00000000, # LUT3 - Red
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # LUT4 - VCOM
# Duration | Repeat
# A B C D |
67, 10, 31, 10, 4, # 0 Flash
16, 8, 4, 4, 6, # 1 clear
4, 8, 8, 16, 16, # 2 bring in the black
2, 4, 4, 64, 32, # 3 time for red
6, 6, 6, 2, 2, # 4 final black sharpen phase
0, 0, 0, 0, 0, # 5
0, 0, 0, 0, 0 # 6
],
'yellow': [
# Phase 0 Phase 1 Phase 2 Phase 3 Phase 4 Phase 5 Phase 6
# A B C D A B C D A B C D A B C D A B C D A B C D A B C D
0b11111010, 0b10010100, 0b10001100, 0b11000000, 0b11010000, 0b00000000, 0b00000000, # LUT0 - Black
0b11111010, 0b10010100, 0b00101100, 0b10000000, 0b11100000, 0b00000000, 0b00000000, # LUTT1 - White
0b11111010, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, # IGNORE
0b11111010, 0b10010100, 0b11111000, 0b10000000, 0b01010000, 0b00000000, 0b11001100, # LUT3 - Yellow (or Red)
0b10111111, 0b01011000, 0b11111100, 0b10000000, 0b11010000, 0b00000000, 0b00010001, # LUT4 - VCOM
# Duration | Repeat
# A B C D |
64, 16, 64, 16, 8,
8, 16, 4, 4, 16,
8, 8, 3, 8, 32,
8, 4, 0, 0, 16,
16, 8, 8, 0, 32,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
]
}
[docs] def setup(self):
"""Set up Inky GPIO and reset display."""
if not self._gpio_setup:
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(self.dc_pin, GPIO.OUT, initial=GPIO.LOW, pull_up_down=GPIO.PUD_OFF)
GPIO.setup(self.reset_pin, GPIO.OUT, initial=GPIO.HIGH, pull_up_down=GPIO.PUD_OFF)
GPIO.setup(self.busy_pin, GPIO.IN, pull_up_down=GPIO.PUD_OFF)
self._spi = spidev.SpiDev()
self._spi.open(0, self.cs_pin)
self._spi.max_speed_hz = 488000
self._gpio_setup = True
GPIO.output(self.reset_pin, GPIO.LOW)
time.sleep(0.1)
GPIO.output(self.reset_pin, GPIO.HIGH)
time.sleep(0.1)
self._send_command(0x12) # Soft Reset
self._busy_wait()
def _busy_wait(self):
"""Wait for busy/wait pin."""
while(GPIO.input(self.busy_pin) != GPIO.LOW):
time.sleep(0.01)
def _update(self, buf_a, buf_b):
"""Update display.
:param buf_a: Black/White pixels
:param buf_b: Yellow/Red pixels
"""
self.setup()
packed_height = list(struct.pack('<H', self.rows))
if isinstance(packed_height[0], str):
packed_height = map(ord, packed_height)
self._send_command(0x74, 0x54) # Set Analog Block Control
self._send_command(0x7e, 0x3b) # Set Digital Block Control
self._send_command(0x01, packed_height + [0x00]) # Gate setting
self._send_command(0x03, [0b10000, 0b0001]) # Gate Driving Voltage
self._send_command(0x3a, 0x07) # Dummy line period
self._send_command(0x3b, 0x04) # Gate line width
self._send_command(0x11, 0x03) # Data entry mode setting 0x03 = X/Y increment
self._send_command(0x04) # Power On
self._send_command(0x2c, 0x3c) # VCOM Register, 0x3c = -1.5v?
self._send_command(0x3c, 0x00)
if self.border_colour == self.BLACK:
self._send_command(0x3c, 0x00)
elif self.border_colour == self.RED:
self._send_command(0x3c, 0x33)
elif self.border_colour == self.YELLOW:
self._send_command(0x3c, 0x33)
elif self.border_colour == self.WHITE:
self._send_command(0x3c, 0xFF)
if self.colour == 'yellow':
self._send_command(0x04, 0x07) # Set voltage of VSH and VSL
self._send_command(0x32, self._luts[self.colour]) # Set LUTs
self._send_command(0x44, [0x00, (self.cols // 8) - 1]) # Set RAM X Start/End
self._send_command(0x45, [0x00, 0x00] + packed_height) # Set RAM Y Start/End
# 0x24 == RAM B/W, 0x26 == RAM Red/Yellow/etc
for data in ((0x24, buf_a), (0x26, buf_b)):
cmd, buf = data
self._send_command(0x4e, 0x00) # Set RAM X Pointer Start
self._send_command(0x4f, [0x00, 0x00]) # Set RAM Y Pointer Start
self._send_command(cmd, buf)
self._send_command(0x22, 0xc7) # Display Update Sequence
self._send_command(0x20) # Trigger Display Update
time.sleep(0.05)
self._busy_wait()
self._send_command(0x10, 0x01) # Enter Deep Sleep
[docs] def set_pixel(self, x, y, v):
"""Set a single pixel.
:param x: x position on display
:param y: y position on display
:param v: colour to set
"""
if v in (WHITE, BLACK, RED):
self.buf[y][x] = v
[docs] def show(self):
"""Show buffer on display."""
region = self.buf
if self.v_flip:
region = numpy.fliplr(region)
if self.h_flip:
region = numpy.flipud(region)
if self.rotation:
region = numpy.rot90(region, self.rotation // 90)
buf_a = numpy.packbits(numpy.where(region == BLACK, 0, 1)).tolist()
buf_b = numpy.packbits(numpy.where(region == RED, 1, 0)).tolist()
self._update(buf_a, buf_b)
[docs] def set_border(self, colour):
"""Set the border colour."""
if colour in (WHITE, BLACK, RED):
self.border_colour = colour
[docs] def set_image(self, image):
"""Copy an image to the display."""
if self.rotation % 180 == 0:
self.buf = numpy.array(image, dtype=numpy.uint8).reshape((self.width, self.height))
else:
self.buf = numpy.array(image, dtype=numpy.uint8).reshape((self.height, self.width))
def _spi_write(self, dc, values):
"""Write values over SPI.
:param dc: whether to write as data or command
:param values: list of values to write
"""
GPIO.output(self.dc_pin, dc)
try:
self._spi.xfer3(values)
except AttributeError:
for x in range(((len(values) - 1) // _SPI_CHUNK_SIZE) + 1):
offset = x * _SPI_CHUNK_SIZE
self._spi.xfer(values[offset:offset + _SPI_CHUNK_SIZE])
def _send_command(self, command, data=None):
"""Send command over SPI.
:param command: command byte
:param data: optional list of values
"""
self._spi_write(_SPI_COMMAND, [command])
if data is not None:
self._send_data(data)
def _send_data(self, data):
"""Send data over SPI.
:param data: list of values
"""
if isinstance(data, int):
data = [data]
self._spi_write(_SPI_DATA, data)