Merge pull request #27 from adafruit/make-package

Allow defining geometries (including more than 2 color lanes) via Python code
This commit is contained in:
foamyguy 2025-03-11 09:40:55 -05:00 committed by GitHub
commit 8d3355fca3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 628 additions and 199 deletions

1
.gitignore vendored
View file

@ -1,4 +1,3 @@
/*.pio.h
protodemo
/build
*.egg-info

View file

@ -0,0 +1,8 @@
HUB75 matrix driver for Raspberry Pi 5 using PIO
------------------------------------------------
.. autosummary::
:toctree: _generate
:recursive:
adafruit_blinka_raspberry_pi5_piomatter

View file

@ -1 +0,0 @@
.. automodule:: adafruit_blinka_raspberry_pi5_piomatter

View file

@ -26,13 +26,27 @@
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"autoapi.extension",
"sphinx.ext.intersphinx",
"sphinx.ext.autosummary",
"sphinx.ext.napoleon",
]
autosummary_generate = True
autoapi_keep_files = True
autoapi_dirs = ["../src/adafruit_blinka_raspberry_pi5_piomatter"]
autoapi_add_toctree_entry = True
autoapi_options = [
"members",
"undoc-members",
"show-inheritance",
"special-members",
"show-module-summary",
]
autoapi_python_class_content = "both"
autoapi_python_use_implicit_namespaces = True
autoapi_template_dir = "autoapi/templates"
autoapi_root = "api"
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
@ -50,7 +64,7 @@ master_doc = "index"
# General information about the project.
project = "adafruit-blinka-pi5-piomatter"
copyright = "2023 Jeff Epler"
copyright = "2025 Jeff Epler"
author = "Jeff Epler"
# The version info for the project you're documenting, acts as replacement for

View file

@ -3,5 +3,6 @@
# SPDX-License-Identifier: Unlicense
sphinx
sphinx-autoapi
sphinx-rtd-theme
sphinxcontrib-jquery

View file

@ -12,10 +12,12 @@ For help with commandline arguments, run `python fbmirror.py --help`
"""
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import click
import numpy as np
import piomatter_click
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import adafruit_blinka_raspberry_pi5_piomatter.click as piomatter_click
from adafruit_blinka_raspberry_pi5_piomatter.pixelmappers import simple_multilane_mapper
with open("/sys/class/graphics/fb0/virtual_size") as f:
screenx, screeny = [int(word) for word in f.read().split(",")]
@ -33,12 +35,31 @@ with open("/sys/class/graphics/fb0/stride") as f:
linux_framebuffer = np.memmap('/dev/fb0',mode='r', shape=(screeny, stride // bytes_per_pixel), dtype=dtype)
def make_pixelmap_multilane(width, height, n_addr_lines, n_lanes):
calc_height = n_lanes << n_addr_lines
if height != calc_height:
raise RuntimeError(f"Calculated height {calc_height} does not match requested height {height}")
n_addr = 1 << n_addr_lines
m = []
for addr in range(n_addr):
for x in range(width):
for lane in range(n_lanes):
y = addr + lane * n_addr
m.append(x + width * y)
print(m)
return m
@click.command
@click.option("--x-offset", "xoffset", type=int, help="The x offset of top left corner of the region to mirror", default=0)
@click.option("--y-offset", "yoffset", type=int, help="The y offset of top left corner of the region to mirror", default=0)
@piomatter_click.standard_options
def main(xoffset, yoffset, width, height, serpentine, rotation, pinout, n_planes, n_addr_lines):
geometry = piomatter.Geometry(width=width, height=height, n_planes=n_planes, n_addr_lines=n_addr_lines, rotation=rotation)
def main(xoffset, yoffset, width, height, serpentine, rotation, pinout, n_planes, n_temporal_planes, n_addr_lines, n_lanes):
if n_lanes != 2:
pixelmap = simple_multilane_mapper(width, height, n_addr_lines, n_lanes)
geometry = piomatter.Geometry(width=width, height=height, n_planes=n_planes, n_addr_lines=n_addr_lines, n_temporal_planes=n_temporal_planes, n_lanes=n_lanes, map=pixelmap)
else:
geometry = piomatter.Geometry(width=width, height=height, n_planes=n_planes, n_addr_lines=n_addr_lines, n_temporal_planes=n_temporal_planes, rotation=rotation)
framebuffer = np.zeros(shape=(geometry.height, geometry.width), dtype=dtype)
matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB565, pinout=pinout, framebuffer=framebuffer, geometry=geometry)

View file

@ -37,11 +37,13 @@ or if `/boot/firmware/cmdline.txt` specifies a resolution such as
`... video=HDMI-A-1:640x480M@60D`.
"""
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import click
import numpy as np
import PIL.Image as Image
import piomatter_click
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import adafruit_blinka_raspberry_pi5_piomatter.click as piomatter_click
from adafruit_blinka_raspberry_pi5_piomatter.pixelmappers import simple_multilane_mapper
with open("/sys/class/graphics/fb0/virtual_size") as f:
screenx, screeny = [int(word) for word in f.read().split(",")]
@ -65,8 +67,12 @@ linux_framebuffer = np.memmap('/dev/fb0',mode='r', shape=(screeny, stride // byt
@click.option("--y-offset", "yoffset", type=int, help="The y offset of top left corner of the region to mirror", default=0)
@click.option("--scale", "scale", type=int, help="The scale factor to reduce the display down by.", default=3)
@piomatter_click.standard_options
def main(xoffset, yoffset, scale, width, height, serpentine, rotation, pinout, n_planes, n_addr_lines):
geometry = piomatter.Geometry(width=width, height=height, n_planes=n_planes, n_addr_lines=n_addr_lines, rotation=rotation)
def main(xoffset, yoffset, scale, width, height, serpentine, rotation, pinout, n_planes, n_temporal_planes, n_addr_lines, n_lanes):
if n_lanes != 2:
pixelmap = simple_multilane_mapper(width, height, n_addr_lines, n_lanes)
geometry = piomatter.Geometry(width=width, height=height, n_planes=n_planes, n_addr_lines=n_addr_lines, n_temporal_planes=n_temporal_planes, n_lanes=n_lanes, map=pixelmap)
else:
geometry = piomatter.Geometry(width=width, height=height, n_planes=n_planes, n_temporal_planes=n_temporal_planes, n_addr_lines=n_addr_lines, rotation=rotation)
matrix_framebuffer = np.zeros(shape=(geometry.height, geometry.width, 3), dtype=np.uint8)
matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB888Packed, pinout=pinout, framebuffer=matrix_framebuffer, geometry=geometry)

View file

@ -11,10 +11,11 @@ The animated gif is played repeatedly until interrupted with ctrl-c.
import time
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import numpy as np
import PIL.Image as Image
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
width = 64
height = 32

View file

@ -13,10 +13,11 @@ import glob
import sys
import time
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import numpy as np
import PIL.Image as Image
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
images = sorted(glob.glob(sys.argv[1]))
geometry = piomatter.Geometry(width=64, height=32, n_addr_lines=4, rotation=piomatter.Orientation.Normal)

View file

@ -14,11 +14,12 @@ $ python quote_scroller.py
"""
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import numpy as np
import requests
from PIL import Image, ImageDraw, ImageFont
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
# 128px for 2x1 matrices. Change to 64 if you're using a single matrix.
total_width = 128
total_height = 32

View file

@ -10,11 +10,12 @@ Run like this:
$ python rainbow_spiral.py
"""
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import numpy as np
import rainbowio
from PIL import Image, ImageDraw
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
width = 64
height = 32
pen_radius = 1

View file

@ -0,0 +1,114 @@
#!/usr/bin/python3
# SPDX-FileCopyrightText: 2025 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
Display a spiral around the display drawn with a rainbow color.
Run like this:
$ python rainbow_spiral.py
"""
import numpy as np
import rainbowio
from PIL import Image, ImageDraw
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
from adafruit_blinka_raspberry_pi5_piomatter.pixelmappers import simple_multilane_mapper
width = 64
n_lanes = 6
n_addr_lines = 5
height = n_lanes << n_addr_lines
pen_radius = 1
canvas = Image.new('RGB', (width, height), (0, 0, 0))
draw = ImageDraw.Draw(canvas)
pixelmap = simple_multilane_mapper(width, height, n_addr_lines, n_lanes)
geometry = piomatter.Geometry(width=width, height=height, n_addr_lines=n_addr_lines, n_planes=10, n_temporal_planes=4, map=pixelmap, n_lanes=n_lanes)
framebuffer = np.asarray(canvas) + 0 # Make a mutable copy
matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB888Packed,
pinout=piomatter.Pinout.Active3,
framebuffer=framebuffer,
geometry=geometry)
color_index = 0
update_interval = 3
update_counter = 0
def update_matrix():
global update_counter
if (update_counter := update_counter + 1) >= update_interval:
framebuffer[:] = np.asarray(canvas)
matrix.show()
update_counter = 0
def darken_color(hex_color, darkness_factor):
# Convert hex color number to RGB
r = (hex_color >> 16) & 0xFF
g = (hex_color >> 8) & 0xFF
b = hex_color & 0xFF
# Apply darkness factor
r = int(r * (1 - darkness_factor))
g = int(g * (1 - darkness_factor))
b = int(b * (1 - darkness_factor))
# Ensure values are within the valid range
r = max(0, min(255, r))
g = max(0, min(255, g))
b = max(0, min(255, b))
# Convert RGB back to hex number
darkened_hex_color = (r << 16) + (g << 8) + b
return darkened_hex_color
step_count = 4
darkness_factor = 0.5
clearing = False
try:
# step_down_size = pen_radius * 2 + 2
while True:
for step in range(step_count):
step_down_size = step * (pen_radius* 2) + (2 * step)
for x in range(pen_radius + step_down_size, width - pen_radius - step_down_size - 1):
color_index = (color_index + 2) % 256
color = darken_color(rainbowio.colorwheel(color_index), darkness_factor) if not clearing else 0x000000
draw.circle((x, pen_radius + step_down_size), pen_radius, color)
update_matrix()
for y in range(pen_radius + step_down_size, height - pen_radius - step_down_size - 1):
color_index = (color_index + 2) % 256
color = darken_color(rainbowio.colorwheel(color_index), darkness_factor) if not clearing else 0x000000
draw.circle((width - pen_radius - step_down_size -1, y), pen_radius, color)
update_matrix()
for x in range(width - pen_radius - step_down_size - 1, pen_radius + step_down_size, -1):
color_index = (color_index + 2) % 256
color = darken_color(rainbowio.colorwheel(color_index), darkness_factor) if not clearing else 0x000000
draw.circle((x, height - pen_radius - step_down_size - 1), pen_radius, color)
update_matrix()
for y in range(height - pen_radius - step_down_size - 1, pen_radius + ((step+1) * (pen_radius* 2) + (2 * (step+1))) -1, -1):
color_index = (color_index + 2) % 256
color = darken_color(rainbowio.colorwheel(color_index), darkness_factor) if not clearing else 0x000000
draw.circle((pen_radius + step_down_size, y), pen_radius, color)
update_matrix()
if step != step_count-1:
# connect to next iter
for x in range(pen_radius + step_down_size, pen_radius + ((step+1) * (pen_radius* 2) + (2 * (step+1)))):
color_index = (color_index + 2) % 256
color = darken_color(rainbowio.colorwheel(color_index),
darkness_factor) if not clearing else 0x000000
draw.circle((x, pen_radius + ((step+1) * (pen_radius* 2) + (2 * (step+1)))), pen_radius, color)
update_matrix()
print(matrix.fps)
clearing = not clearing
except KeyboardInterrupt:
print("Exiting")

View file

@ -13,10 +13,11 @@ The image is displayed until the user hits enter to exit.
import pathlib
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import numpy as np
import PIL.Image as Image
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
geometry = piomatter.Geometry(width=64, height=64, n_addr_lines=4, rotation=piomatter.Orientation.Normal)
framebuffer = np.asarray(Image.open(pathlib.Path(__file__).parent / "blinka64x64.png"))
matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB888Packed,

View file

@ -13,10 +13,11 @@ The image is displayed until the user hits enter to exit.
import pathlib
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import numpy as np
import PIL.Image as Image
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
geometry = piomatter.Geometry(width=64, height=64, n_addr_lines=5, rotation=piomatter.Orientation.Normal, n_planes=8)
framebuffer = np.asarray(Image.open(pathlib.Path(__file__).parent / "blinka64x64.png"))
matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB888Packed, pinout=piomatter.Pinout.AdafruitMatrixBonnetBGR, framebuffer=framebuffer, geometry=geometry)

View file

@ -11,10 +11,11 @@ $ python simpletest.py
"""
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import numpy as np
from PIL import Image, ImageDraw
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
width = 64
height = 32

View file

@ -26,12 +26,13 @@ Here's an example for running an emulator using a rom stored in "/tmp/snesrom.sm
import shlex
from subprocess import Popen
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import click
import numpy as np
import piomatter_click
from pyvirtualdisplay.smartdisplay import SmartDisplay
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import adafruit_blinka_raspberry_pi5_piomatter.click as piomatter_click
@click.command
@click.option("--scale", type=float, help="The scale factor, larger numbers mean more virtual pixels", default=1)

View file

@ -29,12 +29,14 @@ import termios
import tty
from subprocess import Popen, run
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import click
import numpy as np
import piomatter_click
from pyvirtualdisplay.smartdisplay import SmartDisplay
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import adafruit_blinka_raspberry_pi5_piomatter.click as piomatter_click
from adafruit_blinka_raspberry_pi5_piomatter.pixelmappers import simple_multilane_mapper
keyboard_debug = False
keys_down = set()
basic_characters = string.ascii_letters + string.digits
@ -91,8 +93,8 @@ ctrl_modified_range = (1, 26)
@click.option("--ctrl-c-interrupt/--no-ctrl-c-interrupt", help="If Ctrl+C should be handled as an interrupt.", default=True)
@piomatter_click.standard_options
@click.argument("command", nargs=-1)
def main(scale, backend, use_xauth, extra_args, rfbport, width, height, serpentine, rotation, pinout, n_planes,
n_addr_lines, ctrl_c_interrupt, command):
def main(scale, backend, use_xauth, extra_args, rfbport, width, height, serpentine, rotation, pinout, n_planes, n_temporal_planes,
n_addr_lines, n_lanes, ctrl_c_interrupt, command):
def handle_key_event(evt_data):
if evt_data in key_map.keys():
keys_down.add(key_map[evt_data])
@ -132,8 +134,11 @@ def main(scale, backend, use_xauth, extra_args, rfbport, width, height, serpenti
if extra_args:
kwargs['extra_args'] = shlex.split(extra_args)
print("xauth", use_xauth)
geometry = piomatter.Geometry(width=width, height=height, n_planes=n_planes, n_addr_lines=n_addr_lines,
rotation=rotation)
if n_lanes != 2:
pixelmap = simple_multilane_mapper(width, height, n_addr_lines, n_lanes)
geometry = piomatter.Geometry(width=width, height=height, n_planes=n_planes, n_addr_lines=n_addr_lines, n_temporal_planes=n_temporal_planes, n_lanes=n_lanes, map=pixelmap)
else:
geometry = piomatter.Geometry(width=width, height=height, n_planes=n_planes, n_temporal_planes=n_temporal_planes, n_addr_lines=n_addr_lines, rotation=rotation)
framebuffer = np.zeros(shape=(geometry.height, geometry.width, 3), dtype=np.uint8)
matrix = piomatter.PioMatter(colorspace=piomatter.Colorspace.RGB888Packed, pinout=pinout, framebuffer=framebuffer,
geometry=geometry)

View file

@ -9,14 +9,15 @@ build-backend = "setuptools.build_meta"
[tool.setuptools_scm]
[tool.ruff]
extend-select = [
lint.extend-select = [
"B", # flake8-bugbear
"I", # isort
"PGH", # pygrep-hooks
"RUF", # Ruff-specific
"UP", # pyupgrade
]
extend-ignore = [
lint.extend-ignore = [
"E501", # Line too long
"RUF002", # Yes I meant to type 'multiplication sign'!
]
target-version = "py311"

View file

@ -11,7 +11,7 @@ __version__ = get_version()
# say from a submodule.
ext_modules = [
Pybind11Extension("adafruit_blinka_raspberry_pi5_piomatter",
Pybind11Extension("adafruit_blinka_raspberry_pi5_piomatter._piomatter",
["src/pymain.cpp", "src/piolib/piolib.c", "src/piolib/pio_rp1.c"],
define_macros = [('VERSION_INFO', __version__)],
include_dirs = ['./src/include', './src/piolib/include'],
@ -33,6 +33,8 @@ setup(
cmdclass={"build_ext": build_ext},
zip_safe=False,
python_requires=">=3.11",
packages=['adafruit_blinka_raspberry_pi5_piomatter'],
package_dir={'adafruit_blinka_raspberry_pi5_piomatter': 'src/adafruit_blinka_raspberry_pi5_piomatter'},
extras_require={
'docs': ["sphinx", "sphinx-rtd-theme", "sphinxcontrib-jquery"],
},

View file

@ -0,0 +1,33 @@
"""
HUB75 matrix driver for Raspberry Pi 5 using PIO
------------------------------------------------
.. currentmodule:: adafruit_blinka_raspberry_pi5_piomatter
.. autosummary::
:toctree: _generate
:recursive:
:class: Orientation Pinout Colorspace Geometry PioMatter
Orientation
Pinout
Colorspace
Geometry
PioMatter
"""
from ._piomatter import (
Colorspace,
Geometry,
Orientation,
Pinout,
PioMatter,
)
__all__ = [
'Colorspace',
'Geometry',
'Orientation',
'Pinout',
'PioMatter',
]

View file

@ -5,11 +5,12 @@
from collections.abc import Callable
from typing import Any
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
import click
import adafruit_blinka_raspberry_pi5_piomatter as piomatter
class PybindEnumChoice(click.Choice):
class _PybindEnumChoice(click.Choice):
def __init__(self, enum, case_sensitive=False):
self.enum = enum
choices = [k for k, v in enum.__dict__.items() if isinstance(v, enum)]
@ -25,6 +26,11 @@ class PybindEnumChoice(click.Choice):
r = getattr(self.enum, value)
return r
def _validate_temporal_planes(ctx, param, value):
if value not in (0, 2, 4):
raise click.BadParameter("must be 0, 2, or 4")
return value
def standard_options(
f: click.decorators.FC | None = None,
*,
@ -34,7 +40,9 @@ def standard_options(
rotation=piomatter.Orientation.Normal,
pinout=piomatter.Pinout.AdafruitMatrixBonnet,
n_planes=10,
n_temporal_planes=0,
n_addr_lines=4,
n_lanes=2,
) -> Callable[[], None]:
"""Add standard commandline flags, with the defaults given
@ -61,7 +69,7 @@ def standard_options(
f = click.option(
"--pinout",
default=pinout,
type=PybindEnumChoice(piomatter.Pinout),
type=_PybindEnumChoice(piomatter.Pinout),
help="The details of the electrical connection to the panels"
)(f)
if rotation is not None:
@ -69,13 +77,17 @@ def standard_options(
"--orientation",
"rotation",
default=rotation,
type=PybindEnumChoice(piomatter.Orientation),
type=_PybindEnumChoice(piomatter.Orientation),
help="The overall orientation (rotation) of the panels"
)(f)
if n_planes is not None:
f = click.option("--num-planes", "n_planes", default=n_planes, help="The number of bit planes (color depth. Lower values can improve refresh rate in frames per second")(f)
f = click.option("--num-planes", "n_planes", default=n_planes, help="The number of bit planes (color depth). Lower values can improve refresh rate in frames per second")(f)
if n_temporal_planes is not None:
f = click.option("--num-temporal-planes", "n_temporal_planes", default=n_temporal_planes, callback=_validate_temporal_planes, help="The number of temporal bit-planes. May be 0, 2, or 4. Nonzero values improve frame rate but can cause some shimmer")(f)
if n_addr_lines is not None:
f = click.option("--num-address-lines", "n_addr_lines", default=n_addr_lines, help="The number of address lines used by the panels")(f)
if n_lanes is not None:
f = click.option("--num-lanes", "n_lanes", default=n_lanes, help="The number of lanes used by the panels. One 16-pin connector has two lanes (6 RGB pins)")(f)
return f
if f is None:
return wrapper

View file

@ -0,0 +1,31 @@
"""Functions to define the layout of complex setups, particularly multi-connector matrices"""
def simple_multilane_mapper(width, height, n_addr_lines, n_lanes):
"""A simple mapper for 4+ pixel lanes
A framebuffer (width × height) is mapped onto a matrix where the lanes are stacked
top-to-bottom. Panels within a lane may be cascaded left-to-right.
Rotation is not supported, and neither are more complicated arrangements of panels
within a single chain (no support for serpentine or stacked panels within a segment)
.. code-block::
0 -> [panel] -> [panel]
1 -> [panel] -> [panel]
2 -> [panel] -> [panel]
"""
calc_height = n_lanes << n_addr_lines
if height != calc_height:
raise RuntimeError(f"Calculated height {calc_height} does not match requested height {height}")
n_addr = 1 << n_addr_lines
m = []
for addr in range(n_addr):
for x in range(width):
for lane in range(n_lanes):
y = addr + lane * n_addr
m.append(x + width * y)
print(m)
return m

View file

@ -48,7 +48,8 @@ matrix_map make_matrixmap(size_t width, size_t height, size_t n_addr_lines,
size_t panel_height = 2 << n_addr_lines;
if (height % panel_height != 0) {
throw std::range_error("Height does not evenly divide panel height");
throw std::range_error(
"Overall height does not evenly divide calculated panel height");
}
size_t half_panel_height = 1u << n_addr_lines;
@ -80,23 +81,136 @@ matrix_map make_matrixmap(size_t width, size_t height, size_t n_addr_lines,
return result;
}
struct schedule_entry {
uint32_t shift, active_time;
};
using schedule = std::vector<schedule_entry>;
using schedule_sequence = std::vector<schedule>;
schedule_sequence rescale_schedule(schedule_sequence ss, size_t pixels_across) {
uint32_t max_active_time = 0;
for (auto &s : ss) {
for (auto &ent : s) {
max_active_time = std::max(ent.active_time, max_active_time);
}
}
if (max_active_time == 0 || max_active_time >= pixels_across) {
return ss;
}
int scale = (pixels_across + max_active_time - 1) / max_active_time;
for (auto &s : ss) {
for (auto &ent : s) {
ent.active_time *= scale;
}
}
return ss;
}
schedule_sequence make_simple_schedule(int n_planes, size_t pixels_across) {
if (n_planes < 1 || n_planes > 10) {
throw std::range_error("n_planes out of range");
}
schedule result;
for (int i = 0; i < n_planes; i++) {
result.emplace_back(9 - i, (1 << (n_planes - i - 1)));
}
return rescale_schedule({result}, pixels_across);
}
// Make a temporal dither schedule. All the top `n_planes` are shown everytime,
// but the lowest planes are done in a cycle of `n_temporal_planes`:
// 2: {0, 1}; 4: {0, 1, 2, 3}
schedule_sequence make_temporal_dither_schedule(int n_planes,
size_t pixels_across,
int n_temporal_planes) {
if (n_planes < 1 || n_planes > 10) {
throw std::range_error("n_planes out of range");
}
if (n_temporal_planes < 2) {
// either 0 or 1 temporal planes are not really temporal at all
return make_simple_schedule(n_planes, pixels_across);
}
if (n_temporal_planes >= n_planes) {
throw std::range_error("n_temporal_planes can't exceed n_planes");
}
if (n_temporal_planes != 2 && n_temporal_planes != 4) {
// the code can generate a schedule for 8 temporal planes, but it
// flickers intolerably
throw std::range_error("n_temporal_planes must be 0, 1, 2, or 4");
}
int n_real_planes = n_planes - n_temporal_planes;
schedule base_sched;
for (int j = 0; j < n_real_planes; j++) {
base_sched.emplace_back(
9 - j, (1 << (n_temporal_planes + n_real_planes - j - 1)) /
n_temporal_planes);
}
schedule_sequence result;
auto add_sched = [&result, &base_sched](int plane, int count) {
auto sched = base_sched;
sched.emplace_back(9 - plane, count);
result.emplace_back(sched);
};
for (int i = 0; i < n_temporal_planes; i++) {
add_sched(n_real_planes + i, 1 << (n_temporal_planes - i - 1));
}
#if 0
std::vector<uint32_t> counts(10, 0);
for (auto s : result) {
for(auto t: s) {
counts[t.shift] += t.active_time;
}
}
for (auto s : counts) {
printf("%d ", s);
}
printf("\n");
#endif
return rescale_schedule(result, pixels_across);
;
}
struct matrix_geometry {
template <typename Cb>
matrix_geometry(size_t pixels_across, size_t n_addr_lines, int n_planes,
size_t width, size_t height, bool serpentine, const Cb &cb)
int n_temporal_planes, size_t width, size_t height,
bool serpentine, const Cb &cb)
: matrix_geometry(
pixels_across, n_addr_lines, n_planes, n_temporal_planes, width,
height,
make_matrixmap(width, height, n_addr_lines, serpentine, cb), 2) {}
matrix_geometry(size_t pixels_across, size_t n_addr_lines, int n_planes,
int n_temporal_planes, size_t width, size_t height,
matrix_map map, size_t n_lanes)
: matrix_geometry(pixels_across, n_addr_lines, width, height, map,
n_lanes,
make_temporal_dither_schedule(n_planes, pixels_across,
n_temporal_planes)) {}
matrix_geometry(size_t pixels_across, size_t n_addr_lines, size_t width,
size_t height, matrix_map map, size_t n_lanes,
const schedule_sequence &schedules)
: pixels_across(pixels_across), n_addr_lines(n_addr_lines),
n_planes(n_planes), width(width),
height(height), map{make_matrixmap(width, height, n_addr_lines,
serpentine, cb)} {
size_t pixels_down = 2u << n_addr_lines;
n_lanes(n_lanes), width(width), height(height),
map(map), schedules{schedules} {
size_t pixels_down = n_lanes << n_addr_lines;
if (map.size() != pixels_down * pixels_across) {
throw std::range_error(
"map size does not match calculated pixel count");
}
}
size_t pixels_across, n_addr_lines;
int n_planes;
size_t pixels_across, n_addr_lines, n_lanes;
size_t width, height;
matrix_map map;
schedule_sequence schedules;
};
} // namespace piomatter

View file

@ -40,4 +40,42 @@ struct adafruit_matrix_bonnet_pinout_bgr {
static constexpr uint32_t post_addr_delay = 500;
};
struct active3_pinout {
static constexpr pin_t PIN_RGB[] = {7, 27, 11, 10, 9, 8, 6, 5, 12,
20, 13, 19, 3, 2, 14, 21, 16, 26};
static constexpr pin_t PIN_ADDR[] = {22, 23, 24, 25, 15};
static constexpr pin_t PIN_OE = 18; // /OE: output enable when LOW
static constexpr pin_t PIN_CLK = 17; // SRCLK: clocks on RISING edge
static constexpr pin_t PIN_LAT = 4; // RCLK: latches on RISING edge
static constexpr uint32_t clk_bit = 1u << PIN_CLK;
static constexpr uint32_t lat_bit = 1u << PIN_LAT;
static constexpr uint32_t oe_bit = 1u << PIN_OE;
static constexpr uint32_t oe_active = 0;
static constexpr uint32_t oe_inactive = oe_bit;
static constexpr uint32_t post_oe_delay = 0;
static constexpr uint32_t post_latch_delay = 0;
static constexpr uint32_t post_addr_delay = 500;
};
struct active3_pinout_bgr {
static constexpr pin_t PIN_RGB[] = {11, 27, 7, 8, 9, 10, 12, 5, 6,
19, 13, 20, 14, 2, 3, 26, 16, 21};
static constexpr pin_t PIN_ADDR[] = {22, 23, 24, 25, 15};
static constexpr pin_t PIN_OE = 18; // /OE: output enable when LOW
static constexpr pin_t PIN_CLK = 17; // SRCLK: clocks on RISING edge
static constexpr pin_t PIN_LAT = 4; // RCLK: latches on RISING edge
static constexpr uint32_t clk_bit = 1u << PIN_CLK;
static constexpr uint32_t lat_bit = 1u << PIN_LAT;
static constexpr uint32_t oe_bit = 1u << PIN_OE;
static constexpr uint32_t oe_active = 0;
static constexpr uint32_t oe_inactive = oe_bit;
static constexpr uint32_t post_oe_delay = 0;
static constexpr uint32_t post_latch_delay = 0;
static constexpr uint32_t post_addr_delay = 500;
};
} // namespace piomatter

View file

@ -49,22 +49,31 @@ template <class pinout = adafruit_matrix_bonnet_pinout,
class colorspace = colorspace_rgb888>
struct piomatter : piomatter_base {
using buffer_type = std::vector<uint32_t>;
using bufseq_type = std::vector<buffer_type>;
piomatter(std::span<typename colorspace::data_type const> framebuffer,
const matrix_geometry &geometry)
: framebuffer(framebuffer), geometry{geometry}, converter{},
blitter_thread{&piomatter::blit_thread, this} {
blitter_thread{} {
if (geometry.n_addr_lines > std::size(pinout::PIN_ADDR)) {
throw std::runtime_error("too many address lines requested");
}
program_init();
blitter_thread = std::move(std::thread{&piomatter::blit_thread, this});
show();
}
void show() override {
int buffer_idx = manager.get_free_buffer();
auto &buffer = buffers[buffer_idx];
auto &bufseq = buffers[buffer_idx];
bufseq.resize(geometry.schedules.size());
auto converted = converter.convert(framebuffer);
protomatter_render_rgb10<pinout>(buffer, geometry, converted.data());
auto old_active_time = geometry.schedules.back().back().active_time;
for (size_t i = 0; i < geometry.schedules.size(); i++) {
protomatter_render_rgb10<pinout>(bufseq[i], geometry,
geometry.schedules[i],
old_active_time, converted.data());
old_active_time = geometry.schedules[i].back().active_time;
}
manager.put_filled_buffer(buffer_idx);
}
@ -160,26 +169,27 @@ struct piomatter : piomatter_base {
}
void blit_thread() {
const uint32_t *databuf = nullptr;
size_t datasize = 0;
int old_buffer_idx = buffer_manager::no_buffer;
int cur_buffer_idx = buffer_manager::no_buffer;
int buffer_idx;
int seq_idx = -1;
uint64_t t0, t1;
t0 = monotonicns64();
while ((buffer_idx = manager.get_filled_buffer()) !=
buffer_manager::exit_request) {
if (buffer_idx != buffer_manager::no_buffer) {
const auto &buffer = buffers[buffer_idx];
databuf = &buffer[0];
datasize = buffer.size() * sizeof(*databuf);
if (old_buffer_idx != buffer_manager::no_buffer) {
manager.put_free_buffer(old_buffer_idx);
if (cur_buffer_idx != buffer_manager::no_buffer) {
manager.put_free_buffer(cur_buffer_idx);
}
old_buffer_idx = buffer_idx;
cur_buffer_idx = buffer_idx;
}
if (datasize) {
if (cur_buffer_idx != buffer_manager::no_buffer) {
const auto &cur_buf = buffers[cur_buffer_idx];
seq_idx = (seq_idx + 1) % cur_buf.size();
const auto &data = cur_buf[seq_idx];
auto datasize = sizeof(uint32_t) * data.size();
auto dataptr = const_cast<uint32_t *>(&data[0]);
pio_sm_xfer_data_large(pio, sm, PIO_DIR_TO_SM, datasize,
(uint32_t *)databuf);
dataptr);
t1 = monotonicns64();
if (t0 != t1) {
fps = 1e9 / (t1 - t0);
@ -194,7 +204,7 @@ struct piomatter : piomatter_base {
PIO pio = NULL;
int sm = -1;
std::span<typename colorspace::data_type const> framebuffer;
buffer_type buffers[3];
bufseq_type buffers[3];
buffer_manager manager{};
matrix_geometry geometry;
colorspace converter;

View file

@ -3,7 +3,7 @@
const int protomatter_wrap = 4;
const int protomatter_wrap_target = 0;
const int protomatter_sideset_pin_count = 1;
const bool protomatter_sideset_enable = true;
const bool protomatter_sideset_enable = 1;
const uint16_t protomatter[] = {
// ; data format (out-shift-right):
// ; MSB ... LSB

View file

@ -132,6 +132,7 @@ struct colorspace_rgb10 {
template <typename pinout>
void protomatter_render_rgb10(std::vector<uint32_t> &result,
const matrix_geometry &matrixmap,
const schedule &sched, uint32_t old_active_time,
const uint32_t *pixels) {
result.clear();
@ -153,7 +154,7 @@ void protomatter_render_rgb10(std::vector<uint32_t> &result,
data_count = n;
};
int32_t active_time;
int32_t active_time = old_active_time;
auto do_data_clk_active = [&active_time, &data_count, &result](uint32_t d) {
bool active = active_time > 0;
@ -183,68 +184,42 @@ void protomatter_render_rgb10(std::vector<uint32_t> &result,
return data;
};
auto add_pixels = [&do_data_clk_active,
&result](uint32_t addr_bits, bool r0, bool g0, bool b0,
bool r1, bool g1, bool b1) {
uint32_t data = addr_bits;
if (r0)
data |= (1 << pinout::PIN_RGB[0]);
if (g0)
data |= (1 << pinout::PIN_RGB[1]);
if (b0)
data |= (1 << pinout::PIN_RGB[2]);
if (r1)
data |= (1 << pinout::PIN_RGB[3]);
if (g1)
data |= (1 << pinout::PIN_RGB[4]);
if (b1)
data |= (1 << pinout::PIN_RGB[5]);
do_data_clk_active(data);
};
int last_bit = 0;
// illuminate the right row for data in the shift register (the previous
// address)
const size_t n_addr = 1u << matrixmap.n_addr_lines;
const int n_planes = matrixmap.n_planes;
constexpr size_t n_bits = 10u;
unsigned offset = n_bits - n_planes;
const size_t pixels_across = matrixmap.pixels_across;
size_t prev_addr = n_addr - 1;
uint32_t addr_bits = calc_addr_bits(prev_addr);
for (size_t addr = 0; addr < n_addr; addr++) {
// printf("addr=%zu/%zu\n", addr, n_addr);
for (int bit = n_planes - 1; bit >= 0; bit--) {
// printf("bit=%d/%d\n", bit, n_planes);
uint32_t r = 1 << (20 + offset + bit);
uint32_t g = 1 << (10 + offset + bit);
uint32_t b = 1 << (0 + offset + bit);
// the shortest /OE we can do is one DATA_OVERHEAD...
// TODO: should make sure desired duration of MSB is at least
// `pixels_across`
active_time = 1 << last_bit;
last_bit = bit;
for (auto &schedule_ent : sched) {
uint32_t r_mask = 1 << (20 + schedule_ent.shift);
uint32_t g_mask = 1 << (10 + schedule_ent.shift);
uint32_t b_mask = 1 << (0 + schedule_ent.shift);
prep_data(pixels_across);
auto mapiter = matrixmap.map.begin() + 2 * addr * pixels_across;
auto mapiter = matrixmap.map.begin() +
matrixmap.n_lanes * addr * pixels_across;
for (size_t x = 0; x < pixels_across; x++) {
uint32_t data = addr_bits;
for (size_t px = 0; px < matrixmap.n_lanes; px++) {
assert(mapiter != matrixmap.map.end());
auto pixel0 = pixels[*mapiter++];
auto r0 = pixel0 & r;
auto g0 = pixel0 & g;
auto b0 = pixel0 & b;
assert(mapiter != matrixmap.map.end());
auto pixel1 = pixels[*mapiter++];
auto r1 = pixel1 & r;
auto g1 = pixel1 & g;
auto b1 = pixel1 & b;
auto r_bit = pixel0 & r_mask;
auto g_bit = pixel0 & g_mask;
auto b_bit = pixel0 & b_mask;
add_pixels(addr_bits, r0, g0, b0, r1, g1, b1);
if (r_bit)
data |= (1 << pinout::PIN_RGB[px * 3 + 0]);
if (g_bit)
data |= (1 << pinout::PIN_RGB[px * 3 + 1]);
if (b_bit)
data |= (1 << pinout::PIN_RGB[px * 3 + 2]);
}
do_data_clk_active(data);
}
do_data_delay(addr_bits | pinout::oe_active,
@ -256,6 +231,8 @@ void protomatter_render_rgb10(std::vector<uint32_t> &result,
do_data_delay(addr_bits | pinout::oe_inactive | pinout::lat_bit,
pinout::post_latch_delay);
active_time = schedule_ent.active_time;
// with oe inactive, set address bits to illuminate THIS line
if (addr != prev_addr) {
addr_bits = calc_addr_bits(addr);

View file

@ -95,10 +95,56 @@ static uint64_t monotonicns64() {
return tp.tv_sec * UINT64_C(1000000000) + tp.tv_nsec;
}
static void print_dither_schedule(const piomatter::schedule_sequence &ss) {
for (auto s : ss) {
for (auto i : s) {
printf("{%d %d} ", i.shift, i.active_time);
}
printf("\n");
}
printf("\n");
}
static void test_simple_dither_schedule(int n_planes, int pixels_across) {
auto ss = piomatter::make_simple_schedule(n_planes, pixels_across);
print_dither_schedule(ss);
}
static void test_temporal_dither_schedule(int n_planes, int pixels_across,
int n_temporal_frames) {
auto ss = piomatter::make_temporal_dither_schedule(n_planes, pixels_across,
n_temporal_frames);
print_dither_schedule(ss);
}
int main(int argc, char **argv) {
int n = argc > 1 ? atoi(argv[1]) : 0;
piomatter::matrix_geometry geometry(128, 4, 10, 64, 64, true,
test_simple_dither_schedule(5, 1);
test_temporal_dither_schedule(5, 1, 0);
test_temporal_dither_schedule(5, 1, 2);
test_temporal_dither_schedule(5, 1, 4);
test_simple_dither_schedule(6, 1);
test_temporal_dither_schedule(6, 1, 0);
test_temporal_dither_schedule(6, 1, 2);
test_temporal_dither_schedule(6, 1, 4);
test_simple_dither_schedule(5, 16);
test_temporal_dither_schedule(5, 16, 2);
test_temporal_dither_schedule(5, 16, 4);
test_simple_dither_schedule(5, 24);
test_temporal_dither_schedule(5, 24, 2);
test_temporal_dither_schedule(5, 24, 4);
test_simple_dither_schedule(10, 24);
test_temporal_dither_schedule(10, 24, 8);
test_temporal_dither_schedule(5, 128, 4);
test_temporal_dither_schedule(5, 192, 4);
return 0;
piomatter::matrix_geometry geometry(128, 4, 10, 0, 64, 64, true,
piomatter::orientation_normal);
piomatter::piomatter p(std::span(&pixels[0][0], 64 * 64), geometry);

View file

@ -1,5 +1,6 @@
#include <iostream>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <string>
#include "piomatter/piomatter.h"
@ -33,6 +34,14 @@ make_piomatter_pc(py::buffer buffer,
const py::buffer_info info = buffer.request();
const size_t buffer_size_in_bytes = info.size * info.itemsize;
if (geometry.n_lanes * 3 > std::size(pinout::PIN_RGB)) {
throw std::runtime_error(
py::str("Geometry lane count {} exceeds the pinout with {} rgb "
"pins ({} lanes)")
.attr("format")(geometry.n_lanes, std::size(pinout::PIN_RGB),
std::size(pinout::PIN_RGB) / 3)
.template cast<std::string>());
}
if (buffer_size_in_bytes != data_size_in_bytes) {
throw std::runtime_error(
py::str("Framebuffer size must be {} bytes ({} elements of {} "
@ -54,6 +63,8 @@ enum Colorspace { RGB565, RGB888, RGB888Packed };
enum Pinout {
AdafruitMatrixBonnet,
AdafruitMatrixBonnetBGR,
Active3,
Active3BGR,
};
template <class pinout>
@ -70,13 +81,11 @@ make_piomatter_p(Colorspace c, py::buffer buffer,
case RGB888Packed:
return make_piomatter_pc<pinout, piomatter::colorspace_rgb888_packed>(
buffer, geometry);
default:
}
throw std::runtime_error(py::str("Invalid colorspace {!r}")
.attr("format")(c)
.template cast<std::string>());
}
}
std::unique_ptr<PyPiomatter>
make_piomatter(Colorspace c, Pinout p, py::buffer buffer,
@ -88,15 +97,19 @@ make_piomatter(Colorspace c, Pinout p, py::buffer buffer,
case AdafruitMatrixBonnetBGR:
return make_piomatter_p<piomatter::adafruit_matrix_bonnet_pinout_bgr>(
c, buffer, geometry);
default:
case Active3:
return make_piomatter_p<piomatter::active3_pinout>(c, buffer, geometry);
case Active3BGR:
return make_piomatter_p<piomatter::active3_pinout_bgr>(c, buffer,
geometry);
}
throw std::runtime_error(py::str("Invalid pinout {!r}")
.attr("format")(p)
.template cast<std::string>());
}
}
} // namespace
PYBIND11_MODULE(adafruit_blinka_raspberry_pi5_piomatter, m) {
PYBIND11_MODULE(_piomatter, m) {
py::options options;
options.enable_enum_members_docstring();
options.enable_function_signatures();
@ -106,18 +119,7 @@ PYBIND11_MODULE(adafruit_blinka_raspberry_pi5_piomatter, m) {
HUB75 matrix driver for Raspberry Pi 5 using PIO
------------------------------------------------
.. currentmodule:: adafruit_blinka_raspberry_pi5_piomatter
.. autosummary::
:toctree: _generate
Orientation
Pinout
Colorspace
Geometry
PioMatter
AdafruitMatrixBonnetRGB888
AdafruitMatrixBonnetRGB888Packed
.. currentmodule:: adafruit_blinka_raspberry_pi5_piomatter._piomatter
)pbdoc";
py::enum_<piomatter::orientation>(
@ -138,7 +140,10 @@ PYBIND11_MODULE(adafruit_blinka_raspberry_pi5_piomatter, m) {
.value("AdafruitMatrixHat", Pinout::AdafruitMatrixBonnet,
"Adafruit Matrix Bonnet or Matrix Hat")
.value("AdafruitMatrixHatBGR", Pinout::AdafruitMatrixBonnetBGR,
"Adafruit Matrix Bonnet or Matrix Hat with BGR color order");
"Adafruit Matrix Bonnet or Matrix Hat with BGR color order")
.value("Active3", Pinout::Active3, "Active-3 or compatible board")
.value("Active3BGR", Pinout::Active3BGR,
"Active-3 or compatible board with BGR color order");
py::enum_<Colorspace>(
m, "Colorspace",
@ -157,19 +162,33 @@ Describe the geometry of a set of panels
The number of pixels in the shift register is automatically computed from these values.
``n_planes`` controls the color depth of the panel. This is separate from the framebuffer
layout. Decreasing ``n_planes`` can increase FPS at the cost of reduced color fidelity.
The default, 10, is the maximum value.
``n_temporal_planes`` controls temporal dithering of the panel. The acceptable values
are 0 (the default), 2, and 4. A higher setting can increase FPS at the cost of
slightly increasing the variation of brightness across subsequent frames.
For simple panels with just 1 connector (2 color lanes), the following constructor arguments are available:
``serpentine`` controls the arrangement of multiple panels when they are stacked in rows.
If it is `True`, then each row goes in the opposite direction of the previous row.
If this is specified, ``n_lanes`` cannot be, and 2 lanes are always used.
``rotation`` controls the orientation of the panel(s). Must be one of the ``Orientation``
constants. Default is ``Orientation.Normal``.
``n_planes`` controls the color depth of the panel. This is separate from the framebuffer
layout. Decreasing ``n_planes`` can increase FPS at the cost of reduced color fidelity.
The default, 10, is the maximum value.
For panels with more than 2 lanes, or using a custom pixel mapping, the following constructor arguments are available:
``n_lanes`` controls how many color lanes are used. A single 16-pin HUB75 connector has 2 color lanes.
If 2 or 3 connectors are used, then there are 4 or 6 lanes.
``map`` is a Python list of integers giving the framebuffer pixel indices for each matrix pixel.
)pbdoc")
.def(py::init([](size_t width, size_t height, size_t n_addr_lines,
bool serpentine, piomatter::orientation rotation,
size_t n_planes) {
size_t n_planes, size_t n_temporal_planes) {
size_t n_lines = 2 << n_addr_lines;
size_t pixels_across = width * height / n_lines;
size_t odd = (width * height) % n_lines;
@ -185,30 +204,51 @@ The default, 10, is the maximum value.
switch (rotation) {
case piomatter::orientation::normal:
return piomatter::matrix_geometry(
pixels_across, n_addr_lines, n_planes, width, height,
serpentine, piomatter::orientation_normal);
pixels_across, n_addr_lines, n_planes,
n_temporal_planes, width, height, serpentine,
piomatter::orientation_normal);
case piomatter::orientation::r180:
return piomatter::matrix_geometry(
pixels_across, n_addr_lines, n_planes, width, height,
serpentine, piomatter::orientation_r180);
pixels_across, n_addr_lines, n_planes,
n_temporal_planes, width, height, serpentine,
piomatter::orientation_r180);
case piomatter::orientation::ccw:
return piomatter::matrix_geometry(
pixels_across, n_addr_lines, n_planes, width, height,
serpentine, piomatter::orientation_ccw);
pixels_across, n_addr_lines, n_planes,
n_temporal_planes, width, height, serpentine,
piomatter::orientation_ccw);
case piomatter::orientation::cw:
return piomatter::matrix_geometry(
pixels_across, n_addr_lines, n_planes, width, height,
serpentine, piomatter::orientation_cw);
pixels_across, n_addr_lines, n_planes,
n_temporal_planes, width, height, serpentine,
piomatter::orientation_cw);
}
throw std::runtime_error("invalid rotation");
}),
py::arg("width"), py::arg("height"), py::arg("n_addr_lines"),
py::arg("serpentine") = true,
py::arg("rotation") = piomatter::orientation::normal,
py::arg("n_planes") = 10u)
py::arg("n_planes") = 10u, py::arg("n_temporal_planes") = 2)
.def(py::init([](size_t width, size_t height, size_t n_addr_lines,
piomatter::matrix_map map, size_t n_planes,
size_t n_temporal_planes, size_t n_lanes) {
size_t n_lines = n_lanes << n_addr_lines;
size_t pixels_across = width * height / n_lines;
for (auto el : map) {
if ((size_t)el >= width * height) {
throw std::out_of_range("Map element out of range");
}
}
return piomatter::matrix_geometry(pixels_across, n_addr_lines,
n_planes, n_temporal_planes,
width, height, map, n_lanes);
}),
py::arg("width"), py::arg("height"), py::arg("n_addr_lines"),
py::arg("map"), py::arg("n_planes") = 10u,
py::arg("n_temporal_planes") = 0u, py::arg("n_lanes") = 2)
.def_readonly("width", &piomatter::matrix_geometry::width)
.def_readonly("height", &piomatter::matrix_geometry::height);
@ -216,16 +256,16 @@ The default, 10, is the maximum value.
HUB75 matrix driver for Raspberry Pi 5 using PIO
``colorspace`` controls the colorspace that will be used for data to be displayed.
It must be one of the ``Colorspace`` constants. Which to use depends on what data
It must be one of the `Colorspace` constants. Which to use depends on what data
your displaying and how it is processed before copying into the framebuffer.
``pinout`` defines which pins the panels are wired to. Different pinouts can
support different hardware breakouts and panels with different color order. The
value must be one of the ``Pinout`` constants.
value must be one of the `Pinout` constants.
``framebuffer`` a numpy array that holds pixel data in the appropriate colorspace.
``geometry`` controls the size and shape of the panel. The value must be a ``Geometry``
``geometry`` controls the size and shape of the panel. The value must be a `Geometry`
instance.
)pbdoc")
.def(py::init(&make_piomatter), py::arg("colorspace"),
@ -239,55 +279,5 @@ data is triple-buffered to prevent tearing.
)pbdoc")
.def_property_readonly("fps", &PyPiomatter::fps, R"pbdoc(
The approximate number of matrix refreshes per second.
)pbdoc");
m.def(
"AdafruitMatrixBonnetRGB565",
[](py::buffer buffer, const piomatter::matrix_geometry &geometry) {
return make_piomatter(Colorspace::RGB565,
Pinout::AdafruitMatrixBonnet, buffer,
geometry);
},
py::arg("buffer"), py::arg("geometry"),
R"pbdoc(
Construct a PioMatter object to drive panels connected to an
Adafruit Matrix Bonnet using the RGB565 memory layout (2 bytes per
pixel)
This is deprecated shorthand for `PioMatter(Colorspace.RGB565, Pinout.AdafruitMatrixBonnet, ...)`.
)pbdoc");
m.def(
"AdafruitMatrixBonnetRGB888",
[](py::buffer buffer, const piomatter::matrix_geometry &geometry) {
return make_piomatter(Colorspace::RGB888,
Pinout::AdafruitMatrixBonnet, buffer,
geometry);
},
py::arg("framebuffer"), py::arg("geometry"),
R"pbdoc(
Construct a PioMatter object to drive panels connected to an
Adafruit Matrix Bonnet using the RGB888 memory layout (4 bytes per
pixel)
This is deprecated shorthand for `PioMatter(Colorspace.RGB888, Pinout.AdafruitMatrixBonnet, ...)`.
)pbdoc")
//.doc() =
;
m.def(
"AdafruitMatrixBonnetRGB888Packed",
[](py::buffer buffer, const piomatter::matrix_geometry &geometry) {
return make_piomatter(Colorspace::RGB888Packed,
Pinout::AdafruitMatrixBonnet, buffer,
geometry);
},
py::arg("framebuffer"), py::arg("geometry"),
R"pbdoc(
Construct a PioMatter object to drive panels connected to an
Adafruit Matrix Bonnet using the RGB888 packed memory layout (3
bytes per pixel)
This is deprecated shorthand for `PioMatter(Colorspace.RGB888Packed, Pinout.AdafruitMatrixBonnet, ...)`.
)pbdoc");
}