I am writing a Python class that will combine control of two different types of RGB LED "pixel" strands from a RPi. You can see all of my code here on Github. I will quote the relevant code here, but if you want to look at the everything, it's available there.
I'm running on a Raspberry Pi 2, if that matters.
When I run the tests at the end of superpixel.py
with one strand of each type in the superset, I get this error:
$ sudo python superpixel.py
Traceback (most recent call last):
File "superpixel.py", line 196, in <module>
strand.begin()
File "superpixel.py", line 63, in begin
self.show()
File "superpixel.py", line 68, in show
strand.show()
File "/home/pi/Lava-Lounge/tikinook/paleopixel.py", line 100, in show
spidev.close()
IOError: [Errno 110] Connection timed out
Walking backwards, here is superpixel.py
:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import time
import neopixel
import paleopixel
# SuperPixel
# Author: Mark Boszko
#
# Combines both NeoPixels and PaleoPixels (WS2801) into a single addressable
# SuperPixel type. Also allows merging of separate pixel strands into one
# master strand or array.
# My LED strip configurations (for test):
NEOPIXEL_COUNT = 244 # Number of NeoPixels in the strand
NEOPIXEL_PIN = 18 # GPIO pin connected to the pixels (must support PWM!)
PALEOPIXEL_COUNT = 50 # Number of PaleoPixels in the strand
#####
#
# SuperPixel - superset pixel strand class
#
#####
def Color(red, green, blue):
"""Convert the provided red, green, blue color to a 24-bit color value.
Each color component should be a value 0-255 where 0 is the lowest intensity
and 255 is the highest intensity.
"""
return ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | (blue & 0xFF)
class SuperPixel(object):
def __init__(self, *strands):
"""Class to represent a superset of both neopixel and paleopixel strands
strands - Variable argument list of sub-strands which should make up
the one SuperPixel strand. The sub-strands are added to the
super-strand in the order the arguments are listed.
"""
self._strands = strands
pixel_count = 0
for strand in self._strands:
pixel_count = pixel_count + strand.numPixels()
# Create an array for all of the LED color data
self._led_data = [0] * pixel_count
def __del__(self):
# Clean up memory used by the library when not needed anymore.
if self._led_data is not None:
self._led_data = None
if self._strands is not None:
self._strands = None
def begin(self):
"""Initialize _led_data to zeroes and set up NeoPixels
"""
for strand in self._strands:
strand.begin()
self.show()
def show(self):
"""Update the display with the data from the LED buffer."""
for strand in self._strands:
strand.show()
def setPixelColor(self, n, color):
"""Set LED at position n to the provided 24-bit color value (in RGB order).
"""
if (n >= len(self._led_data)):
return # out of bounds; throw it away
# SuperPixel internal representation:
self._led_data[n] = color
# Now also set it in the sub-strand
pixel_offset = 0
pixel_max = 0
for strand in self._strands:
# TODO: Determine which strand this pixel is a part of, and set it.
pixel_max = pixel_offset + strand.numPixels()
if (pixel_offset <= n) and (n < pixel_max):
pixel = n - pixel_offset
strand.setPixelColor(pixel, color)
break
else: # Must be in the next one
pixel_offset = pixel_offset + strand.numPixels()
def setPixelColorRGB(self, n, red, green, blue):
"""Set LED at position n to the provided red, green, and blue color.
Each color component should be a value from 0 to 255 (where 0 is the
lowest intensity and 255 is the highest intensity).
"""
self.setPixelColor(n, Color(red, green, blue))
def getPixels(self):
"""Return an object which allows access to the LED display data as if
it were a sequence of 24-bit RGB values.
"""
return self._led_data
def numPixels(self):
"""Return the number of pixels in the display."""
return len(self._led_data)
def getPixelColor(self, n):
"""Get the 24-bit RGB color value for the LED at position n."""
return self._led_data[n]
#####
#
# Test functions which animate LEDs in various ways.
#
#####
def colorWipe(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 theaterChase(strip, color, wait_ms=50, iterations=10):
"""Movie theater marquee style chaser animation."""
for j in range(iterations):
for q in range(3):
for i in range(0, strip.numPixels(), 3):
strip.setPixelColor(i+q, color)
strip.show()
time.sleep(wait_ms/1000.0)
for i in range(0, strip.numPixels(), 3):
strip.setPixelColor(i+q, 0)
def wheel(pos):
"""Generate rainbow colors across 0-255 positions."""
if pos < 85:
return Color(pos * 3, 255 - pos * 3, 0)
elif pos < 170:
pos -= 85
return Color(255 - pos * 3, 0, pos * 3)
else:
pos -= 170
return Color(0, pos * 3, 255 - pos * 3)
def rainbow(strip, wait_ms=20, iterations=1):
"""Draw rainbow that fades across all pixels at once."""
for j in range(256*iterations):
for i in range(strip.numPixels()):
strip.setPixelColor(i, wheel((i+j) & 255))
strip.show()
time.sleep(wait_ms/1000.0)
def rainbowCycle(strip, wait_ms=20, iterations=5):
"""Draw rainbow that uniformly distributes itself across all pixels."""
for j in range(256*iterations):
for i in range(strip.numPixels()):
strip.setPixelColor(i, wheel(((i * 256 / strip.numPixels()) + j) & 255))
strip.show()
time.sleep(wait_ms/1000.0)
def theaterChaseRainbow(strip, wait_ms=50):
"""Rainbow movie theater marquee style chaser animation."""
for j in range(256):
for q in range(3):
for i in range(0, strip.numPixels(), 3):
strip.setPixelColor(i+q, wheel((i+j) % 255))
strip.show()
time.sleep(wait_ms/1000.0)
for i in range(0, strip.numPixels(), 3):
strip.setPixelColor(i+q, 0)
#####
#
# Let's test it out!
#
#####
# Main program logic follows:
if __name__ == '__main__':
# Create pixel strands with appropriate configuration.
strand1 = neopixel.Adafruit_NeoPixel(NEOPIXEL_COUNT, NEOPIXEL_PIN)
strand2 = paleopixel.PaleoPixel(PALEOPIXEL_COUNT)
# Combine them into one SuperPixel strand
strand = SuperPixel(strand1, strand2)
# Intialize the SuperPixel strand (must be called once, before other
# functions, if the SuperPixel strand contains any NeoPixels)
strand.begin()
print('Press Ctrl-C to quit.')
while True:
# Color wipe animations.
colorWipe(strand, Color(255, 0, 0)) # Red wipe
colorWipe(strand, Color(0, 255, 0)) # Blue wipe
colorWipe(strand, Color(0, 0, 255)) # Green wipe
# Theater chase animations.
theaterChase(strand, Color(127, 127, 127)) # White theater chase
theaterChase(strand, Color(127, 0, 0)) # Red theater chase
theaterChase(strand, Color( 0, 0, 127)) # Blue theater chase
# Rainbow animations.
rainbow(strand)
rainbowCycle(strand)
theaterChaseRainbow(strand)
And here is the first half of paleopixel.py
(excluding tests):
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
Adafruit NeoPixel library port to control the older WS2801 pixels from
Raspberry Pi hardware SPI. Includes Adafruit “strandtest”-style functions
and performs a self-test if run as main.
Author: Mark Boszko
Raspberry Pi SPI driver code for WS2801 pixels based on Adafruit_LEDpixels.py
https://github.com/adafruit/Adafruit-Raspberry-Pi-Python-Code
Python port of NeoPixel library based on the rpi_ws281x library port,
by Tony DiCola and Jeremy Garff
https://github.com/jgarff/rpi_ws281x
Usage:
You'll need to translate 3.3V SPI logic levels from the Raspberry Pi to 5V.
Several options are possible, and I have a suggested circuit on my blog post
that describes this project in more detail:
http://stationinthemetro.com/2016/02/27/tiki-nook-build-part-2-raspberry-pi-led-control
- Connect the RPi's MOSI output to the SDI (Serial Data Input) on the WS2801s
- Connect the RPi SCLK clock output to CKI (ClocK Input) on the WS2801
- The RPi's 5V pins will probably not be enough to power a string of
any significant length. Use an external power supply of appropriate amperage
- Be sure to connect the RPi ground to the LED strip's ground
License:
Licensed under The MIT License (MIT). Please see LICENSE.txt for full text
of the license.
Version History:
- 1.0.1 - 2016-02-29 - Changed license from CC-BY-4.0 to MIT, due to
recommendation by Creative Commons not to apply their
licenses to software. See CC's FAQ for details:
https://creativecommons.org/faq/#can-i-apply-a-creative-commons-license-to-software
- 1.0 - 2016-02-27 - Started development and complete rewrite, all in the
same day!
"""
import RPi.GPIO as GPIO, time, os
# LED strip configuration:
LED_COUNT = 50 # Number of LED pixels.
#####
#
# PaleoPixel - NeoPixel library port for WS2801 control over RPi hardware SPI
#
#####
def Color(red, green, blue):
"""Convert the provided red, green, blue color to a 24-bit color value.
Each color component should be a value 0-255 where 0 is the lowest intensity
and 255 is the highest intensity.
"""
return ((red & 0xFF) << 16) | ((green & 0xFF) << 8) | (blue & 0xFF)
class PaleoPixel(object):
def __init__(self, num):
"""Class to represent a WS2801 LED display.
num - number of pixels in the display.
"""
# Create an array for the LED data
self._led_data = [0] * num
def __del__(self):
# Clean up memory used by the library when not needed anymore.
if self._led_data is not None:
self._led_data = None
def begin(self):
"""Initialize _led_data to zeroes.
Not necessary, since we do this in __init__, but handy.
"""
for i in range(len(self._led_data)):
self._led_data[i] = 0
self.show()
def show(self):
"""Update the display with the data from the LED buffer."""
spidev = file("/dev/spidev0.0", "w")
for i in range(len(self._led_data)):
spidev.write(chr((self._led_data[i]>>16) & 0xFF))
spidev.write(chr((self._led_data[i]>>8) & 0xFF))
spidev.write(chr(self._led_data[i] & 0xFF))
spidev.close()
time.sleep(0.002)
def setPixelColor(self, n, color):
"""Set LED at position n to the provided 24-bit color value (in RGB order).
"""
if (n >= len(self._led_data)):
return
self._led_data[n] = color
def setPixelColorRGB(self, n, red, green, blue):
"""Set LED at position n to the provided red, green, and blue color.
Each color component should be a value from 0 to 255 (where 0 is the
lowest intensity and 255 is the highest intensity).
"""
self.setPixelColor(n, Color(red, green, blue))
def getPixels(self):
"""Return an object which allows access to the LED display data as if
it were a sequence of 24-bit RGB values.
"""
return self._led_data
def numPixels(self):
"""Return the number of pixels in the display."""
return len(self._led_data)
def getPixelColor(self, n):
"""Get the 24-bit RGB color value for the LED at position n."""
return self._led_data[n]
I should note that paleopixel.py
tests fine on its own, and if I test superpixel.py
with one strand (of either type), it works fine. In fact, if I test with two strands of the same type, it also works fine. It's only with one strand of each type that it fails with IOError: [Errno 110] Connection timed out
.
Is it possible to extend the timeout length for the spidev
write? I admit I am not super familiar with SPI, but I tried to dig into the docs, and couldn't find anything. Or would there be a faster way to write out the bits, so that it doesn't time out? (Just making guesses at a solution.)