Skip to content

Commit 0a64fe0

Browse files
authored
Merge pull request #37 from FlantasticDan/chainable-matrices
Cascading Matrices
2 parents 2fe760c + fd61e87 commit 0a64fe0

File tree

6 files changed

+361
-3
lines changed

6 files changed

+361
-3
lines changed

adafruit_max7219/matrices.py

Lines changed: 226 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
# SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries
2+
# SPDX-FileCopyrightText: 2021 Daniel Flanagan
23
#
34
# SPDX-License-Identifier: MIT
45

56
"""
6-
`adafruit_max7219.matrices.Matrix8x8`
7+
`adafruit_max7219.matrices`
78
====================================================
89
"""
910
from micropython import const
11+
from adafruit_framebuf import BitmapFont
1012
from adafruit_max7219 import max7219
1113

1214
try:
1315
# Used only for typing
14-
import typing # pylint: disable=unused-import
16+
from typing import Tuple
1517
import digitalio
1618
import busio
1719
except ImportError:
@@ -66,3 +68,225 @@ def clear_all(self) -> None:
6668
Clears all matrix leds.
6769
"""
6870
self.fill(0)
71+
72+
73+
class CustomMatrix(max7219.ChainableMAX7219):
74+
"""
75+
Driver for a custom 8x8 LED matrix constellation based on daisy chained MAX7219 chips.
76+
77+
:param ~busio.SPI spi: an spi busio or spi bitbangio object
78+
:param ~digitalio.DigitalInOut cs: digital in/out to use as chip select signal
79+
:param int width: the number of pixels wide
80+
:param int height: the number of pixels high
81+
:param int rotation: the number of times to rotate the coordinate system (default 1)
82+
"""
83+
84+
def __init__(
85+
self,
86+
spi: busio.SPI,
87+
cs: digitalio.DigitalInOut,
88+
width: int,
89+
height: int,
90+
*,
91+
rotation: int = 1
92+
):
93+
super().__init__(width, height, spi, cs)
94+
95+
self.y_offset = width // 8
96+
self.y_index = self._calculate_y_coordinate_offsets()
97+
98+
self.framebuf.rotation = rotation
99+
self.framebuf.fill_rect = self._fill_rect
100+
self._font = None
101+
102+
def _calculate_y_coordinate_offsets(self) -> None:
103+
y_chunks = []
104+
for _ in range(self.chain_length // (self.width // 8)):
105+
y_chunks.append([])
106+
chunk = 0
107+
chunk_size = 0
108+
for index in range(self.chain_length * 8):
109+
y_chunks[chunk].append(index)
110+
chunk_size += 1
111+
if chunk_size >= (self.width // 8):
112+
chunk_size = 0
113+
chunk += 1
114+
if chunk >= len(y_chunks):
115+
chunk = 0
116+
117+
y_index = []
118+
for chunk in y_chunks:
119+
y_index += chunk
120+
return y_index
121+
122+
def init_display(self) -> None:
123+
for cmd, data in (
124+
(_SHUTDOWN, 0),
125+
(_DISPLAYTEST, 0),
126+
(_SCANLIMIT, 7),
127+
(_DECODEMODE, 0),
128+
(_SHUTDOWN, 1),
129+
):
130+
self.write_cmd(cmd, data)
131+
132+
self.fill(0)
133+
self.show()
134+
135+
def clear_all(self) -> None:
136+
"""
137+
Clears all matrix leds.
138+
"""
139+
self.fill(0)
140+
141+
# pylint: disable=inconsistent-return-statements
142+
def pixel(self, xpos: int, ypos: int, bit_value: int = None) -> None:
143+
"""
144+
Set one buffer bit
145+
146+
:param int xpos: x position to set bit
147+
:param int ypos: y position to set bit
148+
:param int bit_value: value > 0 sets the buffer bit, else clears the buffer bit
149+
"""
150+
if xpos < 0 or ypos < 0 or xpos >= self.width or ypos >= self.height:
151+
return
152+
buffer_x, buffer_y = self._pixel_coords_to_framebuf_coords(xpos, ypos)
153+
return super().pixel(buffer_x, buffer_y, bit_value=bit_value)
154+
155+
def _pixel_coords_to_framebuf_coords(self, xpos: int, ypos: int) -> Tuple[int]:
156+
"""
157+
Convert matrix pixel coordinates into coordinates in the framebuffer
158+
159+
:param int xpos: x position
160+
:param int ypos: y position
161+
:return: framebuffer coordinates (x, y)
162+
:rtype: Tuple[int]
163+
"""
164+
return (xpos - ((xpos // 8) * 8)) % 8, xpos // 8 + self.y_index[
165+
ypos * self.y_offset
166+
]
167+
168+
def _get_pixel(self, xpos: int, ypos: int) -> int:
169+
"""
170+
Get value of a matrix pixel
171+
172+
:param int xpos: x position
173+
:param int ypos: y position
174+
:return: value of pixel in matrix
175+
:rtype: int
176+
"""
177+
x, y = self._pixel_coords_to_framebuf_coords(xpos, ypos)
178+
buffer_value = self._buffer[-1 * y - 1]
179+
return ((buffer_value & 2 ** x) >> x) & 1
180+
181+
# Adafruit Circuit Python Framebuf Scroll Function
182+
# Authors: Kattni Rembor, Melissa LeBlanc-Williams and Tony DiCola, for Adafruit Industries
183+
# License: MIT License (https://opensource.org/licenses/MIT)
184+
def scroll(self, delta_x: int, delta_y: int) -> None:
185+
"""
186+
Srcolls the display using delta_x, delta_y.
187+
188+
:param int delta_x: positions to scroll in the x direction
189+
:param int delta_y: positions to scroll in the y direction
190+
"""
191+
if delta_x < 0:
192+
shift_x = 0
193+
xend = self.width + delta_x
194+
dt_x = 1
195+
else:
196+
shift_x = self.width - 1
197+
xend = delta_x - 1
198+
dt_x = -1
199+
if delta_y < 0:
200+
y = 0
201+
yend = self.height + delta_y
202+
dt_y = 1
203+
else:
204+
y = self.height - 1
205+
yend = delta_y - 1
206+
dt_y = -1
207+
while y != yend:
208+
x = shift_x
209+
while x != xend:
210+
self.pixel(x, y, self._get_pixel(x - delta_x, y - delta_y))
211+
x += dt_x
212+
y += dt_y
213+
214+
def rect(
215+
self, x: int, y: int, width: int, height: int, color: int, fill: bool = False
216+
) -> None:
217+
"""
218+
Draw a rectangle at the given position of the given size, color, and fill.
219+
220+
:param int x: x position
221+
:param int y: y position
222+
:param int width: width of rectangle
223+
:param int height: height of rectangle
224+
:param int color: color of rectangle
225+
:param bool fill: 1 pixel outline or filled rectangle (default: False)
226+
"""
227+
# pylint: disable=too-many-arguments
228+
for row in range(height):
229+
y_pos = row + y
230+
for col in range(width):
231+
x_pos = col + x
232+
if fill:
233+
self.pixel(x_pos, y_pos, color)
234+
elif y_pos in (y, y + height - 1) or x_pos in (x, x + width - 1):
235+
self.pixel(x_pos, y_pos, color)
236+
else:
237+
continue
238+
239+
def _fill_rect(self, x: int, y: int, width: int, height: int, color: int) -> None:
240+
"""
241+
Draw a filled rectangle at the given position of the given size, color.
242+
243+
:param int x: x position
244+
:param int y: y position
245+
:param int width: width of rectangle
246+
:param int height: height of rectangle
247+
:param int color: color of rectangle
248+
"""
249+
# pylint: disable=too-many-arguments
250+
return self.rect(x, y, width, height, color, True)
251+
252+
# Adafruit Circuit Python Framebuf Text Function
253+
# Authors: Kattni Rembor, Melissa LeBlanc-Williams and Tony DiCola, for Adafruit Industries
254+
# License: MIT License (https://opensource.org/licenses/MIT)
255+
def text(
256+
self,
257+
strg: str,
258+
xpos: int,
259+
ypos: int,
260+
color: int = 1,
261+
*,
262+
font_name: str = "font5x8.bin",
263+
size: int = 1
264+
) -> None:
265+
"""
266+
Draw text in the matrix.
267+
268+
:param str strg: string to place in to display
269+
:param int xpos: x position of LED in matrix
270+
:param int ypos: y position of LED in matrix
271+
:param int color: > 1 sets the text, otherwise resets
272+
:param str font_name: path to binary font file (default: "font5x8.bin")
273+
:param int size: size of the font, acts as a multiplier
274+
"""
275+
for chunk in strg.split("\n"):
276+
if not self._font or self._font.font_name != font_name:
277+
# load the font!
278+
self._font = BitmapFont(font_name)
279+
width = self._font.font_width
280+
height = self._font.font_height
281+
for i, char in enumerate(chunk):
282+
char_x = xpos + (i * (width + 1)) * size
283+
if (
284+
char_x + (width * size) > 0
285+
and char_x < self.width
286+
and ypos + (height * size) > 0
287+
and ypos < self.height
288+
):
289+
self._font.draw_char(
290+
char, char_x, ypos, self.framebuf, color, size=size
291+
)
292+
ypos += height * size

adafruit_max7219/max7219.py

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# SPDX-FileCopyrightText: 2016 Philip R. Moyer for Adafruit Industries
22
# SPDX-FileCopyrightText: 2016 Radomir Dopieralski for Adafruit Industries
3+
# SPDX-FileCopyrightText: 2021 Daniel Flanagan
34
#
45
# SPDX-License-Identifier: MIT
56

@@ -13,6 +14,7 @@
1314
See Also
1415
=========
1516
* matrices.Maxtrix8x8 is a class support an 8x8 led matrix display
17+
* matrices.CustomMatrix is a class support a custom sized constellation of 8x8 led matrix displays
1618
* bcddigits.BCDDigits is a class that support the 8 digit 7-segment display
1719
1820
Beware that most CircuitPython compatible hardware are 3.3v logic level! Make
@@ -60,7 +62,7 @@
6062

6163
class MAX7219:
6264
"""
63-
MAX2719 - driver for displays based on max719 chip_select
65+
MAX7219 - driver for displays based on max7219 chip_select
6466
6567
:param int width: the number of pixels wide
6668
:param int height: the number of pixels high
@@ -157,3 +159,66 @@ def write_cmd(self, cmd: int, data: int) -> None:
157159
self._chip_select.value = False
158160
with self._spi_device as my_spi_device:
159161
my_spi_device.write(bytearray([cmd, data]))
162+
163+
164+
class ChainableMAX7219(MAX7219):
165+
"""
166+
Daisy Chainable MAX7219 - driver for cascading displays based on max7219 chip_select
167+
168+
:param int width: the number of pixels wide
169+
:param int height: the number of pixels high
170+
:param ~busio.SPI spi: an spi busio or spi bitbangio object
171+
:param ~digitalio.DigitalInOut chip_select: digital in/out to use as chip select signal
172+
:param int baudrate: for SPIDevice baudrate (default 8000000)
173+
:param int polarity: for SPIDevice polarity (default 0)
174+
:param int phase: for SPIDevice phase (default 0)
175+
"""
176+
177+
def __init__(
178+
self,
179+
width: int,
180+
height: int,
181+
spi: busio.SPI,
182+
cs: digitalio.DigitalInOut,
183+
*,
184+
baudrate: int = 8000000,
185+
polarity: int = 0,
186+
phase: int = 0
187+
):
188+
self.chain_length = (height // 8) * (width // 8)
189+
190+
super().__init__(
191+
width, height, spi, cs, baudrate=baudrate, polarity=polarity, phase=phase
192+
)
193+
self._buffer = bytearray(self.chain_length * 8)
194+
self.framebuf = framebuf.FrameBuffer1(self._buffer, self.chain_length * 8, 8)
195+
196+
def write_cmd(self, cmd: int, data: int) -> None:
197+
"""
198+
Writes a command to spi device.
199+
200+
:param int cmd: register address to write data to
201+
:param int data: data to be written to commanded register
202+
"""
203+
# print('cmd {} data {}'.format(cmd,data))
204+
self._chip_select.value = False
205+
with self._spi_device as my_spi_device:
206+
for _ in range(self.chain_length):
207+
my_spi_device.write(bytearray([cmd, data]))
208+
209+
def show(self) -> None:
210+
"""
211+
Updates the display.
212+
"""
213+
for ypos in range(8):
214+
self._chip_select.value = False
215+
with self._spi_device as my_spi_device:
216+
for chip in range(self.chain_length):
217+
my_spi_device.write(
218+
bytearray(
219+
[
220+
_DIGIT0 + ypos,
221+
self._buffer[ypos * self.chain_length + chip],
222+
]
223+
)
224+
)

docs/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
44
.. automodule:: adafruit_max7219.max7219
55
:members:
6+
:show-inheritance:
67

78
.. automodule:: adafruit_max7219.matrices
89
:members:

docs/conf.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
# digitalio, micropython and busio. List the modules you use. Without it, the
2525
# autodoc module docs will fail to generate with a warning.
2626
autodoc_mock_imports = ["framebuf"]
27+
autodoc_member_order = "bysource"
2728

2829
intersphinx_mapping = {
2930
"python": ("https://docs.python.org/3.4", None),

docs/examples.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,7 @@ Ensure your device works with this simple test.
1010
.. literalinclude:: ../examples/max7219_showbcddigits.py
1111
:caption: examples/max7219_showbcddigits.py
1212
:linenos:
13+
14+
.. literalinclude:: ../examples/max7219_custommatrixtest.py
15+
:caption: examples/max7219_custommatrixtest.py
16+
:linenos:

0 commit comments

Comments
 (0)