diff --git a/README.md b/README.md index e603bef..0d2d7fa 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@ # RPi WS281x Python -This is an unofficial Python distribution of the ws281x library: http://github.com/richardghirst/rpi_ws281x +This is the official Python distribution of the ws281x library: http://github.com/richardghirst/rpi_ws281x -It's intended to support the Unicorn HAT library, but can be used for driving other WS281x-based LED projects. +# Installing -### Based Upon rpi_ws281x +## From pip -`unicornhat` is based upon a modified, Pi 2/3 compatible version of the RPi ws281x Library by Jeremy Garff. +Most users should simply run: -The library was modified by Richard Hirst. - -* Modified version: https://github.com/richardghirst/rpi_ws281x -* Original: https://github.com/jgarff/rpi_ws281x +``` +sudo pip install rpi_ws281x +``` diff --git a/examples/SK6812_lowlevel.py b/examples/SK6812_lowlevel.py new file mode 100644 index 0000000..67ff899 --- /dev/null +++ b/examples/SK6812_lowlevel.py @@ -0,0 +1,104 @@ +# Example of low-level Python wrapper for rpi_ws281x library. +# Author: Tony DiCola (tony@tonydicola.com), Jeremy Garff (jer@jers.net) +# +# This is an example of how to use the SWIG-generated _rpi_ws281x module. +# You probably don't want to use this unless you are building your own library, +# because the SWIG generated module is clunky and verbose. Instead look at the +# high level Python port of Adafruit's NeoPixel Arduino library in strandtest.py. +# +# This code will animate a number of WS281x LEDs displaying rainbow colors. +import time + +import _rpi_ws281x as ws + +# LED configuration. +LED_CHANNEL = 0 +LED_COUNT = 16 # How many LEDs to light. +LED_FREQ_HZ = 800000 # Frequency of the LED signal. Should be 800khz or 400khz. +LED_DMA_NUM = 10 # DMA channel to use, can be 0-14. +LED_GPIO = 18 # GPIO connected to the LED signal line. Must support PWM! +LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest +LED_INVERT = 0 # Set to 1 to invert the LED signal, good if using NPN + # transistor as a 3.3V->5V level converter. Keep at 0 + # for a normal/non-inverted signal. +#LED_STRIP = ws.WS2811_STRIP_RGB +#LED_STRIP = ws.WS2811_STRIP_GBR +#LED_STRIP = ws.SK6812_STRIP_RGBW +LED_STRIP = ws.SK6812W_STRIP + + +# Define colors which will be used by the example. Each color is an unsigned +# 32-bit value where the lower 24 bits define the red, green, blue data (each +# being 8 bits long). +DOT_COLORS = [ 0x200000, # red + 0x201000, # orange + 0x202000, # yellow + 0x002000, # green + 0x002020, # lightblue + 0x000020, # blue + 0x100010, # purple + 0x200010 ] # pink + + +# Create a ws2811_t structure from the LED configuration. +# Note that this structure will be created on the heap so you need to be careful +# that you delete its memory by calling delete_ws2811_t when it's not needed. +leds = ws.new_ws2811_t() + +# Initialize all channels to off +for channum in range(2): + channel = ws.ws2811_channel_get(leds, channum) + ws.ws2811_channel_t_count_set(channel, 0) + ws.ws2811_channel_t_gpionum_set(channel, 0) + ws.ws2811_channel_t_invert_set(channel, 0) + ws.ws2811_channel_t_brightness_set(channel, 0) + +channel = ws.ws2811_channel_get(leds, LED_CHANNEL) + +ws.ws2811_channel_t_count_set(channel, LED_COUNT) +ws.ws2811_channel_t_gpionum_set(channel, LED_GPIO) +ws.ws2811_channel_t_invert_set(channel, LED_INVERT) +ws.ws2811_channel_t_brightness_set(channel, LED_BRIGHTNESS) +ws.ws2811_channel_t_strip_type_set(channel, LED_STRIP) + +ws.ws2811_t_freq_set(leds, LED_FREQ_HZ) +ws.ws2811_t_dmanum_set(leds, LED_DMA_NUM) + +# Initialize library with LED configuration. +resp = ws.ws2811_init(leds) +if resp != ws.WS2811_SUCCESS: + message = ws.ws2811_get_return_t_str(resp) + raise RuntimeError('ws2811_init failed with code {0} ({1})'.format(resp, message)) + +# Wrap following code in a try/finally to ensure cleanup functions are called +# after library is initialized. +try: + offset = 0 + while True: + # Update each LED color in the buffer. + for i in range(LED_COUNT): + # Pick a color based on LED position and an offset for animation. + color = DOT_COLORS[(i + offset) % len(DOT_COLORS)] + + # Set the LED color buffer value. + ws.ws2811_led_set(channel, i, color) + + # Send the LED color data to the hardware. + resp = ws.ws2811_render(leds) + if resp != ws.WS2811_SUCCESS: + message = ws.ws2811_get_return_t_str(resp) + raise RuntimeError('ws2811_render failed with code {0} ({1})'.format(resp, message)) + + # Delay for a small period of time. + time.sleep(0.25) + + # Increase offset to animate colors moving. Will eventually overflow, which + # is fine. + offset += 1 + +finally: + # Ensure ws2811_fini is called before the program quits. + ws.ws2811_fini(leds) + # Example of calling delete function to clean up structure memory. Isn't + # strictly necessary at the end of the program execution here, but is good practice. + ws.delete_ws2811_t(leds) diff --git a/examples/SK6812_strandtest.py b/examples/SK6812_strandtest.py new file mode 100644 index 0000000..3070f09 --- /dev/null +++ b/examples/SK6812_strandtest.py @@ -0,0 +1,107 @@ +# NeoPixel library strandtest example +# Author: Tony DiCola (tony@tonydicola.com) +# +# Direct port of the Arduino NeoPixel library strandtest example. Showcases +# various animations on a strip of NeoPixels. +import time + +from neopixel import * + + +# LED strip configuration: +LED_COUNT = 40 # 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 = 10 # DMA channel to use for generating signal (try 10) +LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest +LED_INVERT = False # True to invert the signal (when using NPN transistor level shift) +LED_CHANNEL = 0 +LED_STRIP = ws.SK6812_STRIP_RGBW +#LED_STRIP = ws.SK6812W_STRIP + + +# Define 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 light 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 light 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) + + +# Main program logic follows: +if __name__ == '__main__': + # Create NeoPixel object with appropriate configuration. + strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL, LED_STRIP) + # Intialize the library (must be called once before other functions). + strip.begin() + + print ('Press Ctrl-C to quit.') + while True: + # Color wipe animations. + colorWipe(strip, Color(255, 0, 0)) # Red wipe + colorWipe(strip, Color(0, 255, 0)) # Blue wipe + colorWipe(strip, Color(0, 0, 255)) # Green wipe + colorWipe(strip, Color(0, 0, 0, 255)) # White wipe + colorWipe(strip, Color(255, 255, 255)) # Composite White wipe + colorWipe(strip, Color(255, 255, 255, 255)) # Composite White + White LED wipe + # Theater chase animations. + theaterChase(strip, Color(127, 0, 0)) # Red theater chase + theaterChase(strip, Color(0, 127, 0)) # Green theater chase + theaterChase(strip, Color(0, 0, 127)) # Blue theater chase + theaterChase(strip, Color(0, 0, 0, 127)) # White theater chase + theaterChase(strip, Color(127, 127, 127, 0)) # Composite White theater chase + theaterChase(strip, Color(127, 127, 127, 127)) # Composite White + White theater chase + # Rainbow animations. + rainbow(strip) + rainbowCycle(strip) + theaterChaseRainbow(strip) diff --git a/examples/SK6812_white_test.py b/examples/SK6812_white_test.py new file mode 100644 index 0000000..ba0815a --- /dev/null +++ b/examples/SK6812_white_test.py @@ -0,0 +1,52 @@ +# NeoPixel library strandtest example +# Author: Tony DiCola (tony@tonydicola.com) +# +# Direct port of the Arduino NeoPixel library strandtest example. Showcases +# various animations on a strip of NeoPixels. +import time + +from neopixel import * + +# LED strip configuration: +LED_COUNT = 30 # 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 = 10 # DMA channel to use for generating signal (try 10) +LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest +LED_INVERT = False # True to invert the signal (when using NPN transistor level shift) +LED_CHANNEL = 0 +#LED_STRIP = ws.SK6812_STRIP_RGBW +LED_STRIP = ws.SK6812W_STRIP + + +# Define 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) + + +# Main program logic follows: +if __name__ == '__main__': + # Create NeoPixel object with appropriate configuration. + strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL, LED_STRIP) + # Intialize the library (must be called once before other functions). + strip.begin() + + print ('Press Ctrl-C to quit.') + while True: + # Color wipe animations. + colorWipe(strip, Color(255, 0, 0), 0) # Red wipe + time.sleep(2) + colorWipe(strip, Color(0, 255, 0), 0) # Blue wipe + time.sleep(2) + colorWipe(strip, Color(0, 0, 255), 0) # Green wipe + time.sleep(2) + colorWipe(strip, Color(0, 0, 0, 255), 0) # White wipe + time.sleep(2) + colorWipe(strip, Color(255, 255, 255), 0) # Composite White wipe + time.sleep(2) + colorWipe(strip, Color(255, 255, 255, 255), 0) # Composite White + White LED wipe + time.sleep(2) diff --git a/examples/lowlevel.py b/examples/lowlevel.py new file mode 100644 index 0000000..39d6908 --- /dev/null +++ b/examples/lowlevel.py @@ -0,0 +1,98 @@ +# Example of low-level Python wrapper for rpi_ws281x library. +# Author: Tony DiCola (tony@tonydicola.com), Jeremy Garff (jer@jers.net) +# +# This is an example of how to use the SWIG-generated _rpi_ws281x module. +# You probably don't want to use this unless you are building your own library, +# because the SWIG generated module is clunky and verbose. Instead look at the +# high level Python port of Adafruit's NeoPixel Arduino library in strandtest.py. +# +# This code will animate a number of WS281x LEDs displaying rainbow colors. +import time + +import _rpi_ws281x as ws + +# LED configuration. +LED_CHANNEL = 0 +LED_COUNT = 16 # How many LEDs to light. +LED_FREQ_HZ = 800000 # Frequency of the LED signal. Should be 800khz or 400khz. +LED_DMA_NUM = 10 # DMA channel to use, can be 0-14. +LED_GPIO = 18 # GPIO connected to the LED signal line. Must support PWM! +LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest +LED_INVERT = 0 # Set to 1 to invert the LED signal, good if using NPN + # transistor as a 3.3V->5V level converter. Keep at 0 + # for a normal/non-inverted signal. + +# Define colors which will be used by the example. Each color is an unsigned +# 32-bit value where the lower 24 bits define the red, green, blue data (each +# being 8 bits long). +DOT_COLORS = [ 0x200000, # red + 0x201000, # orange + 0x202000, # yellow + 0x002000, # green + 0x002020, # lightblue + 0x000020, # blue + 0x100010, # purple + 0x200010 ] # pink + + +# Create a ws2811_t structure from the LED configuration. +# Note that this structure will be created on the heap so you need to be careful +# that you delete its memory by calling delete_ws2811_t when it's not needed. +leds = ws.new_ws2811_t() + +# Initialize all channels to off +for channum in range(2): + channel = ws.ws2811_channel_get(leds, channum) + ws.ws2811_channel_t_count_set(channel, 0) + ws.ws2811_channel_t_gpionum_set(channel, 0) + ws.ws2811_channel_t_invert_set(channel, 0) + ws.ws2811_channel_t_brightness_set(channel, 0) + +channel = ws.ws2811_channel_get(leds, LED_CHANNEL) + +ws.ws2811_channel_t_count_set(channel, LED_COUNT) +ws.ws2811_channel_t_gpionum_set(channel, LED_GPIO) +ws.ws2811_channel_t_invert_set(channel, LED_INVERT) +ws.ws2811_channel_t_brightness_set(channel, LED_BRIGHTNESS) + +ws.ws2811_t_freq_set(leds, LED_FREQ_HZ) +ws.ws2811_t_dmanum_set(leds, LED_DMA_NUM) + +# Initialize library with LED configuration. +resp = ws.ws2811_init(leds) +if resp != ws.WS2811_SUCCESS: + message = ws.ws2811_get_return_t_str(resp) + raise RuntimeError('ws2811_init failed with code {0} ({1})'.format(resp, message)) + +# Wrap following code in a try/finally to ensure cleanup functions are called +# after library is initialized. +try: + offset = 0 + while True: + # Update each LED color in the buffer. + for i in range(LED_COUNT): + # Pick a color based on LED position and an offset for animation. + color = DOT_COLORS[(i + offset) % len(DOT_COLORS)] + + # Set the LED color buffer value. + ws.ws2811_led_set(channel, i, color) + + # Send the LED color data to the hardware. + resp = ws.ws2811_render(leds) + if resp != ws.WS2811_SUCCESS: + message = ws.ws2811_get_return_t_str(resp) + raise RuntimeError('ws2811_render failed with code {0} ({1})'.format(resp, message)) + + # Delay for a small period of time. + time.sleep(0.25) + + # Increase offset to animate colors moving. Will eventually overflow, which + # is fine. + offset += 1 + +finally: + # Ensure ws2811_fini is called before the program quits. + ws.ws2811_fini(leds) + # Example of calling delete function to clean up structure memory. Isn't + # strictly necessary at the end of the program execution here, but is good practice. + ws.delete_ws2811_t(leds) diff --git a/examples/multistrandtest.py b/examples/multistrandtest.py new file mode 100644 index 0000000..afde32c --- /dev/null +++ b/examples/multistrandtest.py @@ -0,0 +1,78 @@ +# NeoPixel library strandtest example +# Author: Tony DiCola (tony@tonydicola.com) +# +# Direct port of the Arduino NeoPixel library strandtest example. Showcases +# various animations on a strip of NeoPixels. +import time + +from neopixel import * + +# LED strip configuration: +LED_1_COUNT = 30 # Number of LED pixels. +LED_1_PIN = 18 # GPIO pin connected to the pixels (must support PWM! GPIO 13 and 18 on RPi 3). +LED_1_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz) +LED_1_DMA = 10 # DMA channel to use for generating signal (Between 1 and 14) +LED_1_BRIGHTNESS = 128 # Set to 0 for darkest and 255 for brightest +LED_1_INVERT = False # True to invert the signal (when using NPN transistor level shift) +LED_1_CHANNEL = 0 # 0 or 1 +LED_1_STRIP = ws.SK6812_STRIP_GRBW + +LED_2_COUNT = 15 # Number of LED pixels. +LED_2_PIN = 13 # GPIO pin connected to the pixels (must support PWM! GPIO 13 or 18 on RPi 3). +LED_2_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz) +LED_2_DMA = 11 # DMA channel to use for generating signal (Between 1 and 14) +LED_2_BRIGHTNESS = 128 # Set to 0 for darkest and 255 for brightest +LED_2_INVERT = False # True to invert the signal (when using NPN transistor level shift) +LED_2_CHANNEL = 1 # 0 or 1 +LED_2_STRIP = ws.WS2811_STRIP_GRB + +def multiColorWipe(color1, color2, wait_ms=5): + global strip1 + global strip2 + """Wipe color across multiple LED strips a pixel at a time.""" + for i in range(strip1.numPixels()): + if i % 2: + # even number + strip1.setPixelColor(i, color1) + strip2.setPixelColor(i / 2, color2) + strip1.show() + time.sleep(wait_ms/1000.0) + strip2.show() + time.sleep(wait_ms/1000.0) + else: + # odd number + strip1.setPixelColor(i, color1) + strip1.show() + time.sleep(wait_ms/1000.0) + time.sleep(1) + +def blackout(strip): + for i in range(max(strip1.numPixels(), strip1.numPixels())): + strip.setPixelColor(i, Color(0,0,0)) + strip.show() + +# Main program logic follows: +if __name__ == '__main__': + # Create NeoPixel objects with appropriate configuration for each strip. + strip1 = Adafruit_NeoPixel(LED_1_COUNT, LED_1_PIN, LED_1_FREQ_HZ, LED_1_DMA, LED_1_INVERT, LED_1_BRIGHTNESS, LED_1_CHANNEL, LED_1_STRIP) + strip2 = Adafruit_NeoPixel(LED_2_COUNT, LED_2_PIN, LED_2_FREQ_HZ, LED_2_DMA, LED_2_INVERT, LED_2_BRIGHTNESS, LED_2_CHANNEL, LED_2_STRIP) + + # Intialize the library (must be called once before other functions). + strip1.begin() + strip2.begin() + + print ('Press Ctrl-C to quit.') + + # Black out any LEDs that may be still on for the last run + blackout(strip1) + blackout(strip2) + + while True: + + # Multi Color wipe animations. + multiColorWipe(Color(255, 0, 0), Color(255, 0, 0)) # Red wipe + multiColorWipe(Color(0, 255, 0), Color(0, 255, 0)) # Blue wipe + multiColorWipe(Color(0, 0, 255), Color(0, 0, 255)) # Green wipe + multiColorWipe(Color(255, 255, 255), Color(255, 255, 255)) # Composite White wipe + multiColorWipe(Color(0, 0, 0, 255), Color(0, 0, 0)) # White wipe + multiColorWipe(Color(255, 255, 255, 255), Color(0, 0, 0)) # Composite White + White LED wipe diff --git a/examples/neopixelclock.py b/examples/neopixelclock.py new file mode 100644 index 0000000..e950157 --- /dev/null +++ b/examples/neopixelclock.py @@ -0,0 +1,72 @@ +# Based on NeoPixel library and strandtest example by Tony DiCola (tony@tonydicola.com) +# To be used with a 12x1 NeoPixel LED stripe. +# Place the LEDs in a circle an watch the time go by ... +# red = hours +# blue = minutes 1-5 +# green = seconds +# (To run the program permanently and with autostart use systemd.) + +import time +import datetime +import math + +from neopixel import * + +# LED strip configuration: +LED_COUNT = 12 # 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 = 10 # DMA channel to use for generating signal (try 10) +LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest +# True to invert the signal (when using NPN transistor level shift) +LED_INVERT = False + +# Main program logic follows: +if __name__ == '__main__': + # Create NeoPixel object with appropriate configuration. + strip = Adafruit_NeoPixel( + LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS) + # Intialize the library (must be called once before other functions). + strip.begin() + + for i in range(0, strip.numPixels(), 1): + strip.setPixelColor(i, Color(0, 0, 0)) + while True: + now = datetime.datetime.now() + + # Low light during 19-8 o'clock + if(8 < now.hour < 19): + strip.setBrightness(200) + else: + strip.setBrightness(25) + + hour = now.hour % 12 + minute = now.minute / 5 + second = now.second / 5 + secondmodulo = now.second % 5 + timeslot_in_microseconds = secondmodulo * 1000000 + now.microsecond + for i in range(0, strip.numPixels(), 1): + secondplusone = second + 1 if(second < 11) else 0 + secondminusone = second - 1 if(second > 0) else 11 + colorarray = [0, 0, 0] + + if i == second: + if timeslot_in_microseconds < 2500000: + colorarray[0] = int( + 0.0000508 * timeslot_in_microseconds) + 126 + else: + colorarray[0] = 382 - \ + int(0.0000508 * timeslot_in_microseconds) + if i == secondplusone: + colorarray[0] = int(0.0000256 * timeslot_in_microseconds) + if i == secondminusone: + colorarray[0] = int( + 0.0000256 * timeslot_in_microseconds) * -1 + 128 + if i == minute: + colorarray[2] = 200 + if i == hour: + colorarray[1] = 200 + strip.setPixelColor( + i, Color(colorarray[0], colorarray[1], colorarray[2])) + strip.show() + time.sleep(0.1) diff --git a/examples/strandtest.py b/examples/strandtest.py new file mode 100644 index 0000000..0f4c32b --- /dev/null +++ b/examples/strandtest.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# NeoPixel library strandtest example +# Author: Tony DiCola (tony@tonydicola.com) +# +# Direct port of the Arduino NeoPixel library strandtest example. Showcases +# various animations on a strip of NeoPixels. + +import time +from neopixel import * +import argparse + +# LED strip configuration: +LED_COUNT = 16 # Number of LED pixels. +LED_PIN = 18 # GPIO pin connected to the pixels (18 uses PWM!). +#LED_PIN = 10 # GPIO pin connected to the pixels (10 uses SPI /dev/spidev0.0). +LED_FREQ_HZ = 800000 # LED signal frequency in hertz (usually 800khz) +LED_DMA = 10 # DMA channel to use for generating signal (try 10) +LED_BRIGHTNESS = 255 # Set to 0 for darkest and 255 for brightest +LED_INVERT = False # True to invert the signal (when using NPN transistor level shift) +LED_CHANNEL = 0 # set to '1' for GPIOs 13, 19, 41, 45 or 53 + + + +# Define 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 light 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((int(i * 256 / strip.numPixels()) + j) & 255)) + strip.show() + time.sleep(wait_ms/1000.0) + +def theaterChaseRainbow(strip, wait_ms=50): + """Rainbow movie theater light 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) + +# Main program logic follows: +if __name__ == '__main__': + # Process arguments + parser = argparse.ArgumentParser() + parser.add_argument('-c', '--clear', action='store_true', help='clear the display on exit') + args = parser.parse_args() + + # Create NeoPixel object with appropriate configuration. + strip = Adafruit_NeoPixel(LED_COUNT, LED_PIN, LED_FREQ_HZ, LED_DMA, LED_INVERT, LED_BRIGHTNESS, LED_CHANNEL) + # Intialize the library (must be called once before other functions). + strip.begin() + + print ('Press Ctrl-C to quit.') + if not args.clear: + print('Use "-c" argument to clear LEDs on exit') + + try: + + while True: + print ('Color wipe animations.') + colorWipe(strip, Color(255, 0, 0)) # Red wipe + colorWipe(strip, Color(0, 255, 0)) # Blue wipe + colorWipe(strip, Color(0, 0, 255)) # Green wipe + print ('Theater chase animations.') + theaterChase(strip, Color(127, 127, 127)) # White theater chase + theaterChase(strip, Color(127, 0, 0)) # Red theater chase + theaterChase(strip, Color( 0, 0, 127)) # Blue theater chase + print ('Rainbow animations.') + rainbow(strip) + rainbowCycle(strip) + theaterChaseRainbow(strip) + + except KeyboardInterrupt: + if args.clear: + colorWipe(strip, Color(0,0,0), 10) diff --git a/library/CHANGELOG.txt b/library/CHANGELOG.txt new file mode 100644 index 0000000..d9bb763 --- /dev/null +++ b/library/CHANGELOG.txt @@ -0,0 +1,11 @@ +3.1.0 +----- + +* New: Exposed all contents of ws for easy access to strip_type constants +* Fix: Matched functionality of upstream legacy library by supporting strip_type +* Note: Default strip type is WS2811_STRIP_GRB + +3.0.7 +----- + +* New: Added support for Pi 3B+ diff --git a/library/MANIFEST.in b/library/MANIFEST.in index 2b04061..19e8492 100644 --- a/library/MANIFEST.in +++ b/library/MANIFEST.in @@ -6,6 +6,7 @@ include setup.py include rpi_ws281x_wrap.c include neopixel/*.py include rpi_ws281x/*.py -include README.md +include README.rst +include CHANGELOG.txt include LICENSE include Makefile diff --git a/library/README.md b/library/README.md deleted file mode 100644 index 2fc4dd4..0000000 --- a/library/README.md +++ /dev/null @@ -1,88 +0,0 @@ -rpi_ws281x -========== - -Userspace Raspberry Pi library for controlling WS281X LEDs. -This includes WS2812 and SK6812RGB RGB LEDs -Preliminary support is now included for SK6812RGBW LEDs (yes, RGB + W) -The LEDs can be controlled by either the PWM (2 independent channels) -or PCM controller (1 channel) or the SPI interfacei (1 channel). - -Background: ------------ - -The BCM2835 in the Raspberry Pi has both a PWM and a PCM module that -are well suited to driving individually controllable WS281X LEDs. -Using the DMA, PWM or PCM FIFO, and serial mode in the PWM, it's -possible to control almost any number of WS281X LEDs in a chain connected -to the appropirate output pin. -For SPI the Raspbian spidev driver is used (/dev/spi0.0). -This library and test program set the clock rate to 3X the desired output -frequency and creates a bit pattern in RAM from an array of colors where -each bit is represented by 3 bits as follows. - - Bit 1 - 1 1 0 - Bit 0 - 1 0 0 - - -Hardware: ---------- - -WS281X LEDs are generally driven at 5V, which requires that the data -signal be at the same level. Converting the output from a Raspberry -Pi GPIO/PWM to a higher voltage through a level shifter is required. - -It is possible to run the LEDs from a 3.3V - 3.6V power source, and -connect the GPIO directly at a cost of brightness, but this isn't -recommended. - -Limitations: -============ - -PWM ---- - -Since this library and the onboard Raspberry Pi audio -both use the PWM, they cannot be used together. You will need to -blacklist the Broadcom audio kernel module by creating a file -/etc/modprobe.d/snd-blacklist.conf with - - blacklist snd_bcm2835 - -If the audio device is still loading after blacklisting, you may also -need to comment it out in the /etc/modules file. - -Some distributions use audio by default, even if nothing is being played. -If audio is needed, you can use a USB audio device instead. - -PCM ---- - -When using PCM you cannot use digital audio devices which use I2S since I2S -uses the PCM hardware, but you can use analog audio. - -SPI ---- - -When using SPI the ledstring is the only device which can be connected to -the SPI bus. Both digital (I2S/PCM) and analog (PWM) audio can be used. - -Comparison PWM/PCM/SPI -====================== - -Both PWM and PCM use DMA transfer to output the control signal for the LEDs. -The max size of a DMA transfer is 65536 bytes. Since each LED needs 12 bytes -(4 colors, 8 symbols per color, 3 bits per symbol) this means you can -control approximately 5400 LEDs for a single strand in PCM and 2700 LEDs per string -for PWM (Only PWM can control 2 independent strings simultaneously). - -SPI uses the SPI device driver in the kernel. For transfers larger than 96 bytes -the kernel driver also uses DMA. - -Of course there are practical limits on power and signal quality. These will -be more constraining in practice than the theoretical limits above. - -When controlling a LED string of 240 LEDs the CPU load on the original Pi 2 (BCM2836) are: - - PWM 5% - PCM 5% - SPI 1% diff --git a/library/README.rst b/library/README.rst new file mode 100644 index 0000000..09a3218 --- /dev/null +++ b/library/README.rst @@ -0,0 +1,171 @@ +rpi\_ws281x +=========== + +Userspace Raspberry Pi library for controlling WS281X LEDs. This +includes WS2812 and SK6812RGB RGB LEDs Preliminary support is now +included for SK6812RGBW LEDs (yes, RGB + W) The LEDs can be controlled +by either the PWM (2 independent channels) or PCM controller (1 channel) +or the SPI interface (1 channel). + +Background: +----------- + +The BCM2835 in the Raspberry Pi has both a PWM and a PCM module that are +well suited to driving individually controllable WS281X LEDs. Using the +DMA, PWM or PCM FIFO, and serial mode in the PWM, it's possible to +control almost any number of WS281X LEDs in a chain connected to the +appropriate output pin. For SPI the Raspbian spidev driver is used +(``/dev/spidev0.0``). This library and test program set the clock rate +to 3X the desired output frequency and creates a bit pattern in RAM from +an array of colors where each bit is represented by 3 bits as follows. + +:: + + Bit 1 - 1 1 0 + Bit 0 - 1 0 0 + +GPIO Usage: +----------- + +The GPIOs that can be used are limited by the hardware of the Pi and +will vary based on the method used to drive them (PWM, PCM or SPI). +Beware that the GPIO numbers are not the same as the physical pin +numbers on the header. + +PWM: + +:: + + PWM0, which can be set to use GPIOs 12, 18, 40, and 52. + Only 12 (pin 32) and 18 (pin 12) are available on the B+/2B/3B + + PWM1 which can be set to use GPIOs 13, 19, 41, 45 and 53. + Only 13 is available on the B+/2B/PiZero/3B, on pin 33 + +PCM: + +:: + + PCM_DOUT, which can be set to use GPIOs 21 and 31. + Only 21 is available on the B+/2B/PiZero/3B, on pin 40. + +SPI: + +:: + + SPI0-MOSI is available on GPIOs 10 and 38. + Only GPIO 10 is available on all models. + See also note for RPi 3 below. + +Power and voltage requirements +------------------------------ + +WS281X LEDs are generally driven at 5V. Depending on your actual LED +model and data line length you might be able to successfully drive the +data input with 3.3V. However in the general case you probably want to +use a level shifter to convert from the Raspberry Pi GPIO/PWM to 5V. + +It is also possible to run the LEDs from a 3.3V - 3.6V power source, and +connect the GPIO directly at a cost of brightness, but this isn't +recommended. + +The test program is designed to drive a 8x8 grid of LEDs e.g.from +Adafruit (http://www.adafruit.com/products/1487) or Pimoroni +(https://shop.pimoroni.com/products/unicorn-hat). Please see the +Adafruit and Pimoroni websites for more information. + +Know what you're doing with the hardware and electricity. I take no +reponsibility for damage, harm, or mistakes. + +Important warning about DMA channels +------------------------------------ + +You must make sure that the DMA channel you choose to use for the LEDs +is not `already in +use `__ +by the operating system. + +For example, **using DMA channel 5 will cause filesystem corruption** +on the Raspberry Pi 3 Model B. +See: https://github.com/jgarff/rpi_ws281x/issues/224 + +The default DMA channel (10) should be safe for the Raspberry Pi 3 Model +B, but this may change in future software releases. + +Limitations: +------------ + +PWM +~~~ + +Since this library and the onboard Raspberry Pi audio both use the PWM, +they cannot be used together. You will need to blacklist the Broadcom +audio kernel module by creating a file +``/etc/modprobe.d/snd-blacklist.conf`` with + +:: + + blacklist snd_bcm2835 + +If the audio device is still loading after blacklisting, you may also +need to comment it out in the /etc/modules file. + +On headless systems you may also need to force audio through hdmi Edit +config.txt and add: + +:: + + hdmi_force_hotplug=1 + hdmi_force_edid_audio=1 + +A reboot is required for this change to take effect + +Some distributions use audio by default, even if nothing is being +played. If audio is needed, you can use a USB audio device instead. + +PCM +~~~ + +When using PCM you cannot use digital audio devices which use I2S since +I2S uses the PCM hardware, but you can use analog audio. + +SPI +~~ + +When using SPI the ledstring is the only device which can be connected +to the SPI bus. Both digital (I2S/PCM) and analog (PWM) audio can be +used. + +Many distributions have a maximum SPI transfer of 4096 bytes. This can +be changed in ``/boot/cmdline.txt`` by appending + +:: + + spidev.bufsiz=32768 + +On a RPi 3 you have to change the GPU core frequency to 250 MHz, +otherwise the SPI clock has the wrong frequency. Do this by adding the +following line to /boot/config.txt and reboot. + +:: + + core_freq=250 + +SPI requires you to be in the ``gpio`` group if you wish to control your +LEDs without root. + +Comparison PWM/PCM/SPI +---------------------- + +Both PWM and PCM use DMA transfer to output the control signal for the +LEDs. The max size of a DMA transfer is 65536 bytes. Since each LED +needs 12 bytes (4 colors, 8 symbols per color, 3 bits per symbol) this +means you can control approximately 5400 LEDs for a single strand in PCM +and 2700 LEDs per string for PWM (Only PWM can control 2 independent +strings simultaneously) SPI uses the SPI device driver in the kernel. +For transfers larger than 96 bytes the kernel driver also uses DMA. Of +course there are practical limits on power and signal quality. These +will be more constraining in practice than the theoretical limits above. + +When controlling a LED string of 240 LEDs the CPU load on the original +Pi 2 (BCM2836) are: PWM 5% PCM 5% SPI 1% diff --git a/library/lib b/library/lib index eb493ae..b56f6ab 160000 --- a/library/lib +++ b/library/lib @@ -1 +1 @@ -Subproject commit eb493aeda25a744dd06366fe9608485feb703610 +Subproject commit b56f6abee272730aa00102c495ec992831ccab5c diff --git a/library/neopixel/__init__.py b/library/neopixel/__init__.py index d21660f..84f0c3f 100644 --- a/library/neopixel/__init__.py +++ b/library/neopixel/__init__.py @@ -1,2 +1,3 @@ # Included for backwards-compatibility with old import name -from rpi_ws281x import __version__, PixelStrip, Adafruit_NeoPixel, Color +from rpi_ws281x import __version__, PixelStrip, Adafruit_NeoPixel, Color, ws +from _rpi_ws281x import * diff --git a/library/rpi_ws281x/__init__.py b/library/rpi_ws281x/__init__.py index 2eb1f3f..f9fd4fc 100644 --- a/library/rpi_ws281x/__init__.py +++ b/library/rpi_ws281x/__init__.py @@ -1,4 +1,5 @@ # New canonical package, to support `import rpi_ws281x` -from .rpi_ws281x import PixelStrip, Adafruit_NeoPixel, Color +from .rpi_ws281x import PixelStrip, Adafruit_NeoPixel, Color, ws +from _rpi_ws281x import * -__version__ = '3.0.6' +__version__ = '3.1.0' diff --git a/library/rpi_ws281x/rpi_ws281x.py b/library/rpi_ws281x/rpi_ws281x.py index e948c23..58b09bb 100644 --- a/library/rpi_ws281x/rpi_ws281x.py +++ b/library/rpi_ws281x/rpi_ws281x.py @@ -49,7 +49,8 @@ def __setitem__(self, pos, value): class PixelStrip(object): - def __init__(self, num, pin, freq_hz=800000, dma=10, invert=False, brightness=128, channel=0, gamma=None): + def __init__(self, num, pin, freq_hz=800000, dma=10, invert=False, + brightness=255, channel=0, strip_type=None, gamma=None): """Class to represent a SK6812/WS281x LED display. Num should be the number of pixels in the display, and pin should be the GPIO pin connected to the display signal line (must be a PWM pin like 18!). Optional @@ -60,9 +61,16 @@ def __init__(self, num, pin, freq_hz=800000, dma=10, invert=False, brightness=12 """ if gamma is None: - gamma = list(range(256)) + # Support gamma in place of strip_type for back-compat with + # previous version of forked library + if type(strip_type) is list and len(strip_type) == 256: + gamma = strip_type + strip_type = None + else: + gamma = list(range(256)) - self._gamma = gamma + if strip_type is None: + strip_type = ws.WS2811_STRIP_GRB # Create ws2811_t structure and fill in parameters. self._leds = ws.new_ws2811_t() @@ -77,12 +85,12 @@ def __init__(self, num, pin, freq_hz=800000, dma=10, invert=False, brightness=12 # Initialize the channel in use self._channel = ws.ws2811_channel_get(self._leds, channel) - ws.ws2811_channel_t_gamma_set(self._channel, self._gamma) + ws.ws2811_channel_t_gamma_set(self._channel, gamma) ws.ws2811_channel_t_count_set(self._channel, num) ws.ws2811_channel_t_gpionum_set(self._channel, pin) ws.ws2811_channel_t_invert_set(self._channel, 0 if not invert else 1) ws.ws2811_channel_t_brightness_set(self._channel, brightness) - ws.ws2811_channel_t_strip_type_set(self._channel, ws.WS2811_STRIP_GRB) + ws.ws2811_channel_t_strip_type_set(self._channel, strip_type) # Initialize the controller ws.ws2811_t_freq_set(self._leds, freq_hz) @@ -103,8 +111,7 @@ def _cleanup(self): def setGamma(self, gamma): if type(gamma) is list and len(gamma) == 256: - self._gamma = gamma - ws.ws2811_channel_t_gamma_set(self._channel, self._gamma) + ws.ws2811_channel_t_gamma_set(self._channel, gamma) def begin(self): """Initialize library, must be called once before other functions are diff --git a/library/setup.py b/library/setup.py index afd4e32..b5adc87 100755 --- a/library/setup.py +++ b/library/setup.py @@ -17,12 +17,13 @@ def run(self): build_py.run(self) setup(name = 'rpi_ws281x', - version = '3.0.6', + version = '3.1.0', author = 'Jeremy Garff , Phil Howard ', - author_email = 'jer@jers.net', + author_email = 'jer@jers.net, phil@pimoroni.com', description = 'Userspace Raspberry Pi PWM/PCM/SPI library for SK6812 and WS281X LEDs.', + long_description = open('README.rst').read() + "\n" + open('CHANGELOG.txt').read(), license = 'MIT', - url = 'https://github.com/pimoroni/rpi_ws281x-python/', + url = 'https://github.com/rpi-ws281x/rpi-ws281x-python/', cmdclass = {'build_py':CustomInstallCommand}, packages = ['neopixel', 'rpi_ws281x'], ext_modules = [Extension('_rpi_ws281x',