Skip to content

Commit b756b5e

Browse files
committed
Add Rotation and Typing
1 parent 6d364d1 commit b756b5e

File tree

3 files changed

+161
-48
lines changed

3 files changed

+161
-48
lines changed

adafruit_is31fl3731/__init__.py

Lines changed: 97 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,15 @@
5555

5656
from adafruit_bus_device.i2c_device import I2CDevice
5757

58+
try:
59+
from typing import TYPE_CHECKING, List, Tuple
60+
61+
if TYPE_CHECKING:
62+
from circuitpython_typing import ReadableBuffer
63+
import busio
64+
except ImportError as e:
65+
pass
66+
5867
__version__ = "0.0.0+auto.0"
5968
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_IS31FL3731.git"
6069

@@ -89,17 +98,18 @@ class IS31FL3731:
8998
9099
:param ~busio.I2C i2c: the connected i2c bus i2c_device
91100
:param int address: the device address; defaults to 0x74
101+
:param int frames: static 0 or animation frames (0-7)
92102
"""
93103

94-
width = 16
95-
height = 9
104+
width: int = 16
105+
height: int = 9
96106

97-
def __init__(self, i2c, address=0x74, frames=None):
107+
def __init__(self, i2c: busio.I2C, address: int = 0x74, frames: int = None) -> None:
98108
self.i2c_device = I2CDevice(i2c, address)
99109
self._frame = None
100110
self._init(frames=frames)
101111

102-
def _i2c_read_reg(self, reg, result):
112+
def _i2c_read_reg(self, reg: int = None, result: bytes = None) -> bytes:
103113
# Read a buffer of data from the specified 8-bit I2C register address.
104114
# The provided result parameter will be filled to capacity with bytes
105115
# of data read from the register.
@@ -108,36 +118,40 @@ def _i2c_read_reg(self, reg, result):
108118
return result
109119
return None
110120

111-
def _i2c_write_reg(self, reg, data):
121+
def _i2c_write_reg(self, reg: int = None, data: bytes = None) -> bytes:
112122
# Write a contiguous block of data (bytearray) starting at the
113123
# specified I2C register address (register passed as argument).
114124
self._i2c_write_block(bytes([reg]) + data)
115125

116-
def _i2c_write_block(self, data):
126+
def _i2c_write_block(self, data: bytes = None) -> bytes:
117127
# Write a buffer of data (byte array) to the specified I2C register
118128
# address.
119129
with self.i2c_device as i2c:
120130
i2c.write(data)
121131

122-
def _bank(self, bank=None):
132+
def _bank(self, bank: int = None) -> int:
123133
if bank is None:
124134
result = bytearray(1)
125135
return self._i2c_read_reg(_BANK_ADDRESS, result)[0]
126136
self._i2c_write_reg(_BANK_ADDRESS, bytearray([bank]))
127137
return None
128138

129-
def _register(self, bank, register, value=None):
139+
def _register(
140+
self, bank: int = None, register: int = None, value: int = None
141+
) -> int:
130142
self._bank(bank)
131143
if value is None:
132144
result = bytearray(1)
145+
print(f"Register: {result}")
133146
return self._i2c_read_reg(register, result)[0]
134147
self._i2c_write_reg(register, bytearray([value]))
135148
return None
136149

137150
def _mode(self, mode=None):
151+
"""Function for setting _register mode"""
138152
return self._register(_CONFIG_BANK, _MODE_REGISTER, mode)
139153

140-
def _init(self, frames=None):
154+
def _init(self, frames: int = 0) -> int:
141155
self.sleep(True)
142156
# Clear config; sets to Picture Mode, no audio sync, maintains sleep
143157
self._bank(_CONFIG_BANK)
@@ -160,15 +174,15 @@ def reset(self):
160174
time.sleep(0.01) # 10 MS pause to reset.
161175
self.sleep(False)
162176

163-
def sleep(self, value):
177+
def sleep(self, value: bool = False):
164178
"""
165179
Set the Software Shutdown Register bit
166180
167181
:param value: True to set software shutdown bit; False unset
168182
"""
169183
return self._register(_CONFIG_BANK, _SHUTDOWN_REGISTER, not value)
170184

171-
def autoplay(self, delay=0, loops=0, frames=0):
185+
def autoplay(self, delay: float = 0.0, loops: int = 0, frames: int = 0) -> int:
172186
"""
173187
Start autoplay
174188
@@ -190,15 +204,15 @@ def autoplay(self, delay=0, loops=0, frames=0):
190204
self._register(_CONFIG_BANK, _AUTOPLAY2_REGISTER, delay % 64)
191205
self._mode(_AUTOPLAY_MODE | self._frame)
192206

193-
def fade(self, fade_in=None, fade_out=None, pause=0):
207+
def fade(self, fade_in: int = None, fade_out: int = None, pause: int = 0) -> int:
194208
"""
195209
Start and stop the fade feature. If both fade_in and fade_out are None (the
196210
default), the breath feature is used for fading. if fade_in is None, then
197211
fade_in = fade_out. If fade_out is None, then fade_out = fade_in
198212
199-
:param fade_in: positive number; 0->100
200-
:param fade-out: positive number; 0->100
201-
:param pause: breath register 2 pause value
213+
:param fade_in: int positive number; 0->100
214+
:param fade-out: int positive number; 0->100
215+
:param pause: int breath register 2 pause value
202216
"""
203217
if fade_in is None and fade_out is None:
204218
self._register(_CONFIG_BANK, _BREATH2_REGISTER, 0)
@@ -223,12 +237,12 @@ def fade(self, fade_in=None, fade_out=None, pause=0):
223237
self._register(_CONFIG_BANK, _BREATH1_REGISTER, fade_out << 4 | fade_in)
224238
self._register(_CONFIG_BANK, _BREATH2_REGISTER, 1 << 4 | pause)
225239

226-
def frame(self, frame=None, show=True):
240+
def frame(self, frame: int = None, show: bool = True) -> int:
227241
"""
228242
Set the current frame
229243
230-
:param frame: frame number; 0-7 or None. If None function returns current frame
231-
:param show: True to show the frame; False to not show.
244+
:param frame: int frame number; 0-7 or None. If None function returns current frame
245+
:param show: bool True to show the frame; False to not show.
232246
"""
233247
if frame is None:
234248
return self._frame
@@ -239,11 +253,17 @@ def frame(self, frame=None, show=True):
239253
self._register(_CONFIG_BANK, _FRAME_REGISTER, frame)
240254
return None
241255

242-
def audio_sync(self, value=None):
256+
def audio_sync(self, value: int = None) -> int:
243257
"""Set the audio sync feature register"""
244258
return self._register(_CONFIG_BANK, _AUDIOSYNC_REGISTER, value)
245259

246-
def audio_play(self, sample_rate, audio_gain=0, agc_enable=False, agc_fast=False):
260+
def audio_play(
261+
self,
262+
sample_rate: int = 0,
263+
audio_gain: int = 0,
264+
agc_enable: bool = False,
265+
agc_fast: bool = False,
266+
) -> int:
247267
"""Controls the audio play feature"""
248268
if sample_rate == 0:
249269
self._mode(_PICTURE_MODE)
@@ -262,7 +282,7 @@ def audio_play(self, sample_rate, audio_gain=0, agc_enable=False, agc_fast=False
262282
)
263283
self._mode(_AUDIOPLAY_MODE)
264284

265-
def blink(self, rate=None):
285+
def blink(self, rate: int = None) -> int:
266286
"""Updates the blink register"""
267287
# pylint: disable=no-else-return
268288
# This needs to be refactored when it can be tested
@@ -275,13 +295,13 @@ def blink(self, rate=None):
275295
self._register(_CONFIG_BANK, _BLINK_REGISTER, rate & 0x07 | 0x08)
276296
return None
277297

278-
def fill(self, color=None, blink=None, frame=None):
298+
def fill(self, color: int = None, blink: bool = False, frame: int = 0) -> int:
279299
"""
280300
Fill the display with a brightness level
281301
282302
:param color: brightness 0->255
283-
:param blink: True if blinking is required
284-
:param frame: which frame to fill 0->7
303+
:param blink: bool True to blink
304+
:param frame: int the frame to set the pixel, default 0
285305
"""
286306
if frame is None:
287307
frame = self._frame
@@ -306,30 +326,66 @@ def pixel_addr(x, y):
306326
return x + y * 16
307327

308328
# pylint: disable-msg=too-many-arguments
309-
def pixel(self, x, y, color=None, blink=None, frame=None):
329+
def pixel(
330+
self,
331+
x: int,
332+
y: int,
333+
color: int = 255,
334+
blink: bool = False,
335+
frame: int = 0,
336+
rotate: int = 0,
337+
):
310338
"""
311-
Blink or brightness for x-, y-pixel
312-
313-
:param x: horizontal pixel position
314-
:param y: vertical pixel position
315-
:param color: brightness value 0->255
316-
:param blink: True to blink
317-
:param frame: the frame to set the pixel
339+
Matrix display configuration
340+
341+
:param x: int horizontal pixel position
342+
:param y: int vertical pixel position
343+
:param color: int brightness value 0->255
344+
:param blink: bool True to blink
345+
:param frame: int the frame to set the pixel, default 0
346+
:param rotate: int display rotation (0, 90, 180, 270)
318347
"""
319-
if not 0 <= x <= self.width:
320-
return None
321-
if not 0 <= y <= self.height:
322-
return None
323-
pixel = self.pixel_addr(x, y)
348+
349+
if rotate not in (0, 90, 180, 270):
350+
raise ValueError("Rotation must be 0, 90, 180, or 270 degrees")
351+
352+
if rotate == 0:
353+
if not 0 <= x <= self.width:
354+
return None
355+
if not 0 <= y <= self.height:
356+
return None
357+
pixel = self.pixel_addr(x, y)
358+
elif rotate == 90:
359+
if not 0 <= y <= self.width:
360+
return None
361+
if not 0 <= x <= self.height:
362+
return None
363+
pixel = self.pixel_addr(y, self.height - x - 1)
364+
elif rotate == 180:
365+
if not 0 <= x <= self.width:
366+
return None
367+
if not 0 <= y <= self.height:
368+
return None
369+
pixel = self.pixel_addr(self.width - x - 1, self.height - y - 1)
370+
elif rotate == 270:
371+
if not 0 <= y <= self.width:
372+
return None
373+
if not 0 <= x <= self.height:
374+
return None
375+
pixel = self.pixel_addr(self.width - y - 1, x)
376+
324377
if color is None and blink is None:
325378
return self._register(self._frame, pixel)
379+
# frames other than 0 only used in animation. allow None.
326380
if frame is None:
327381
frame = self._frame
382+
# Brightness
328383
if color is not None:
329384
if not 0 <= color <= 255:
330-
raise ValueError("Color out of range")
385+
raise ValueError("Brightness or Color out of range (0-255)")
331386
self._register(frame, _COLOR_OFFSET + pixel, color)
332-
if blink is not None:
387+
# Blink works but not well while animated
388+
if blink:
333389
addr, bit = divmod(pixel, 8)
334390
bits = self._register(frame, _BLINK_OFFSET + addr)
335391
if blink:
@@ -341,13 +397,13 @@ def pixel(self, x, y, color=None, blink=None, frame=None):
341397

342398
# pylint: enable-msg=too-many-arguments
343399

344-
def image(self, img, blink=None, frame=None):
400+
def image(self, img: bytes = None, blink: bool = False, frame: int = 0) -> bytes:
345401
"""Set buffer to value of Python Imaging Library image. The image should
346402
be in 8-bit mode (L) and a size equal to the display size.
347403
348404
:param img: Python Imaging Library image
349405
:param blink: True to blink
350-
:param frame: the frame to set the image
406+
:param frame: the frame to set the image, default 0
351407
"""
352408
if img.mode != "L":
353409
raise ValueError("Image must be in mode L.")

adafruit_is31fl3731/matrix.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,23 @@
2929
# imports
3030
from . import IS31FL3731
3131

32+
try:
33+
from typing import TYPE_CHECKING, List, Tuple
34+
35+
if TYPE_CHECKING:
36+
from circuitpython_typing import ReadableBuffer
37+
except ImportError as e:
38+
pass
39+
3240

3341
class Matrix(IS31FL3731):
34-
"""Supports the Charlieplexed feather wing"""
42+
"""Charlieplexed Featherwing & IS31FL3731 I2C Modules"""
3543

36-
width = 16
37-
height = 9
44+
width: int = 16
45+
height: int = 9
3846

3947
@staticmethod
40-
def pixel_addr(x, y):
48+
def pixel_addr(x: int, y: int) -> int:
4149
"""Calulate the offset into the device array for x,y pixel"""
4250
return x + y * 16
4351

@@ -48,7 +56,7 @@ def pixel_addr(x, y):
4856
# for animation. Buffering the full matrix for a quick write is not a
4957
# memory concern here, as by definition this method is used with PIL
5058
# images; we're not running on a RAM-constrained microcontroller.
51-
def image(self, img, blink=None, frame=None):
59+
def image(self, img: str = None, blink: bool = False, frame: int = 0):
5260
"""Set buffer to value of Python Imaging Library image.
5361
The image should be in 8-bit mode (L) and a size equal to the
5462
display size.
@@ -67,13 +75,13 @@ def image(self, img, blink=None, frame=None):
6775
)
6876

6977
# Frame-select and then write pixel data in one big operation
70-
if frame is not None:
78+
if frame is not 0:
7179
self._bank(frame)
7280
# We can safely reduce the image to a "flat" byte sequence because
7381
# the matrix layout is known linear; no need to go through a 2D
7482
# pixel array or invoke pixel_addr().
7583
self._i2c_write_block(bytes([0x24]) + img.tobytes())
7684
# Set or clear blink state if requested, for all pixels at once
77-
if blink is not None:
85+
if blink:
7886
# 0x12 is _BLINK_OFFSET in __init__.py
7987
self._i2c_write_block(bytes([0x12] + [1 if blink else 0] * 18))
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# SPDX-FileCopyrightText: 2024 DJDevon3
2+
# SPDX-License-Identifier: MIT
3+
""" Adafruit 16x9 Charlieplexed PWM LED Matrix Example """
4+
5+
import board
6+
import adafruit_framebuf
7+
8+
from adafruit_is31fl3731.matrix import Matrix as Display
9+
10+
# Uncomment for Pi Pico
11+
# import busio
12+
# i2c = busio.I2C(board.GP21, board.GP20)
13+
14+
i2c = board.STEMMA_I2C()
15+
display = Display(i2c, address=0x74)
16+
17+
pixel_rotation = 90 # display rotation (0,90,180,270)
18+
pixel_brightness = 20 # values (0-255)
19+
pixel_blink = False # blink entire display
20+
21+
text_to_show = "Hello World!" # Scrolling marquee text
22+
23+
print(f"Display Dimensions: {display.width}x{display.height}")
24+
print(f"Text: {text_to_show}")
25+
26+
# Create a framebuffer for our display
27+
buf = bytearray(32) # 2 bytes tall x 16 wide = 32 bytes (9 bits is 2 bytes)
28+
buffer = adafruit_framebuf.FrameBuffer(
29+
buf, display.width, display.height, adafruit_framebuf.MVLSB
30+
)
31+
32+
frame = 0 # start with frame 0
33+
while True:
34+
# Looping marquee
35+
for i in range(len(text_to_show) * 9):
36+
buffer.fill(0)
37+
buffer.text(text_to_show, -i + display.width, 0, color=1)
38+
display.frame(frame, show=False)
39+
display.fill(0)
40+
for x in range(display.width):
41+
# using the FrameBuffer text result
42+
bite = buf[x]
43+
for y in range(display.height):
44+
bit = 1 << y & bite
45+
# if bit > 0 then set the pixel brightness
46+
if bit:
47+
display.pixel(
48+
x, y, pixel_brightness, blink=pixel_blink, rotate=pixel_rotation
49+
)

0 commit comments

Comments
 (0)