/ adafruit_neopxl8.py
adafruit_neopxl8.py
1 # SPDX-FileCopyrightText: 2016 Damien P. George 2 # SPDX-FileCopyrightText: 2017 Scott Shawcroft for Adafruit Industries 3 # SPDX-FileCopyrightText: 2019 Carter Nelson 4 # SPDX-FileCopyrightText: 2019 Roy Hooper 5 # SPDX-FileCopyrightText: 2022 Jeff Epler 6 # 7 # SPDX-License-Identifier: MIT 8 9 """ 10 `adafruit_neopxl8` - Neopixel strip driver using RP2040's PIO 11 ============================================================= 12 13 * Author(s): Damien P. George, Scott Shawcroft, Carter Nelson, Roy Hooper, Jeff Epler 14 """ 15 16 import struct 17 import adafruit_pioasm 18 import bitops 19 import adafruit_pixelbuf 20 import rp2pio 21 22 __version__ = "0.0.0+auto.0" 23 __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_NeoPxl8.git" 24 25 26 _PROGRAM = """ 27 .program piopixl8 28 top: 29 mov pins, null ; always-low part (last cycle is the 'pull ifempty' after wrap) 30 pull block ; wait for fresh data 31 out y, 32 ; get count of NeoPixel bits 32 33 ; NeoPixels are 800khz bit streams. We are choosing zeros as <312ns hi, 936 lo> 34 ; and ones as <700 ns hi, 546 ns lo> and a clock of 16*800kHz, so the always-high 35 ; time is 4 cycles, the variable time is 5 cycles, and the always-low time is 7 cycles 36 bitloop: 37 pull ifempty [1] ; don't start outputting HIGH unless data is available (always-low part) 38 mov pins, ~ null [3] ; always-high part 39 {} ; variable part 40 mov pins, null ; always-low part (last cycle is the 'pull ifempty' after wrap) 41 42 jmp y--, bitloop ; always-low part 43 44 ; A minimum delay is required so that the next pixel starts refreshing the front of the strands 45 pull block 46 out y, 32 47 48 wait_reset: 49 jmp y--, wait_reset 50 jmp top 51 """ 52 53 54 # Pixel color order constants 55 RGB = "RGB" 56 """Red Green Blue""" 57 GRB = "GRB" 58 """Green Red Blue""" 59 RGBW = "RGBW" 60 """Red Green Blue White""" 61 GRBW = "GRBW" 62 """Green Red Blue White""" 63 64 65 class NeoPxl8(adafruit_pixelbuf.PixelBuf): 66 """ 67 A sequence of neopixels. 68 69 :param ~microcontroller.Pin data0: The first of 8 data registers, in GPIO order 70 :param int n: The total number of neopixels. Must be a multiple of the number of strands. 71 :param int num_strands: The number of neopixels in each strand. 72 :param int bpp: Bytes per pixel. 3 for RGB and 4 for RGBW pixels. 73 :param float brightness: Brightness of the pixels between 0.0 and 1.0 where 1.0 is full 74 brightness 75 :param bool auto_write: True if the neopixels should immediately change when set. If False, 76 `show` must be called explicitly. 77 :param str pixel_order: Set the pixel color channel order. GRB or GRBW is 78 set by default, depending on bpp. 79 80 Example for Adafruit Feather RP2040 Scorpio, sets 240 LEDs in 8 strands to faint red: 81 82 .. code-block:: python 83 84 pixels = adafruit_neopxl8.NeoPxl8(board.NEOPIXEL0, 8*30, num_strands=8, auto_write=False) 85 pixels.fill(0x010000) 86 pixels.show() 87 88 .. py:method:: NeoPxl8.show() 89 90 Shows the new colors on the pixels themselves if they haven't already 91 been autowritten. Note that with NeoPxl8 the show operation takes place 92 in the background; when this routine returns, the new pixel data has just 93 started to be written but your Python code can continue operating in the 94 foreground, updating pixel values or performing other computations. 95 96 .. py:method:: NeoPxl8.fill(color) 97 98 Colors all pixels the given ***color***. 99 100 .. py:attribute:: brightness 101 102 Overall brightness of the pixel (0 to 1.0) 103 104 """ 105 106 def __init__( 107 self, 108 data0, 109 n, 110 *, 111 num_strands=8, 112 bpp=3, 113 brightness=1.0, 114 auto_write=True, 115 pixel_order=None, 116 ): # pylint: disable=too-many-locals 117 if n % num_strands: 118 raise ValueError("Length must be a multiple of num_strands") 119 if not pixel_order: 120 pixel_order = GRB if bpp == 3 else GRBW 121 else: 122 if isinstance(pixel_order, tuple): 123 order_list = [RGBW[order] for order in pixel_order] 124 pixel_order = "".join(order_list) 125 126 super().__init__( 127 n, brightness=brightness, byteorder=pixel_order, auto_write=auto_write 128 ) 129 130 if num_strands == 1: 131 data_len = bpp * n 132 pack = ">L" 133 osr = False 134 loop_count = 8 * data_len 135 else: 136 data_len = bpp * n * 8 // num_strands 137 pack = "<L" 138 osr = True 139 loop_count = data_len 140 padding_count = -data_len % 4 141 142 self._num_strands = num_strands 143 self._data = bytearray(8 + data_len + padding_count) 144 self._data32 = memoryview(self._data).cast("L") 145 self._pixels = memoryview(self._data)[4 : 4 + data_len] 146 self._data[:4] = struct.pack(pack, loop_count - 1) 147 self._data[-4:] = struct.pack(pack, 3840) 148 149 self._num_strands = num_strands 150 151 if num_strands == 8: 152 variable_part = "out pins, 8 [4] ; variable part" 153 elif num_strands == 1: 154 variable_part = "out pins, 1 [4] ; variable part" 155 else: 156 variable_part = f""" 157 out pins, {num_strands} [3] ; variable part 158 out x, {8-num_strands} ; variable part 159 """ 160 161 program = _PROGRAM.format(variable_part) 162 assembled = adafruit_pioasm.assemble(program) 163 164 self._sm = rp2pio.StateMachine( 165 assembled, 166 frequency=800_000 * 16, 167 first_out_pin=data0, 168 out_pin_count=num_strands, 169 first_set_pin=data0, 170 auto_pull=False, 171 out_shift_right=osr, 172 ) 173 174 def deinit(self): 175 """Blank out the neopixels and release the state machine.""" 176 self.fill(0) 177 self.show() 178 self._sm.deinit() 179 180 def __enter__(self): 181 return self 182 183 def __exit__(self, exception_type, exception_value, traceback): 184 self.deinit() 185 186 def __repr__(self): 187 return "[" + ", ".join([str(x) for x in self]) + "]" 188 189 @property 190 def n(self): 191 """ 192 The total number of neopixels in all strands (read-only) 193 """ 194 return len(self) 195 196 @property 197 def num_strands(self): 198 """ 199 The total number of neopixels in all strands (read-only) 200 """ 201 return self._num_strands 202 203 def _transmit(self, buffer): 204 while self._sm.pending: 205 pass 206 if self.num_strands == 1: 207 self._pixels[:] = buffer 208 self._sm.background_write(self._data32, swap=True) 209 else: 210 bitops.bit_transpose(buffer, self._pixels, self._num_strands) 211 self._sm.background_write(self._data32)