/ adafruit_ws2801.py
adafruit_ws2801.py
  1  # The MIT License (MIT)
  2  #
  3  # Copyright (c) 2016 Damien P. George (original Neopixel object)
  4  # Copyright (c) 2017 Ladyada
  5  # Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
  6  # Copyright (c) 2018 Kevin J Walters
  7  #
  8  # Permission is hereby granted, free of charge, to any person obtaining a copy
  9  # of this software and associated documentation files (the "Software"), to deal
 10  # in the Software without restriction, including without limitation the rights
 11  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 12  # copies of the Software, and to permit persons to whom the Software is
 13  # furnished to do so, subject to the following conditions:
 14  #
 15  # The above copyright notice and this permission notice shall be included in
 16  # all copies or substantial portions of the Software.
 17  #
 18  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 19  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 20  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 21  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 22  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 23  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 24  # THE SOFTWARE.
 25  
 26  """
 27  `adafruit_ws2801` - WS2801 LED pixel string driver
 28  ====================================================
 29  
 30  * Author(s): Damien P. George, Limor Fried & Scott Shawcroft, Kevin J Walters
 31  """
 32  import math
 33  
 34  import busio
 35  import digitalio
 36  
 37  __version__ = "0.0.0-auto.0"
 38  __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_WS2801.git"
 39  
 40  ### based on https://github.com/adafruit/Adafruit_CircuitPython_DotStar
 41  
 42  
 43  class WS2801:
 44      """
 45      A sequence of WS2801 controlled LEDs.
 46  
 47      :param ~microcontroller.Pin clock: The pin to output dotstar clock on.
 48      :param ~microcontroller.Pin data: The pin to output dotstar data on.
 49      :param int n: The number of LEDs in the chain.
 50      :param float brightness: The brightness between 0.0 and (default) 1.0.
 51      :param bool auto_write: True if the dotstars should immediately change when
 52          set. If False, `show` must be called explicitly.
 53  
 54  
 55      Example for Gemma M0:
 56  
 57      .. code-block:: python
 58  
 59          import adafruit_ws2801
 60          import time
 61          import board
 62  
 63          darkred = 0x100000
 64  
 65          with adafruit_ws2801.WS2801(board.D2, board.D0, 25, brightness=1.0) as pixels:
 66              pixels[0] = darkred
 67              time.sleep(2)
 68      """
 69  
 70      def __init__(self, clock, data, n, *, brightness=1.0, auto_write=True):
 71          self._spi = None
 72          try:
 73              self._spi = busio.SPI(clock, MOSI=data)
 74              while not self._spi.try_lock():
 75                  pass
 76              self._spi.configure(baudrate=1000 * 1000)
 77          except ValueError:
 78              self.dpin = digitalio.DigitalInOut(data)
 79              self.cpin = digitalio.DigitalInOut(clock)
 80              self.dpin.direction = digitalio.Direction.OUTPUT
 81              self.cpin.direction = digitalio.Direction.OUTPUT
 82              self.cpin.value = False
 83          self._n = n
 84          self._buf = bytearray(n * 3)
 85          self._brightness = 1.0  ### keeps pylint happy
 86          # Set auto_write to False temporarily so brightness setter does _not_
 87          # call show() while in __init__.
 88          self.auto_write = False
 89          self.brightness = brightness
 90          self.auto_write = auto_write
 91          ### TODO - review/consider adding GRB support like that in c++ version
 92  
 93      def deinit(self):
 94          """Blank out the DotStars and release the resources."""
 95          self.auto_write = False
 96          black = (0, 0, 0)
 97          self.fill(black)
 98          self.show()
 99          if self._spi:
100              self._spi.deinit()
101          else:
102              self.dpin.deinit()
103              self.cpin.deinit()
104  
105      def __enter__(self):
106          return self
107  
108      def __exit__(self, exception_type, exception_value, traceback):
109          self.deinit()
110  
111      def __repr__(self):
112          return "[" + ", ".join([str(x) for x in self]) + "]"
113  
114      def _set_item(self, index, value):
115          offset = index * 3
116          if isinstance(value, int):
117              r = value >> 16
118              g = (value >> 8) & 0xFF
119              b = value & 0xFF
120          else:
121              r, g, b = value
122          # red/green/blue order for WS2801
123          self._buf[offset] = r
124          self._buf[offset + 1] = g
125          self._buf[offset + 2] = b
126  
127      def __setitem__(self, index, val):
128          if isinstance(index, slice):
129              start, stop, step = index.indices(self._n)
130              length = stop - start
131              if step != 0:
132                  length = math.ceil(length / step)
133              if len(val) != length:
134                  raise ValueError("Slice and input sequence size do not match.")
135              for val_i, in_i in enumerate(range(start, stop, step)):
136                  self._set_item(in_i, val[val_i])
137          else:
138              self._set_item(index, val)
139  
140          if self.auto_write:
141              self.show()
142  
143      def __getitem__(self, index):
144          if isinstance(index, slice):
145              out = []
146              for in_i in range(*index.indices(self._n)):
147                  out.append(tuple(self._buf[in_i * 3 + i] for i in range(3)))
148              return out
149          if index < 0:
150              index += len(self)
151          if index >= self._n or index < 0:
152              raise IndexError
153          offset = index * 3
154          return tuple(self._buf[offset + i] for i in range(3))
155  
156      def __len__(self):
157          return self._n
158  
159      @property
160      def brightness(self):
161          """Overall brightness of the pixel"""
162          return self._brightness
163  
164      @brightness.setter
165      def brightness(self, brightness):
166          self._brightness = min(max(brightness, 0.0), 1.0)
167          if self.auto_write:
168              self.show()
169  
170      def fill(self, color):
171          """Colors all pixels the given ***color***."""
172          auto_write = self.auto_write
173          self.auto_write = False
174          for i, _ in enumerate(self):
175              self[i] = color
176          if auto_write:
177              self.show()
178          self.auto_write = auto_write
179  
180      def _ds_writebytes(self, buf):
181          for b in buf:
182              for _ in range(8):
183                  self.dpin.value = b & 0x80
184                  self.cpin.value = True
185                  self.cpin.value = False
186                  b = b << 1
187  
188      def show(self):
189          """Shows the new colors on the pixels themselves if they haven't already
190          been autowritten.
191  
192          The colors may or may not be showing after this function returns because
193          it may be done asynchronously."""
194          # Create a second output buffer if we need to compute brightness
195          buf = self._buf
196          if self.brightness < 1.0:
197              buf = bytearray(len(self._buf))
198              for i in range(self._n):
199                  buf[i] = int(self._buf[i] * self._brightness)
200  
201          if self._spi:
202              self._spi.write(buf)
203          else:
204              self._ds_writebytes(buf)
205              self.cpin.value = False