|
1 | 1 | # SPDX-FileCopyrightText: 2017 Dan Halbert for Adafruit Industries
|
| 2 | +# SPDX-FileCopyrightText: 2021 Daniel Flanagan |
2 | 3 | #
|
3 | 4 | # SPDX-License-Identifier: MIT
|
4 | 5 |
|
5 | 6 | """
|
6 |
| -`adafruit_max7219.matrices.Matrix8x8` |
| 7 | +`adafruit_max7219.matrices` |
7 | 8 | ====================================================
|
8 | 9 | """
|
9 | 10 | from micropython import const
|
| 11 | +from adafruit_framebuf import BitmapFont |
10 | 12 | from adafruit_max7219 import max7219
|
11 | 13 |
|
12 | 14 | try:
|
13 | 15 | # Used only for typing
|
14 |
| - import typing # pylint: disable=unused-import |
| 16 | + from typing import Tuple |
15 | 17 | import digitalio
|
16 | 18 | import busio
|
17 | 19 | except ImportError:
|
@@ -66,3 +68,225 @@ def clear_all(self) -> None:
|
66 | 68 | Clears all matrix leds.
|
67 | 69 | """
|
68 | 70 | 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 |
0 commit comments