Raspberry Pi Pico
Pimoroni inky:bit
The inky:bit is a micro:bit accessory which is strictly for version 2.0 of the micro:bit. It is a gorgeous epaper display. I have had one of these since its release and haven't used it a great deal with the micro:bit. I thought it would be worth having a go at connecting it to the Pico.
In the photograph, I have connected the inky:bit using a Pinbetween. There are numerous connections to make.
- micro:bit pin2 to Pico GP2
- micro:bit pin8 to Pico GP3
- micro:bit pin12 to Pico GP4
- micro:bit pin16 to Pico GP5
- micro:bit pin13 to Pico GP14
- micro:bit pin14 to Pico GP15

I needed a font to use for the project, It is the one I have used in several projects and is saved as font.py.
font7x5 = bytearray(b'\x00\x00\x00\x00\x00\x00\x00\x5f\x00\x00\x00\x07\x00\x07\x00\x14\x7f\x14'
b'\x7f\x14\x24\x2a\x7f\x2a\x12\x23\x13\x08\x64\x62\x36\x49\x55\x22\x50\x00'
b'\x05\x03\x00\x00\x00\x1c\x22\x41\x00\x00\x41\x22\x1c\x00\x14\x08\x3e\x08'
b'\x14\x08\x08\x3e\x08\x08\x00\x50\x30\x00\x00\x08\x08\x08\x08\x08\x00\x60'
b'\x60\x00\x00\x20\x10\x08\x04\x02\x3e\x51\x49\x45\x3e\x00\x42\x7f\x40\x00'
b'\x42\x61\x51\x49\x46\x21\x41\x45\x4b\x31\x18\x14\x12\x7f\x10\x27\x45\x45'
b'\x45\x39\x3c\x4a\x49\x49\x30\x01\x71\x09\x05\x03\x36\x49\x49\x49\x36\x06'
b'\x49\x49\x29\x1e\x00\x36\x36\x00\x00\x00\x56\x36\x00\x00\x08\x14\x22\x41'
b'\x00\x14\x14\x14\x14\x14\x00\x41\x22\x14\x08\x02\x01\x51\x09\x06\x32\x49'
b'\x79\x41\x3e\x7e\x11\x11\x11\x7e\x7f\x49\x49\x49\x36\x3e\x41\x41\x41\x22'
b'\x7f\x41\x41\x22\x1c\x7f\x49\x49\x49\x41\x7f\x09\x09\x09\x01\x3e\x41\x49'
b'\x49\x7a\x7f\x08\x08\x08\x7f\x00\x41\x7f\x41\x00\x20\x40\x41\x3f\x01\x7f'
b'\x08\x14\x22\x41\x7f\x40\x40\x40\x40\x7f\x02\x0c\x02\x7f\x7f\x04\x08\x10'
b'\x7f\x3e\x41\x41\x41\x3e\x7f\x09\x09\x09\x06\x3e\x41\x51\x21\x5e\x7f\x09'
b'\x19\x29\x46\x46\x49\x49\x49\x31\x01\x01\x7f\x01\x01\x3f\x40\x40\x40\x3f'
b'\x1f\x20\x40\x20\x1f\x3f\x40\x38\x40\x3f\x63\x14\x08\x14\x63\x07\x08\x70'
b'\x08\x07\x61\x51\x49\x45\x43\x00\x7f\x41\x41\x00\x02\x04\x08\x10\x20\x00'
b'\x41\x41\x7f\x00\x04\x02\x01\x02\x04\x40\x40\x40\x40\x40\x00\x01\x02\x04'
b'\x00\x20\x54\x54\x54\x78\x7f\x48\x44\x44\x38\x38\x44\x44\x44\x20\x38\x44'
b'\x44\x48\x7f\x38\x54\x54\x54\x18\x08\x7e\x09\x01\x02\x0c\x52\x52\x52\x3e'
b'\x7f\x08\x04\x04\x78\x00\x44\x7d\x40\x00\x20\x40\x44\x3d\x00\x7f\x10\x28'
b'\x44\x00\x00\x41\x7f\x40\x00\x7c\x04\x18\x04\x78\x7c\x08\x04\x04\x78\x38'
b'\x44\x44\x44\x38\x7c\x14\x14\x14\x08\x08\x14\x14\x18\x7c\x7c\x08\x04\x04'
b'\x08\x48\x54\x54\x54\x20\x04\x3f\x44\x40\x20\x3c\x40\x40\x20\x7c\x1c\x20'
b'\x40\x20\x1c\x3c\x40\x30\x40\x3c\x44\x28\x10\x28\x44\x0c\x50\x50\x50\x3c'
b'\x44\x64\x54\x4c\x44\x00\x08\x36\x41\x00\x00\x00\x7f\x00\x00\x00\x41\x36'
b'\x08\x00\x10\x08\x08\x10\x08\x00\x00\x00\x00\x00')
This is a copy and update of the Pimoroni library. I have added a couple of features and rewritten the text library. It is saved as inky.py.
from micropython import const
from time import sleep
from font import font7x5
from math import sqrt
# 250 x 122 effective pixels on the display
COLS = const(136)
ROWS = const(250)
OFFSET_X = const(0)
OFFSET_Y = const(6)
WIDTH = const(250)
HEIGHT = const(122)
DRIVER_CONTROL = const(0x01)
GATE_VOLTAGE = const(0x03)
SOURCE_VOLTAGE = const(0x04)
DISPLAY_CONTROL = const(0x07)
NON_OVERLAP = const(0x0B)
BOOSTER_SOFT_START = const(0x0C)
GATE_SCAN_START = const(0x0F)
DEEP_SLEEP = const(0x10)
DATA_MODE = const(0x11)
SW_RESET = const(0x12)
TEMP_WRITE = const(0x1A)
TEMP_READ = const(0x1B)
TEMP_CONTROL = const(0x1C)
TEMP_LOAD = const(0x1D)
MASTER_ACTIVATE = const(0x20)
DISP_CTRL1 = const(0x21)
DISP_CTRL2 = const(0x22)
WRITE_RAM = const(0x24)
WRITE_ALTRAM = const(0x26)
READ_RAM = const(0x25)
VCOM_SENSE = const(0x28)
VCOM_DURATION = const(0x29)
WRITE_VCOM = const(0x2C)
READ_OTP = const(0x2D)
WRITE_LUT = const(0x32)
WRITE_DUMMY = const(0x3A)
WRITE_GATELINE = const(0x3B)
WRITE_BORDER = const(0x3C)
SET_RAMXPOS = const(0x44)
SET_RAMYPOS = const(0x45)
SET_RAMXCOUNT = const(0x4E)
SET_RAMYCOUNT = const(0x4F)
NOP = const(0xFF)
CS_ACTIVE = const(0)
CS_INACTIVE = const(0)
TEXT_TINY = const(1)
TEXT_NORMAL = const(2)
TEXT_MEDIUM = const(3)
TEXT_LARGE = const(4)
WHITE = const(0)
BLACK = const(1)
ACCENT = const(2)
LUTS_BLACK = bytearray([
0x02, 0x02, 0x01, 0x11, 0x12, 0x12, 0x22, 0x22, 0x66, 0x69,
0x69, 0x59, 0x58, 0x99, 0x99, 0x88, 0x00, 0x00, 0x00, 0x00,
0xF8, 0xB4, 0x13, 0x51, 0x35, 0x51, 0x51, 0x19, 0x01, 0x00
])
class InkyBit:
def __init__(self, spi, dc, cs, reset, busy):
self.spi = spi
self.dc = dc
self.cs = cs
self.reset = reset
self.busy = busy
self.buf_b = bytearray(b'\xFF' * (COLS // 8) * ROWS)
self.buf_r = bytearray((COLS // 8) * ROWS)
def clear(self):
self.buf_b = bytearray(b'\xFF' * (COLS // 8) * ROWS)
self.buf_r = bytearray((COLS // 8) * ROWS)
def set_pixel(self, x, y, color = 1):
y += OFFSET_Y
y = COLS - 1 - y
shift = 7 - y % 8
y //= 8
offset = x * (COLS // 8) + y
if offset >= len(self.buf_b):
return
byte_b = self.buf_b[offset] | (0b1 << shift)
byte_r = self.buf_r[offset] & ~(0b1 << shift)
if color == 2:
# Set a bit to set as red/yellow
byte_r |= 0b1 << shift
if color == 1:
# Mask *out* a bit to set as black
byte_b &= ~(0b1 << shift)
self.buf_b[offset] = byte_b
self.buf_r[offset] = byte_r
def draw_line(self, x0, y0, x1, y1, color=1):
dx = abs(x1 - x0)
sx = 1 if x0 < x1 else -1
dy = -abs(y1 - y0)
sy = 1 if y0 < y1 else -1
err = dx + dy
while True:
self.set_pixel(x0, y0, color)
if x0==x1 and y0==y1:
break
e2 = 2 * err
if e2 > dy:
err += dy
x0 += sx
if e2 <= dx:
err += dx
y0 += sy
def draw_rectangle(self, x, y, width, height, color=1, filled=False):
width -= 1
height -= 1
self.draw_line(x, y, x + width, y, color)
self.draw_line(x, y, x, y + height, color)
self.draw_line(x + width, y, x + width, y + height, color)
self.draw_line(x, y + height, x + width, y + height, color)
if filled:
x += 1
y += 1
width -= 1
height -= 1
for px in range(width):
for py in range(height):
self.set_pixel(x + px, y + py, color)
def draw_circle(self, cx, cy, r, color = 1, filled = False):
d = (5 - r * 4) // 4
x = 0
y = r
while x <= y:
self.set_pixel(cx + x, cy + y, color)
self.set_pixel(cx + x, cy - y, color)
self.set_pixel(cx - x, cy + y, color)
self.set_pixel(cx - x, cy - y, color)
self.set_pixel(cx + y, cy + x, color)
self.set_pixel(cx + y, cy - x, color)
self.set_pixel(cx - y, cy + x, color)
self.set_pixel(cx - y, cy - x, color)
if filled:
self.draw_line(cx - x, cy + y, cx + x, cy + y, color)
self.draw_line(cx - x, cy - y, cx + x, cy - y, color)
self.draw_line(cx - y, cy + x, cx + y, cy + x, color)
self.draw_line(cx - y, cy - x, cx + y, cy - x, color)
if d < 0:
d += 2* x + 1
else:
d += 2 * (x - y) + 1
y -= 1
x += 1
def _spi_cmd(self, command, data = None):
self.cs.value(CS_ACTIVE)
self.dc.value(0)
self.spi.write(bytearray([command]))
if data is not None:
self.dc.value(1)
self.spi.write(bytearray(data))
self.cs.value(CS_INACTIVE)
def _spi_data(self, data):
self.cs.value(CS_ACTIVE)
self.dc.value(1)
self.spi.write(bytearray(data))
self.cs.value(CS_INACTIVE)
def _busy_wait(self):
while self.busy.value():
v = busy.value()
print(v)
sleep(0.5)
def draw_text(self, x, y, text, color = 1, scale = 1):
if scale < 1 or scale>3: return
starty = y
for c in text:
data = font7x5[(ord(c)-32)*5:(ord(c)-32)*5+5]
for d in range(5):
for k in range(scale):
y = starty
for i in range(7):
for j in range(scale):
if data[d] >> i & 1:
self.set_pixel(x, y, color)
y += 1
x += 1
def show(self):
self.spi.init()
self.reset.value(0)
sleep(0.5)
self.reset.value(1)
sleep(0.5)
self._spi_cmd(0x12)
sleep(1)
self._busy_wait()
self._spi_cmd(DRIVER_CONTROL, [ROWS - 1, (ROWS - 1) >> 8, 0x00])
self._spi_cmd(WRITE_DUMMY, [0x1B])
self._spi_cmd(WRITE_GATELINE, [0x0B])
self._spi_cmd(DATA_MODE, [0x03])
self._spi_cmd(SET_RAMXPOS, [0x00, COLS // 8 - 1])
self._spi_cmd(SET_RAMYPOS, [0x00, 0x00, (ROWS - 1) & 0xFF, (ROWS - 1) >> 8])
self._spi_cmd(WRITE_VCOM, [0x70])
self._spi_cmd(WRITE_LUT, LUTS_BLACK)
self._spi_cmd(SET_RAMXCOUNT, [0x00])
self._spi_cmd(SET_RAMYCOUNT, [0x00, 0x00])
self._spi_cmd(WRITE_RAM)
self._spi_data(self.buf_b)
self._spi_cmd(WRITE_ALTRAM)
self._spi_data(self.buf_r)
self._busy_wait()
self._spi_cmd(MASTER_ACTIVATE)
Here is my test code,
from machine import Pin, SPI
from time import sleep
from inky import InkyBit
spi = SPI(1, 10_000_000, sck=Pin(14), mosi=Pin(15))
dc = Pin(4, Pin.OUT)
cs = Pin(3, Pin.OUT)
reset = Pin(2, Pin.OUT)
busy = Pin(5, Pin.IN)
ink = InkyBit(spi, dc, cs, reset, busy)
# make a border with rectangles
ink.draw_rectangle(0 , 0, 250, 136, color = 2, filled = True)
ink.draw_rectangle(4 , 4, 242, 114, color = 0, filled = True)
# add a large title - inky:bit is 8 chars - 10px * 8 wide, 14px tall
ink.draw_text(81, 10, "inky:bit", scale=2)
# underline the title
ink.draw_line(81, 26, 161, 26, color=2)
ink.draw_line(81, 27, 161, 27, color=2)
# some diagonal lines
ink.draw_line(0, 0, 20, 20, color=2)
ink.draw_line(230, 20, 250, 0, color=2)
# do a couple of circles -
ink.draw_circle(230, 20, 5, color = 2, filled = True)
ink.draw_circle(175, 16, 8)
# some more text, standard size
ink.draw_text(24, 40, "inky:bit is a micro:bit accessory.")
ink.draw_text(24, 60, "It can be controlled with a Pico.")
ink.draw_text(24, 80, "It's a nice-looking display.")
ink.draw_text(24, 100, "I should spend more time on the fonts.")
# some bullets to go with the text
ink.draw_rectangle(10, 42, 4, 4, filled=True)
ink.draw_rectangle(10, 62, 4, 4, filled=True)
ink.draw_rectangle(10, 82, 4, 4, filled=True)
ink.draw_rectangle(10, 102, 4, 4, filled=True)
ink.show()
print("Done")
The circle drawing, particularly the filled version needs some work to be quick enough to use. I could also put more work into the fonts. The display is working though and, in real life, looks really sharp.

