Raspberry Pi Zero driving APA102 leds - RC Groups
Shop our Airplanes Products Drone Products Sales
Thread Tools
Dec 10, 2015, 02:02 PM
Registered User
Discussion

Raspberry Pi Zero driving APA102 leds


/// EDIT \\\
This post was originally about using an Arduino with the leds.
I found that the arduino did not have anough RAM to display large images.
/// EDIT \\\


I will be attempting to create a "Persistence of Vision" RC Wing using:
Arduino UNO
APA102 "Dotstar" Led Strip

Build log of the wing is over @ RCG wings forum HERE

These LEDs are FAST!!!!!
I'm talking TWO THOUSAND frames per second!


Each LED in the strip I have is 16.6mm apart.
Plane is flying at 65mph (29057.6mm per second)
For a perfect no stretch image I want leds to change every 16.6mm.
so 29057.6/16.6 = 1743 fps @ 65mph

I will be using a GPS to get accurate velocity so that I can change the refresh rate to maintain a no-stretch image.


Here they are changing from red -> green -> blue at 1,700fps being recorded with 30fps video.


and here is a very fast example of a pulse along the strip. recorded at 240fps.



Controlling the code with my transmitter by reading the pulse width modulation of the data pin on the receiver.
Last edited by mtgtopdeck; Feb 22, 2016 at 03:49 PM.
Sign up now
to remove ads between posts
Dec 15, 2015, 11:35 PM
Registered User
Here are some tests as I work towards making the light painting work.

Initial working sketch is not optimized and takes up way to much memory to run anything more than 24x24 or 119x4

Here is a little umbrella




And here is some checkers
Dec 20, 2015, 01:40 AM
Registered User
I've run out of memory on the arduino.

I will be using a Raspberry Pi Zero instead.

more to come as i get things working
Dec 21, 2015, 06:45 PM
Registered User
I've drawn up a first draft wiring diagram.

If anyone here has enough knowledge to give me some feedback I would appreciate it.
I'm seeking advice on other forums as well so no worries if no one here can help.


My biggest concern is if I can use a MOSFET to switch the single color LEDS on/off.
Or if I should instead use a mini Solid State Relay.
Dec 23, 2015, 08:37 AM
Sky good, Earth bad!
Dakrat's Avatar
I'm signing on! Very interested.....
Dak
Dec 29, 2015, 02:39 AM
Registered User
Here is the setup for just the APA102 leds

APA102 Leds
74AHCT125 Quad Level-Shifter
Raspberry Pi
Stripboard



above image credit - Phillip Burgess








Some sample code I made for APA102 LEDs using Adafruit Dotstar Pi and some strings from the examples in fastled3.1 library for C++ that I converted to python

First here is the most important one.
Turn everything off
off.py
Code:
from dotstar import Adafruit_DotStar
numpixels = 119     #number of pixels in your strip
strip = Adafruit_DotStar(numpixels)
strip.begin()
strip.setPixelColor(numpixels, 0x000000)
strip.show()
next make pixel 0 1 and 2 blink red green and blue to check the color order is correct.
blinktest.py
Code:
import time
from dotstar import Adafruit_DotStar

numpixels = 119
strip = Adafruit_DotStar(numpixels, 16000000,  order='bgr')
strip.begin()
strip.setBrightness(64)

while True:
    strip.setPixelColor(0, 0xFF0000)
    strip.setPixelColor(1, 0x00FF00)
    strip.setPixelColor(2, 0x0000FF)
    strip.show()
    time.sleep(0.1)

    strip.setPixelColor(0, 0x000000)
    strip.setPixelColor(1, 0x000000)
    strip.setPixelColor(2, 0x000000)
    strip.show()
    time.sleep(0.1)
A pulse that starts in the center of the strip and goes to each end.
Cycles through red, green, blue. Or if you uncomment a few lines cycle through random colors, or just a single color.
pulse.py
Code:
import time
import random
from dotstar import Adafruit_DotStar

numpixels = 119
strip = Adafruit_DotStar(numpixels, 16000000, order='bgr')      # Use SPI (pins 10=MOSI, 11=SCLK), fix color order

strip.begin()            # Initialize pins for output
strip.setBrightness(128) # Limit brightness to

center = 59             # center of strip
step = -1               # don't change
maxsteps = 61           # center + 2
#color_r = 0             # red. for random colors
#color_g = 0             # green. for random colors
#color_b = 0             # blue. for random colors
color = 0xFF0000        # start red for red->green->blue->black cycle

while True:
    if step == -1:
#        color_r = random.randint(0,255)        #
#        color_g = random.randint(0,255)        # pick a random color
#        color_b = random.randint(0,255)        #
        step = 0
    if step == 0:
#        strip.setPixelColor(center, color_r, color_g, color_b)     #set starting pixel to the random color
#        strip.setPixelColor(center, 0, 0, 255)      #set starting pixel to blue
        strip.setPixelColor(center, color)      #set starting pixel to blue
        strip.show()
        step += 1
    if step == maxsteps:
        color >>= 8     # red->green->blue->black
        if(color == 0): color = 0xFF0000        # If black, reset to red
#        strip.setPixelColor(numpixels, 0, 0, 0)     #set all pixels to off
        strip.show()
        step = -1
    else:
#        strip.setPixelColor(((center + step + numpixels) % numpixels), color_r, color_g, color_b)      #set the next pixel to random color in the positive direction
#        strip.setPixelColor(((center - step + numpixels) % numpixels), color_r, color_g, color_b)      #set the next pixel to random color in the negative direction
#        strip.setPixelColor(((center + step + numpixels) % numpixels), 0, 0, 255)       #set the next pixel to blue in the positive direction
#        strip.setPixelColor(((center - step + numpixels) % numpixels), 0, 0, 255)       #set the next pixel to blue in the negative direction
        strip.setPixelColor(((center + step + numpixels) % numpixels), color)       #set the next pixel color in the positive direction
        strip.setPixelColor(((center - step + numpixels) % numpixels), color)       #set the next pixel to blue in the negative direction
        strip.setPixelColor((center + step - 1), 0, 0, 0)       #turn off the pixel behind the on pixel
        strip.setPixelColor((center - step + 1), 0, 0, 0)       #turn off the pixel behind the on pixel
        strip.show()
        time.sleep(0.001)
        step += 1
Jan 02, 2016, 03:57 AM
Registered User
Here is the same setup powered entirely by a LiPo battery.
This time running the example image-pov.py from the Adafruit DotStar Pi library.



Jan 07, 2016, 02:15 AM
Registered User
I finalized most of the wiring of the LEDs

I mounted the level shifter and mosfet on top of the raspberry pi zero.


I tested the mosfet controlled 12v LEDS and the APA102 leds at the same time with success.






Jan 21, 2016, 03:42 PM
Registered User
GRXXL APA102 LEDs 6 defined functions Beta Code (0 min 38 sec)


Here is the working Beta Code for this project.

It's not optimized. It's not written by a professional programmer.
It works and that is all that I need.

6 different defined functions.
instant switching between each.
constant data rate set at 1,700fps for best viewing at ~60mph.

Attached images are what I am displaying in the video


grxxl.py
Code:
import time
import pigpio
from dotstar import Adafruit_DotStar
import Image
import random
import readrx


numpixels = 119 #number of pixels in the strip
strip = Adafruit_DotStar(numpixels, 24000000, order='bgr')

filename1 = "pattern03.jpg" #file name of image to be converted
filename2 = "triangles.jpg"
filename3 = "pattern04.jpg"
filename4 = "closeencounters.jpg"

rOffset = 3     #fix RGB color order
gOffset = 2
bOffset = 1

strip.begin()
strip.setBrightness(127)

class pov1:# converting an image to a byte array for sending to led strip
        print "Loading {}...".format(filename1)
        img       = Image.open(filename1).convert("RGB")
        pixels    = img.load()
        width     = img.size[0]
        height    = img.size[1]
#       print "%dx%d pixels" % img.size
        gamma = bytearray(256)
        for i in range(256):
                gamma[i] = int(pow(float(i) / 255.0, 2.7) * 255.0 + 0.5)
#       print "Allocating..."
        column = [0 for x in range(width)]
        for x in range(width):
                column[x] = bytearray(height * 4)
#       print "Converting..."
        for x in range(width):          # For each column of image...
                for y in range(height): # For each pixel in column...
                        value             = pixels[x, y]    # Read pixel in image
                        y4                = y * 4           # Position in raw buffer
                        column[x][y4]     = 0xFF            # Pixel start marker
                        column[x][y4 + rOffset] = gamma[value[0]] # Gamma-corrected R
                        column[x][y4 + gOffset] = gamma[value[1]] # Gamma-corrected G
                        column[x][y4 + bOffset] = gamma[value[2]] # Gamma-corrected B
        print "Done Loading {}...".format(filename1)

class pov2:# converting an image to a byte array for sending to led strip
        print "Loading {}...".format(filename2)
        img       = Image.open(filename2).convert("RGB")
        pixels    = img.load()
        width     = img.size[0]
        height    = img.size[1]
#       print "%dx%d pixels" % img.size
        gamma = bytearray(256)
        for i in range(256):
                gamma[i] = int(pow(float(i) / 255.0, 2.7) * 255.0 + 0.5)
#       print "Allocating..."
        column = [0 for x in range(width)]
        for x in range(width):
                column[x] = bytearray(height * 4)
#       print "Converting..."
        for x in range(width):          # For each column of image...
                for y in range(height): # For each pixel in column...
                        value             = pixels[x, y]    # Read pixel in image
                        y4                = y * 4           # Position in raw buffer
                        column[x][y4]     = 0xFF            # Pixel start marker
                        column[x][y4 + rOffset] = gamma[value[0]] # Gamma-corrected R
                        column[x][y4 + gOffset] = gamma[value[1]] # Gamma-corrected G
                        column[x][y4 + bOffset] = gamma[value[2]] # Gamma-corrected B
        print "Done Loading {}...".format(filename2)

class pov3:# converting an image to a byte array for sending to led strip
        print "Loading {}...".format(filename3)
        img       = Image.open(filename3).convert("RGB")
        pixels    = img.load()
        width     = img.size[0]
        height    = img.size[1]
#       print "%dx%d pixels" % img.size
        gamma = bytearray(256)
        for i in range(256):
                gamma[i] = int(pow(float(i) / 255.0, 2.7) * 255.0 + 0.5)
#       print "Allocating..."
        column = [0 for x in range(width)]
        for x in range(width):
                column[x] = bytearray(height * 4)
#       print "Converting..."
        for x in range(width):          # For each column of image...
                for y in range(height): # For each pixel in column...
                        value             = pixels[x, y]    # Read pixel in image
                        y4                = y * 4           # Position in raw buffer
                        column[x][y4]     = 0xFF            # Pixel start marker
                        column[x][y4 + rOffset] = gamma[value[0]] # Gamma-corrected R
                        column[x][y4 + gOffset] = gamma[value[1]] # Gamma-corrected G
                        column[x][y4 + bOffset] = gamma[value[2]] # Gamma-corrected B
        print "Done Loading {}...".format(filename3)

class pov4:# converting an image to a byte array for sending to led strip
        print "Loading {}...".format(filename4)
        img       = Image.open(filename4).convert("RGB")
        pixels    = img.load()
        width     = img.size[0]
        height    = img.size[1]
#       print "%dx%d pixels" % img.size
        gamma = bytearray(256)
        for i in range(256):
                gamma[i] = int(pow(float(i) / 255.0, 2.7) * 255.0 + 0.5)
#       print "Allocating..."
        column = [0 for x in range(width)]
        for x in range(width):
                column[x] = bytearray(height * 4)
#       print "Converting..."
        for x in range(width):          # For each column of image...
                for y in range(height): # For each pixel in column...
                        value             = pixels[x, y]    # Read pixel in image
                        y4                = y * 4           # Position in raw buffer
                        column[x][y4]     = 0xFF            # Pixel start marker
                        column[x][y4 + rOffset] = gamma[value[0]] # Gamma-corrected R
                        column[x][y4 + gOffset] = gamma[value[1]] # Gamma-corrected G
                        column[x][y4 + bOffset] = gamma[value[2]] # Gamma-corrected B
        print "Done Loading {}...".format(filename4)


def hsv2rgb(HSV):#convert HSV 255 values to RGB 255
    H, S, V = HSV
    if S == 0:
        R = V
        G = V
        B = V
        return (R, G, B)
    region = H // 43;
    remainder = (H - (region * 43)) * 6;
    P = (V * (255 - S)) >> 8;
    Q = (V * (255 - ((S * remainder) >> 8))) >> 8;
    T = (V * (255 - ((S * (255 - remainder)) >> 8))) >> 8;
    if region == 0:
        R = V
        G = T
        B = P
    elif region == 1:
        R = Q;
        G = V;
        B = P;
    elif region == 2:
        R = P;
        G = V;
        B = T;
    elif region == 3:
        R = P;
        G = Q;
        B = V;
    elif region == 4:
        R = T;
        G = P;
        B = V;
    else:
        R = V;
        G = P;
        B = Q;
    return R, G, B

"""
six toggle states.
toggle readrx.pw1()(aux1) has three positions
toggle readrx.pw2()(gear) has two positions
"""

def toggle31():
        if readrx.pw1() == 1900 and readrx.pw2() == 1100:
                print("aux 3 pw ={}, gear 1 pw ={}".format(int(readrx.pw1()), int(readrx.pw2())))
                pi.set_PWM_dutycycle(23, 0)
                running = True
                while running:
                        for x in range(pov4.width):
                                strip.show(pov4.column[x])
                                time.sleep(.02)
                                if readrx.pw1() != 1900:
                                        running = False
                                        break
                                if readrx.pw2() != 1100:
                                        running = False
                                        break
                        break
                """
                cylon
                """
                """
                running = True
                while running:
                        i = 0
                        while (i < numpixels):
                                if readrx.pw1() != 1900:
                                        running = False
                                        break
                                if readrx.pw2() != 1100:
                                        running = False
                                        break
                                strip.setPixelColor(i, 0x0000FF)
                                strip.show()
                                strip.setPixelColor(i, 0x000000)
                                time.sleep(0.001)
                                i += 1
                        i = numpixels - 1
                        while (i < numpixels):
                                if readrx.pw1() != 1900:
                                        running = False
                                        break
                                if readrx.pw2() != 1100:
                                        running = False
                                        break
                                strip.setPixelColor(i, 0xFF0000)
                                strip.show()
                                strip.setPixelColor(i, 0x000000)
                                time.sleep(0.001)
                                i -= 1
                                if i <= 0:
                                        break
                """

def toggle21():
        if readrx.pw1() == 1500 and readrx.pw2() == 1100:
                print("aux 2 pw ={}, gear 1 pw ={}".format(int(readrx.pw1()), int(readrx.pw2())))
                pi.set_PWM_dutycycle(23, 0)
                """
                confetti
                """
                running = True
                while running:
                        if readrx.pw1() != 1500:
                                running = False
                                break
                        if readrx.pw2() != 1100:
                                running = False
                                break
                        randompixel = random.randint(0, 119)
                        randomcolor = random.randint(0, 255), 255, 255
                        randomhsv = hsv2rgb(randomcolor)
                        strip.setPixelColor((randompixel), randomhsv[0], randomhsv[1], randomhsv[2])
                        strip.show()
                        time.sleep(.01)
                        strip.setPixelColor((randompixel), 0, 0, 0)
                        strip.show()

def toggle11():
        if readrx.pw1() == 1100 and readrx.pw2() == 1100:
                print("aux 1 pw ={}, gear 1 pw ={}".format(int(readrx.pw1()), int(readrx.pw2())))
                strip.show()
                running = True
                while running:
                        i = 0
                        while (i < 200):
                                if readrx.pw1() != 1100:
                                        running = False
                                        break
                                if readrx.pw2() != 1100:
                                        running = False
                                        break
                                pi.set_PWM_dutycycle(23, i)
                                time.sleep(0.001)
                                i += 1
                        while (i == 200):
                                if readrx.pw1() != 1100:
                                        running = False
                                        break
                                if readrx.pw2() != 1100:
                                        running = False
                                        break
                                time.sleep(.1)
                                i -= 1
                        while (i < 200):
                                if readrx.pw1() != 1100:
                                        running = False
                                        break
                                if readrx.pw2() != 1100:
                                        running = False
                                        break
                                pi.set_PWM_dutycycle(23, i)
                                time.sleep(0.001)
                                i -= 1
                                if i <= 0:
                                        break

def toggle32():
        if readrx.pw1() == 1900 and readrx.pw2() == 1900:
                print("aux 3 pw ={}, gear 2 pw ={}".format(int(readrx.pw1()), int(readrx.pw2())))
                pi.set_PWM_dutycycle(23, 255)
#               print "image-pov 3"
                while True:
                        for x in range(pov3.width):
                                strip.show(pov3.column[x])
                                time.sleep(.0005)
                                if readrx.pw1() != 1900:
                                        running = False
                                        break
                                if readrx.pw2() != 1900:
                                        running = False
                                        break
                        break


def toggle22():
        if readrx.pw1() == 1500 and readrx.pw2() == 1900:
                print("aux 2 pw ={}, gear 2 pw ={}".format(int(readrx.pw1()), int(readrx.pw2())))
                pi.set_PWM_dutycycle(23, 255)
#               print "image-pov 2"
                while True:
                        for x in range(pov2.width):
                                strip.show(pov2.column[x])
                                time.sleep(.0005)
                                if readrx.pw1() != 1500:
                                        running = False
                                        break
                                if readrx.pw2() != 1900:
                                        running = False
                                        break
                        break


def toggle12():
        running = True
        if readrx.pw1() == 1100 and readrx.pw2() == 1900:
                print("aux 1 pw ={}, gear 2 pw ={}".format(int(readrx.pw1()), int(readrx.pw2())))
                pi.set_PWM_dutycycle(23, 255)
#               print "image-pov 1"
                running = True
                while running:
                        for x in range(pov1.width):
                                strip.show(pov1.column[x])
                                time.sleep(.0005)
                                if readrx.pw1() != 1100:
                                        running = False
                                        break
                                if readrx.pw2() != 1900:
                                        running = False
                                        break
                        break


if __name__ == "__main__":
        SAMPLE_TIME = 0.01
        pi = pigpio.pi()
        pov1()#convert the image file
        pov2()
        pov3()

        print "Starting Main Loop"
        while True:
#               print dir(readrx)
#               print readrx.pw1()
                time.sleep(SAMPLE_TIME)
                toggle31()
                toggle21()
                toggle11()
                toggle32()
                toggle22()
                toggle12()


readrx.py
Code:
import time
import pigpio

PWM_GPIO_1 = 12 #PWM input gpio pins
PWM_GPIO_2 = 16

class reader:# Reads the Pulse Width signal of a gpio input and returns it's value

    def __init__(self, pi, gpio, weighting=0.0):
        self.pi = pi
        self.gpio = gpio

        if weighting < 0.0:
            weighting = 0.0
        elif weighting > 0.99:
            weighting = 0.99

        self._new = 1.0 - weighting # Weighting for new reading.
        self._old = weighting       # Weighting for old reading.

        self._high_tick = None
        self._period = None
        self._high = None

        pi.set_mode(gpio, pigpio.INPUT)

        self._cb = pi.callback(gpio, pigpio.EITHER_EDGE, self._cbf)

    def _cbf(self, gpio, level, tick):

        if level == 1:

            if self._high_tick is not None:
                t = pigpio.tickDiff(self._high_tick, tick)

                if self._period is not None:
                    self._period = (self._old * self._period) + (self._new * t)
                else:
                    self._period = t

            self._high_tick = tick

        elif level == 0:

            if self._high_tick is not None:
                t = pigpio.tickDiff(self._high_tick, tick)

                if self._high is not None:
                    self._high = (self._old * self._high) + (self._new * t)
                else:
                    self._high = t

    def pulse_width(self):       #Returns the PWM pulse width in microseconds.
        if self._high is not None:
          return ((round(self._high/100))*100) #round pwm to nerest hundred
        else:
          return 2.0

    def cancel(self):#      Cancels the reader and releases resources.
        self._cb.cancel()

pi = pigpio.pi()
p1 = reader(pi, PWM_GPIO_1)
p2 = reader(pi, PWM_GPIO_2)

def pw1():
    pw1 = p1.pulse_width()
    return pw1

def pw2():
    pw2 = p2.pulse_width()
    return pw2
Last edited by mtgtopdeck; Jan 21, 2016 at 03:48 PM.
Jan 25, 2016, 12:19 PM
DFC~ We Do Flyin' Right
Vapor Trails's Avatar
how much weight does all that LED controller and computer way?
Jan 25, 2016, 05:25 PM
Registered User
Quote:
Originally Posted by HaventFlownSince2014
how much weight does all that LED controller and computer way?
The computer itself weighs 9 grams
level shifter is 0.9 grams
wires + solder + breadboard + wood case is 20-30grams

APA102 60LED/Meter strips are about 11grams per meter
Jan 31, 2016, 08:23 PM
When pigs fly rc
rcrich's Avatar
Cool
Feb 07, 2016, 12:45 AM
Registered User
Quote:
Originally Posted by mtgtopdeck
I am planning to take it out to Sunnyvale Baylands on Saturday evening.
I went out to baylands today at around 3pm and flew a bit with my other planes while I waited for the sun to start setting. Three other pilots were out flying with me. Not a lot for a saturday.

Around 4:50 a sqaud car pulls up and an Officer walks over and waits for people to land. He tells us all the park is closing at 5pm today. It usually closes 30mins after sunset.
There goes my plans to take some long exposure shots of this wing.

So I pack up and head out. I go back home to a local park and decide to hop on a bike to try out a long exposure shot on the ground. With speeds less than half of what they would be in the air my camera man was able to get this AMAZING shot.
Feb 12, 2016, 04:36 AM
A.K.A sir Crashalot
methuso's Avatar
Omg. That's just too cool. Nice work. Piety you couldn't make it "of ground".
Mar 12, 2016, 10:45 PM
Registered User
Hi. I new at Raspberry Pis and SPI in general. I have all the code from https://github.com/tinue/APA102_Pi running on my Raspberry Pi using APA102 type led strip, so the code does work ok.


Thread Tools