Skip to content
Merged
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