Pico Unicorn Pack not working in Micropython

Hi,
I recently bought 3 Picos with Unicorn Packs to go with them - because sparkly lights is what I think my kids need to get them to do some Python.

I cannot get them to work.
I’ve successfully flashed the Picos with your latest MicroPython firmware (currently 0.0.5 alpha)
following pimoroni-pico/setting-up-micropython.md at main · pimoroni/pimoroni-pico · GitHub.

Using Thonny I can connect to them and run scripts or use the REPL command prompt.
The Picos are alive and responding.

BUT:

  1. There are no examples of use of the Unicorn pack in your micropython example folder (pimoroni-pico/micropython/examples at main · pimoroni/pimoroni-pico · GitHub)
  2. The following code works:
    import picounicorn as uni
    w = uni.get_width()
    h = uni.get_height()
    print(‘Unicorn size’,w,‘by’,h)
    displaying 'Unicorn size 16 x 7
  3. The following doesn’t:
    print('uni.is_pressed(uni.BUTTON_A)') # Prints True whether button is pressed or not.
    uni.clear() # Causes some LEDs to briefly flash.
    uni.clear() # Second call causes all LEDs except first four columns to light up cyan - then freezes the REPL.

Does the Unicorn Pack actually work under Micropython yet?

Documentation and examples for the Pico stuff seem to be in draft/not done yet unfortunately.

Have you updated to the latest v0.0.5 Alpha firmware? I think there was a note of some sort of bug in the older versions of the Uniforn firmware, that might help with the odd LED issues.

Ok, some progress at last.
Not very exciting, but this at least does work:
import picounicorn as uni

uni.init() # This must be called EXACTLY ONCE

w = uni.get_width()
h = uni.get_height()
print('Unicorn size',w,'by',h)

def fill_rect(minx, miny, maxx, maxy, r, g, b):
    for x in range(minx, maxx):
        for y in range(miny, maxy):
            uni.set_pixel(x,y, r, g, b)

while True:
    for r in range(16):
        for g in range(16):
            for b in range(16):
                fill_rect(0,0, w,h, r*16,g*16,b*16)

Do not use uni.clear() - it hangs.
is_pressed(…) works once init() called.
Calling init() multiple times tends to cause a crash - this is a problem when rerunning a script during dev.

Anyway, signs of life at last!

Yes, I’m using the 0.0.5 release.

Spent the evening fiddling with this - successfully got a Conway’s Life implementation running!
If anyone wants to try it, here you go. Happy to donate this to the examples.

import picounicorn as uni
import random
import time

uni.init()

w = uni.get_width()
h = uni.get_height()

class Cells:
    def __init__(self):
        self.cells = [[0]*h for i in range(w)]

    def clear_all(self):
        for x in range(w):
            for y in range(h):
                self.cells[x][y] = 0

    def set_random_cells_to_value(self, prob, value):
        for x in range(w):
            for y in range(h):
                if random.random() < prob:
                    self.cells[x][y] = value

    def is_alive(self,x,y):
        x = x % w
        y = y % h
        return self.cells[x][y] == 255

    def get_num_live_neighbours(self, x, y):
        num = 0
        num += (1 if self.is_alive(x-1,y-1) else 0)
        num += (1 if self.is_alive(x  ,y-1) else 0)
        num += (1 if self.is_alive(x+1,y-1) else 0)
        num += (1 if self.is_alive(x-1,y) else 0)
        num += (1 if self.is_alive(x+1,y) else 0)
        num += (1 if self.is_alive(x-1,y+1) else 0)
        num += (1 if self.is_alive(x  ,y+1) else 0)
        num += (1 if self.is_alive(x+1,y+1) else 0)
        return num
        
    def iterate_from(self, fromCells):
        for x in range(w):
            for y in range(h):
                num_live_nbrs = fromCells.get_num_live_neighbours(x,y)
                is_alive = fromCells.is_alive(x,y)
                if is_alive and (num_live_nbrs < 2 or num_live_nbrs > 3):
                    self.cells[x][y] = 0 # Died
                elif not is_alive and num_live_nbrs == 3:
                    self.cells[x][y] = 255 # Born
                else:
                    self.cells[x][y] = fromCells.cells[x][y] # Unchanged state

def ExportToLeds(cells):
    for x in range(w):
        for y in range(h):
            value = cells[x][y]
            uni.set_pixel_value(x,y,value)


cellsA = Cells()
cellsB = Cells()
start = True

while True:
    if start:
        cellsA.clear_all()
        cellsA.set_random_cells_to_value(0.2, 255)
        start = False
    ExportToLeds(cellsA.cells)
    time.sleep_ms(200)
    cellsB.iterate_from(cellsA)
    (cellsA, cellsB) = (cellsB, cellsA)
    start = uni.is_pressed(uni.BUTTON_A)

It is a shame about the low level of documentation at the moment. We just have to post our findings when we work something out.

Thank you for this code. I picked up a great deal from reading it.

I bought an Explorer and it was very easy to port it to the Explorer screen with 48 x 48 cells displayed.

I’ve put the Explorer version below to help those users. I hope this is OK with you.

It will be very easy to port to Display as well.

# Conway's Game of Life
# Converted by Tony Goodhew from Steve Baine's post for Pico Unicorn Pack
# https://forums.pimoroni.com/t/pico-unicorn-pack-not-working-in-micropython/15997/4
# 13 Feb 2021 Tested with Pimoroni UF2 Version 0.0.7
import picoexplorer as display
import time, random
width = display.get_width()
height = display.get_height()
display_buffer = bytearray(width * height * 2)
display.init(display_buffer)

w = 48 # Horizontal cell
h = 48 # Vertical cells
class Cells:
    def __init__(self):
        self.cells = [[0]*h for i in range(w)]

    def clear_all(self):
        for x in range(w):
            for y in range(h):
                self.cells[x][y] = 0

    def set_random_cells_to_value(self, prob, value):
        for x in range(w):
            for y in range(h):
                if random.random() < prob:
                    self.cells[x][y] = value

    def is_alive(self,x,y):
        x = x % w
        y = y % h
        return self.cells[x][y] == 255

    def get_num_live_neighbours(self, x, y):
        num = 0
        num += (1 if self.is_alive(x-1,y-1) else 0)
        num += (1 if self.is_alive(x  ,y-1) else 0)
        num += (1 if self.is_alive(x+1,y-1) else 0)
        num += (1 if self.is_alive(x-1,y) else 0)
        num += (1 if self.is_alive(x+1,y) else 0)
        num += (1 if self.is_alive(x-1,y+1) else 0)
        num += (1 if self.is_alive(x  ,y+1) else 0)
        num += (1 if self.is_alive(x+1,y+1) else 0)
        return num
        
    def iterate_from(self, fromCells):
        for x in range(w):
            for y in range(h):
                num_live_nbrs = fromCells.get_num_live_neighbours(x,y)
                is_alive = fromCells.is_alive(x,y)
                if is_alive and (num_live_nbrs < 2 or num_live_nbrs > 3):
                    self.cells[x][y] = 0 # Died
                elif not is_alive and num_live_nbrs == 3:
                    self.cells[x][y] = 255 # Born
                else:
                    self.cells[x][y] = fromCells.cells[x][y] # Unchanged state

def ExportToScreen(cells):
    for x in range(w):
        for y in range(h):
            value = cells[x][y] 
            display.set_pen(value,value,0)
            xx = x * 5
            yy = y * 5
            for xr in range(5):
                for yr in range(5):
                    display.pixel(xx+xr,yy+yr)
    display.update()
    
def blk():
    display.set_pen(0,0,0)
    display.clear()
    display.update()
    
def title(msg,r,g,b):
    blk()
    display.set_pen(r,g,b)
    display.text(msg, 20, 70, 200, 4)
    display.update()
    time.sleep(2)
   
title("Conway's game of life",0,0,255)
display.set_pen(120,120,120)
display.text(" Hold button Y to halt", 5, 190, 230, 2)
display.text("  Button A to re-seed", 5, 210, 230, 2)
display.update()
time.sleep(3)
cellsA = Cells()
cellsB = Cells()
start = True
display.set_pen(0,0,0)
display.update()
running = True
while running:
    if start:
        cellsA.clear_all()
        cellsA.set_random_cells_to_value(0.2, 255)
        blk()
        display.set_pen(200,200,200)
        display.text("New Colony", 40, 120, 230, 3)
        time.sleep(0.5)
        display.update()
        start = False
    ExportToScreen(cellsA.cells)

    cellsB.iterate_from(cellsA)
    (cellsA, cellsB) = (cellsB, cellsA)
    start = display.is_pressed(display.BUTTON_A)
    if display.is_pressed(3): # Y button is pressed ?
        running = False
blk()
print("Finished")

Video added here:
Conway’s Game of Life - Demonstration With Code - Instructables

For those of you with a Pico Display here is a working version - tested with Version 0.0.7
Thank you Steve.

# Conway's Game of Life
# Converted by Tony Goodhew from Steve Baine's post for Pico Unicorn Pack
# https://forums.pimoroni.com/t/pico-unicorn-pack-not-working-in-micropython/15997/4
# 14 Feb 2021 Tested with 0.0.7
import picodisplay as display
import time, random
width = display.get_width()
height = display.get_height()
display_buffer = bytearray(width * height * 2)
display.init(display_buffer)
# Set the backlight to 50%
display.set_backlight(0.5)

w = 60 # Horizontal cell
h = 33 # Vertical cells
class Cells:
    def __init__(self):
        self.cells = [[0]*h for i in range(w)]

    def clear_all(self):
        for x in range(w):
            for y in range(h):
                self.cells[x][y] = 0

    def set_random_cells_to_value(self, prob, value):
        for x in range(w):
            for y in range(h):
                if random.random() < prob:
                    self.cells[x][y] = value

    def is_alive(self,x,y):
        x = x % w
        y = y % h
        return self.cells[x][y] == 255

    def get_num_live_neighbours(self, x, y):
        num = 0
        num += (1 if self.is_alive(x-1,y-1) else 0)
        num += (1 if self.is_alive(x  ,y-1) else 0)
        num += (1 if self.is_alive(x+1,y-1) else 0)
        num += (1 if self.is_alive(x-1,y) else 0)
        num += (1 if self.is_alive(x+1,y) else 0)
        num += (1 if self.is_alive(x-1,y+1) else 0)
        num += (1 if self.is_alive(x  ,y+1) else 0)
        num += (1 if self.is_alive(x+1,y+1) else 0)
        return num
        
    def iterate_from(self, fromCells):
        for x in range(w):
            for y in range(h):
                num_live_nbrs = fromCells.get_num_live_neighbours(x,y)
                is_alive = fromCells.is_alive(x,y)
                if is_alive and (num_live_nbrs < 2 or num_live_nbrs > 3):
                    self.cells[x][y] = 0 # Died
                elif not is_alive and num_live_nbrs == 3:
                    self.cells[x][y] = 255 # Born
                else:
                    self.cells[x][y] = fromCells.cells[x][y] # Unchanged state

def ExportToScreen(cells):
    for x in range(w):
        for y in range(h):
            value = cells[x][y] 
            display.set_pen(value,value,0)
            xx = x * 4
            yy = y * 4
            for xr in range(4):
                for yr in range(4):
                    display.pixel(xx+xr,yy+yr +1)
    display.update()
    
def blk():
    display.set_pen(0,0,0)
    display.clear()
    display.update()
    
def title(msg,r,g,b):
    blk()
    display.set_pen(r,g,b)
    display.text(msg, 5, 5, 200, 4)
    display.update()
    time.sleep(2)
   
title("Conway's game of life",0,0,255)
display.set_pen(120,120,120)
display.text(" Hold button Y to halt", 5, 100, 230, 2)
display.text("  Button A to re-seed", 5, 120, 230, 2)
display.update()
time.sleep(3)
cellsA = Cells()
cellsB = Cells()
start = True
display.set_pen(0,0,0)
display.update()
running = True
while running:
    if start:
        cellsA.clear_all()
        cellsA.set_random_cells_to_value(0.2, 255)
        blk()
        display.set_pen(200,200,200)
        display.text("New Colony", 40, 60, 230, 3)
        time.sleep(0.5)
        display.update()
        start = False
    ExportToScreen(cellsA.cells)

    cellsB.iterate_from(cellsA)
    (cellsA, cellsB) = (cellsB, cellsA)
    start = display.is_pressed(display.BUTTON_A)
    if display.is_pressed(3): # Y button is pressed ?
        running = False
blk()
display.set_pen(200,0,0)
display.text("All Done!", 55, 40, 200, 3)
display.update()
time.sleep(2)
blk()

I recently bought various Pimoroni Pico packs, like the ‘Pico scroll pack’ and the ‘Pico unicorn pack’. During experiments with python scripts, I discovered that for the scroll pack it is needed to issue a ‘scroll.update()’ command. I also discovered that this command is not needed for a Pico unicorn pack. I state this with the restriction that I am using both type of packs to display ‘moving texts’ (also called “marquee’s”).

Using the Pimoroni Pico firmware version 0.0.7 Alpha, using the Pico unicorn pack, I experienced a - for me - strange behaviour of the ‘picounicorn.clear()’. It did not clear the display to black but to a display full of colored pixels with high brilliance intensity.
Beside this I also experienced that my RPi Pico not anymore seem to respond to the commands I issued from within the Thonny IDE (e.g.: Ctrl+C or Ctrl+D). The only way to get out of this appearantly ‘bricked’ (or constantly ‘busy’ Pico) was to flash it with the standard firmware for the Raspberry Pi Pico (not the one from Pimoroni – for the moment – that contains the modules for the various Pico packs). After flashing the standard firmware I changed the script. I took out the line ‘picounicorn.clear()’. Then I re-flashed the Pimoroni firmware that support the Pimoroni Pico packs.
Then I wrote (and tested) a function ‘clr_scrn()’ that writes ‘black’ color to all pixel led’s.
Below the important parts of my script regarding the handling of the clear screen problem:

It could be that I did something wrong in the first place. In that case I would like to hear how I had to do better.

Don’t forget the flash_nuke.UF2 to properly clear your PICO. Very quick and useful.

Glad you found the code helpful!

@Tonygo2 Thanx (again) for you contribution… This time about Conway’s Game of Life. (Pico Display)
But (and I really hate the word ‘but’) I get this error (also a word I hate…)
'Traceback (most recent call last):
File “stdin>”, line 100, in (stdin by hand added)
File “stdin>”, line 75, in blk
NameError: name ‘display’ isn’t defined
Where should I look for a solution?
Cheers, Bo

I’ve been getting similar errors in another program.

I’ve changes over to saving the script to the PICO and calling it main.py. It’s not quite so convenient but the resetting the PICO by re-powering appears to improve matters.

Let me know if this helps.

WoW, fast answer! I’ll do that and let you know…
Well I did, without any success…
I have this version 0.0.7 version.
cheers

Just ran this with Version 8 installed from Thonny via the green run button. It worked perfectly.

# Conway's Game of Life for Pico Display
# Converted by Tony Goodhew from Steve Baine's post for Pico Unicorn Pack
# https://forums.pimoroni.com/t/pico-unicorn-pack-not-working-in-micropython/15997/4
# 3rd March Tested with 0.0.8
import picodisplay as display
import time, random
width = display.get_width()
height = display.get_height()
display_buffer = bytearray(width * height * 2)
display.init(display_buffer)
# Set the backlight to 50%
display.set_backlight(0.5)
print("Start")
w = 48 # Horizontal cell
h = 24 # Vertical cells

class Cells:
    def __init__(self):
        self.cells = [[0]*h for i in range(w)]

    def clear_all(self):
        for x in range(w):
            for y in range(h):
                self.cells[x][y] = 0

    def set_random_cells_to_value(self, prob, value):
        for x in range(w):
            for y in range(h):
                if random.random() < prob:
                    self.cells[x][y] = value

    def is_alive(self,x,y):
        x = x % w
        y = y % h
        return self.cells[x][y] == 255

    def get_num_live_neighbours(self, x, y):
        num = 0
        num += (1 if self.is_alive(x-1,y-1) else 0)
        num += (1 if self.is_alive(x  ,y-1) else 0)
        num += (1 if self.is_alive(x+1,y-1) else 0)
        num += (1 if self.is_alive(x-1,y) else 0)
        num += (1 if self.is_alive(x+1,y) else 0)
        num += (1 if self.is_alive(x-1,y+1) else 0)
        num += (1 if self.is_alive(x  ,y+1) else 0)
        num += (1 if self.is_alive(x+1,y+1) else 0)
        return num
        
    def iterate_from(self, fromCells):
        for x in range(w):
            for y in range(h):
                num_live_nbrs = fromCells.get_num_live_neighbours(x,y)
                is_alive = fromCells.is_alive(x,y)
                if is_alive and (num_live_nbrs < 2 or num_live_nbrs > 3):
                    self.cells[x][y] = 0 # Died
                elif not is_alive and num_live_nbrs == 3:
                    self.cells[x][y] = 255 # Born
                else:
                    self.cells[x][y] = fromCells.cells[x][y] # Unchanged state

def ExportToScreen(cells):
    for x in range(w):
        for y in range(h):
            value = cells[x][y] 
            display.set_pen(value,value,0)
            xx = x * 5
            yy = y * 5
            for xr in range(5):
                for yr in range(5):
                    display.pixel(xx+xr,yy+yr)
    display.update()
    
def blk():
    display.set_pen(20,10,10)
    display.clear()
    display.update()

def title(msg,r,g,b):
    display.set_pen(20,10,10)
    display.clear()
    display.update()
    display.set_pen(r,g,b)
    display.text(msg, 5, 5, 200, 4)
    display.update()
    time.sleep(2)

display.set_pen(20,10,10)
display.clear()
display.update()
title("Conway's game of life",0,0,255)
display.set_pen(120,120,120)
display.text(" Hold button Y to halt", 5, 100, 230, 2)
display.text("  Button A to re-seed", 5, 120, 230, 2)
display.update()
time.sleep(3)

cellsA = Cells()
cellsB = Cells()
start = True
display.set_pen(0,0,0)
display.update()
running = True
while running:
    if start:
        cellsA.clear_all()
        cellsA.set_random_cells_to_value(0.2, 255)
        display.set_pen(20,10,10)
        display.clear()
        display.update()
        display.set_pen(200,200,200)
        display.text("New Colony", 40, 60, 230, 3)
        time.sleep(0.5)
        display.update()
        start = False
    ExportToScreen(cellsA.cells)

    cellsB.iterate_from(cellsA)
    (cellsA, cellsB) = (cellsB, cellsA)
    start = display.is_pressed(display.BUTTON_A)
    if display.is_pressed(3): # Y button is pressed ?
        running = False
    display.set_pen(20,10,10)
    display.clear()
    display.update()
print("Finished")

I’ve no idea where these “stdin” errors have come from. I’ve only noticed them in the last week or so but they are really annoying.

After removing the unicorn pack, after reboot, your Conway works…
Thanx.

Is there a clash between the pins of the Display and the Unicorn?

I’m still investigating… sure is that all is very sluggish: I have to reset often. Your advice in saving as main.py works most of the time. When I do so, and have the ‘original.py’ file (and filename) next to this main.py open in microPython, although they are indentical, one runs, the other not… I hope you understand this somehow cryptic explanation…
Maybe I go back to my den, and resume my hibernation, and see all problems solved by Pimoroni in a few month… I have DHT22, BMP180, BME280, with a 16x2 (also 20x4) LCD working, over I2C. Also the built in RTC, with a simple ‘set-time’ command per Bash. The BME280 worked out-of-the-box…
The 0.0.8 release offers not much relief, but this is maybe a premature conclusion…

When you save as main.py are you saving it to the PICO? In Thonny when you do a “save as” you’ll get two options, to the local computer or to the PICO. Thats not the exact wording but its something like that. If you save it to the PICO, its stored on the PICO and run from the PICO automatically when it boots up. No need to connect via Thonny to run it.

I’m afraid there is… After removing the unicorn, your Display-Conway lives again…

I save to the Pico, as main.py. After reset, it doesn’t work IF the unicorn is plugged in…

Ok, just wanted to confirm how you were doing it.