From 4d15f21405280111d6ba4d21734b0d6ee345352d Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 14 Nov 2022 12:32:58 -0600 Subject: [PATCH 1/6] Support fewer than 8 parallel strips This requires slightly different PIO code, because a variable number of bits have to be thrown away --- adafruit_neopxl8.py | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/adafruit_neopxl8.py b/adafruit_neopxl8.py index b7d0eb0..051ceec 100644 --- a/adafruit_neopxl8.py +++ b/adafruit_neopxl8.py @@ -19,7 +19,7 @@ import adafruit_pixelbuf import rp2pio -_PROGRAM = """ +_PROGRAM8 = """ .program piopixl8 top: mov pins, null ; always-low part (last cycle is the 'pull ifempty' after wrap) @@ -46,7 +46,33 @@ jmp top """ -_ASSEMBLED = adafruit_pioasm.assemble(_PROGRAM) +_PROGRAMN = """ +.program piopixl8 +top: + mov pins, null ; always-low part (last cycle is the 'pull ifempty' after wrap) + pull block ; wait for fresh data + out y, 32 ; get count of NeoPixel bits + +; NeoPixels are 800khz bit streams. We are choosing zeros as <312ns hi, 936 lo> +; and ones as <700 ns hi, 546 ns lo> and a clock of 16*800kHz, so the always-high +; time is 4 cycles, the variable time is 5 cycles, and the always-low time is 7 cycles +bitloop: + pull ifempty [1] ; don't start outputting HIGH unless data is available (always-low part) + mov pins, ~ null [3] ; always-high part + out pins, {} [3] ; variable part + out x, {} ; variable part + mov pins, null ; always-low part (last cycle is the 'pull ifempty' after wrap) + + jmp y--, bitloop ; always-low part + +; A minimum delay is required so that the next pixel starts refreshing the front of the strands + pull block + out y, 32 + +wait_reset: + jmp y--, wait_reset + jmp top +""" # Pixel color order constants RGB = "RGB" @@ -135,11 +161,19 @@ def __init__( self._num_strands = num_strands + if num_strands == 8: + assembled = adafruit_pioasm.assemble(_PROGRAM8) + else: + program = _PROGRAMN.format(num_strands, 8 - num_strands) + print(program) + assembled = adafruit_pioasm.assemble(program) + + print("num strands is", num_strands) self._sm = rp2pio.StateMachine( - _ASSEMBLED, + assembled, frequency=800_000 * 16, first_out_pin=data0, - out_pin_count=8, + out_pin_count=num_strands, first_set_pin=data0, auto_pull=False, out_shift_right=True, From 267aca57e0e807fe3c99b4fc3aad1f1631c67dd6 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Mon, 14 Nov 2022 12:34:14 -0600 Subject: [PATCH 2/6] Make the animation demo more like the standard arduino chase demo This shows a phase shifted chase animation on each strip, each in a different color. --- examples/neopxl8_animations.py | 48 ++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/examples/neopxl8_animations.py b/examples/neopxl8_animations.py index 06bc417..8b64712 100644 --- a/examples/neopxl8_animations.py +++ b/examples/neopxl8_animations.py @@ -2,17 +2,12 @@ # # SPDX-License-Identifier: Unlicense -# Set up a separate animation on each of 8 strips with NeoPxl8 - import board -from adafruit_led_animation.animation.rainbow import Rainbow +import rainbowio +import adafruit_ticks from adafruit_led_animation.animation.comet import Comet -from adafruit_led_animation.animation.rainbowcomet import RainbowComet -from adafruit_led_animation.animation.rainbowchase import RainbowChase -from adafruit_led_animation.animation.chase import Chase from adafruit_led_animation.group import AnimationGroup from adafruit_led_animation.helper import PixelMap -from adafruit_led_animation import color from adafruit_neopxl8 import NeoPxl8 # Customize for your strands here @@ -28,30 +23,43 @@ num_pixels, num_strands=num_strands, auto_write=False, - brightness=0.07, + brightness=0.50, ) -def strand(i): +def strand(n): return PixelMap( pixels, - range(i * strand_length, (i + 1) * strand_length), + range(n * strand_length, (n + 1) * strand_length), individual_pixels=True, ) +# Create the 8 virtual strands strands = [strand(i) for i in range(num_strands)] -animations = AnimationGroup( - Comet(strands[0], 0.5, color.CYAN), - Comet(strands[1], 0.4, color.AMBER), - RainbowComet(strands[2], 0.3), - RainbowComet(strands[3], 0.7), - Chase(strands[4], 0.05, size=2, spacing=3, color=color.PURPLE), - RainbowChase(strands[5], 0.05, size=2, spacing=3), - Rainbow(strands[6], 0.6), - Rainbow(strands[7], 0.23, step=21), -) +# For each strand, create a comet animation of a different color +animations = [ + Comet(strand, 0.02, rainbowio.colorwheel(3 * 32 * i), ring=True) + for i, strand in enumerate(strands) +] + +# Advance the animations by varying amounts so that they become staggered +for i, animation in enumerate(animations): + animation._tail_start = 30 * 5 * i // 8 # pylint: disable=protected-access + +# Group them so we can run them all at once +animations = AnimationGroup(*animations) +# Run the animations and report on the speed in frame per secodn +t0 = adafruit_ticks.ticks_ms() +frame_count = 0 while True: animations.animate() + frame_count += 1 + t1 = adafruit_ticks.ticks_ms() + dt = adafruit_ticks.ticks_diff(t1, t0) + if dt > 1000: + print(f"{frame_count * 1000/dt:.1f}fps") + t0 = t1 + frame_count = 0 From 377e59b94405efeacd5b95e126ec8c2f51f93020 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 16 Nov 2022 10:11:48 -0600 Subject: [PATCH 3/6] Remove debug prints --- adafruit_neopxl8.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/adafruit_neopxl8.py b/adafruit_neopxl8.py index 051ceec..d77879b 100644 --- a/adafruit_neopxl8.py +++ b/adafruit_neopxl8.py @@ -165,10 +165,8 @@ def __init__( assembled = adafruit_pioasm.assemble(_PROGRAM8) else: program = _PROGRAMN.format(num_strands, 8 - num_strands) - print(program) assembled = adafruit_pioasm.assemble(program) - print("num strands is", num_strands) self._sm = rp2pio.StateMachine( assembled, frequency=800_000 * 16, From c86c6c0b625f4d83588febf5251b4ff89d7376ce Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 16 Nov 2022 10:12:25 -0600 Subject: [PATCH 4/6] Support 1-pixel strips .. this calls for a different memory layout to save memory size in the 'background' buffer --- adafruit_neopxl8.py | 57 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/adafruit_neopxl8.py b/adafruit_neopxl8.py index d77879b..6631a74 100644 --- a/adafruit_neopxl8.py +++ b/adafruit_neopxl8.py @@ -74,6 +74,33 @@ jmp top """ +_PROGRAM1 = """ +.program piopixl8 +top: + mov pins, null ; always-low part (last cycle is the 'pull ifempty' after wrap) + pull block ; wait for fresh data + out y, 32 ; get count of NeoPixel bits + +; NeoPixels are 800khz bit streams. We are choosing zeros as <312ns hi, 936 lo> +; and ones as <700 ns hi, 546 ns lo> and a clock of 16*800kHz, so the always-high +; time is 4 cycles, the variable time is 5 cycles, and the always-low time is 7 cycles +bitloop: + pull ifempty [1] ; don't start outputting HIGH unless data is available (always-low part) + mov pins, ~ null [3] ; always-high part + out pins, 1 [4] ; variable part + mov pins, null ; always-low part (last cycle is the 'pull ifempty' after wrap) + + jmp y--, bitloop ; always-low part + +; A minimum delay is required so that the next pixel starts refreshing the front of the strands + pull block + out y, 32 + +wait_reset: + jmp y--, wait_reset + jmp top +""" + # Pixel color order constants RGB = "RGB" """Red Green Blue""" @@ -150,19 +177,31 @@ def __init__( n, brightness=brightness, byteorder=pixel_order, auto_write=auto_write ) - data_len = bpp * n * 8 // num_strands + if num_strands == 1: + data_len = bpp * n + pack = ">L" + osr = False + n = (8 * data_len) - 1 + else: + data_len = bpp * n * 8 // num_strands + pack = " Date: Wed, 16 Nov 2022 10:16:58 -0600 Subject: [PATCH 5/6] reduce amount of pio code strings --- adafruit_neopxl8.py | 71 +++++++-------------------------------------- 1 file changed, 11 insertions(+), 60 deletions(-) diff --git a/adafruit_neopxl8.py b/adafruit_neopxl8.py index 6631a74..135b2e5 100644 --- a/adafruit_neopxl8.py +++ b/adafruit_neopxl8.py @@ -19,7 +19,7 @@ import adafruit_pixelbuf import rp2pio -_PROGRAM8 = """ +_PROGRAM = """ .program piopixl8 top: mov pins, null ; always-low part (last cycle is the 'pull ifempty' after wrap) @@ -32,7 +32,7 @@ bitloop: pull ifempty [1] ; don't start outputting HIGH unless data is available (always-low part) mov pins, ~ null [3] ; always-high part - out pins, 8 [4] ; variable part + {} ; variable part mov pins, null ; always-low part (last cycle is the 'pull ifempty' after wrap) jmp y--, bitloop ; always-low part @@ -46,60 +46,6 @@ jmp top """ -_PROGRAMN = """ -.program piopixl8 -top: - mov pins, null ; always-low part (last cycle is the 'pull ifempty' after wrap) - pull block ; wait for fresh data - out y, 32 ; get count of NeoPixel bits - -; NeoPixels are 800khz bit streams. We are choosing zeros as <312ns hi, 936 lo> -; and ones as <700 ns hi, 546 ns lo> and a clock of 16*800kHz, so the always-high -; time is 4 cycles, the variable time is 5 cycles, and the always-low time is 7 cycles -bitloop: - pull ifempty [1] ; don't start outputting HIGH unless data is available (always-low part) - mov pins, ~ null [3] ; always-high part - out pins, {} [3] ; variable part - out x, {} ; variable part - mov pins, null ; always-low part (last cycle is the 'pull ifempty' after wrap) - - jmp y--, bitloop ; always-low part - -; A minimum delay is required so that the next pixel starts refreshing the front of the strands - pull block - out y, 32 - -wait_reset: - jmp y--, wait_reset - jmp top -""" - -_PROGRAM1 = """ -.program piopixl8 -top: - mov pins, null ; always-low part (last cycle is the 'pull ifempty' after wrap) - pull block ; wait for fresh data - out y, 32 ; get count of NeoPixel bits - -; NeoPixels are 800khz bit streams. We are choosing zeros as <312ns hi, 936 lo> -; and ones as <700 ns hi, 546 ns lo> and a clock of 16*800kHz, so the always-high -; time is 4 cycles, the variable time is 5 cycles, and the always-low time is 7 cycles -bitloop: - pull ifempty [1] ; don't start outputting HIGH unless data is available (always-low part) - mov pins, ~ null [3] ; always-high part - out pins, 1 [4] ; variable part - mov pins, null ; always-low part (last cycle is the 'pull ifempty' after wrap) - - jmp y--, bitloop ; always-low part - -; A minimum delay is required so that the next pixel starts refreshing the front of the strands - pull block - out y, 32 - -wait_reset: - jmp y--, wait_reset - jmp top -""" # Pixel color order constants RGB = "RGB" @@ -199,12 +145,17 @@ def __init__( self._num_strands = num_strands if num_strands == 8: - assembled = adafruit_pioasm.assemble(_PROGRAM8) + variable_part = "out pins, 8 [4] ; variable part" elif num_strands == 1: - assembled = adafruit_pioasm.assemble(_PROGRAM1) + variable_part = "out pins, 1 [4] ; variable part" else: - program = _PROGRAMN.format(num_strands, 8 - num_strands) - assembled = adafruit_pioasm.assemble(program) + variable_part = f""" + out pins, {num_strands} [3] ; variable part + out x, {8-num_strands} ; variable part + """ + + program = _PROGRAM.format(variable_part) + assembled = adafruit_pioasm.assemble(program) self._sm = rp2pio.StateMachine( assembled, From 674782fe45307492c63277e29a20bc1fc36317b2 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Wed, 16 Nov 2022 10:20:28 -0600 Subject: [PATCH 6/6] pylint --- adafruit_neopxl8.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/adafruit_neopxl8.py b/adafruit_neopxl8.py index 135b2e5..1ee5404 100644 --- a/adafruit_neopxl8.py +++ b/adafruit_neopxl8.py @@ -109,7 +109,7 @@ def __init__( brightness=1.0, auto_write=True, pixel_order=None, - ): + ): # pylint: disable=too-many-locals if n % num_strands: raise ValueError("Length must be a multiple of num_strands") if not pixel_order: @@ -127,20 +127,20 @@ def __init__( data_len = bpp * n pack = ">L" osr = False - n = (8 * data_len) - 1 + loop_count = 8 * data_len else: data_len = bpp * n * 8 // num_strands pack = "