Weekend Project: POVStick

To showcase how much the AllPixel and BiblioPixel can simplify your projects, we wanted to put together a fun project that really highlighted their versatility. So we decided to build a persistence of vision light painter, or POVStick as we keep calling it.

The POVStick consists of 2 meters of 48 LED/m LPD8806 strips, for a total of 96 pixels vertical resolution. This is controlled by an AllPixel connected to a Raspberry Pi B+ with a USB WiFi module. Finally, this is all powered by a 16Ah lithium-ion battery pack. It’s 2.1A per port output was more than enough to run the LEDs and Pi for quite some time.

Next is the special that makes it all work… the software. Fortunately, BiblioPixel is really easy to enhance and extend, so I came up with the LEDPOV class. This builds on top of LEDMatrix but in stead of displaying the full matrix, breaks the image up into vertical columns and displays those one at a time, split over the given total frame time.

from bibliopixel.led import *
import bibliopixel.image as img
from bibliopixel.drivers.serial_driver import *
import bibliopixel.gamma as gamma

#Takes a matrix and displays it as individual columns over time
class LEDPOV(LEDMatrix):

    def __init__(self, driver, povHeight, width, rotation = MatrixRotation.ROTATE_0, vert_flip = False):
        self.numLEDs = povHeight * width

        super(LEDPOV, self).__init__(driver, width, povHeight, None, rotation, vert_flip, False)
    
    #This is the magic. Overriding the normal update() method
    #It will automatically break up the frame into columns spread over frameTime (ms)    
    def update(self, frameTime = None):
        if frameTime:
            self._frameTotalTime = frameTime

        sleep = None
        if self._frameTotalTime:
            sleep = (self._frameTotalTime - self._frameGenTime) / self.width

        width = self.width
        for h in range(width):
            start = time.time() * 1000.0

            buf = [item for sublist in [self.buffer[(width*i*3)+(h*3):(width*i*3)+(h*3)+(3)] for i in range(self.height)] for item in sublist]
            self.driver.update(buf)
            sendTime = (time.time() * 1000.0) - start
            if sleep:
                time.sleep(max(0, (sleep - sendTime) / 1000.0))

#convert 6 character hex colors to RGB tuple
def hex2rgb(hex):
    """Helper for converting RGB and RGBA hex values to Color"""
    hex = hex.strip('#')
    if len(hex) == 6:
        split = (hex[0:2],hex[2:4],hex[4:6])
    else:
        raise ValueError('Must pass in either a 6 character hex value!')

    r, g, b = [int(x, 16) for x in split]

    return (r, g, b)

argv = sys.argv
argc = len(argv)

file = ""
bright = 255
col_time = 50
bgcolor = (0,0,0)

#load in command line args
if argc > 1:
    file = argv[1]
else:
    print "Must specifiy an input file!"
    sys.exit(2)

#get brightness value
if argc > 2:
    bright = int(x=argv[2])

#col_time is time to display each vertical line for, in ms
if argc > 3:
    col_time = int(argv[3])

#bgcolor is the color that will be used on transparent pixels
if argc > 4:
    bgcolor = hex2rgb(argv[4])

#open the file so it is not loaded each time through
i = img.Image.open(file)

img_width = i.size[0]
totalFrameTime = img_width * col_time

print "Image Display Time: {0:.1f}s".format(totalFrameTime/1000.0)

driver = DriverSerial(LEDTYPE.LPD8806, num = 96, c_order=ChannelOrder.BRG, SPISpeed=16, gamma=gamma.LPD8806)

#automatically configure matrix width to image width. 
#change povHeight to match your setup
led = LEDPOV(driver, povHeight = 96, width = img_width, rotation=MatrixRotation.ROTATE_0, vert_flip=True)
led.setMasterBrightness(bright)

img.showImage(led, imageObj = i, bgcolor=bgcolor)

try:
    while True:
        led.update(frameTime=totalFrameTime)
except KeyboardInterrupt:
    led.all_off()
    led.update()

Usage is as follows:

python LEDPOV.py image_file brightness column_time background_color
brightness: 0-255
column_time: time (ms) to display each vertical column of the image
background_color: hex color value to use for transparent pixels, otherwise black (#000000)

After a little more testing, LEDPOV will be added to the main BiblioPixel code so that it will be more easily available to all.

To use, just run the script with the desired image and set your camera to do a long exposure for slightly longer than it will take the image to display. To help with this, the script outputs the total display time of each image. You will likely need to play around with ISO and aperture settings before getting it exactly right and, of course, doing this at night will provide the best results. It is also best to add a little blank lead-in on each image so that you know when to start moving. When the lights go out have someone hit the shutter and start walking at a steady pace. If you get it right, you should get some awesome pictures like these:

To keep things fully mobile, we controlled the POV script from an SSH session running on an Android tablet. We highly recommend JuiceSSH and Hacker’s Keyboard for a great mobile SSH terminal experience.

If you like this and want to make your own, check out our Kickstarter for the AllPixel!

AllPixel Week 1 Update

It has been an amazing first week! 21 Days to go and we are already at over 200% funding. Thanks so much to all our supporters!

We’ve also announced some new stretch goals, so check those out. We feel certain we will hit the first goal in no time!

One of the coolest things so far though is that we were featured on the Atmel blog! It’s a great honor to get some recognition from such an awesome company. And one that made this project possible!

That’s all for now. Keep being awesome and tell your friends!

AllPixel Update – Staff Pick!

First of all, the AllPixel Kickstarter has been going great! Fully funded in less than 48 hours, chosen as a Kickstarter Staff Pick on the third day, and currently at over 130% funded and 150 backers… with 26 days left!

We didn’t want to bog down the main page with really technical details about how the AllPixel works, but we thought we should give some more background.

The Hardware

At the core, the AllPixel is not much different from devices like the Arduino Leonardo, Arduino Pro Micro, or Teensy 2. In fact, our original prototype used a Pro Micro.

IMG_0528-16x9

IMG_0537-Edit-16x9

The heart of the AllPixel is an ATMega32u4 which is what allowed us to provide such amazing frame rates, since it is capable of full 12Mbps throughput on the USB Serial connection. It also provides 2.5KB of SRAM, 2K of which is used to buffer the pixel data. Not needing to waste SRAM on the serial buffer was also a huge advantage of using this chip over the venerable FTDI and something without built-in USB support.

All of this is supplemented by the optional hardware which makes hooking up your LED strips a breeze.

  • 2.1mm barrel jack – makes providing up to 5A of power at 5V super easy.
  • 4-pin screw terminal – no more need to solder your strip connections directly. The AllPixel was designed with using any kind of strip in mind, so making it hard to swap them out wouldn’t have made much sense!
  • 1000uF capacitor – Power requirements can fluctuate wildly, depending on the state of an animation. This ensures nice even power when your power supply needs a few fractions of a second to catch up.
  • 300 ohm resistors – These can be placed in R6 and R7 to drop the logic level slightly. This can help prevent damage to some strips, like the WS2812.
  • 1N5817 Schottky diode – Installing this allows for the AllPixel and a small number of attached LEDs to be powered directly from USB. It also protects against accidental external power connections damaging the board and your computer when operating in USB mode.

Last thing of hardware note is that, in order to make the AllPixel work as seamlessly as possible, we provided a super simple method in hardware for the board to reboot itself. This is necessary to allow the firmware to reconfigure and allocate all the necessary resources and provide the best speed when driving all the different chipsets. This is achieved by connecting an NPN transistor and pull-up resistor to the IC’s reset pin. To reboot the board, we just drive the transistor input high, which brings the output and the reset low, resetting the chip. Since the digital IO pins all default to input and low on reboot, that logic is immediately flipped upon successful reboot and the firmware restarts.

The Firmware

All the above alone was not enough to achieve the speeds we wanted. The base Arduino core libraries for the ATMega32u4 max out around 60KB/s. When you need 2KB for each frame, that would’ve meant at most 30fps, not counting other overhead. This just wasn’t good enough.

But then we found the amazing Teensy Arduino cores which could handle full USB 1.1 speed! So we got to work adapting those core libraries to what we needed and came up with our own variant core. These modified core libraries allow us to run full USB speed on anything with a 32u4, even our original Arduino Pro Micro mock-up, and have a few changes such as the ability to change the USB VID and PID from the boards.txt file.

One thing of note, however, is that the current Arduino bootloaders for the Leonardo and Pro Micro do not work with our modified core libraries. This is mainly due to some significant differences in the way the Teensy USB code handles the automatic reboot that puts the device in bootloader mode. In the end, we decided that it was best since we wanted the AllPixel to be able to reboot as fast as possible and having to wait for the bootloader was not an option. The board can still, however, be reprogrammed with any compatible ICSP programmer like the AVR ISP MkII or even another arduino with the “Arduino as ICSP” sketch.

Finally, the real party piece of the whole setup is the great FastLED library. This is the “universal translator” of the whole operation. FastLED is an amazing library that not only knows how to talk to all the different LED strips, but is super fast doing it.

We hope that answers some of the questions that people have had about what makes the AllPixel tick.
We’ll have more details and some fun little projects for you soon. So, stay tuned and keep spreading the word!