Skip to content

Cascading Matrices #37

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Dec 15, 2021
228 changes: 226 additions & 2 deletions adafruit_max7219/matrices.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
# SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries
# SPDX-FileCopyrightText: 2021 Daniel Flanagan
#
# SPDX-License-Identifier: MIT

"""
`adafruit_max7219.matrices.Matrix8x8`
`adafruit_max7219.matrices`
====================================================
"""
from micropython import const
from adafruit_framebuf import BitmapFont
from adafruit_max7219 import max7219

try:
# Used only for typing
import typing # pylint: disable=unused-import
from typing import Tuple
import digitalio
import busio
except ImportError:
Expand Down Expand Up @@ -66,3 +68,225 @@ def clear_all(self) -> None:
Clears all matrix leds.
"""
self.fill(0)


class CustomMatrix(max7219.ChainableMAX7219):
"""
Driver for a custom 8x8 LED matrix constellation based on daisy chained MAX7219 chips.

:param ~busio.SPI spi: an spi busio or spi bitbangio object
:param ~digitalio.DigitalInOut cs: digital in/out to use as chip select signal
:param int width: the number of pixels wide
:param int height: the number of pixels high
:param int rotation: the number of times to rotate the coordinate system (default 1)
"""

def __init__(
self,
spi: busio.SPI,
cs: digitalio.DigitalInOut,
width: int,
height: int,
*,
rotation: int = 1
):
super().__init__(width, height, spi, cs)

self.y_offset = width // 8
self.y_index = self._calculate_y_coordinate_offsets()

self.framebuf.rotation = rotation
self.framebuf.fill_rect = self._fill_rect
self._font = None

def _calculate_y_coordinate_offsets(self) -> None:
y_chunks = []
for _ in range(self.chain_length // (self.width // 8)):
y_chunks.append([])
chunk = 0
chunk_size = 0
for index in range(self.chain_length * 8):
y_chunks[chunk].append(index)
chunk_size += 1
if chunk_size >= (self.width // 8):
chunk_size = 0
chunk += 1
if chunk >= len(y_chunks):
chunk = 0

y_index = []
for chunk in y_chunks:
y_index += chunk
return y_index

def init_display(self) -> None:
for cmd, data in (
(_SHUTDOWN, 0),
(_DISPLAYTEST, 0),
(_SCANLIMIT, 7),
(_DECODEMODE, 0),
(_SHUTDOWN, 1),
):
self.write_cmd(cmd, data)

self.fill(0)
self.show()

def clear_all(self) -> None:
"""
Clears all matrix leds.
"""
self.fill(0)

# pylint: disable=inconsistent-return-statements
def pixel(self, xpos: int, ypos: int, bit_value: int = None) -> None:
"""
Set one buffer bit

:param int xpos: x position to set bit
:param int ypos: y position to set bit
:param int bit_value: value > 0 sets the buffer bit, else clears the buffer bit
"""
if xpos < 0 or ypos < 0 or xpos >= self.width or ypos >= self.height:
return
buffer_x, buffer_y = self._pixel_coords_to_framebuf_coords(xpos, ypos)
return super().pixel(buffer_x, buffer_y, bit_value=bit_value)

def _pixel_coords_to_framebuf_coords(self, xpos: int, ypos: int) -> Tuple[int]:
"""
Convert matrix pixel coordinates into coordinates in the framebuffer

:param int xpos: x position
:param int ypos: y position
:return: framebuffer coordinates (x, y)
:rtype: Tuple[int]
"""
return (xpos - ((xpos // 8) * 8)) % 8, xpos // 8 + self.y_index[
ypos * self.y_offset
]

def _get_pixel(self, xpos: int, ypos: int) -> int:
"""
Get value of a matrix pixel

:param int xpos: x position
:param int ypos: y position
:return: value of pixel in matrix
:rtype: int
"""
x, y = self._pixel_coords_to_framebuf_coords(xpos, ypos)
buffer_value = self._buffer[-1 * y - 1]
return ((buffer_value & 2 ** x) >> x) & 1

# Adafruit Circuit Python Framebuf Scroll Function
# Authors: Kattni Rembor, Melissa LeBlanc-Williams and Tony DiCola, for Adafruit Industries
# License: MIT License (https://opensource.org/licenses/MIT)
def scroll(self, delta_x: int, delta_y: int) -> None:
"""
Srcolls the display using delta_x, delta_y.

:param int delta_x: positions to scroll in the x direction
:param int delta_y: positions to scroll in the y direction
"""
if delta_x < 0:
shift_x = 0
xend = self.width + delta_x
dt_x = 1
else:
shift_x = self.width - 1
xend = delta_x - 1
dt_x = -1
if delta_y < 0:
y = 0
yend = self.height + delta_y
dt_y = 1
else:
y = self.height - 1
yend = delta_y - 1
dt_y = -1
while y != yend:
x = shift_x
while x != xend:
self.pixel(x, y, self._get_pixel(x - delta_x, y - delta_y))
x += dt_x
y += dt_y

def rect(
self, x: int, y: int, width: int, height: int, color: int, fill: bool = False
) -> None:
"""
Draw a rectangle at the given position of the given size, color, and fill.

:param int x: x position
:param int y: y position
:param int width: width of rectangle
:param int height: height of rectangle
:param int color: color of rectangle
:param bool fill: 1 pixel outline or filled rectangle (default: False)
"""
# pylint: disable=too-many-arguments
for row in range(height):
y_pos = row + y
for col in range(width):
x_pos = col + x
if fill:
self.pixel(x_pos, y_pos, color)
elif y_pos in (y, y + height - 1) or x_pos in (x, x + width - 1):
self.pixel(x_pos, y_pos, color)
else:
continue

def _fill_rect(self, x: int, y: int, width: int, height: int, color: int) -> None:
"""
Draw a filled rectangle at the given position of the given size, color.

:param int x: x position
:param int y: y position
:param int width: width of rectangle
:param int height: height of rectangle
:param int color: color of rectangle
"""
# pylint: disable=too-many-arguments
return self.rect(x, y, width, height, color, True)

# Adafruit Circuit Python Framebuf Text Function
# Authors: Kattni Rembor, Melissa LeBlanc-Williams and Tony DiCola, for Adafruit Industries
# License: MIT License (https://opensource.org/licenses/MIT)
def text(
self,
strg: str,
xpos: int,
ypos: int,
color: int = 1,
*,
font_name: str = "font5x8.bin",
size: int = 1
) -> None:
"""
Draw text in the matrix.

:param str strg: string to place in to display
:param int xpos: x position of LED in matrix
:param int ypos: y position of LED in matrix
:param int color: > 1 sets the text, otherwise resets
:param str font_name: path to binary font file (default: "font5x8.bin")
:param int size: size of the font, acts as a multiplier
"""
for chunk in strg.split("\n"):
if not self._font or self._font.font_name != font_name:
# load the font!
self._font = BitmapFont(font_name)
width = self._font.font_width
height = self._font.font_height
for i, char in enumerate(chunk):
char_x = xpos + (i * (width + 1)) * size
if (
char_x + (width * size) > 0
and char_x < self.width
and ypos + (height * size) > 0
and ypos < self.height
):
self._font.draw_char(
char, char_x, ypos, self.framebuf, color, size=size
)
ypos += height * size
67 changes: 66 additions & 1 deletion adafruit_max7219/max7219.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# SPDX-FileCopyrightText: 2016 Philip R. Moyer for Adafruit Industries
# SPDX-FileCopyrightText: 2016 Radomir Dopieralski for Adafruit Industries
# SPDX-FileCopyrightText: 2021 Daniel Flanagan
#
# SPDX-License-Identifier: MIT

Expand All @@ -13,6 +14,7 @@
See Also
=========
* matrices.Maxtrix8x8 is a class support an 8x8 led matrix display
* matrices.CustomMatrix is a class support a custom sized constellation of 8x8 led matrix displays
* bcddigits.BCDDigits is a class that support the 8 digit 7-segment display

Beware that most CircuitPython compatible hardware are 3.3v logic level! Make
Expand Down Expand Up @@ -60,7 +62,7 @@

class MAX7219:
"""
MAX2719 - driver for displays based on max719 chip_select
MAX7219 - driver for displays based on max7219 chip_select

:param int width: the number of pixels wide
:param int height: the number of pixels high
Expand Down Expand Up @@ -157,3 +159,66 @@ def write_cmd(self, cmd: int, data: int) -> None:
self._chip_select.value = False
with self._spi_device as my_spi_device:
my_spi_device.write(bytearray([cmd, data]))


class ChainableMAX7219(MAX7219):
"""
Daisy Chainable MAX7219 - driver for cascading displays based on max7219 chip_select

:param int width: the number of pixels wide
:param int height: the number of pixels high
:param ~busio.SPI spi: an spi busio or spi bitbangio object
:param ~digitalio.DigitalInOut chip_select: digital in/out to use as chip select signal
:param int baudrate: for SPIDevice baudrate (default 8000000)
:param int polarity: for SPIDevice polarity (default 0)
:param int phase: for SPIDevice phase (default 0)
"""

def __init__(
self,
width: int,
height: int,
spi: busio.SPI,
cs: digitalio.DigitalInOut,
*,
baudrate: int = 8000000,
polarity: int = 0,
phase: int = 0
):
self.chain_length = (height // 8) * (width // 8)

super().__init__(
width, height, spi, cs, baudrate=baudrate, polarity=polarity, phase=phase
)
self._buffer = bytearray(self.chain_length * 8)
self.framebuf = framebuf.FrameBuffer1(self._buffer, self.chain_length * 8, 8)

def write_cmd(self, cmd: int, data: int) -> None:
"""
Writes a command to spi device.

:param int cmd: register address to write data to
:param int data: data to be written to commanded register
"""
# print('cmd {} data {}'.format(cmd,data))
self._chip_select.value = False
with self._spi_device as my_spi_device:
for _ in range(self.chain_length):
my_spi_device.write(bytearray([cmd, data]))

def show(self) -> None:
"""
Updates the display.
"""
for ypos in range(8):
self._chip_select.value = False
with self._spi_device as my_spi_device:
for chip in range(self.chain_length):
my_spi_device.write(
bytearray(
[
_DIGIT0 + ypos,
self._buffer[ypos * self.chain_length + chip],
]
)
)
1 change: 1 addition & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

.. automodule:: adafruit_max7219.max7219
:members:
:show-inheritance:

.. automodule:: adafruit_max7219.matrices
:members:
Expand Down
1 change: 1 addition & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
# digitalio, micropython and busio. List the modules you use. Without it, the
# autodoc module docs will fail to generate with a warning.
autodoc_mock_imports = ["framebuf"]
autodoc_member_order = "bysource"

intersphinx_mapping = {
"python": ("https://docs.python.org/3.4", None),
Expand Down
4 changes: 4 additions & 0 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,7 @@ Ensure your device works with this simple test.
.. literalinclude:: ../examples/max7219_showbcddigits.py
:caption: examples/max7219_showbcddigits.py
:linenos:

.. literalinclude:: ../examples/max7219_custommatrixtest.py
:caption: examples/max7219_custommatrixtest.py
:linenos:
Loading