diff --git a/adafruit_is31fl3731/__init__.py b/adafruit_is31fl3731/__init__.py index 2d65f4e..55b53df 100644 --- a/adafruit_is31fl3731/__init__.py +++ b/adafruit_is31fl3731/__init__.py @@ -92,11 +92,11 @@ class IS31FL3731: width = 16 height = 9 - def __init__(self, i2c, address=0x74): + def __init__(self, i2c, address=0x74, frames=None): self.i2c = i2c self.address = address self._frame = None - self._init() + self._init(frames=frames) def _i2c_read_reg(self, reg, result): # Read a buffer of data from the specified 8-bit I2C register address. @@ -111,19 +111,21 @@ def _i2c_read_reg(self, reg, result): self.i2c.unlock() return None - def _i2c_write_reg(self, reg, data): - # Write a buffer of data (byte array) to the specified I2C register - # address. + def _i2c_write_block(self, data): + # Writes a contiguous block of data (bytearray) where the first byte + # is the starting I2C register address (register is not an argument). while not self.i2c.try_lock(): pass try: - buf = bytearray(1) - buf[0] = reg - buf.extend(data) - self.i2c.writeto(self.address, buf) + self.i2c.writeto(self.address, data) finally: self.i2c.unlock() + def _i2c_write_reg(self, reg, data): + # Write a contiguous block of data (bytearray) starting at the + # specified I2C register address (register passed as argument). + self._i2c_write_block(bytes([reg]) + data) + def _bank(self, bank=None): if bank is None: result = bytearray(1) @@ -142,16 +144,21 @@ def _register(self, bank, register, value=None): def _mode(self, mode=None): return self._register(_CONFIG_BANK, _MODE_REGISTER, mode) - def _init(self): + def _init(self, frames=None): self.sleep(True) - time.sleep(0.01) # 10 MS pause to reset. - self._mode(_PICTURE_MODE) - self.frame(0) - for frame in range(8): - self.fill(0, False, frame=frame) - for col in range(18): - self._register(frame, _ENABLE_OFFSET + col, 0xFF) - self.audio_sync(False) + # Clear config; sets to Picture Mode, no audio sync, maintains sleep + self._bank(_CONFIG_BANK) + self._i2c_write_block(bytes([0] * 14)) + enable_data = bytes([_ENABLE_OFFSET] + [255] * 18) + fill_data = bytearray([0] * 25) + # Initialize requested frames, or all 8 if unspecified + for frame in frames if frames else range(8): + self._bank(frame) + self._i2c_write_block(enable_data) # Set all enable bits + for row in range(6): # Barebones quick fill() w/0 + fill_data[0] = _COLOR_OFFSET + row * 24 + self._i2c_write_block(fill_data) + self._frame = 0 # To match config bytes above self.sleep(False) def reset(self): diff --git a/adafruit_is31fl3731/matrix.py b/adafruit_is31fl3731/matrix.py index 94a2c9e..93b3034 100644 --- a/adafruit_is31fl3731/matrix.py +++ b/adafruit_is31fl3731/matrix.py @@ -40,3 +40,40 @@ class Matrix(IS31FL3731): def pixel_addr(x, y): """Calulate the offset into the device array for x,y pixel""" return x + y * 16 + + # This takes precedence over image() in __init__ and is tuned for the + # Matrix class. Some shortcuts can be taken because matrix layout is + # very straightforward, and a few large write operations are used + # rather than pixel-by-pixel writes, yielding significant speed gains + # for animation. Buffering the full matrix for a quick write is not a + # memory concern here, as by definition this method is used with PIL + # images; we're not running on a RAM-constrained microcontroller. + def image(self, img, blink=None, frame=None): + """Set buffer to value of Python Imaging Library image. + The image should be in 8-bit mode (L) and a size equal to the + display size. + + :param img: Python Imaging Library image + :param blink: True to blink + :param frame: the frame to set the image + """ + if img.mode != "L": + raise ValueError("Image must be in mode L.") + if img.size[0] != self.width or img.size[1] != self.height: + raise ValueError( + "Image must be same dimensions as display ({0}x{1}).".format( + self.width, self.height + ) + ) + + # Frame-select and then write pixel data in one big operation + if frame is not None: + self._bank(frame) + # We can safely reduce the image to a "flat" byte sequence because + # the matrix layout is known linear; no need to go through a 2D + # pixel array or invoke pixel_addr(). + self._i2c_write_block(bytes([0x24]) + img.tobytes()) + # Set or clear blink state if requested, for all pixels at once + if blink is not None: + # 0x12 is _BLINK_OFFSET in __init__.py + self._i2c_write_block(bytes([0x12] + [1 if blink else 0] * 18))