24
24
25
25
"""
26
26
27
+ from sys import implementation
27
28
from adafruit_bus_device import i2c_device
28
29
from adafruit_register .i2c_struct import ROUnaryStruct , UnaryStruct
29
30
from adafruit_register .i2c_bit import RWBit
33
34
# Used only for typing
34
35
from typing import Optional , Tuple , Union # pylint: disable=unused-import
35
36
from PIL import Image
37
+ from adafruit_framebuf import FrameBuffer
36
38
except ImportError :
37
39
pass
38
40
59
61
60
62
class IS31FL3741 :
61
63
"""
62
- The IS31FL3741 is an abstract class contain the main function related to this chip.
63
- Each board needs to define width, height and pixel_addr.
64
+ The IS31FL3741 is an abstract class containing the main function related
65
+ to this chip. It focuses on lowest-level I2C operations and chip
66
+ registers, and has no concept of a 2D graphics coordinate system, nor of
67
+ RGB colors (subclasses provide these). It is linear and monochromatic.
64
68
65
- :param ~adafruit_bus_device.i2c_device i2c_device: the connected i2c bus i2c_device
69
+ :param ~adafruit_bus_device.i2c_device i2c_device: the connected i2c bus
70
+ i2c_device
66
71
:param address: the device address; defaults to 0x30
67
72
:param allocate: buffer allocation strategy: NO_BUFFER = pixels are always
68
73
sent to device as they're set. PREFER_BUFFER = RAM
@@ -72,9 +77,6 @@ class IS31FL3741:
72
77
MemoryError if allocation fails.
73
78
"""
74
79
75
- width = 13
76
- height = 9
77
-
78
80
_page_reg = UnaryStruct (_IS3741_COMMANDREGISTER , "<B" )
79
81
_lock_reg = UnaryStruct (_IS3741_COMMANDREGISTERLOCK , "<B" )
80
82
_id_reg = UnaryStruct (_IS3741_IDREGISTER , "<B" )
@@ -117,18 +119,18 @@ def unlock(self) -> None:
117
119
self ._lock_reg = 0xC5
118
120
119
121
def set_led_scaling (self , scale : int ) -> None :
120
- """Set LED scaling.
122
+ """Set scaling level for all LEDs .
121
123
122
- param scale: The scale .
124
+ : param scale: Scaling level from 0 (off) to 255 (brightest) .
123
125
"""
124
- scalebuf = [scale ] * 181
125
- scalebuf [0 ] = 0
126
+ scalebuf = bytearray ( [scale ] * 181 ) # 180 bytes + 1 for reg addr
127
+ scalebuf [0 ] = 0 # Initial register address
126
128
self .page = 2
127
129
with self .i2c_device as i2c :
128
- i2c .write (bytes ( scalebuf ) )
130
+ i2c .write (scalebuf )
129
131
self .page = 3
130
132
with self .i2c_device as i2c :
131
- i2c .write (bytes ( scalebuf ))
133
+ i2c .write (scalebuf , end = 172 ) # 2nd page is smaller
132
134
133
135
@property
134
136
def global_current (self ) -> int :
@@ -186,75 +188,26 @@ def __getitem__(self, led: int) -> int:
186
188
return self ._buf [1 ]
187
189
188
190
def __setitem__ (self , led : int , pwm : int ) -> None :
189
- if not 0 <= led <= 350 :
190
- raise ValueError ("LED must be 0 ~ 350" )
191
- if not 0 <= pwm <= 255 :
192
- raise ValueError ("PWM must be 0 ~ 255" )
193
- # print(led, pwm)
194
191
if self ._pixel_buffer :
192
+ # Buffered version doesn't require range checks --
193
+ # Python will throw its own IndexError/ValueError as needed.
195
194
self ._pixel_buffer [1 + led ] = pwm
196
- else :
197
- if led < 180 :
198
- self .page = 0
199
- self ._buf [0 ] = led
195
+ elif 0 <= led <= 350 :
196
+ if 0 <= pwm <= 255 :
197
+ # print(led, pwm)
198
+ if led < 180 :
199
+ self .page = 0
200
+ self ._buf [0 ] = led
201
+ else :
202
+ self .page = 1
203
+ self ._buf [0 ] = led - 180
204
+ self ._buf [1 ] = pwm
205
+ with self .i2c_device as i2c :
206
+ i2c .write (self ._buf )
200
207
else :
201
- self .page = 1
202
- self ._buf [0 ] = led - 180
203
- self ._buf [1 ] = pwm
204
- with self .i2c_device as i2c :
205
- i2c .write (self ._buf )
206
-
207
- # This function must be replaced for each board
208
- @staticmethod
209
- def pixel_addrs (x : int , y : int ) -> Tuple [int , ...]:
210
- """Calulate the offset into the device array for x,y pixel"""
211
- raise NotImplementedError ("Supported in subclasses only" )
212
-
213
- # pylint: disable-msg=too-many-arguments
214
- def pixel (self , x : int , y : int , color : Optional [int ] = None ) -> Union [int , None ]:
215
- """
216
- Color of for x-, y-pixel
217
-
218
- :param x: horizontal pixel position
219
- :param y: vertical pixel position
220
- :param color: hex color value 0x000000 to 0xFFFFFF to set,
221
- or None to return current pixel value
222
- """
223
-
224
- if 0 <= x < self .width and 0 <= y < self .height : # Clip
225
- addrs = self .pixel_addrs (x , y ) # LED indices
226
- # print(addrs)
227
- if color is None : # Return current pixel color if unspecified
228
- return (self [addrs [0 ]] << 16 ) | (self [addrs [1 ]] << 8 ) | self [addrs [2 ]]
229
- self [addrs [0 ]] = (color >> 16 ) & 0xFF
230
- self [addrs [1 ]] = (color >> 8 ) & 0xFF
231
- self [addrs [2 ]] = color & 0xFF
232
- return None
233
-
234
- # pylint: enable-msg=too-many-arguments
235
-
236
- def image (self , img : Image ) -> None :
237
- """Set buffer to value of Python Imaging Library image. The image should
238
- be in 8-bit mode (L) and a size equal to the display size.
239
-
240
- :param img: Python Imaging Library image
241
- """
242
- if img .mode != "RGB" :
243
- raise ValueError ("Image must be in mode RGB." )
244
- imwidth , imheight = img .size
245
- if imwidth != self .width or imheight != self .height :
246
- raise ValueError (
247
- "Image must be same dimensions as display ({0}x{1})." .format (
248
- self .width , self .height
249
- )
250
- )
251
- # Grab all the pixels from the image, faster than getpixel.
252
- pixels = img .load ()
253
-
254
- # Iterate through the pixels
255
- for x in range (self .width ): # yes this double loop is slow,
256
- for y in range (self .height ): # but these displays are small!
257
- self .pixel (x , y , pixels [(x , y )])
208
+ raise ValueError ("PWM must be 0 ~ 255" )
209
+ else :
210
+ raise ValueError ("LED must be 0 ~ 350" )
258
211
259
212
def show (self ) -> None :
260
213
"""Issue in-RAM pixel data to device. No effect if pixels are
@@ -279,3 +232,134 @@ def show(self) -> None:
279
232
self ._pixel_buffer [180 ] = 0
280
233
i2c .write (self ._pixel_buffer , start = 180 , end = 352 )
281
234
self ._pixel_buffer [180 ] = save
235
+
236
+
237
+ IS3741_RGB = (0 << 4 ) | (1 << 2 ) | (2 ) # Encode as R,G,B
238
+ IS3741_RBG = (0 << 4 ) | (2 << 2 ) | (1 ) # Encode as R,B,G
239
+ IS3741_GRB = (1 << 4 ) | (0 << 2 ) | (2 ) # Encode as G,R,B
240
+ IS3741_GBR = (2 << 4 ) | (0 << 2 ) | (1 ) # Encode as G,B,R
241
+ IS3741_BRG = (1 << 4 ) | (2 << 2 ) | (0 ) # Encode as B,R,G
242
+ IS3741_BGR = (2 << 4 ) | (1 << 2 ) | (0 ) # Encode as B,G,R
243
+
244
+
245
+ class IS31FL3741_colorXY (IS31FL3741 ):
246
+ """
247
+ Class encompassing IS31FL3741 and a minimal layer for RGB color 2D
248
+ pixel operations (base class is hardware- and register-centric and
249
+ lacks these concepts). Specific boards like the QT matrix or EyeLights
250
+ glasses then subclass this. In theory, a companion monochrome XY class
251
+ could be separately implemented in the future if required for anything.
252
+ Mostly though, this is about providing a place for common RGB matrix
253
+ functions like fill() that then work across all such devices.
254
+
255
+ :param ~adafruit_bus_device.i2c_device i2c_device: the connected i2c bus
256
+ i2c_device
257
+ :param width: Matrix width in pixels.
258
+ :param height: Matrix height in pixels.
259
+ :param address: the device address; defaults to 0x30
260
+ :param allocate: buffer allocation strategy: NO_BUFFER = pixels are always
261
+ sent to device as they're set. PREFER_BUFFER = RAM
262
+ permitting, buffer pixels in RAM, updating device only
263
+ when show() is called, but fall back on NO_BUFFER
264
+ behavior. MUST_BUFFER = buffer pixels in RAM, throw
265
+ MemoryError if allocation fails.
266
+ :param order: Pixel RGB color order, one of the IS3741_* color types
267
+ above. Default is IS3741_BGR.
268
+ """
269
+
270
+ # pylint: disable-msg=too-many-arguments
271
+ def __init__ (
272
+ self ,
273
+ i2c : busio .I2C ,
274
+ width : int ,
275
+ height : int ,
276
+ address : int = _IS3741_ADDR_DEFAULT ,
277
+ allocate : int = NO_BUFFER ,
278
+ order : int = IS3741_BGR ,
279
+ ):
280
+ super ().__init__ (i2c , address = address , allocate = allocate )
281
+ self .order = order
282
+ self .width = width
283
+ self .height = height
284
+ self .r_offset = (order >> 4 ) & 3
285
+ self .g_offset = (order >> 2 ) & 3
286
+ self .b_offset = order & 3
287
+
288
+ # pylint: enable-msg=too-many-arguments
289
+
290
+ # This function must be replaced for each board
291
+ @staticmethod
292
+ def pixel_addrs (x : int , y : int ) -> Tuple [int , ...]:
293
+ """Calculate a device-specific LED offset for an X,Y 2D pixel."""
294
+ raise NotImplementedError ("Supported in subclasses only" )
295
+
296
+ def fill (self , color : int = 0 ) -> None :
297
+ """Set all pixels to a given RGB color.
298
+
299
+ :param color: Packed 24-bit color value (0xRRGGBB).
300
+ """
301
+ red = (color >> 16 ) & 0xFF
302
+ green = (color >> 8 ) & 0xFF
303
+ blue = color & 0xFF
304
+ for y in range (self .height ):
305
+ for x in range (self .width ):
306
+ addrs = self .pixel_addrs (x , y )
307
+ self [addrs [self .r_offset ]] = red
308
+ self [addrs [self .g_offset ]] = green
309
+ self [addrs [self .b_offset ]] = blue
310
+
311
+ def pixel (self , x : int , y : int , color : int = None ) -> Union [int , None ]:
312
+ """
313
+ Set or retrieve RGB color of pixel at position (X,Y).
314
+
315
+ :param x: Horizontal pixel position.
316
+ :param y: Vertical pixel position.
317
+ :param color: If setting, a packed 24-bit color value (0xRRGGBB).
318
+ If getting, either None or leave off this argument.
319
+ :returns: If setting, returns None. If getting, returns a packed
320
+ 24-bit color value (0xRRGGBB).
321
+ """
322
+
323
+ if 0 <= x < self .width and 0 <= y < self .height : # Clip
324
+ addrs = self .pixel_addrs (x , y ) # LED indices
325
+ # print(addrs)
326
+ if color is not None :
327
+ self [addrs [self .r_offset ]] = (color >> 16 ) & 0xFF
328
+ self [addrs [self .g_offset ]] = (color >> 8 ) & 0xFF
329
+ self [addrs [self .b_offset ]] = color & 0xFF
330
+ else : # Return current pixel color if unspecified
331
+ return (
332
+ (self [addrs [self .r_offset ]] << 16 )
333
+ | (self [addrs [self .g_offset ]] << 8 )
334
+ | self [addrs [self .b_offset ]]
335
+ )
336
+ return None
337
+
338
+ def image (self , img : Union [FrameBuffer , Image ]) -> None :
339
+ """Copy an in-memory image to the LED matrix. Image should be in
340
+ 24-bit format (e.g. "RGB888") and dimensions should match matrix,
341
+ this isn't super robust yet or anything.
342
+
343
+ :param img: Source image -- either a FrameBuffer object if running
344
+ CircuitPython, or PIL image if running CPython w/Python
345
+ Imaging Lib.
346
+ """
347
+ if implementation .name == "circuitpython" :
348
+ for y in range (self .height ):
349
+ for x in range (self .width ):
350
+ self .pixel (x , y , img .pixel (x , y ))
351
+ else :
352
+ if img .mode != "RGB" :
353
+ raise ValueError ("Image must be in mode RGB." )
354
+ if img .size [0 ] != self .width or img .size [1 ] != self .height :
355
+ raise ValueError (
356
+ "Image must be same dimensions as display ({0}x{1})." .format (
357
+ self .width , self .height
358
+ )
359
+ )
360
+
361
+ # Iterate X/Y through all image pixels
362
+ pixels = img .load () # Grab all pixels, faster than getpixel on each
363
+ for y in range (self .height ):
364
+ for x in range (self .width ):
365
+ self .pixel (x , y , pixels [(x , y )])
0 commit comments