From 8c76ff665759aeaac9c03375ba1b64ac84d95f37 Mon Sep 17 00:00:00 2001 From: Max Carlson <> Date: Mon, 5 Feb 2024 11:27:41 +0200 Subject: [PATCH 1/7] Replace PIO code with an inbuilt timer, based on https://learn.adafruit.com/intro-to-rp2040-pio-with-circuitpython/advanced-using-pio-to-drive-neopixels-in-the-background --- neopixel.py | 65 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/neopixel.py b/neopixel.py index 6bb00a4..d24a035 100644 --- a/neopixel.py +++ b/neopixel.py @@ -1,4 +1,4 @@ -import array, time +import array, time, struct from machine import Pin import rp2 @@ -20,18 +20,28 @@ def ws2812(): # PIO state machine for RGBW. Pulls 32 bits (rgbw -> 4 * 8bit) automatically -@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=32) +@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=False, pull_thresh=32) def sk6812(): - T1 = 2 - T2 = 5 - T3 = 3 wrap_target() + pull(block) .side(0) + out(y, 32) .side(0) + label("bitloop") - out(x, 1) .side(0) [T3 - 1] - jmp(not_x, "do_zero") .side(1) [T1 - 1] - jmp("bitloop") .side(1) [T2 - 1] + pull(ifempty) .side(0) + out(x, 1) .side(0) [5] + jmp(not_x, "do_zero") .side(1) [3] + jmp(y_dec, "bitloop") .side(1) [4] + jmp("end_sequence") .side(0) + label("do_zero") - nop() .side(0) [T2 - 1] + jmp(y_dec, "bitloop") .side(0) [4] + + label("end_sequence") + pull(block) .side(0) + out(y, 32) .side(0) + + label("wait_reset") + jmp(y_dec, "wait_reset") .side(0) wrap() @@ -73,7 +83,7 @@ class Neopixel: # 'brightnessvalue', # brightness scale factor 1..255 # ] - def __init__(self, num_leds, state_machine, pin, mode="RGB", delay=0.0001): + def __init__(self, num_leds, state_machine, pin, mode="RGB", delay=3): """ Constructor for library class @@ -85,16 +95,19 @@ def __init__(self, num_leds, state_machine, pin, mode="RGB", delay=0.0001): :param delay: [default: 0.0001] delay used for latching of leds when sending data """ self.pixels = array.array("I", [0] * num_leds) + # self.pixels_out = array.array("I", [0] * num_leds) self.mode = mode self.W_in_mode = 'W' in mode + bpp = 3 if self.W_in_mode: # RGBW uses different PIO state machine configuration - self.sm = rp2.StateMachine(state_machine, sk6812, freq=8000000, sideset_base=Pin(pin)) + self.sm = rp2.StateMachine(state_machine, sk6812, freq=12_800_000, sideset_base=Pin(pin)) # tuple of values required to shift bit into position (check class desc.) self.shift = ((mode.index('R') ^ 3) * 8, (mode.index('G') ^ 3) * 8, (mode.index('B') ^ 3) * 8, (mode.index('W') ^ 3) * 8) + bpp = 4 else: - self.sm = rp2.StateMachine(state_machine, ws2812, freq=8000000, sideset_base=Pin(pin)) + self.sm = rp2.StateMachine(state_machine, ws2812, freq=12_800_000, sideset_base=Pin(pin)) self.shift = (((mode.index('R') ^ 3) - 1) * 8, ((mode.index('G') ^ 3) - 1) * 8, ((mode.index('B') ^ 3) - 1) * 8, 0) self.sm.active(1) @@ -102,6 +115,13 @@ def __init__(self, num_leds, state_machine, pin, mode="RGB", delay=0.0001): self.delay = delay self.brightnessvalue = 255 + byte_count = bpp * num_leds + bit_count = byte_count * 8 + padding_count = -byte_count % 4 + self.header = bytearray(struct.pack("L", bit_count - 1)) + self.trailer = bytearray(b"\0" * padding_count + struct.pack("L", 3840)) + + def brightness(self, brightness=None): """ Set the overall value to adjust brightness when updating leds @@ -229,6 +249,7 @@ def __setitem__(self, idx, rgb_w): npix[15:21] = (255,0,0) # <- sets 16,17 .. 20 to red npix[21:29:2] = (0,0,255) # <- sets 21,23,25,27 to blue npix[1::2] = (0,0,0) # <- sets all odd pixels to 'off' + npix[:] = [(0,5,0),(0,5,0)] # <- replaces all pixels with those from the array (the 'slice' cases pass idx as a 'slice' object, and set_pixel processes the slice) @@ -236,7 +257,17 @@ def __setitem__(self, idx, rgb_w): :param rgb_w: Tuple of form (r, g, b) or (r, g, b, w) representing color to be used :return: """ - self.set_pixel(idx, rgb_w) + if type(rgb_w) is list: + for i in range(self.num_leds): + self.set_pixel(i, rgb_w[i]) + else: + self.set_pixel(idx, rgb_w) + + def __len__(self): + return self.num_leds + + def __getitem__(self, idx): + return self.get_pixel(idx) def colorHSV(self, hue, sat, val): """ @@ -326,10 +357,10 @@ def show(self): cut = 8 if self.W_in_mode: cut = 0 - sm_put = self.sm.put - for pixval in self.pixels: - sm_put(pixval, cut) - time.sleep(self.delay) + + self.sm.put(self.header, 0) + self.sm.put(memoryview(self.pixels), cut) + self.sm.put(self.trailer, 0) def fill(self, rgb_w, how_bright=None): """ From 16026e5d887b3149f3e747cb9fa8f51b068234fa Mon Sep 17 00:00:00 2001 From: Max Carlson <> Date: Wed, 7 Feb 2024 16:15:35 +0200 Subject: [PATCH 2/7] Back out changes from https://github.com/blaz-r/pi_pico_neopixel/pull/19 --- neopixel.py | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/neopixel.py b/neopixel.py index d24a035..a4ba900 100644 --- a/neopixel.py +++ b/neopixel.py @@ -249,7 +249,6 @@ def __setitem__(self, idx, rgb_w): npix[15:21] = (255,0,0) # <- sets 16,17 .. 20 to red npix[21:29:2] = (0,0,255) # <- sets 21,23,25,27 to blue npix[1::2] = (0,0,0) # <- sets all odd pixels to 'off' - npix[:] = [(0,5,0),(0,5,0)] # <- replaces all pixels with those from the array (the 'slice' cases pass idx as a 'slice' object, and set_pixel processes the slice) @@ -257,17 +256,7 @@ def __setitem__(self, idx, rgb_w): :param rgb_w: Tuple of form (r, g, b) or (r, g, b, w) representing color to be used :return: """ - if type(rgb_w) is list: - for i in range(self.num_leds): - self.set_pixel(i, rgb_w[i]) - else: - self.set_pixel(idx, rgb_w) - - def __len__(self): - return self.num_leds - - def __getitem__(self, idx): - return self.get_pixel(idx) + self.set_pixel(idx, rgb_w) def colorHSV(self, hue, sat, val): """ From a145677e59784b855e724195cf023168d90a2c47 Mon Sep 17 00:00:00 2001 From: Max Carlson <> Date: Wed, 7 Feb 2024 16:47:32 +0200 Subject: [PATCH 3/7] Add more comments and credits to the source --- neopixel.py | 43 +++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/neopixel.py b/neopixel.py index a4ba900..770955f 100644 --- a/neopixel.py +++ b/neopixel.py @@ -3,45 +3,29 @@ import rp2 -# PIO state machine for RGB. Pulls 24 bits (rgb -> 3 * 8bit) automatically -@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=True, pull_thresh=24) -def ws2812(): - T1 = 2 - T2 = 5 - T3 = 3 - wrap_target() - label("bitloop") - out(x, 1) .side(0) [T3 - 1] - jmp(not_x, "do_zero") .side(1) [T1 - 1] - jmp("bitloop") .side(1) [T2 - 1] - label("do_zero") - nop() .side(0) [T2 - 1] - wrap() - - -# PIO state machine for RGBW. Pulls 32 bits (rgbw -> 4 * 8bit) automatically +# based on https://learn.adafruit.com/intro-to-rp2040-pio-with-circuitpython/advanced-using-pio-to-drive-neopixels-in-the-background by https://learn.adafruit.com/u/jepler @rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=False, pull_thresh=32) def sk6812(): wrap_target() - pull(block) .side(0) - out(y, 32) .side(0) + pull(block) .side(0) # get fresh NeoPixel bit count value + out(y, 32) .side(0) # get count of NeoPixel bits label("bitloop") - pull(ifempty) .side(0) + pull(ifempty) .side(0) # drive low out(x, 1) .side(0) [5] - jmp(not_x, "do_zero") .side(1) [3] - jmp(y_dec, "bitloop") .side(1) [4] - jmp("end_sequence") .side(0) + jmp(not_x, "do_zero") .side(1) [3] # drive high and branch depending on bit val + jmp(y_dec, "bitloop") .side(1) [4] # drive high for a one (long pulse) + jmp("end_sequence") .side(0) # sequence is over label("do_zero") - jmp(y_dec, "bitloop") .side(0) [4] + jmp(y_dec, "bitloop") .side(0) [4] # drive low for a zero (short pulse) label("end_sequence") - pull(block) .side(0) - out(y, 32) .side(0) + pull(block) .side(0) # get fresh delay value + out(y, 32) .side(0) # get delay count label("wait_reset") - jmp(y_dec, "wait_reset") .side(0) + jmp(y_dec, "wait_reset") .side(0) # wait until delay elapses wrap() @@ -107,7 +91,7 @@ def __init__(self, num_leds, state_machine, pin, mode="RGB", delay=3): (mode.index('B') ^ 3) * 8, (mode.index('W') ^ 3) * 8) bpp = 4 else: - self.sm = rp2.StateMachine(state_machine, ws2812, freq=12_800_000, sideset_base=Pin(pin)) + self.sm = rp2.StateMachine(state_machine, sk6812, freq=12_800_000, sideset_base=Pin(pin)) self.shift = (((mode.index('R') ^ 3) - 1) * 8, ((mode.index('G') ^ 3) - 1) * 8, ((mode.index('B') ^ 3) - 1) * 8, 0) self.sm.active(1) @@ -115,10 +99,13 @@ def __init__(self, num_leds, state_machine, pin, mode="RGB", delay=3): self.delay = delay self.brightnessvalue = 255 + # from https://learn.adafruit.com/intro-to-rp2040-pio-with-circuitpython/advanced-using-pio-to-drive-neopixels-in-the-background byte_count = bpp * num_leds bit_count = byte_count * 8 padding_count = -byte_count % 4 + # send number of bits to read self.header = bytearray(struct.pack("L", bit_count - 1)) + # pad if needed, then send number of cycles to delay self.trailer = bytearray(b"\0" * padding_count + struct.pack("L", 3840)) From 51f1f46230336821e9aa0cfd5394835d1975403b Mon Sep 17 00:00:00 2001 From: Max Carlson <> Date: Thu, 8 Feb 2024 17:53:08 +0200 Subject: [PATCH 4/7] Use a single array as a buffer, including header and trailer Uses an offset to ensure get/set() happens in the right place. Also caches the memoryview once to make show() as efficient as possible. It turned out passing the header/trailer values as-is from python is the right approach. Everything works well for me now, maybe I'll look into DMA next :) --- neopixel.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/neopixel.py b/neopixel.py index 770955f..36ee3d3 100644 --- a/neopixel.py +++ b/neopixel.py @@ -4,7 +4,7 @@ # based on https://learn.adafruit.com/intro-to-rp2040-pio-with-circuitpython/advanced-using-pio-to-drive-neopixels-in-the-background by https://learn.adafruit.com/u/jepler -@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=False, pull_thresh=32) +@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=False, pull_thresh=32) def sk6812(): wrap_target() pull(block) .side(0) # get fresh NeoPixel bit count value @@ -78,7 +78,6 @@ def __init__(self, num_leds, state_machine, pin, mode="RGB", delay=3): This can be any order of RGB or RGBW (neopixels are usually GRB) :param delay: [default: 0.0001] delay used for latching of leds when sending data """ - self.pixels = array.array("I", [0] * num_leds) # self.pixels_out = array.array("I", [0] * num_leds) self.mode = mode self.W_in_mode = 'W' in mode @@ -103,11 +102,25 @@ def __init__(self, num_leds, state_machine, pin, mode="RGB", delay=3): byte_count = bpp * num_leds bit_count = byte_count * 8 padding_count = -byte_count % 4 + + # use a single array + self.pixels = array.array("I") + # send number of bits to read - self.header = bytearray(struct.pack("L", bit_count - 1)) - # pad if needed, then send number of cycles to delay - self.trailer = bytearray(b"\0" * padding_count + struct.pack("L", 3840)) + self.pixels.append(bit_count - 1) + + # store offset into pixels array for get/set later + self.offset = len(self.pixels) + + # add starting values for each pixel + pix = array.array("I", [0] * num_leds) + self.pixels.extend(pix) + # send number of cycles to delay + self.pixels.append(3840) + + # use a memoryview to + self.mv = memoryview(self.pixels) def brightness(self, brightness=None): """ @@ -199,12 +212,13 @@ def set_pixel(self, pixel_num, rgb_w, how_bright=None): white = round(rgb_w[3] * bratio) pix_value = white << sh_W | blue << sh_B | red << sh_R | green << sh_G + offset = self.offset # set some subset, if pixel_num is a slice: if type(pixel_num) is slice: for i in range(*pixel_num.indices(self.num_leds)): - self.pixels[i] = pix_value + self.pixels[i + offset] = pix_value else: - self.pixels[pixel_num] = pix_value + self.pixels[pixel_num + offset] = pix_value def get_pixel(self, pixel_num): """ @@ -213,7 +227,7 @@ def get_pixel(self, pixel_num): :param pixel_num: Index of pixel to be set :return rgb_w: Tuple of form (r, g, b) or (r, g, b, w) representing color to be used """ - balance = self.pixels[pixel_num] + balance = self.pixels[pixel_num + self.offset] sh_R, sh_G, sh_B, sh_W = self.shift if self.W_in_mode: w = (balance >> sh_W) & 255 @@ -329,14 +343,7 @@ def show(self): This method should be used after every method that changes the state of leds or after a chain of changes. :return: None """ - # If mode is RGB, we cut 8 bits of, otherwise we keep all 32 - cut = 8 - if self.W_in_mode: - cut = 0 - - self.sm.put(self.header, 0) - self.sm.put(memoryview(self.pixels), cut) - self.sm.put(self.trailer, 0) + self.sm.put(self.mv) def fill(self, rgb_w, how_bright=None): """ From 812de90e5e2acda0f3cd86f3a37aea0493940b29 Mon Sep 17 00:00:00 2001 From: Max Carlson <> Date: Thu, 8 Feb 2024 17:58:02 +0200 Subject: [PATCH 5/7] Remove references to delay as it's no longer needed --- neopixel.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/neopixel.py b/neopixel.py index 36ee3d3..8aa6feb 100644 --- a/neopixel.py +++ b/neopixel.py @@ -40,10 +40,6 @@ def __getitem__(self, slc): slice_maker = slice_maker_class() -# Delay here is the reset time. You need a pause to reset the LED strip back to the initial LED -# however, if you have quite a bit of processing to do before the next time you update the strip -# you could put in delay=0 (or a lower delay) -# # Class supports different order of individual colors (GRB, RGB, WRGB, GWRB ...). In order to achieve # this, we need to flip the indexes: in 'RGBW', 'R' is on index 0, but we need to shift it left by 3 * 8bits, # so in it's inverse, 'WBGR', it has exactly right index. Since micropython doesn't have [::-1] and recursive rev() @@ -63,11 +59,10 @@ class Neopixel: # 'W_in_mode', # bool: is 'W' in mode # 'sm', # state machine # 'shift', # shift amount for each component, in a tuple for (R,B,G,W) - # 'delay', # delay amount # 'brightnessvalue', # brightness scale factor 1..255 # ] - def __init__(self, num_leds, state_machine, pin, mode="RGB", delay=3): + def __init__(self, num_leds, state_machine, pin, mode="RGB"): """ Constructor for library class @@ -76,7 +71,6 @@ def __init__(self, num_leds, state_machine, pin, mode="RGB", delay=3): :param pin: pin on which data line to led-strip is connected :param mode: [default: "RGB"] mode and order of bits representing the color value. This can be any order of RGB or RGBW (neopixels are usually GRB) - :param delay: [default: 0.0001] delay used for latching of leds when sending data """ # self.pixels_out = array.array("I", [0] * num_leds) self.mode = mode @@ -95,7 +89,6 @@ def __init__(self, num_leds, state_machine, pin, mode="RGB", delay=3): ((mode.index('B') ^ 3) - 1) * 8, 0) self.sm.active(1) self.num_leds = num_leds - self.delay = delay self.brightnessvalue = 255 # from https://learn.adafruit.com/intro-to-rp2040-pio-with-circuitpython/advanced-using-pio-to-drive-neopixels-in-the-background From d281ce4f1a6232332efc6dbc6c2806bd75dac7fb Mon Sep 17 00:00:00 2001 From: blaz-r Date: Mon, 1 Apr 2024 13:16:48 +0200 Subject: [PATCH 6/7] Optimize and add WS2821 support --- neopixel.py | 95 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 31 deletions(-) diff --git a/neopixel.py b/neopixel.py index c3917f1..fd9fd7c 100644 --- a/neopixel.py +++ b/neopixel.py @@ -4,7 +4,7 @@ # based on https://learn.adafruit.com/intro-to-rp2040-pio-with-circuitpython/advanced-using-pio-to-drive-neopixels-in-the-background by https://learn.adafruit.com/u/jepler -@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=False, pull_thresh=32) +@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=False) def sk6812(): wrap_target() pull(block) .side(0) # get fresh NeoPixel bit count value @@ -29,6 +29,37 @@ def sk6812(): wrap() +# based on https://learn.adafruit.com/intro-to-rp2040-pio-with-circuitpython/advanced-using-pio-to-drive-neopixels-in-the-background by https://learn.adafruit.com/u/jepler +@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT, autopull=False) +def ws2812(): + wrap_target() + pull(block) .side(0) # get fresh NeoPixel bit count value + out(y, 32) .side(0) # get count of NeoPixel bits + + label("bitloop") + jmp(not_osre, "bit_out") .side(0) # if OSR still contains data, skip this section + pull(ifempty) .side(0) # pull new 32bits of RGB data + out(x, 8) .side(0) # skip first 8 as RGB is 24bits + + label("bit_out") + pull(ifempty) .side(0) # drive low + out(x, 1) .side(0) [5] + jmp(not_x, "do_zero") .side(1) [3] # drive high and branch depending on bit val + jmp(y_dec, "bitloop") .side(1) [4] # drive high for a one (long pulse) + jmp("end_sequence") .side(0) # sequence is over + + label("do_zero") + jmp(y_dec, "bitloop") .side(0) [4] # drive low for a zero (short pulse) + + label("end_sequence") + pull(block) .side(0) # get fresh delay value + out(y, 32) .side(0) # get delay count + + label("wait_reset") + jmp(y_dec, "wait_reset") .side(0) # wait until delay elapses + wrap() + + # we need this because Micropython can't construct slice objects directly, only by # way of supporting slice notation. # So, e.g. slice_maker[1::4] gives a slice(1,None,4) object. @@ -54,12 +85,14 @@ class Neopixel: # to describe the data members... # __slots__ = [ # 'num_leds', # number of LEDs - # 'pixels', # array.array('I') of raw data for LEDs + # 'pixels', # array.array('I') of raw data for LEDs (with header and trailer) # 'mode', # mode 'RGB' etc # 'W_in_mode', # bool: is 'W' in mode # 'sm', # state machine # 'shift', # shift amount for each component, in a tuple for (R,B,G,W) # 'brightnessvalue', # brightness scale factor 1..255 + # 'header', # header - num of data bits in the data stream + # 'trailer', # trailer - delay at the end # ] def __init__(self, num_leds, state_machine, pin, mode="RGB"): @@ -72,7 +105,6 @@ def __init__(self, num_leds, state_machine, pin, mode="RGB"): :param mode: [default: "RGB"] mode and order of bits representing the color value. This can be any order of RGB or RGBW (neopixels are usually GRB) """ - # self.pixels_out = array.array("I", [0] * num_leds) self.mode = mode self.W_in_mode = 'W' in mode bpp = 3 @@ -84,7 +116,7 @@ def __init__(self, num_leds, state_machine, pin, mode="RGB"): (mode.index('B') ^ 3) * 8, (mode.index('W') ^ 3) * 8) bpp = 4 else: - self.sm = rp2.StateMachine(state_machine, sk6812, freq=12_800_000, sideset_base=Pin(pin)) + self.sm = rp2.StateMachine(state_machine, ws2812, freq=12_800_000, sideset_base=Pin(pin)) self.shift = (((mode.index('R') ^ 3) - 1) * 8, ((mode.index('G') ^ 3) - 1) * 8, ((mode.index('B') ^ 3) - 1) * 8, 0) self.sm.active(1) @@ -94,26 +126,15 @@ def __init__(self, num_leds, state_machine, pin, mode="RGB"): # from https://learn.adafruit.com/intro-to-rp2040-pio-with-circuitpython/advanced-using-pio-to-drive-neopixels-in-the-background byte_count = bpp * num_leds bit_count = byte_count * 8 - padding_count = -byte_count % 4 - - # use a single array - self.pixels = array.array("I") - - # send number of bits to read - self.pixels.append(bit_count - 1) - - # store offset into pixels array for get/set later - self.offset = len(self.pixels) - - # add starting values for each pixel - pix = array.array("I", [0] * num_leds) - self.pixels.extend(pix) - - # send number of cycles to delay - self.pixels.append(3840) - # use a memoryview to - self.mv = memoryview(self.pixels) + # array of 32bit integers, with header and trailer at the beginning and end + self.pixels = array.array("I", [0] * (num_leds + 2)) + # header - num of bits in the data stream + self.header = bit_count - 1 + # trailer - delay at the end (12.8 Mhz clk -> 1 period = 7.8125e-8, 3840 * 7.8125e-8 = 0.0003s delay) + self.trailer = 3840 + self.pixels[0] = self.header + self.pixels[-1] = self.trailer def brightness(self, brightness=None): """ @@ -205,13 +226,14 @@ def set_pixel(self, pixel_num, rgb_w, how_bright=None): white = round(rgb_w[3] * bratio) pix_value = white << sh_W | blue << sh_B | red << sh_R | green << sh_G - offset = self.offset # set some subset, if pixel_num is a slice: if type(pixel_num) is slice: for i in range(*pixel_num.indices(self.num_leds)): - self.pixels[i + offset] = pix_value + # offset of 1 due to header + self.pixels[i + 1] = pix_value else: - self.pixels[pixel_num + offset] = pix_value + # offset of 1 due to header + self.pixels[pixel_num + 1] = pix_value def get_pixel(self, pixel_num): """ @@ -220,7 +242,9 @@ def get_pixel(self, pixel_num): :param pixel_num: Index of pixel to be set :return rgb_w: Tuple of form (r, g, b) or (r, g, b, w) representing color to be used """ - balance = self.pixels[pixel_num + self.offset] + # offset of 1 due to header + balance = self.pixels[pixel_num + 1] + sh_R, sh_G, sh_B, sh_W = self.shift if self.W_in_mode: w = (balance >> sh_W) & 255 @@ -331,7 +355,10 @@ def rotate_left(self, num_of_pixels=None): """ if num_of_pixels is None: num_of_pixels = 1 - self.pixels = self.pixels[num_of_pixels:] + self.pixels[:num_of_pixels] + # offset of 1 due to header + num_of_pixels += 1 + #self.pixels = self.pixels[num_of_pixels:] + self.pixels[:num_of_pixels] + self.pixels = array.array("I", [self.pixels[0]] + list(self.pixels[num_of_pixels:-1]) + list(self.pixels[1:num_of_pixels]) + [self.pixels[-1]]) def rotate_right(self, num_of_pixels=None): """ @@ -342,8 +369,11 @@ def rotate_right(self, num_of_pixels=None): """ if num_of_pixels is None: num_of_pixels = 1 + # offset of 1 due to header + num_of_pixels += 1 num_of_pixels = -1 * num_of_pixels - self.pixels = self.pixels[num_of_pixels:] + self.pixels[:num_of_pixels] + #self.pixels = self.pixels[num_of_pixels:] + self.pixels[:num_of_pixels] + self.pixels = array.array("I", [self.pixels[0]] + list(self.pixels[num_of_pixels:-1]) + list(self.pixels[1:num_of_pixels]) + [self.pixels[-1]]) def show(self): """ @@ -351,7 +381,8 @@ def show(self): This method should be used after every method that changes the state of leds or after a chain of changes. :return: None """ - self.sm.put(self.mv) + # put entire pixel data along with header and trailer into TX FIFO + self.sm.put(memoryview(self.pixels)) def fill(self, rgb_w, how_bright=None): @@ -371,4 +402,6 @@ def clear(self): :return: None """ - self.pixels = array.array("I", [0] * self.num_leds) \ No newline at end of file + self.pixels = array.array("I", [0] * (self.num_leds + 2)) + self.pixels[0] = self.header + self.pixels[-1] = self.trailer \ No newline at end of file From b02b657555f07266ef109e5f2ab5940d5c6e416f Mon Sep 17 00:00:00 2001 From: blaz-r Date: Mon, 1 Apr 2024 13:19:18 +0200 Subject: [PATCH 7/7] Remove unused imports --- neopixel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/neopixel.py b/neopixel.py index fd9fd7c..b69bf60 100644 --- a/neopixel.py +++ b/neopixel.py @@ -1,4 +1,4 @@ -import array, time, struct +import array from machine import Pin import rp2 @@ -244,7 +244,7 @@ def get_pixel(self, pixel_num): """ # offset of 1 due to header balance = self.pixels[pixel_num + 1] - + sh_R, sh_G, sh_B, sh_W = self.shift if self.W_in_mode: w = (balance >> sh_W) & 255