/ 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)