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.