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