Skip to content

Commit f4f66fa

Browse files
authored
Merge pull request #48 from dunkmann00/pixelbuf
Add support for Pixelbuf
2 parents 0e9a311 + 7e5c1b8 commit f4f66fa

File tree

6 files changed

+98
-192
lines changed

6 files changed

+98
-192
lines changed

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ It should be a float. For example, (0xFF,0,0, 1.0) is the brightest red possible
2626

2727
.. note:: The int hex API represents the brightness of the white pixel when
2828
present by setting the RGB channels to identical values. For example, full
29-
white is 0xffffff but is actually (0xff, 0xff, 0xff) in the tuple syntax.
29+
white is 0xffffff but is actually (0xff, 0xff, 0xff) in the tuple syntax.
3030

3131
Dependencies
3232
=============

adafruit_dotstar.py

Lines changed: 78 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
# Copyright (c) 2016 Damien P. George (original Neopixel object)
44
# Copyright (c) 2017 Ladyada
55
# Copyright (c) 2017 Scott Shawcroft for Adafruit Industries
6+
# Copyright (c) 2019 Roy Hooper
67
#
78
# Permission is hereby granted, free of charge, to any person obtaining a copy
89
# of this software and associated documentation files (the "Software"), to deal
@@ -23,30 +24,46 @@
2324
# THE SOFTWARE.
2425

2526
"""
26-
`adafruit_dotstar` - DotStar strip driver
27-
====================================================
27+
`adafruit_dotstar` - DotStar strip driver (for CircuitPython 5.0+ with _pixelbuf)
28+
=================================================================================
2829
29-
* Author(s): Damien P. George, Limor Fried & Scott Shawcroft
30+
* Author(s): Damien P. George, Limor Fried, Scott Shawcroft & Roy Hooper
3031
"""
32+
33+
# pylint: disable=ungrouped-imports
34+
import sys
3135
import busio
3236
import digitalio
3337

38+
if sys.implementation.version[0] < 5:
39+
import adafruit_pypixelbuf as _pixelbuf
40+
else:
41+
try:
42+
import _pixelbuf
43+
except ImportError:
44+
import adafruit_pypixelbuf as _pixelbuf
45+
3446
__version__ = "0.0.0-auto.0"
3547
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_DotStar.git"
3648

3749
START_HEADER_SIZE = 4
38-
LED_START = 0b11100000 # Three "1" bits, followed by 5 brightness bits
3950

4051
# Pixel color order constants
41-
RGB = (0, 1, 2)
42-
RBG = (0, 2, 1)
43-
GRB = (1, 0, 2)
44-
GBR = (1, 2, 0)
45-
BRG = (2, 0, 1)
46-
BGR = (2, 1, 0)
47-
48-
49-
class DotStar:
52+
RBG = "PRBG"
53+
"""Red Blue Green"""
54+
RGB = "PRGB"
55+
"""Red Green Blue"""
56+
GRB = "PGRB"
57+
"""Green Red Blue"""
58+
GBR = "PGBR"
59+
"""Green Blue Red"""
60+
BRG = "PBRG"
61+
"""Blue Red Green"""
62+
BGR = "PBGR"
63+
"""Blue Green Red"""
64+
65+
66+
class DotStar(_pixelbuf.PixelBuf):
5067
"""
5168
A sequence of dotstars.
5269
@@ -56,16 +73,14 @@ class DotStar:
5673
:param float brightness: Brightness of the pixels between 0.0 and 1.0
5774
:param bool auto_write: True if the dotstars should immediately change when
5875
set. If False, `show` must be called explicitly.
59-
:param tuple pixel_order: Set the pixel order on the strip - different
60-
strips implement this differently. If you send red, and it looks blue
61-
or green on the strip, modify this! It should be one of the values
62-
above.
76+
:param str pixel_order: Set the pixel order on the strip - different
77+
strips implement this differently. If you send red, and it looks blue
78+
or green on the strip, modify this! It should be one of the values above.
6379
:param int baudrate: Desired clock rate if using hardware SPI (ignored if
6480
using 'soft' SPI). This is only a recommendation; the actual clock
6581
rate may be slightly different depending on what the system hardware
6682
can provide.
6783
68-
6984
Example for Gemma M0:
7085
7186
.. code-block:: python
@@ -79,6 +94,22 @@ class DotStar:
7994
with adafruit_dotstar.DotStar(APA102_SCK, APA102_MOSI, 1) as pixels:
8095
pixels[0] = RED
8196
time.sleep(2)
97+
98+
.. py:method:: DotStar.show()
99+
100+
Shows the new colors on the dotstars themselves if they haven't already
101+
been autowritten.
102+
103+
The colors may or may not be showing after this function returns because
104+
it may be done asynchronously.
105+
106+
.. py:method:: DotStar.fill(color)
107+
108+
Colors all dotstars the given ***color***.
109+
110+
.. py:attribute:: brightness
111+
112+
Overall brightness of all dotstars (0 to 1.0)
82113
"""
83114

84115
def __init__(
@@ -105,36 +136,29 @@ def __init__(
105136
self.dpin.direction = digitalio.Direction.OUTPUT
106137
self.cpin.direction = digitalio.Direction.OUTPUT
107138
self.cpin.value = False
108-
self._n = n
139+
109140
# Supply one extra clock cycle for each two pixels in the strip.
110-
self.end_header_size = n // 16
141+
trailer_size = n // 16
111142
if n % 16 != 0:
112-
self.end_header_size += 1
113-
self._buf = bytearray(n * 4 + START_HEADER_SIZE + self.end_header_size)
114-
self.end_header_index = len(self._buf) - self.end_header_size
115-
self.pixel_order = pixel_order
116-
# Four empty bytes to start.
117-
for i in range(START_HEADER_SIZE):
118-
self._buf[i] = 0x00
119-
# Mark the beginnings of each pixel.
120-
for i in range(START_HEADER_SIZE, self.end_header_index, 4):
121-
self._buf[i] = 0xFF
122-
# 0xff bytes at the end.
123-
for i in range(self.end_header_index, len(self._buf)):
124-
self._buf[i] = 0xFF
125-
self._brightness = 1.0
126-
# Set auto_write to False temporarily so brightness setter does _not_
127-
# call show() while in __init__.
128-
self.auto_write = False
129-
self.brightness = brightness
130-
self.auto_write = auto_write
143+
trailer_size += 1
144+
145+
# Four empty bytes for the header.
146+
header = bytearray(START_HEADER_SIZE)
147+
# 0xff bytes for the trailer.
148+
trailer = bytearray(b"\xff") * trailer_size
149+
150+
super().__init__(
151+
n,
152+
byteorder=pixel_order,
153+
brightness=brightness,
154+
auto_write=auto_write,
155+
header=header,
156+
trailer=trailer,
157+
)
131158

132159
def deinit(self):
133160
"""Blank out the DotStars and release the resources."""
134-
self.auto_write = False
135-
for i in range(START_HEADER_SIZE, self.end_header_index):
136-
if i % 4 != 0:
137-
self._buf[i] = 0
161+
self.fill(0)
138162
self.show()
139163
if self._spi:
140164
self._spi.deinit()
@@ -151,136 +175,24 @@ def __exit__(self, exception_type, exception_value, traceback):
151175
def __repr__(self):
152176
return "[" + ", ".join([str(x) for x in self]) + "]"
153177

154-
def _set_item(self, index, value):
178+
@property
179+
def n(self):
155180
"""
156-
value can be one of three things:
157-
a (r,g,b) list/tuple
158-
a (r,g,b, brightness) list/tuple
159-
a single, longer int that contains RGB values, like 0xFFFFFF
160-
brightness, if specified should be a float 0-1
161-
162-
Set a pixel value. You can set per-pixel brightness here, if it's not passed it
163-
will use the max value for pixel brightness value, which is a good default.
164-
165-
Important notes about the per-pixel brightness - it's accomplished by
166-
PWMing the entire output of the LED, and that PWM is at a much
167-
slower clock than the rest of the LEDs. This can cause problems in
168-
Persistence of Vision Applications
181+
The number of dotstars in the chain (read-only)
169182
"""
183+
return len(self)
170184

171-
offset = index * 4 + START_HEADER_SIZE
172-
rgb = value
173-
if isinstance(value, int):
174-
rgb = (value >> 16, (value >> 8) & 0xFF, value & 0xFF)
175-
176-
if len(rgb) == 4:
177-
brightness = value[3]
178-
# Ignore value[3] below.
179-
else:
180-
brightness = 1
181-
182-
# LED startframe is three "1" bits, followed by 5 brightness bits
183-
# then 8 bits for each of R, G, and B. The order of those 3 are configurable and
184-
# vary based on hardware
185-
# same as math.ceil(brightness * 31) & 0b00011111
186-
# Idea from https://www.codeproject.com/Tips/700780/Fast-floor-ceiling-functions
187-
brightness_byte = 32 - int(32 - brightness * 31) & 0b00011111
188-
self._buf[offset] = brightness_byte | LED_START
189-
self._buf[offset + 1] = rgb[self.pixel_order[0]]
190-
self._buf[offset + 2] = rgb[self.pixel_order[1]]
191-
self._buf[offset + 3] = rgb[self.pixel_order[2]]
192-
193-
def __setitem__(self, index, val):
194-
if isinstance(index, slice):
195-
start, stop, step = index.indices(self._n)
196-
length = stop - start
197-
if step != 0:
198-
# same as math.ceil(length / step)
199-
# Idea from https://fizzbuzzer.com/implement-a-ceil-function/
200-
length = (length + step - 1) // step
201-
if len(val) != length:
202-
raise ValueError("Slice and input sequence size do not match.")
203-
for val_i, in_i in enumerate(range(start, stop, step)):
204-
self._set_item(in_i, val[val_i])
185+
def _transmit(self, buffer):
186+
if self._spi:
187+
self._spi.write(buffer)
205188
else:
206-
self._set_item(index, val)
207-
208-
if self.auto_write:
209-
self.show()
210-
211-
def __getitem__(self, index):
212-
if isinstance(index, slice):
213-
out = []
214-
for in_i in range(*index.indices(self._n)):
215-
out.append(
216-
tuple(
217-
self._buf[in_i * 4 + (3 - i) + START_HEADER_SIZE]
218-
for i in range(3)
219-
)
220-
)
221-
return out
222-
if index < 0:
223-
index += len(self)
224-
if index >= self._n or index < 0:
225-
raise IndexError
226-
offset = index * 4
227-
return tuple(self._buf[offset + (3 - i) + START_HEADER_SIZE] for i in range(3))
228-
229-
def __len__(self):
230-
return self._n
189+
self._ds_writebytes(buffer)
231190

232-
@property
233-
def brightness(self):
234-
"""Overall brightness of the pixel"""
235-
return self._brightness
236-
237-
@brightness.setter
238-
def brightness(self, brightness):
239-
self._brightness = min(max(brightness, 0.0), 1.0)
240-
if self.auto_write:
241-
self.show()
242-
243-
def fill(self, color):
244-
"""Colors all pixels the given ***color***."""
245-
auto_write = self.auto_write
246-
self.auto_write = False
247-
for i in range(self._n):
248-
self[i] = color
249-
if auto_write:
250-
self.show()
251-
self.auto_write = auto_write
252-
253-
def _ds_writebytes(self, buf):
254-
for b in buf:
191+
def _ds_writebytes(self, buffer):
192+
for b in buffer:
255193
for _ in range(8):
256194
self.dpin.value = b & 0x80
257195
self.cpin.value = True
258196
self.cpin.value = False
259197
b = b << 1
260-
261-
def show(self):
262-
"""Shows the new colors on the pixels themselves if they haven't already
263-
been autowritten.
264-
265-
The colors may or may not be showing after this function returns because
266-
it may be done asynchronously."""
267-
# Create a second output buffer if we need to compute brightness
268-
buf = self._buf
269-
if self.brightness < 1.0:
270-
buf = bytearray(self._buf)
271-
# Four empty bytes to start.
272-
for i in range(START_HEADER_SIZE):
273-
buf[i] = 0x00
274-
for i in range(START_HEADER_SIZE, self.end_header_index):
275-
buf[i] = (
276-
self._buf[i] if i % 4 == 0 else int(self._buf[i] * self._brightness)
277-
)
278-
# Four 0xff bytes at the end.
279-
for i in range(self.end_header_index, len(buf)):
280-
buf[i] = 0xFF
281-
282-
if self._spi:
283-
self._spi.write(buf)
284-
else:
285-
self._ds_writebytes(buf)
286-
self.cpin.value = False
198+
self.cpin.value = False

examples/dotstar_image_pov.py

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
# than through function calls or setters/getters...this is poor form as it
1010
# could break easily with future library changes, but is the only way right
1111
# now to do the POV as quickly as possible.
12+
# May require installing separate libraries.
1213

1314
import board
1415
from PIL import Image
@@ -25,7 +26,7 @@
2526
board.MOSI,
2627
NUMPIXELS,
2728
auto_write=False,
28-
brightness=0.25,
29+
brightness=1.0,
2930
pixel_order=ORDER,
3031
)
3132

@@ -42,41 +43,29 @@
4243

4344
# Calculate gamma correction table, makes mid-range colors look 'right':
4445
GAMMA = bytearray(256)
46+
brightness = 0.25
4547
for i in range(256):
46-
# Notice we access DOTS.brightness directly here...the gamma table will
47-
# handle any brightness-scaling, so we can set the object brightness back
48-
# to max and it won't need to perform brightness scaling on every write.
49-
GAMMA[i] = int(pow(float(i) / 255.0, 2.7) * DOTS.brightness * 255.0 + 0.5)
50-
DOTS.brightness = 1.0
48+
GAMMA[i] = int(pow(float(i) / 255.0, 2.7) * brightness * 255.0 + 0.5)
5149

52-
# Allocate list of bytearrays, one for each column of image.
53-
# Each pixel REQUIRES 4 bytes (0xFF, B, G, R).
50+
# Allocate list of lists, one for each column of image.
5451
print("Allocating...")
5552
COLUMN = [0 for x in range(WIDTH)]
5653
for x in range(WIDTH):
57-
COLUMN[x] = bytearray(HEIGHT * 4)
54+
COLUMN[x] = [[0, 0, 0, 0] for _ in range(HEIGHT)]
5855

59-
# Convert entire RGB image into column-wise bytearray list.
60-
# The dotstar_image_paint.py example uses the library's 'setter' operation
61-
# for each pixel to do any R/G/B reordering. Because we're preparing data
62-
# directly for the strip, there's a reference to 'ORDER' here to rearrange
63-
# the color bytes as needed.
56+
# Convert entire RGB image into columnxrow 2D list.
6457
print("Converting...")
6558
for x in range(WIDTH): # For each column of image
6659
for y in range(HEIGHT): # For each pixel in column
6760
value = PIXELS[x, y] # Read RGB pixel in image
68-
y4 = y * 4 # Position in raw buffer
69-
COLUMN[x][y4] = 0xFF # Pixel start marker
70-
y4 += 1 # Pixel color data start
71-
COLUMN[x][y4 + ORDER[0]] = GAMMA[value[0]] # Gamma-corrected R
72-
COLUMN[x][y4 + ORDER[1]] = GAMMA[value[1]] # Gamma-corrected G
73-
COLUMN[x][y4 + ORDER[2]] = GAMMA[value[2]] # Gamma-corrected B
61+
COLUMN[x][y][0] = GAMMA[value[0]] # Gamma-corrected R
62+
COLUMN[x][y][1] = GAMMA[value[1]] # Gamma-corrected G
63+
COLUMN[x][y][2] = GAMMA[value[2]] # Gamma-corrected B
64+
COLUMN[x][y][3] = 1.0 # Brightness
7465

7566
print("Displaying...")
7667
while True: # Loop forever
7768

78-
# pylint: disable=protected-access
79-
# (Really shouldn't access _buf directly, but needed for fastest POV)
8069
for x in range(WIDTH): # For each column of image...
81-
DOTS._buf[4 : 4 + HEIGHT * 4] = COLUMN[x] # Copy column to DotStar buffer
70+
DOTS[0 : DOTS.n] = COLUMN[x] # Copy column to DotStar buffer
8271
DOTS.show() # Send data to strip

0 commit comments

Comments
 (0)