diff --git a/examples/pioasm_neopixel.py b/examples/pioasm_neopixel.py index 00ced1f..0c043d5 100644 --- a/examples/pioasm_neopixel.py +++ b/examples/pioasm_neopixel.py @@ -8,19 +8,22 @@ import microcontroller import adafruit_pioasm -# NeoPixels are 800khz bit streams. Zeroes are 1/3 duty cycle (~416ns) and ones -# are 2/3 duty cycle (~833ns). +# NeoPixels are 800khz bit streams. We are choosing zeros as <312ns hi, 936 lo> +# and ones as <700 ns hi, 556 ns lo>. +# The first two instructions always run while only one of the two final +# instructions run per bit. We start with the low period because it can be +# longer while waiting for more data. program = """ .program ws2812 .side_set 1 .wrap_target bitloop: - out x 1 side 0 [1]; Side-set still takes place when instruction stalls - jmp !x do_zero side 1 [1]; Branch on the bit we shifted out. Positive pulse -do_one: - jmp bitloop side 1 [1]; Continue driving high, for a long pulse -do_zero: - nop side 0 [1]; Or drive low, for a short pulse + out x 1 side 0 [6]; Drive low. Side-set still takes place before instruction stalls. + jmp !x do_zero side 1 [3]; Branch on the bit we shifted out previous delay. Drive high. + do_one: + jmp bitloop side 1 [4]; Continue driving high, for a one (long pulse) + do_zero: + nop side 0 [4]; Or drive low, for a zero (short pulse) .wrap """ @@ -35,7 +38,7 @@ sm = rp2pio.StateMachine( assembled, - frequency=800000 * 6, # 800khz * 6 clocks per bit + frequency=12_800_000, # to get appropriate sub-bit times in PIO program first_sideset_pin=NEOPIXEL, auto_pull=True, out_shift_right=False, diff --git a/examples/pioasm_neopixel_bg.py b/examples/pioasm_neopixel_bg.py new file mode 100644 index 0000000..c3ec39a --- /dev/null +++ b/examples/pioasm_neopixel_bg.py @@ -0,0 +1,122 @@ +# SPDX-FileCopyrightText: 2022 Jeff Epler, written for Adafruit Industries +# +# SPDX-License-Identifier: MIT + +"""Demonstrate background writing with NeoPixels + +The NeoPixelBackground class defined here is largely compatible with the +standard NeoPixel class, except that the ``show()`` method returns immediately, +writing data to the LEDs in the background. + +Writing the LED data in the background will allow more time for your +Python code to run, so it may be possible to slightly increase the refresh +rate of your LEDs or do more complicated processing. + +The demonstration code, under ``if __name__ == '__main__':`` is intended +for the Adafruit MacroPad, with 12 NeoPixel LEDs. It shows a cycling rainbow +pattern across all the LEDs. +""" + +import struct +import adafruit_pixelbuf +from ulab import numpy as np +from rp2pio import StateMachine +from adafruit_pioasm import Program + +# Pixel color order constants +RGB = "RGB" +"""Red Green Blue""" +GRB = "GRB" +"""Green Red Blue""" +RGBW = "RGBW" +"""Red Green Blue White""" +GRBW = "GRBW" +"""Green Red Blue White""" + +# NeoPixels are 800khz bit streams. We are choosing zeros as <312ns hi, 936 lo> +# and ones as <700 ns hi, 556 ns lo>. +_program = Program( + """ +.side_set 1 opt +.wrap_target + pull block side 0 + out y, 16 side 0 ; get count of NeoPixel bits + +bitloop: + pull ifempty side 0 ; drive low + out x 1 side 0 [5] + jmp !x do_zero side 1 [3] ; drive high and branch depending on bit val + jmp y--, bitloop side 1 [4] ; drive high for a one (long pulse) + jmp end_sequence side 0 ; sequence is over + +do_zero: + jmp y--, bitloop side 0 [4] ; drive low for a zero (short pulse) + +end_sequence: + pull block side 0 ; get fresh 16 bit delay value + out y, 16 side 0 ; get delay count +wait_reset: + jmp y--, wait_reset side 0 ; wait until delay elapses +.wrap + """ +) + + +class NeoPixelBackground( # pylint: disable=too-few-public-methods + adafruit_pixelbuf.PixelBuf +): + def __init__( + self, pin, n, *, bpp=3, brightness=1.0, auto_write=True, pixel_order=None + ): + if not pixel_order: + pixel_order = GRB if bpp == 3 else GRBW + elif isinstance(pixel_order, tuple): + order_list = [RGBW[order] for order in pixel_order] + pixel_order = "".join(order_list) + + byte_count = bpp * n + bit_count = byte_count * 8 + padding_count = byte_count % 2 + + if bit_count > 65536: + raise ValueError("Too many pixels") + + # backwards, so that ulab byteswap corrects it! + header = struct.pack(">H", (bit_count - 1) & 0xFFFF) + trailer = b"\0" * padding_count + struct.pack(">H", 3840) + + self._sm = StateMachine( + _program.assembled, + auto_pull=False, + first_sideset_pin=pin, + out_shift_right=False, + pull_threshold=16, + frequency=12_800_000, + **_program.pio_kwargs, + ) + + super().__init__( + n, + brightness=brightness, + byteorder=pixel_order, + auto_write=auto_write, + header=header, + trailer=trailer, + ) + + def _transmit(self, buf): + self._sm.background_write(np.frombuffer(buf, dtype=np.uint16).byteswap()) + + +if __name__ == "__main__": + import board + import rainbowio + import time + + NEOPIXEL = board.NEOPIXEL + NUM_PIXELS = 12 + pixels = NeoPixelBackground(NEOPIXEL, NUM_PIXELS) + i = 0 + while True: + pixels.fill(rainbowio.colorwheel(i := (i + 1) % 256)) + time.sleep(0.01)