diff --git a/README.rst b/README.rst index 1c4f99c..ee0c479 100644 --- a/README.rst +++ b/README.rst @@ -78,6 +78,50 @@ This example demonstrates the library with the single built-in DotStar on the pixels = adafruit_dotstar.DotStar(board.APA102_SCK, board.APA102_MOSI, 1) pixels[0] = (10, 0, 0) + +This example demonstrates the library with the DotStar Feather Wing and bounces Blinka. + +`Feather M4 Express `_ and +`DotStar FeatherWing `_. + +.. code-block:: python + + import board + import adafruit_dotstar + import time + + + import adafruit_dotstar + dotstar = adafruit_dotstar.DotStar(board.D13, board.D11, 72, + pixel_order=adafruit_dotstar.BGR, + brightness=0.3, auto_write=False) + + blinka = ( + (0, 0x0f0716, 0x504069, 0x482e63, 0, 0), + (0, 0x3d1446, 0x502b74, 0x622f8c, 0, 0), + (0, 0x2e021b, 0x2e021b, 0x2e021b, 0, 0), + (0, 0, 0x2e021b, 0x2e021b, 0, 0), + (0, 0x591755, 0x912892, 0x3f205c, 0x282828, 0x301844), + (0x65206b, 0x932281, 0x6e318f, 0x6d2b7e, 0x7e2686, 0x8c2c8f), + (0x7c2d8c, 0xa21c81, 0x6b308e, 0x74257b, 0x7b2482, 0x742f8d), + (0x23051a, 0x5c0f45, 0x81227b, 0x551a5b, 0x691b5d, 0x4d0c39), + ) + offset = 0 + direction = 1 + while True: + dotstar.fill(0) + for y, row in enumerate(blinka): + for x, value in enumerate(row): + n = x * 12 + (y + offset) + dotstar[n] = row[x] + dotstar.show() + time.sleep(0.1) + offset += direction + if offset > 4 or offset < 0: + direction = -direction + offset += direction + + Contributing ============ diff --git a/adafruit_dotstar.py b/adafruit_dotstar.py index b475044..fe36046 100755 --- a/adafruit_dotstar.py +++ b/adafruit_dotstar.py @@ -3,6 +3,7 @@ # Copyright (c) 2016 Damien P. George (original Neopixel object) # Copyright (c) 2017 Ladyada # Copyright (c) 2017 Scott Shawcroft for Adafruit Industries +# Copyright (c) 2019 Roy Hooper # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -23,30 +24,33 @@ # THE SOFTWARE. """ -`adafruit_dotstar` - DotStar strip driver -==================================================== +`adafruit_dotstar` - DotStar strip driver (for CircuitPython 5.0+ with _pixelbuf) +================================================================================= -* Author(s): Damien P. George, Limor Fried & Scott Shawcroft +* Author(s): Damien P. George, Limor Fried, Scott Shawcroft & Roy Hooper """ import busio import digitalio +try: + import _pixelbuf +except ImportError: + import adafruit_pypixelbuf as _pixelbuf __version__ = "0.0.0-auto.0" __repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DotStar.git" START_HEADER_SIZE = 4 -LED_START = 0b11100000 # Three "1" bits, followed by 5 brightness bits -# Pixel color order constants -RGB = (0, 1, 2) -RBG = (0, 2, 1) -GRB = (1, 0, 2) -GBR = (1, 2, 0) -BRG = (2, 0, 1) -BGR = (2, 1, 0) +RBG = 'PRBG' +RGB = 'PRGB' +GRB = 'PGRB' +GBR = 'PGBR' +BRG = 'PBRG' +BGR = 'PBGR' +BGR = 'PBGR' -class DotStar: +class DotStar(_pixelbuf.PixelBuf): """ A sequence of dotstars. @@ -56,16 +60,14 @@ class DotStar: :param float brightness: Brightness of the pixels between 0.0 and 1.0 :param bool auto_write: True if the dotstars should immediately change when set. If False, `show` must be called explicitly. - :param tuple pixel_order: Set the pixel order on the strip - different - strips implement this differently. If you send red, and it looks blue - or green on the strip, modify this! It should be one of the values - above. + :param str pixel_order: Set the pixel order on the strip - different + strips implement this differently. If you send red, and it looks blue + or green on the strip, modify this! It should be one of the values above. :param int baudrate: Desired clock rate if using hardware SPI (ignored if using 'soft' SPI). This is only a recommendation; the actual clock rate may be slightly different depending on what the system hardware can provide. - Example for Gemma M0: .. code-block:: python @@ -96,36 +98,60 @@ def __init__(self, clock, data, n, *, brightness=1.0, auto_write=True, self.dpin.direction = digitalio.Direction.OUTPUT self.cpin.direction = digitalio.Direction.OUTPUT self.cpin.value = False - self._n = n + self.n = n + # Supply one extra clock cycle for each two pixels in the strip. - self.end_header_size = n // 16 + end_header_size = n // 16 if n % 16 != 0: - self.end_header_size += 1 - self._buf = bytearray(n * 4 + START_HEADER_SIZE + self.end_header_size) - self.end_header_index = len(self._buf) - self.end_header_size + end_header_size += 1 + bufsize = 4 * n + START_HEADER_SIZE + end_header_size + end_header_index = bufsize - end_header_size self.pixel_order = pixel_order + + self._buf = bytearray(bufsize) + self._rawbuf = bytearray(bufsize) + # Four empty bytes to start. for i in range(START_HEADER_SIZE): - self._buf[i] = 0x00 - # Mark the beginnings of each pixel. - for i in range(START_HEADER_SIZE, self.end_header_index, 4): - self._buf[i] = 0xff + self._rawbuf[i] = 0x00 # 0xff bytes at the end. - for i in range(self.end_header_index, len(self._buf)): - self._buf[i] = 0xff - self._brightness = 1.0 - # Set auto_write to False temporarily so brightness setter does _not_ - # call show() while in __init__. - self.auto_write = False - self.brightness = brightness - self.auto_write = auto_write + for i in range(end_header_index, bufsize): + self._rawbuf[i] = 0xff + # Mark the beginnings of each pixel. + for i in range(START_HEADER_SIZE, end_header_index, 4): + self._rawbuf[i] = 0xff + self._buf[:] = self._rawbuf[:] + + super(DotStar, self).__init__(n, self._buf, byteorder=pixel_order, + rawbuf=self._rawbuf, offset=START_HEADER_SIZE, + brightness=brightness, auto_write=auto_write) + + def show(self): + """Shows the new colors on the pixels themselves if they haven't already + been autowritten. + + The colors may or may not be showing after this method returns because + it may be done asynchronously. + + This method is called automatically if auto_write is set to True. + """ + if self._spi: + self._spi.write(self._buf) + else: + self.ds_writebytes() + + def _ds_writebytes(self): + for b in self.buf: + for _ in range(8): + self.dpin.value = (b & 0x80) + self.cpin.value = True + self.cpin.value = False + b = b << 1 + self.cpin.value = False def deinit(self): """Blank out the DotStars and release the resources.""" - self.auto_write = False - for i in range(START_HEADER_SIZE, self.end_header_index): - if i % 4 != 0: - self._buf[i] = 0 + self.fill(0) self.show() if self._spi: self._spi.deinit() @@ -142,131 +168,6 @@ def __exit__(self, exception_type, exception_value, traceback): def __repr__(self): return "[" + ", ".join([str(x) for x in self]) + "]" - def _set_item(self, index, value): - """ - value can be one of three things: - a (r,g,b) list/tuple - a (r,g,b, brightness) list/tuple - a single, longer int that contains RGB values, like 0xFFFFFF - brightness, if specified should be a float 0-1 - - Set a pixel value. You can set per-pixel brightness here, if it's not passed it - will use the max value for pixel brightness value, which is a good default. - - Important notes about the per-pixel brightness - it's accomplished by - PWMing the entire output of the LED, and that PWM is at a much - slower clock than the rest of the LEDs. This can cause problems in - Persistence of Vision Applications - """ - - offset = index * 4 + START_HEADER_SIZE - rgb = value - if isinstance(value, int): - rgb = (value >> 16, (value >> 8) & 0xff, value & 0xff) - - if len(rgb) == 4: - brightness = value[3] - # Ignore value[3] below. - else: - brightness = 1 - - # LED startframe is three "1" bits, followed by 5 brightness bits - # then 8 bits for each of R, G, and B. The order of those 3 are configurable and - # vary based on hardware - # same as math.ceil(brightness * 31) & 0b00011111 - # Idea from https://www.codeproject.com/Tips/700780/Fast-floor-ceiling-functions - brightness_byte = 32 - int(32 - brightness * 31) & 0b00011111 - self._buf[offset] = brightness_byte | LED_START - self._buf[offset + 1] = rgb[self.pixel_order[0]] - self._buf[offset + 2] = rgb[self.pixel_order[1]] - self._buf[offset + 3] = rgb[self.pixel_order[2]] - - def __setitem__(self, index, val): - if isinstance(index, slice): - start, stop, step = index.indices(self._n) - length = stop - start - if step != 0: - # same as math.ceil(length / step) - # Idea from https://fizzbuzzer.com/implement-a-ceil-function/ - length = (length + step - 1) // step - if len(val) != length: - raise ValueError("Slice and input sequence size do not match.") - for val_i, in_i in enumerate(range(start, stop, step)): - self._set_item(in_i, val[val_i]) - else: - self._set_item(index, val) - - if self.auto_write: - self.show() - - def __getitem__(self, index): - if isinstance(index, slice): - out = [] - for in_i in range(*index.indices(self._n)): - out.append( - tuple(self._buf[in_i * 4 + (3 - i) + START_HEADER_SIZE] for i in range(3))) - return out - if index < 0: - index += len(self) - if index >= self._n or index < 0: - raise IndexError - offset = index * 4 - return tuple(self._buf[offset + (3 - i) + START_HEADER_SIZE] - for i in range(3)) - - def __len__(self): - return self._n - - @property - def brightness(self): - """Overall brightness of the pixel""" - return self._brightness - - @brightness.setter - def brightness(self, brightness): - self._brightness = min(max(brightness, 0.0), 1.0) - if self.auto_write: - self.show() - def fill(self, color): """Colors all pixels the given ***color***.""" - auto_write = self.auto_write - self.auto_write = False - for i in range(self._n): - self[i] = color - if auto_write: - self.show() - self.auto_write = auto_write - - def _ds_writebytes(self, buf): - for b in buf: - for _ in range(8): - self.dpin.value = (b & 0x80) - self.cpin.value = True - self.cpin.value = False - b = b << 1 - - def show(self): - """Shows the new colors on the pixels themselves if they haven't already - been autowritten. - - The colors may or may not be showing after this function returns because - it may be done asynchronously.""" - # Create a second output buffer if we need to compute brightness - buf = self._buf - if self.brightness < 1.0: - buf = bytearray(self._buf) - # Four empty bytes to start. - for i in range(START_HEADER_SIZE): - buf[i] = 0x00 - for i in range(START_HEADER_SIZE, self.end_header_index): - buf[i] = self._buf[i] if i % 4 == 0 else int(self._buf[i] * self._brightness) - # Four 0xff bytes at the end. - for i in range(self.end_header_index, len(buf)): - buf[i] = 0xff - - if self._spi: - self._spi.write(buf) - else: - self._ds_writebytes(buf) - self.cpin.value = False + _pixelbuf.fill(self, color) diff --git a/docs/conf.py b/docs/conf.py index 81225e3..e539d9b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,7 +15,7 @@ 'sphinx.ext.viewcode', ] -# autodoc_mock_imports = ["digitalio", "busio"] +autodoc_mock_imports = ["pypixelbuf"] intersphinx_mapping = {'python': ('https://docs.python.org/3.4', None), 'CircuitPython': ('https://circuitpython.readthedocs.io/en/latest/', None)} diff --git a/requirements.txt b/requirements.txt index 3031961..3c95591 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ Adafruit-Blinka adafruit-circuitpython-busdevice +adafruit-circuitpython-pypixelbuf diff --git a/setup.py b/setup.py index a3f6e94..d4a2fec 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,8 @@ author='Adafruit Industries', author_email='circuitpython@adafruit.com', - install_requires=['Adafruit-Blinka', 'adafruit-circuitpython-busdevice'], + install_requires=['Adafruit-Blinka', 'adafruit-circuitpython-busdevice', + 'adafruit-circuitpython-pypixelbuf'], # Choose your license license='MIT',