Skip to content

Commit

Permalink
Finished two LED strip examples
Browse files Browse the repository at this point in the history
  • Loading branch information
ZodiusInfuser committed Oct 12, 2023
1 parent 5afaf19 commit 2afd769
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 24 deletions.
106 changes: 106 additions & 0 deletions examples/modules/led_strip/multiple_strips.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
from pimoroni_yukon import Yukon
from pimoroni_yukon.modules import LEDStripModule
from pimoroni_yukon.timing import ticks_ms, ticks_add
from pimoroni_yukon.logging import LOG_WARN

"""
How to drive multiple Neopixel or Dotstar LED strips with a set of LED Strip Modules connected to slots.
A cycling rainbow pattern will be played on the attached strips.
"""

# Constants
STRIP_TYPE = LEDStripModule.NEOPIXEL # Change to LEDStripModule.DOTSTAR for APA102 style strips
# Two Neopixel strips can be driven too, by using LEDStripModule.DUAL_NEOPIXEL
LEDS_PER_STRIP = 60 # How many LEDs are on the strip. If using DUAL_NEOPIXEL this can be a tuple
BRIGHTNESS = 1.0 # The max brightness of the LEDs (only supported by APA102s)
SLEEP = 0.02 # The time to sleep between each update
SPEED = 0.01 # How much to advance the rainbow hue offset by each update
RAINBOW_SAT = 1.0 # The saturation of the rainbow
RAINBOW_VAL = 1.0 # The value (brightness) of the rainbow

# Variables
yukon = Yukon(logging_level=LOG_WARN) # A new Yukon object, with its logging level lowered
modules = [] # A list to store LEDStripModule objects created later
hue_offset = 0 # The offset used to animate the rainbow


# Function for applying a rainbow pattern to an LED strip
def update_rainbow(strip, offset):
for led in range(strip.num_leds()):
# Calculate a hue for the LED based on its position in the strip and the offset
hue = (led / strip.num_leds()) + offset
strip.set_hsv(led, hue, RAINBOW_SAT, RAINBOW_VAL)

# Send the new colours to the LED strip
strip.update()

# Generator to get the next PIO and State Machine numbers
def pio_and_sm_generator():
pio = 0
sm = 0
while True:
yield (pio, sm) # Return the next pair of PIO and SM values

if STRIP_TYPE == LEDStripModule.DUAL_NEOPIXEL:
sm += 2 # Advance by two SMs if two Neopixel strips are being used per module
else:
sm += 1 # Otherwise a single strip is being used so advance by one SM

# Wrap the SM and increment the PIO
if sm > 3:
sm -= 4
pio += 1


pio_and_sm = pio_and_sm_generator() # An instance of the generator


# Wrap the code in a try block, to catch any exceptions (including KeyboardInterrupt)
try:

# Find out which slots of Yukon has LEDStripModules attached
for slot in yukon.find_slots_with(LEDStripModule):
pio, sm = next(pio_and_sm) # Get the next PIO and State Machine numbers
module = LEDStripModule(STRIP_TYPE, # Create a LEDStripModule object, with the details of the attached strip(s)
pio,
sm,
LEDS_PER_STRIP,
BRIGHTNESS)
yukon.register_with_slot(module, slot) # Register the LEDStripModule object with the slot
modules.append(module) # Add the object to the module list

yukon.verify_and_initialise() # Verify that LEDStripModules are attached to Yukon, and initialise them
yukon.enable_main_output() # Turn on power to the module slots

for module in modules:
module.enable() # Enable each LEDStripModule's onboard 5V regulator

current_time = ticks_ms() # Record the start time of the program loop

# Loop until the BOOT/USER button is pressed
while not yukon.is_boot_pressed():

for module in modules:
if STRIP_TYPE == LEDStripModule.DUAL_NEOPIXEL:
# Update the rainbows of both Neopixel strips, if in that mode
update_rainbow(module.strip1, hue_offset)
update_rainbow(module.strip2, hue_offset)
else:
# Otherwise, just update the single Neopixel or Dotstar strip
update_rainbow(module.strip, hue_offset)

# Advance the rainbow offset, wrapping if it exceeds 1.0
hue_offset += SPEED
if hue_offset >= 1.0:
hue_offset -= 1.0

# Advance the current time by a number of seconds
current_time = ticks_add(current_time, int(SLEEP * 1000))

# Monitor sensors until the current time is reached, recording the min, max, and average for each
# This approach accounts for the updating of the rainbows taking a non-zero amount of time to complete
yukon.monitor_until_ms(current_time)

finally:
# Put the board back into a safe state, regardless of how the program may have ended
yukon.reset()
79 changes: 79 additions & 0 deletions examples/modules/led_strip/single_strip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from pimoroni_yukon import Yukon
from pimoroni_yukon import SLOT1 as SLOT
from pimoroni_yukon.modules import LEDStripModule
from pimoroni_yukon.timing import ticks_ms, ticks_add
from pimoroni_yukon.logging import LOG_WARN

"""
How to drive a Neopixel or Dotstar LED strip with a LED Strip Module connected to Slot1.
A cycling rainbow pattern will be played on the attached strip(s).
"""

# Constants
STRIP_TYPE = LEDStripModule.NEOPIXEL # Change to LEDStripModule.DOTSTAR for APA102 style strips
# Two Neopixel strips can be driven too, by using LEDStripModule.DUAL_NEOPIXEL
STRIP_PIO = 0 # The PIO system to use (0 or 1) to drive the strip(s)
STRIP_SM = 0 # The State Machines (SM) to use to drive the strip(s)
LEDS_PER_STRIP = 60 # How many LEDs are on the strip. If using DUAL_NEOPIXEL this can be a tuple
BRIGHTNESS = 1.0 # The max brightness of the LEDs (only supported by APA102s)
SLEEP = 0.02 # The time to sleep between each update
SPEED = 0.01 # How much to advance the rainbow hue offset by each update
RAINBOW_SAT = 1.0 # The saturation of the rainbow
RAINBOW_VAL = 1.0 # The value (brightness) of the rainbow

# Variables
yukon = Yukon(logging_level=LOG_WARN) # Create a new Yukon object, with its logging level lowered
module = LEDStripModule(STRIP_TYPE, # Create a LEDStripModule object, with the details of the attached strip(s)
STRIP_PIO,
STRIP_SM,
LEDS_PER_STRIP,
BRIGHTNESS)
hue_offset = 0 # The offset used to animate the rainbow


# Function for applying a rainbow pattern to an LED strip
def update_rainbow(strip, offset):
for led in range(strip.num_leds()):
# Calculate a hue for the LED based on its position in the strip and the offset
hue = (led / strip.num_leds()) + offset
strip.set_hsv(led, hue, RAINBOW_SAT, RAINBOW_VAL)

# Send the new colours to the LED strip
strip.update()


# Wrap the code in a try block, to catch any exceptions (including KeyboardInterrupt)
try:
yukon.register_with_slot(module, SLOT) # Register the LEDStripModule object with the slot
yukon.verify_and_initialise() # Verify that a LEDStripModule is attached to Yukon, and initialise it
yukon.enable_main_output() # Turn on power to the module slots
module.enable() # Enable the LEDStripModule's onboard regulator

current_time = ticks_ms() # Record the start time of the program loop

# Loop until the BOOT/USER button is pressed
while not yukon.is_boot_pressed():

if STRIP_TYPE == LEDStripModule.DUAL_NEOPIXEL:
# Update the rainbows of both Neopixel strips, if in that mode
update_rainbow(module.strip1, hue_offset)
update_rainbow(module.strip2, hue_offset)
else:
# Otherwise, just update the single Neopixel or Dotstar strip
update_rainbow(module.strip, hue_offset)

# Advance the rainbow offset, wrapping if it exceeds 1.0
hue_offset += SPEED
if hue_offset >= 1.0:
hue_offset -= 1.0

# Advance the current time by a number of seconds
current_time = ticks_add(current_time, int(SLEEP * 1000))

# Monitor sensors until the current time is reached, recording the min, max, and average for each
# This approach accounts for the updating of the rainbows taking a non-zero amount of time to complete
yukon.monitor_until_ms(current_time)

finally:
# Put the board back into a safe state, regardless of how the program may have ended
yukon.reset()
2 changes: 1 addition & 1 deletion examples/yukon/set_expansion.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"""

# Constants
SLEEP = 0.5 # The time to sleep between each reading
SLEEP = 0.5 # The time to sleep between each toggle

# Variables
yukon = Yukon() # A new Yukon object
Expand Down
2 changes: 1 addition & 1 deletion examples/yukon/set_slot.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"""

# Constants
SLEEP = 0.5 # The time to sleep between each reading
SLEEP = 0.5 # The time to sleep between each toggle

# Variables
yukon = Yukon() # A new Yukon object
Expand Down
48 changes: 31 additions & 17 deletions lib/pimoroni_yukon/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class Yukon:

OUTPUT_DISSIPATE_TIMEOUT_S = 15 # When a bench power module is attached and there is no additional output load, it can take a while for it to return to an idle state
OUTPUT_DISSIPATE_TIMEOUT_US = OUTPUT_DISSIPATE_TIMEOUT_S * 1000 * 1000
OUTPUT_DISSIPATE_TIME_US = 10 * 1000
OUTPUT_DISSIPATE_LEVEL = 0.4 # The voltage below which we can reliably obtain the address of attached modules

def __init__(self, voltage_limit=DEFAULT_VOLTAGE_LIMIT, current_limit=DEFAULT_CURRENT_LIMIT, temperature_limit=DEFAULT_TEMPERATURE_LIMIT, logging_level=logging.LOG_INFO):
Expand Down Expand Up @@ -237,10 +238,33 @@ def __check_slot(self, slot):

return slot

def find_slots_with_module(self, module_type):
def __check_output_dissipated(self, message):
logging.info("> Checking output voltage ...")
if self.read_output_voltage() >= self.OUTPUT_DISSIPATE_LEVEL:
logging.warn("> Waiting for output voltage to dissipate ...")

start = time.ticks_us()
first_below_time = 0
while True:
new_voltage = self.read_output_voltage()
new_time = time.ticks_us()
if new_voltage < self.OUTPUT_DISSIPATE_LEVEL:
if first_below_time == 0:
first_below_time = new_time
elif ticks_diff(new_time, first_below_time) > self.OUTPUT_DISSIPATE_TIME_US:
break
else:
first_below_time = 0

if ticks_diff(new_time, start) > self.OUTPUT_DISSIPATE_TIMEOUT_US:
raise FaultError(f"[Yukon] Output voltage did not dissipate in an acceptable time. Aborting {message}")

def find_slots_with(self, module_type):
if self.is_main_output_enabled():
raise RuntimeError("Cannot find slots with modules whilst the main output is active")

self.__check_output_dissipated("module finding")

logging.info(f"> Finding slots with '{module_type.NAME}' module")

slots = []
Expand Down Expand Up @@ -325,9 +349,11 @@ def __detect_module(self, slot):

return detected

def detect_module(self, slot):
def detect_in_slot(self, slot):
if self.is_main_output_enabled():
raise RuntimeError("Cannot detect modules whilst the main output is active")
raise RuntimeError("Cannot detect module whilst the main output is active")

self.__check_output_dissipated("module detection")

slot = self.__check_slot(slot)

Expand Down Expand Up @@ -399,23 +425,11 @@ def __verify_modules(self, allow_unregistered, allow_undetected, allow_discrepen

logging.info() # New line

def initialise_modules(self, allow_unregistered=False, allow_undetected=False, allow_discrepencies=False, allow_no_modules=False):
def verify_and_initialise(self, allow_unregistered=False, allow_undetected=False, allow_discrepencies=False, allow_no_modules=False):
if self.is_main_output_enabled():
raise RuntimeError("Cannot verify modules whilst the main output is active")

logging.info("> Checking output voltage ...")
if self.read_output_voltage() >= self.OUTPUT_DISSIPATE_LEVEL:
logging.info("> Waiting for output voltage to dissipate ...")

start = time.ticks_us()
while True:
new_voltage = self.read_output_voltage()
if new_voltage < self.OUTPUT_DISSIPATE_LEVEL:
break

new_time = time.ticks_us()
if ticks_diff(new_time, start) > self.OUTPUT_DISSIPATE_TIMEOUT_US:
raise FaultError("[Yukon] Output voltage did not dissipate in an acceptable time. Aborting module initialisation")
self.__check_output_dissipated("module initialisation")

logging.info("> Verifying modules")

Expand Down
28 changes: 23 additions & 5 deletions lib/pimoroni_yukon/modules/led_strip.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,24 @@ class LEDStripModule(YukonModule):
def is_module(adc_level, slow1, slow2, slow3):
return adc_level == ADC_LOW and slow1 is HIGH and slow2 is HIGH and slow3 is HIGH

def __init__(self, strip_type, num_leds, brightness=1.0, halt_on_not_pgood=False):
def __init__(self, strip_type, pio, sm, num_leds, brightness=1.0, halt_on_not_pgood=False):
super().__init__()

if strip_type < 0 or strip_type > 2:
raise ValueError("strip_type out of range. Expected 0 (NEOPIXEL), 1 (DUAL_NEOPIXEL) or 2 (DOTSTAR)")

if pio < 0 or pio > 1:
raise ValueError("pio out of range. Expected 0 or 1")

if sm < 0 or sm > 3:
raise ValueError("sm out of range. Expected 0 to 3")

if num_leds <= 0:
raise ValueError("num_leds out of range. Expected greater than 0")

if strip_type == self.DOTSTAR and (brightness < 0.0 or brightness > 1.0):
raise ValueError("brightness out of range. Expected 0.0 to 1.0")

self.__strip_type = strip_type
if self.__strip_type == self.NEOPIXEL:
self.NAME += " (NeoPixel)"
Expand All @@ -33,6 +49,8 @@ def __init__(self, strip_type, num_leds, brightness=1.0, halt_on_not_pgood=False
else:
self.NAME += " (DotStar)"

self.__pio = pio
self.__sm = sm
self.__num_leds = num_leds
self.__brightness = brightness
self.halt_on_not_pgood = halt_on_not_pgood
Expand All @@ -48,13 +66,13 @@ def initialise(self, slot, adc1_func, adc2_func):
if not isinstance(num_leds, (list, tuple)):
num_leds = (num_leds, num_leds)

self.strips = [WS2812(num_leds[0], 0, 0, slot.FAST4),
WS2812(num_leds[1], 0, 1, slot.FAST3)]
self.strips = [WS2812(num_leds[0], self.__pio, self.__sm, slot.FAST4),
WS2812(num_leds[1], self.__pio, (self.__sm + 1) % 4, slot.FAST3)]
else:
self.strip = WS2812(self.__num_leds, 0, 0, slot.FAST4)
self.strip = WS2812(self.__num_leds, self.__pio, self.__sm, slot.FAST4)
else:
from plasma import APA102
self.strip = APA102(self.__num_leds, 0, 0, slot.FAST4, slot.FAST3)
self.strip = APA102(self.__num_leds, self.__pio, self.__sm, slot.FAST4, slot.FAST3)
self.strip.set_brightness(int(self.__brightness * 31))

# Create the power control pin objects
Expand Down

0 comments on commit 2afd769

Please sign in to comment.