Skip to content

Commit

Permalink
Work in progress: Use Christmas tree as an example
Browse files Browse the repository at this point in the history
  • Loading branch information
tinue committed Dec 23, 2021
1 parent 527e800 commit 7be3ae2
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 32 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ This way I can reprogram the light show from my desk, even if the strips sit out
as a Christmas light. Compare this to an Arduino/WS2812 based installation: To reprogram one has
to take the Arduino inside, or a laptop outside.


This is a Raspberry Pi 4 with a 3D RGB Xmas Tree from Pi Hut:

![Raspberry Pi 4 with Xmas tree](xmastree.jpg)

## Quick Raspberry Pi setup
Because the Raspberry Pi Zero runs headless, I recommend using the Raspberry Pi OS *Lite* image.
This image only contains the bare minimum of packages, and some packages have be added manually.
Expand Down
62 changes: 32 additions & 30 deletions apa102_pi/driver/apa102.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""This is the main driver module for APA102 LEDs"""
import busio
from math import ceil

import adafruit_bitbangio as bitbangio
import digitalio
import board
import busio
import digitalio
from adafruit_bus_device.spi_device import SPIDevice
from microcontroller.pin import spiPorts
from math import ceil

RGB_MAP = {'rgb': [3, 2, 1], 'rbg': [3, 1, 2], 'grb': [2, 3, 1],
'gbr': [2, 1, 3], 'brg': [1, 3, 2], 'bgr': [1, 2, 3]}
Expand Down Expand Up @@ -76,7 +77,8 @@ class APA102:
# Constants
LED_START = 0b11100000 # Three "1" bits, followed by 5 brightness bits

def __init__(self, num_led=8, order='rgb', bus_method='spi', spi_bus=0, mosi=None, sclk=None, ce=None, bus_speed_hz=8000000, global_brightness=4):
def __init__(self, num_led=8, order='rgb', bus_method='spi', spi_bus=0, mosi=None, sclk=None, ce=None,
bus_speed_hz=8000000, global_brightness=4):
"""Initializes the library
:param num_led: Number of LEDs in the strip
Expand All @@ -92,34 +94,14 @@ def __init__(self, num_led=8, order='rgb', bus_method='spi', spi_bus=0, mosi=Non
"""

spi_ports = {}
for id, SCLK, MOSI, MISO in spiPorts:
spi_ports[id] = {'SCLK':SCLK, 'MOSI':MOSI, 'MISO':MISO}
for id_port, sclk_port, mosi_port, miso_port in spiPorts:
spi_ports[id_port] = {'SCLK': sclk_port, 'MOSI': mosi_port, 'MISO': miso_port}

# Just in case someone use CAPS here.
order = order.lower()
bus_method = bus_method.lower()

if num_led <= 0:
raise ValueError("Illegal num_led can not be 0 or less")
if num_led > 1024:
raise ValueError("Illegal num_led only supported upto 1024 leds")

if order not in RGB_MAP:
raise ValueError("Illegal order not in %s" % list(RGB_MAP.keys()))

if bus_method not in ['spi', 'bitbang']:
raise ValueError("Illegal bus_method use spi or bitbang")

if bus_method == 'spi':
if spi_bus not in spi_ports:
raise ValueError("Illegal spi_bus not in %s" % list(temp.keys()))

if bus_method == 'bitbang':
if mosi == sclk:
raise ValueError("Illegal MOSI / SCLK can not be the same")

if global_brightness < 0 or global_brightness > 31:
raise ValueError("Illegal global_brightness min 0 max 31")
self.check_input(bus_method, global_brightness, mosi, num_led, order, sclk, spi_bus, spi_ports)

self.num_led = num_led
self.rgb = RGB_MAP.get(order, RGB_MAP['rgb'])
Expand All @@ -134,7 +116,7 @@ def __init__(self, num_led=8, order='rgb', bus_method='spi', spi_bus=0, mosi=Non
self.spi = busio.SPI(clock=selected['SCLK'], MOSI=selected['MOSI'])

elif bus_method == 'bitbang':
self.spi = bitbangio.SPI(clock=eval("board.D"+str(sclk)), MOSI=eval("board.D"+str(mosi)))
self.spi = bitbangio.SPI(clock=eval("board.D" + str(sclk)), MOSI=eval("board.D" + str(mosi)))
self.use_bitbang = True

if ce is not None:
Expand All @@ -143,12 +125,12 @@ def __init__(self, num_led=8, order='rgb', bus_method='spi', spi_bus=0, mosi=Non
# The next line is just here to prevent an "unused" warning from the IDE
digitalio.DigitalInOut(board.D1)
# Convert the chip enable pin number into an object (reflection à la Python)
ce = eval("digitalio.DigitalInOut(board.D"+str(ce)+")")
ce = eval("digitalio.DigitalInOut(board.D" + str(ce) + ")")
self.use_ce = True

# Add the BusDevice on top of the raw SPI
if self.use_ce:
self.spibus = SPIDevice(spi=self.spi, chip_select=ce, baudrate=bus_speed_hz)
self.spibus = SPIDevice(spi=self.spi, chip_select=ce, baudrate=bus_speed_hz)
else:
# If the BusDevice is not used, the bus speed is set here instead
while not self.spi.try_lock():
Expand All @@ -164,6 +146,26 @@ def __init__(self, num_led=8, order='rgb', bus_method='spi', spi_bus=0, mosi=Non
else:
print("Use hardware SPI")

@staticmethod
def check_input(bus_method, global_brightness, mosi, num_led, order, sclk, spi_bus, spi_ports):
"""
Checks the input values for validity
1 """
if num_led <= 0:
raise ValueError("Illegal num_led can not be 0 or less")
if num_led > 1024:
raise ValueError("Illegal num_led only supported upto 1024 leds")
if order not in RGB_MAP:
raise ValueError("Illegal order not in %s" % list(RGB_MAP.keys()))
if bus_method not in ['spi', 'bitbang']:
raise ValueError("Illegal bus_method use spi or bitbang")
if bus_method == 'spi' and spi_bus not in spi_ports:
raise ValueError("Illegal spi_bus not in %s" % list(spi_ports))
if bus_method == 'bitbang' and mosi == sclk:
raise ValueError("Illegal MOSI / SCLK can not be the same")
if global_brightness < 0 or global_brightness > 31:
raise ValueError("Illegal global_brightness min 0 max 31")

def clock_start_frame(self):
"""Sends a start frame to the LED strip.
Expand Down
46 changes: 46 additions & 0 deletions apa102_pi/driver/test_apa102.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Very rudimentary test class, might get extended in the future"""
from unittest import TestCase

import apa102


class TestAPA102(TestCase):
# Check num_led
def test_check_init(self):
with self.assertRaises(ValueError):
apa102.APA102(num_led=-1)
with self.assertRaises(ValueError):
apa102.APA102(num_led=0)
with self.assertRaises(ValueError):
apa102.APA102(num_led=1025)
try:
apa102.APA102(num_led=1)
except ValueError:
self.fail("num_led 1 should be valid")
try:
apa102.APA102(num_led=1024)
except ValueError:
self.fail("num_led 1024 should be valid")
# Check bus_method
with self.assertRaises(ValueError):
apa102.APA102(bus_method='invalid')
try:
apa102.APA102(bus_method='spi')
except ValueError:
self.fail("spi should be valid")
try:
apa102.APA102(bus_method='bitbang', mosi=12, sclk=25)
except ValueError:
self.fail("bitbang should be valid")
# Check spi-bus
with self.assertRaises(ValueError):
apa102.APA102(bus_method='spi', spi_bus=9)
try:
apa102.APA102(bus_method='spi', spi_bus=0)
except ValueError:
self.fail("bus 0 should exist")
# Check bitbang
with self.assertRaises(ValueError):
apa102.APA102(bus_method='bitbang')
with self.assertRaises(ValueError):
apa102.APA102(bus_method='bitbang', mosi=25, sclk=25)
4 changes: 2 additions & 2 deletions runcolorcycle_tree.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Sample script to run a few colour tests on a christmas tree."""
"""Sample script to run a few colour tests on a Pi Hut '3D RGB Xmas Tree for Raspberry Pi'."""
from apa102_pi.colorschemes import colorschemes

NUM_LED = 25
Expand All @@ -17,7 +17,7 @@ def main():
# Five trips through the rainbow
print('Five trips through the rainbow')
my_cycle = colorschemes.Rainbow(num_led=NUM_LED, pause_value=0, order='rgb',
num_steps_per_cycle=255, num_cycles=5, bus_method='bitbang', mosi=MOSI, sclk=SCLK)
num_steps_per_cycle=100, num_cycles=10, bus_method='bitbang', mosi=MOSI, sclk=SCLK)
my_cycle.start()

print('Finished the test')
Expand Down
Binary file added xmastree.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 7be3ae2

Please sign in to comment.