Skip to content

Commit be8931b

Browse files
Merge pull request #4 from adafruit/philb-buffer
Add optional LED buffering
2 parents ae0d9e6 + 1ddc425 commit be8931b

File tree

4 files changed

+158
-84
lines changed

4 files changed

+158
-84
lines changed

adafruit_is31fl3741/__init__.py

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
2525
"""
2626

27-
import adafruit_bus_device.i2c_device as i2c_device
27+
from adafruit_bus_device import i2c_device
2828
from adafruit_register.i2c_struct import ROUnaryStruct, UnaryStruct
2929
from adafruit_register.i2c_bit import RWBit
3030

@@ -43,6 +43,11 @@
4343
_IS3741_FUNCREG_GCURRENT = 0x01
4444
_IS3741_FUNCREG_RESET = 0x3F
4545

46+
# Buffer allocation behaviors passed to constructor
47+
NO_BUFFER = 0x00 # DO NOT buffer pixel data, write pixels as needed
48+
PREFER_BUFFER = 0x01 # OPTIONALLY buffer pixel data, RAM permitting
49+
MUST_BUFFER = 0x02 # MUST buffer pixel data, else throw MemoryError
50+
4651

4752
class IS31FL3741:
4853
"""
@@ -51,6 +56,12 @@ class IS31FL3741:
5156
5257
:param ~adafruit_bus_device.i2c_device i2c_device: the connected i2c bus i2c_device
5358
:param address: the device address; defaults to 0x30
59+
:param allocate: buffer allocation strategy: NO_BUFFER = pixels are always
60+
sent to device as they're set. PREFER_BUFFER = RAM
61+
permitting, buffer pixels in RAM, updating device only
62+
when show() is called, but fall back on NO_BUFFER
63+
behavior. MUST_BUFFER = buffer pixels in RAM, throw
64+
MemoryError if allocation fails.
5465
"""
5566

5667
width = 13
@@ -63,8 +74,19 @@ class IS31FL3741:
6374
_gcurrent_reg = UnaryStruct(_IS3741_FUNCREG_GCURRENT, "<B")
6475
_reset_reg = UnaryStruct(_IS3741_FUNCREG_RESET, "<B")
6576
_shutdown_bit = RWBit(_IS3741_FUNCREG_CONFIG, 0)
66-
67-
def __init__(self, i2c, address=_IS3741_ADDR_DEFAULT):
77+
_pixel_buffer = None
78+
79+
def __init__(self, i2c, address=_IS3741_ADDR_DEFAULT, allocate=NO_BUFFER):
80+
if allocate >= PREFER_BUFFER:
81+
try:
82+
# Pixel buffer intentionally has an extra item at the start
83+
# (value of 0) so we can i2c.write() from the buffer directly
84+
# (don't need a temp/copy buffer to pre-pend the register
85+
# address).
86+
self._pixel_buffer = bytearray(352)
87+
except MemoryError:
88+
if allocate == MUST_BUFFER:
89+
raise
6890
self.i2c_device = i2c_device.I2CDevice(i2c, address)
6991
if self._id_reg != 2 * address:
7092
raise AttributeError("Cannot find a IS31FL3741 at address 0x", address)
@@ -135,6 +157,8 @@ def page(self, page_value):
135157
def __getitem__(self, led):
136158
if not 0 <= led <= 350:
137159
raise ValueError("LED must be 0 ~ 350")
160+
if self._pixel_buffer:
161+
return self._pixel_buffer[1 + led]
138162
if led < 180:
139163
self.page = 0
140164
self._buf[0] = led
@@ -154,16 +178,18 @@ def __setitem__(self, led, pwm):
154178
if not 0 <= pwm <= 255:
155179
raise ValueError("PWM must be 0 ~ 255")
156180
# print(led, pwm)
157-
158-
if led < 180:
159-
self.page = 0
160-
self._buf[0] = led
181+
if self._pixel_buffer:
182+
self._pixel_buffer[1 + led] = pwm
161183
else:
162-
self.page = 1
163-
self._buf[0] = led - 180
164-
self._buf[1] = pwm
165-
with self.i2c_device as i2c:
166-
i2c.write(self._buf)
184+
if led < 180:
185+
self.page = 0
186+
self._buf[0] = led
187+
else:
188+
self.page = 1
189+
self._buf[0] = led - 180
190+
self._buf[1] = pwm
191+
with self.i2c_device as i2c:
192+
i2c.write(self._buf)
167193

168194
# This function must be replaced for each board
169195
@staticmethod
@@ -223,3 +249,27 @@ def image(self, img):
223249
for x in range(self.width): # yes this double loop is slow,
224250
for y in range(self.height): # but these displays are small!
225251
self.pixel(x, y, pixels[(x, y)])
252+
253+
def show(self):
254+
"""Issue in-RAM pixel data to device. No effect if pixels are
255+
unbuffered.
256+
"""
257+
if self._pixel_buffer:
258+
self.page = 0
259+
with self.i2c_device as i2c:
260+
# _pixel_buffer[0] is always 0! (First register addr)
261+
i2c.write(self._pixel_buffer, start=0, end=181)
262+
self.page = 1
263+
with self.i2c_device as i2c:
264+
# In order to write from pixel buffer directly (without a
265+
# whole extra temp buffer), element 180 is saved in a temp var
266+
# and replaced with 0 (representing the first regisyer addr on
267+
# page 1), then we can i2c.write() directly from that position
268+
# in the buffer. Element 180 is restored afterward. This is
269+
# the same strategy as used in the Arduino library.
270+
# 'end' below is 352 (not 351) because of the extra byte at
271+
# the start of the pixel buffer.
272+
save = self._pixel_buffer[180]
273+
self._pixel_buffer[180] = 0
274+
i2c.write(self._pixel_buffer, start=180, end=352)
275+
self._pixel_buffer[180] = save

adafruit_is31fl3741/adafruit_ledglasses.py

Lines changed: 75 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,98 +23,117 @@
2323
2424
"""
2525
# pylint: disable=abstract-method, super-init-not-called
26+
import adafruit_is31fl3741
2627
from . import IS31FL3741
2728

2829

2930
class Right_Ring(IS31FL3741):
3031
"""The right eye ring of the LED glasses"""
3132

33+
ledmap = (
34+
(287, 31, 30), # 0
35+
(278, 1, 0), # 1
36+
(273, 274, 275), # 2
37+
(282, 283, 284), # 3
38+
(270, 271, 272), # 4
39+
(27, 28, 29), # 5
40+
(23, 24, 25), # 6
41+
(276, 277, 22), # 7
42+
(20, 21, 26), # 8
43+
(50, 51, 56), # 9
44+
(80, 81, 86), # 10
45+
(110, 111, 116), # 11
46+
(140, 141, 146), # 12
47+
(170, 171, 176), # 13
48+
(200, 201, 206), # 14
49+
(230, 231, 236), # 15
50+
(260, 261, 266), # 16
51+
(348, 349, 262), # 17
52+
(233, 234, 235), # 18
53+
(237, 238, 239), # 19
54+
(339, 340, 232), # 20
55+
(327, 328, 329), # 21
56+
(305, 91, 90), # 22
57+
(296, 61, 60), # 23
58+
)
59+
3260
def __init__(self, is31_controller):
3361
self._is31 = is31_controller
3462

3563
def __setitem__(self, led, color):
3664
if not 0 <= led <= 23:
3765
raise ValueError("led must be 0~23")
3866

39-
ledmap = (
40-
(287, 31, 30), # 0
41-
(278, 1, 0), # 1
42-
(273, 274, 275), # 2
43-
(282, 283, 284), # 3
44-
(270, 271, 272), # 4
45-
(27, 28, 29), # 5
46-
(23, 24, 25), # 6
47-
(276, 277, 22), # 7
48-
(20, 21, 26), # 8
49-
(50, 51, 56), # 9
50-
(80, 81, 86), # 10
51-
(110, 111, 116), # 11
52-
(140, 141, 146), # 12
53-
(170, 171, 176), # 13
54-
(200, 201, 206), # 14
55-
(230, 231, 236), # 15
56-
(260, 261, 266), # 16
57-
(348, 349, 262), # 17
58-
(233, 234, 235), # 18
59-
(237, 238, 239), # 19
60-
(339, 340, 232), # 20
61-
(327, 328, 329), # 21
62-
(305, 91, 90), # 22
63-
(296, 61, 60), # 23
64-
)
65-
rgb = ledmap[led]
67+
rgb = Right_Ring.ledmap[led]
6668
self._is31[rgb[0]] = (color >> 16) & 0xFF
6769
self._is31[rgb[1]] = (color >> 8) & 0xFF
6870
self._is31[rgb[2]] = color & 0xFF
6971

72+
def __getitem__(self, led):
73+
if not 0 <= led <= 23:
74+
raise ValueError("led must be 0~23")
75+
rgb = Right_Ring.ledmap[led]
76+
return (
77+
(self._is31[rgb[0]] << 16) | (self._is31[rgb[1]] << 8) | self._is31[rgb[2]]
78+
)
79+
7080

7181
class Left_Ring:
7282
"""The left eye ring of the LED glasses"""
7383

84+
ledmap = (
85+
(341, 211, 210), # 0
86+
(332, 181, 180), # 1
87+
(323, 151, 150), # 2
88+
(127, 126, 125), # 3
89+
(154, 153, 152), # 4
90+
(163, 162, 161), # 5
91+
(166, 165, 164), # 6
92+
(244, 243, 242), # 7
93+
(259, 258, 257), # 8
94+
(169, 168, 167), # 9
95+
(139, 138, 137), # 10
96+
(109, 108, 107), # 11
97+
(79, 78, 77), # 12
98+
(49, 48, 47), # 13
99+
(199, 198, 197), # 14
100+
(229, 228, 227), # 15
101+
(19, 18, 17), # 16
102+
(4, 3, 2), # 17
103+
(16, 15, 14), # 18
104+
(13, 12, 11), # 19
105+
(10, 9, 8), # 20
106+
(217, 216, 215), # 21
107+
(7, 6, 5), # 22
108+
(350, 241, 240), # 23
109+
)
110+
74111
def __init__(self, is31_controller):
75112
self._is31 = is31_controller
76113

77114
def __setitem__(self, led, color):
78115
if not 0 <= led <= 23:
79116
raise ValueError("led must be 0~23")
80117

81-
ledmap = (
82-
(341, 211, 210), # 0
83-
(332, 181, 180), # 1
84-
(323, 151, 150), # 2
85-
(127, 126, 125), # 3
86-
(154, 153, 152), # 4
87-
(163, 162, 161), # 5
88-
(166, 165, 164), # 6
89-
(244, 243, 242), # 7
90-
(259, 258, 257), # 8
91-
(169, 168, 167), # 9
92-
(139, 138, 137), # 10
93-
(109, 108, 107), # 11
94-
(79, 78, 77), # 12
95-
(49, 48, 47), # 13
96-
(199, 198, 197), # 14
97-
(229, 228, 227), # 15
98-
(19, 18, 17), # 16
99-
(4, 3, 2), # 17
100-
(16, 15, 14), # 18
101-
(13, 12, 11), # 19
102-
(10, 9, 8), # 20
103-
(217, 216, 215), # 21
104-
(7, 6, 5), # 22
105-
(350, 241, 240), # 23
106-
)
107-
rgb = ledmap[led]
118+
rgb = Left_Ring.ledmap[led]
108119
self._is31[rgb[0]] = (color >> 16) & 0xFF
109120
self._is31[rgb[1]] = (color >> 8) & 0xFF
110121
self._is31[rgb[2]] = color & 0xFF
111122

123+
def __getitem__(self, led):
124+
if not 0 <= led <= 23:
125+
raise ValueError("led must be 0~23")
126+
rgb = Left_Ring.ledmap[led]
127+
return (
128+
(self._is31[rgb[0]] << 16) | (self._is31[rgb[1]] << 8) | self._is31[rgb[2]]
129+
)
130+
112131

113132
class LED_Glasses(IS31FL3741):
114133
"""Class representing LED Glasses"""
115134

116-
def __init__(self, i2c):
117-
super().__init__(i2c)
135+
def __init__(self, i2c, allocate=adafruit_is31fl3741.NO_BUFFER):
136+
super().__init__(i2c, allocate=allocate)
118137
self.set_led_scaling(0xFF) # turn on LEDs all the way
119138
self.global_current = 0xFE # set current to max
120139
self.enable = True # enable!

examples/is31fl3741_glassesrings.py

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1-
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
2-
# SPDX-License-Identifier: MIT
3-
4-
import board
5-
from rainbowio import colorwheel
6-
from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses
7-
8-
glasses = LED_Glasses(board.I2C())
9-
10-
wheeloffset = 0
11-
while True:
12-
for i in range(24):
13-
glasses.right_ring[i] = colorwheel(i / 24 * 255 + wheeloffset)
14-
glasses.left_ring[23 - i] = colorwheel(i / 24 * 255 + wheeloffset)
15-
wheeloffset += 10
1+
# SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
4+
import board
5+
from rainbowio import colorwheel
6+
from adafruit_is31fl3741.adafruit_ledglasses import LED_Glasses
7+
import adafruit_is31fl3741
8+
9+
glasses = LED_Glasses(board.I2C(), allocate=adafruit_is31fl3741.MUST_BUFFER)
10+
11+
wheeloffset = 0
12+
while True:
13+
for i in range(24):
14+
hue = colorwheel(i * 256 // 24 + wheeloffset)
15+
glasses.right_ring[i] = hue
16+
glasses.left_ring[23 - i] = hue
17+
glasses.show()
18+
wheeloffset += 10

examples/is31fl3741_rgbswirl.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
from rainbowio import colorwheel
66

77
from adafruit_is31fl3741.adafruit_rgbmatrixqt import Adafruit_RGBMatrixQT
8+
import adafruit_is31fl3741
89

9-
is31 = Adafruit_RGBMatrixQT(board.I2C())
10+
is31 = Adafruit_RGBMatrixQT(board.I2C(), allocate=adafruit_is31fl3741.PREFER_BUFFER)
1011
is31.set_led_scaling(0xFF)
1112
is31.global_current = 0xFF
1213
# print("Global current is: ", is31.global_current)
@@ -19,3 +20,4 @@
1920
for x in range(13):
2021
is31.pixel(x, y, colorwheel((y * 13 + x) * 2 + wheeloffset))
2122
wheeloffset += 1
23+
is31.show()

0 commit comments

Comments
 (0)