Juni 2023
Mit Hilfe des Pico To HAT kann der Unicon HAT Mini auch am Pi Pico betrieben werden.
Dazu muss der Python-Treiber vorher noch auf Micropython umgestellt werden, z.B. so:
UnicornHATMini.py
from machine import Pin, SPI, Timer
import time
import random
import math
# Unicorn Hat Mini driver for Pi Pico
# PINs
# Raspberry - PICO
# GPIO 10 - SPI0 MOSI - GP 11
# GPIO 11 - SPI0 SCLK - GP 10
# GPIO 8 - SPI0 CE0 - GP 8
# GPIO 7 - SPI0 CE1 - GP 7
MOSI = 11
SCK = 10
CE0 = 8
CE1 = 7
__version__ = '0.0.2'
# Holtek HT16D35
CMD_SOFT_RESET = 0xCC
CMD_GLOBAL_BRIGHTNESS = 0x37
CMD_COM_PIN_CTRL = 0x41
CMD_ROW_PIN_CTRL = 0x42
CMD_WRITE_DISPLAY = 0x80
CMD_READ_DISPLAY = 0x81
CMD_SYSTEM_CTRL = 0x35
CMD_SCROLL_CTRL = 0x20
_COLS = 17
_ROWS = 7
BUTTON_A = 5
BUTTON_B = 6
BUTTON_X = 16
BUTTON_Y = 24
class UnicornHATMini():
lut = [[139, 138, 137], [223, 222, 221], [167, 166, 165], [195, 194, 193], [111, 110, 109], [55, 54, 53], [83, 82, 81], [136, 135, 134], [220, 219, 218], [164, 163, 162], [192, 191, 190], [108, 107, 106], [52, 51, 50], [80, 79, 78], [113, 115, 114], [197, 199, 198], [141, 143, 142], [169, 171, 170], [85, 87, 86], [29, 31, 30], [57, 59, 58], [116, 118, 117], [200, 202, 201], [144, 146, 145], [172, 174, 173], [88, 90, 89], [32, 34, 33], [60, 62, 61], [119, 121, 120], [203, 205, 204], [147, 149, 148], [175, 177, 176], [91, 93, 92], [35, 37, 36], [63, 65, 64], [122, 124, 123], [206, 208, 207], [150, 152, 151], [178, 180, 179], [94, 96, 95], [38, 40, 39], [66, 68, 67], [125, 127, 126], [209, 211, 210], [153, 155, 154], [181, 183, 182], [97, 99, 98], [41, 43, 42], [69, 71, 70], [128, 130, 129], [212, 214, 213], [156, 158, 157], [184, 186, 185], [100, 102, 101], [44, 46, 45], [72, 74, 73], [131, 133, 132], [215, 217, 216], [159, 161, 160], [187, 189, 188], [103, 105, 104], [47, 49, 48], [75, 77, 76], [363, 362, 361], [447, 446, 445], [391, 390, 389], [419, 418, 417], [335, 334, 333], [279, 278, 277], [307, 306, 305], [360, 359, 358], [444, 443, 442], [388, 387, 386], [416, 415, 414], [332, 331, 330], [276, 275, 274], [304, 303, 302], [337, 339, 338], [421, 423, 422], [365, 367, 366], [393, 395, 394], [309, 311, 310], [253, 255, 254], [281, 283, 282], [340, 342, 341], [424, 426, 425], [368, 370, 369], [396, 398, 397], [312, 314, 313], [256, 258, 257], [284, 286, 285], [343, 345, 344], [427, 429, 428], [371, 373, 372], [399, 401, 400], [315, 317, 316], [259, 261, 260], [287, 289, 288], [346, 348, 347], [430, 432, 431], [374, 376, 375], [402, 404, 403], [318, 320, 319], [262, 264, 263], [290, 292, 291], [349, 351, 350], [433, 435, 434], [377, 379, 378], [405, 407, 406], [321, 323, 322], [265, 267, 266], [293, 295, 294], [352, 354, 353], [436, 438, 437], [380, 382, 381], [408, 410, 409], [324, 326, 325], [268, 270, 269], [296, 298, 297]]
def __init__(self, spi_max_speed_hz=600000):
"""Initialise unicornhatmini
Tested to around 6MHz (500fps) on a Pi 4 and 6KHz (50fps) on an A+.
:param spi_max_speed_hz: SPI speed in Hz
"""
self.spi = SPI(1)
self.spi = SPI(1,600000)
self.spi = SPI(1,600000,polarity=0, phase=0,sck=Pin(SCK),mosi=Pin(MOSI),miso=None)
self.disp = [[0, 0, 0] for _ in range(_COLS * _ROWS)]
self.left_matrix = (self.spi, Pin(8, mode=Pin.OUT), 0) # device, pin, offset
self.right_matrix = (self.spi, Pin(7, mode=Pin.OUT), 28 * 8) # device, pin, offset
self.buf = [0 for _ in range(28 * 8 * 2)]
self._rotation = 0
self.callback_x = None
self.debounce_time = 0
for device, pin, offset in self.left_matrix, self.right_matrix:
self.xfer(device, pin, [CMD_SOFT_RESET])
self.xfer(device, pin, [CMD_GLOBAL_BRIGHTNESS, 0x01])
self.xfer(device, pin, [CMD_SCROLL_CTRL, 0x00])
self.xfer(device, pin, [CMD_SYSTEM_CTRL, 0x00])
self.xfer(device, pin, [CMD_WRITE_DISPLAY, 0x00] + self.buf[offset:offset + (28 * 8)])
self.xfer(device, pin, [CMD_COM_PIN_CTRL, 0xff])
self.xfer(device, pin, [CMD_ROW_PIN_CTRL, 0xff, 0xff, 0xff, 0xff])
self.xfer(device, pin, [CMD_SYSTEM_CTRL, 0x03])
# Init Pins
self.pin_x = Pin(BUTTON_X, Pin.IN, Pin.PULL_UP)
self.pin_x.irq(trigger=Pin.IRQ_FALLING, handler=lambda pin: self.callback(pin))
def callback(self, pin):
if (time.ticks_ms()-self.debounce_time) > 100:
self.debounce_time=time.ticks_ms()
if pin == self.pin_x and self.callback_x != None:
self.callback_x()
def _shutdown(self):
for device, pin, _ in self.left_matrix, self.right_matrix:
self.xfer(device, pin, [CMD_COM_PIN_CTRL, 0x00])
self.xfer(device, pin, [CMD_ROW_PIN_CTRL, 0x00, 0x00, 0x00, 0x00])
self.xfer(device, pin, [CMD_SYSTEM_CTRL, 0x00])
def _exit(self):
self._shutdown()
def xfer(self, device, pin, command):
pin.low()
device.write(bytearray(command))
pin.high()
def set_pixel(self, x, y, r, g, b):
"""Set a single pixel."""
offset = (x * _ROWS) + y
if self._rotation == 90:
y = _COLS - 1 - y
offset = (y * _ROWS) + x
if self._rotation == 180:
x = _COLS - 1 - x
y = _ROWS - 1 - y
offset = (x * _ROWS) + y
if self._rotation == 270:
x = _ROWS - 1 - x
offset = (y * _ROWS) + x
self.disp[offset] = [r >> 2, g >> 2, b >> 2]
def set_all(self, r, g, b):
"""Set all pixels."""
r >>= 2
g >>= 2
b >>= 2
for i in range(_ROWS * _COLS):
self.disp[i] = [r, g, b]
def set_image(self, image, offset_x=0, offset_y=0, wrap=False, bg_color=(0, 0, 0)):
"""Set a PIL image to the display buffer."""
image_width, image_height = image.size
if image.mode != "RGB":
image = image.convert('RGB')
display_width, display_height = self.get_shape()
for y in range(display_height):
for x in range(display_width):
r, g, b = bg_color
i_x = x + offset_x
i_y = y + offset_y
if wrap:
while i_x >= image_width:
i_x -= image_width
while i_y >= image_height:
i_y -= image_height
if i_x < image_width and i_y < image_height:
r, g, b = image.getpixel((i_x, i_y))
self.set_pixel(x, y, r, g, b)
def clear(self):
"""Set all pixels to 0."""
self.set_all(0, 0, 0)
def set_brightness(self, b=0.2):
for device, pin, _ in self.left_matrix, self.right_matrix:
self.xfer(device, pin, [CMD_GLOBAL_BRIGHTNESS, int(63 * b)])
def set_rotation(self, rotation=0):
if rotation not in [0, 90, 180, 270]:
raise ValueError("Rotation must be one of 0, 90, 180, 270")
self._rotation = rotation
def show(self):
for i in range(_COLS * _ROWS):
ir, ig, ib = self.lut[i]
r, g, b = self.disp[i]
self.buf[ir] = r
self.buf[ig] = g
self.buf[ib] = b
for device, pin, offset in self.left_matrix, self.right_matrix:
self.xfer(device, pin, [CMD_WRITE_DISPLAY, 0x00] + self.buf[offset:offset + (28 * 8)])
def get_shape(self):
if self._rotation in [90, 270]:
return _ROWS, _COLS
else:
return _COLS, _ROWS
def rgb_to_hsv(self, r,g,b):
high = max(r, g, b)
low = min(r, g, b)
h, s, v = high, high, high
d = high - low
s = 0 if high == 0 else d/high
if high == low:
h = 0.0
else:
h = {
r: (g - b) / d + (6 if g < b else 0),
g: (b - r) / d + 2,
b: (r - g) / d + 4,
}[high]
h /= 6
return h, s, v
def hsv_to_rgb(self, h, s, v):
if s == 0.0: return (v, v, v)
i = int(h*6.) # XXX assume int() truncates!
f = (h*6.)-i; p,q,t = v*(1.-s), v*(1.-s*f), v*(1.-s*(1.-f)); i%=6
if i == 0: return (v, t, p)
if i == 1: return (q, v, p)
if i == 2: return (p, v, t)
if i == 3: return (p, q, v)
if i == 4: return (t, p, v)
if i == 5: return (v, p, q)
if __name__ == "__main__":
unicornhatmini = UnicornHATMini()
unicornhatmini.set_brightness(0.2)
width, height = unicornhatmini.get_shape()
step = 0
# see examples here:
# https://github.com/pimoroni/unicornhatmini-python/blob/master/examples/text.py
while True:
step += 1
for x in range(0, width):
for y in range(0, height):
dx = (math.sin(step / width + 20) * width) + height
dy = (math.cos(step / height) * height) + height
sc = (math.cos(step / height) * height) + width
hue = math.sqrt(math.pow(x - dx, 2) + math.pow(y - dy, 2)) / sc
r, g, b = [int(c * 255) for c in unicornhatmini.hsv_to_rgb(hue, 1, 1)]
unicornhatmini.set_pixel(x, y, r, g, b)
unicornhatmini.show()
time.sleep(1.0 / 60)
Die Pimoroni Beispielprogramme (siehe https://github.com/pimoroni/unicornhatmini-python/) können dann fast unverändert übernommen werden, z.B. rainbows.py:
from UnicornHATMini import UnicornHATMini
import time
import random
import math
if __name__ == "__main__":
brightness = 0.1
def change_brightness():
global brightness
brightness = max(0.1, (brightness + 0.1) % 1);
unicornhatmini.set_brightness(brightness)
unicornhatmini = UnicornHATMini()
unicornhatmini.set_brightness(0.1)
unicornhatmini.callback_x = change_brightness;
width, height = unicornhatmini.get_shape()
step = 0
# see examples here:
# https://github.com/pimoroni/unicornhatmini-python/blob/master/examples/text.py
while True:
step += 1
for x in range(0, width):
for y in range(0, height):
dx = (math.sin(step / width + 20) * width) + height
dy = (math.cos(step / height) * height) + height
sc = (math.cos(step / height) * height) + width
hue = math.sqrt(math.pow(x - dx, 2) + math.pow(y - dy, 2)) / sc
r, g, b = [int(c * 255) for c in unicornhatmini.hsv_to_rgb(hue, 1, 1)]
unicornhatmini.set_pixel(x, y, r, g, b)
unicornhatmini.show()
time.sleep(1.0 / 60)
Als Dateien:
./UnicornHATMini.py
./rainbows.py