Unicorn pHAT HD and NeoPixel Strip


#1

Hi, I am working on a project where I want to use the Unicorn pHAT HD with a strip of Neopixels. I can get both working fine independently, but I would like to use both at the same time. In my program I will turn the neopixels on then create another thread to turn the Unicorn pHAT HD on at the same time.

However I receive the following error:

Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python2.7/threading.py", line 810, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.7/threading.py", line 763, in run
    self.__target(*self.__args, **self.__kwargs)
  File "threadTest.py", line 50, in hat
    draw_animation(img)
  File "threadTest.py", line 74, in draw_animation
    unicorn.show()
  File "/usr/local/lib/python2.7/dist-packages/unicornhathd/__init__.py", line 127, in show
    _spi.xfer2([_SOF] + (numpy.rot90(_buf,_rotation).reshape(768) * _brightness).astype(numpy.uint8).tolist())
IOError: [Errno 110] Connection timed out

Is there any technical reason why I couldn’t run both at the same time?


#2

Depends how you’re running the NeoPixels, which pins you’re connecting them two and what’s happening in your code. Could you post a schematic/description of your NeoPixel wiring and code?


#3

Thanks for the reply. Neopixels are wired as per the diagram below and the Unicorn pHAT HD wired up as per pinout diagram

My code to test this setup is below. Both Unicorn pHAT HD and Neopixels work briefly at the same time before I observe the stack trace in my last post.

import RPi.GPIO as GPIO
import unicornhathd as unicorn
import threading
import time
from neopixel import *

try:
    from PIL import Image
except ImportError:
    exit('This script requires the pillow module\nInstall with: sudo pip install pillow')


# LED strip configuration:
LED_COUNT      = 120     # Number of LED pixels.
LED_PIN        = 18      # GPIO pin connected to the pixels (must support PWM!).
LED_FREQ_HZ    = 800000  # LED signal frequency in hertz (usually 800khz)
LED_DMA        = 5       # DMA channel to use for generating signal (try 5)
LED_BRIGHTNESS = 60      # Set to 0 for darkest and 255 for brightest
LED_INVERT     = False   # True to invert the signal (when using NPN transistor

strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS)
strip.begin()


GPIO.setmode(GPIO.BCM)
PIR = 4
GPIO.setup(PIR, GPIO.IN)

# Unicorn phat configuration:
unicorn.brightness(0.6)
unicorn.rotation(0)
folder_path = 'icons/'
icon_extension = '.png'
width, height = unicorn.get_shape()
cycle_time = 0.25


def colorUp(strip, color, wait_ms=50):
        """Wipe color across display a pixel at a time."""
        for i in range(strip.numPixels()):
                strip.setPixelColor(i, color)
                strip.show()
                time.sleep(wait_ms/1000.0)


def hat():
	print "thread called"
	img = Image.open(folder_path + "rain.png")
	draw_animation(img)
	unicorn.off()


def draw_animation(image):
    # this is the original pimoroni function for drawing sprites
    try:

        for o_x in range(int(image.size[0] / width)):

            for o_y in range(int(image.size[1] / height)):

                valid = False

                for x in range(width):

                    for y in range(height):
                        pixel = image.getpixel(((o_x * width) + y, (o_y * height) + x))
                        r, g, b = int(pixel[0]), int(pixel[1]), int(pixel[2])
                        if r or g or b:
                            valid = True
                        unicorn.set_pixel(x, y, r, g, b)

                if valid:
                    unicorn.show()
                    time.sleep(cycle_time)

    except KeyboardInterrupt:
        unicorn.off()

def motionDetected(PIR):
	if GPIO.input(PIR) == 1:
    	unicorn_thread = threading.Thread(target=hat)
		unicorn_thread.start()
		colorUp(strip, Color(125,0,0))
            colorUp(strip, Color(0,0,0))

time.sleep(1)
print "Ready"

try:
           GPIO.add_event_detect(PIR, GPIO.RISING, callback=motionDetected, bouncetime=400)
	       while 1:
			time.sleep(500)
except KeyboardInterrupt:
               print " Quit"
               GPIO.cleanup()

#4

I wonder if motionDetected is being triggered twice in quick-succession and the routine to update Unicorn HAT HD is potentially blocking itself. You might want to try some kind of crude mutually-exclusive lock to verify this, or if you’re comfortable going full-beans you could use threading.Lock. See: https://docs.python.org/3/library/threading.html#threading.Lock

The goal is to prevent concurrent calls to unicorn.show(), it might be you wrap your entire draw_animation function in a blocking Lock that attempts to acquire the Lock before running, and releases it once done. This would, effectively, turn parallel calls to draw_animation into sequential ones, since the first one to be called would acquire the Lock, and the rest would have to wait for it to release.

Note: This would introduce a potential race-condition where multiple triggers of motionDetected would spawn threads that sit around waiting to get the lock, and you’d have no control over what order they actually executed in.

A simpler approach might be to continually call unicorn.show() in your main loop, and in draw_animation just handle setting the pixels. This has its own drawbacks, though, since you could call show() half way between one animation and another- crudely simulating screen tearing by having a display and image refresh out of sync.

Programming is fun :D


#5

It sure is! Thanks for the suggestions. I tried the threading.Lock but I still experience the same stack trace. I don’t think it’s down to concurrent calls to the unicorn.show() as it works without the locking if I comment out the code in colourUP and just print out a message in this method (all be it I can see the threads redrawing the screen and have no NeoPixels!).
I am wondering if there is some other issue going on. If I leave the code as shown above and comment out strip.show() from colorUP the Unicorn pHAT HD works as expected, but I obviously have no Neopixels. :-/
Would there be any shared hardware resource that they are both competing for??


#6

Oh wow, I think I had a brainfart above and totally forgot the “running NeoPixels and Unicorn HAT at the same time” part of your setup. Yes, they’re very much sharing the same resources, and in fact using the same library to run the display.

Multiple displays is tricky, but the short answer is that it’s impossible using “Adafruit_NeoPixel” since it explicitly wraps the rpi_ws281x library and only configures one channel, see: https://github.com/pimoroni/rpi_ws281x-python/blob/master/library/rpi_ws281x/rpi_ws281x.py#L51-L98

Since Unicorn HAT uses “Adafruit_NeoPixel” internally, and you’re also setting it up again, all the configuration for the second instance will clobber the first and break it in ways that I couldn’t imagine :D

If you want the two to work together, you would need to throw away the libraries for both and write your own library based upon the “_rpi_ws281x” module, which is the underlying C-module that “Adafruit_NeoPixel” provides a convenience wrapper for.

It’s possible, but a little tricky! I’d say your best bet is to swap your additional strip of NeoPixels for APA102s (which Adafruit called DotStar IIRC) and it should be easy.


#7

Oh no! That’s unfortunate, writing a new wrapper might be above me for the time being! I could probably live with displaying them both individually. Thanks for taking the time with your detailed responses!


#8

Sorry there’s no simple answer- WS2812 pixels are one of the more esoteric things people have made the Pi drive, and they’re definitely pushing the envelope a bit!