How to scroll text over 2 Unicorn Hat HD's?

Hi there,

how is it possible to scroll text over 2 Unicorns on Raspi3?

I tried to change the width from 16 to 32 in the file and connected the Unicorns on the same SPI with the same CS via daisy-chaining.
But the second matrix always remains blank!?

After that i gave them each its own CS (CS0 and CS1) and changed the again.
Then i tried to wait for reaching the end of the first matrix with the first letter to start scrolling on the second matrix to keep the text running, but the text shows up identically as on the first matrix so it doesn’t end or start delayed on the second one.
I guess I have some mistakes in my code.

also it’s a bit strange to wait for scroll until it reaches 32 (end of first matrix) shouldn’t it be 16 (16 leds or 0-15)?

But the best thing would be if daisy-chaining would work, so I can use the second CS for another hardware.
Someone know where is the mistake?
Many thanks in advance!

Some code from

version = ‘0.0.3’

    _SOF = 0x72
    _DELAY = 1.0/120

    WIDTH = 16
    #WIDTH = 32
    HEIGHT = 16

    PHAT = None
    HAT = None
    AUTO = None

    _rotation = 0
    _brightness = 0.5
    #_buf = numpy.zeros((32,16,3), dtype=int)
    _buf = numpy.zeros((16,16,3), dtype=int)
    _buf2 = numpy.zeros((16,16,3), dtype=int)

    is_setup = False

    def setup():
        global _spi, _spi0, _spi1, _buf, is_setup

        if is_setup:
    #general spi0
        _spi = spidev.SpiDev(), 0)
        _spi.max_speed_hz = 9000000
    #spi0 with cs0
        _spi0 = spidev.SpiDev(), 0)
        _spi0.max_speed_hz = 9000000
    #spi0 with cs1
        _spi1 = spidev.SpiDev(), 1)
        _spi1.max_speed_hz = 9000000

        is_setup = True

    def show():
        """Output the contents of the buffer to Unicorn HAT HD."""
        #_spi.xfer2([_SOF] + (numpy.rot90(_buf,_rotation).reshape(1536) * _brightness).astype(numpy.uint8).tolist())
    #array for 16x16
        _spi.xfer2([_SOF] + (numpy.rot90(_buf,_rotation).reshape(768) * _brightness).astype(numpy.uint8).tolist())

    def showLED(matrix):
        if matrix == 0:
            _spi0.xfer2([_SOF] + (numpy.rot90(_buf,_rotation).reshape(768) * _brightness).astype(numpy.uint8).tolist())
        elif matrix == 1:
            _spi1.xfer2([_SOF] + (numpy.rot90(_buf2,_rotation).reshape(768) * _brightness).astype(numpy.uint8).tolist())


and from text

#!/usr/bin/env python

import colorsys
import signal
import time
from sys import exit

from PIL import Image, ImageDraw, ImageFont
except ImportError:
exit(“This script requires the pillow module\nInstall with: sudo pip install pillow”)

import unicornhathd

lines = [“ATTENTION!”,
“This is a text!”]

colours = [tuple([int(n * 255) for n in colorsys.hsv_to_rgb(x/float(len(lines)), 1.0, 1.0)]) for x in range(len(lines))]

FONT = ("/usr/share/fonts/truetype/roboto/Roboto-Bold.ttf", 10)


width, height = unicornhathd.get_shape()
#width = width*2
text_x = width
text_y = 3

#start_matrix1 = 0
scroll2 = -1

font_file, font_size = FONT

font = ImageFont.truetype(font_file, font_size)

text_width, text_height = width, 0

for line in lines:
w, h = font.getsize(line)
text_width += w + width
text_height = max(text_height,h)

text_width += width + text_x + 1

image =“RGB”, (text_width,max(16, text_height)), (0,0,0))
draw = ImageDraw.Draw(image)

offset_left = 16 # begin on right side

for index, line in enumerate(lines):
draw.text((text_x + offset_left, text_y), line, colours[index], font=font)

    offset_left += font.getsize(line)[0] + width

for scroll in range(text_width - width):# 228-16 -> scroll until 211 (0-211)
for x in range(width):#32
for y in range(height):
pixel = image.getpixel((x+scroll, y))
r, g, b = [int(n) for n in pixel]
unicornhathd.set_pixel(width-1-x, y, r, g, b)
if scroll >= 32:
pixel2 = image.getpixel((x+scroll2, y))
r2, g2, b2 = [int(k) for k in pixel2]
unicornhathd.set_pixel2(width-1+scroll2, y, r2, g2, b2)
#unicornhathd.set_pixel2(scroll2, y, r, g, b)

Pimoroni originals from github

Looks like you took a slightly different approach to mine, by having a separate buffer for each display. I wanted a single coherent display, so I used a single large buffer which I then use numpy to slice the relevant portions from. Otherwise your code is close in concept to mine.

I also replaced SPI with my own output method which allows each Unicorn HAT HD its own Data pin but shares the Clock across them all. This means:

  • You can have a silly number hooked up to your Pi at once
  • They are all updated simultaneously

Anyway, without further ado here’s my replacement for driving multiple Unicorn HAT HDs:

It’s not quite finished yet, since I wanted to make it a little more flexible so you could add/remove displays at will. You can just comment out the update line for the third display.

Thank you very much, your code works.

But the scrolling is very slow. Is there a way to speed it up or set the speed manually?

And is it even possible to use it with the spidev module and xfer2?
I tried to adopt your code to my code/the standard code, but unforntunately it doesn’t work.

def showLED(matrix):


    _buf1 = numpy.rot90(_buf[:16,:HEIGHT],_rotation)
    _buf2 = numpy.rot90(_buf[16:32,:HEIGHT],_rotation)

    _buf1, _buf2 = [(x.reshape(768) * _brightness).astype(numpy.uint8).tolist() for x in (_buf1, _buf2)]

    #_spi.xfer2([_SOF] + _buf1 + _buf2)

    if matrix == 0:
        for k in (_buf1):
            _spi0.xfer2([_SOF] + _buf1)
    #    _spi0.writebytes([_SOF] + (numpy.rot90(_buf[:16,:HEIGHT],_rotation).reshape(768) * _brightness).astype(numpy.uint8).tolist())
    elif matrix == 1:
        _spi1.xfer2([_SOF] + _buf2)
    #    _spi1.writebytes([_SOF] + (numpy.rot90(_buf[16:32,:HEIGHT],_rotation).reshape(768) * _brightness).astype(numpy.uint8).tolist())



It is also working now with my code and following lines in my .py file (had to reboot my raspi)

for scroll in range(text_width - width):

    for x in range(width):

        for y in range(height):

            pixel = image.getpixel((x+scroll, y))

            r, g, b = [int(n) for n in pixel]

            unicornhathd.set_pixel(width-1-x, y, r, g, b)

    if scroll >= 32:


But it is even slower, very slow :( (duration from each x position to the next is almost 1 sec)
and there are always some intereferences each line update

I can’t figure out why it is so slow and why it does show up intereferences in between

It’s slow because you’re sending the same 769 bytes 768 times which is half a megabyte of data!

You don’t need for k in (_buf1):

My fault, a remnant of my experiments.
Now it works as it should. Thank you!

I’ve only got 2 hats so I removed every mention of the 3rd one.
My code, which looks like the one from the gist works exactly as expected on python2 but fails on python3:

_buf1, _buf2 = [(x.reshape(768) * _brightness).astype(numpy.uint8).tolist() for x in _buf1, _buf2]
SyntaxError: invalid syntax

(the arrow actually points at the comma between _buf1 and _buf2)
How can I fix this? I’ve tried to split this line in two separate ones, but I don’t understand what value I need to insert.
Help would be greatly appreciated.

Probably need something like this:

_buf1, _buf2 = [(x.reshape(768) * _brightness).astype(numpy.uint8).tolist() for x in (_buf1, _buf2)]

I’m guessing Python 3.x doesn’t like [n for n in x, y, z] in list comprehension.