Display Configuration
This example will mirror the console's framebuffer to the matrix panel. In order to make it fit better, and to ensure that the requisite /dev/fb0 exists whether or not an HDMI display is plugged in, there will be a modification to the file /boot/firmware/cmdline.txt that adds a video configuration with the smallest allowed display size.
sudo nano /boot/firmware/cmdline.txt
Scroll to the end of the line by pressing end or holding right arrow for a while.
Press space bar to insert a space at the end of the existing line.
Add the following after the space at the end of the existing line.
video=HDMI-A-1:640x480M@60D
Press Ctrl-S to save the file.
Press Ctrl-X to exit.
Then reboot the Pi for it to take effect
sudo reboot
When the Pi boots back up, the file /dev/fb0 should now exist even if you don't have an HDMI display plugged in. If you do have an HDMI display, you'll notice that the text on it is now scaled bigger since we've overridden the display size to be 640x480
As always, you need to activate the virtual environment before running the example. If you haven't already, then activate it like this.
source ~/venvs/blinka_venv/bin/activate
Next you can run the fbmirror_scaled.py script. The script accepts several arguments to configure the panels. The first 3 are specific to the fbmirror examples.
-
--scale- a number to scale the full display down by. What is best depends on what you're showing.2or4are good places to start and then tune it to your content and desired look. -
--x-offset- the number of pixels in the x axis to skip when getting the region to mirror. -
--y-offset- the number of pixels in the y axis to skip when getting the region to mirror.
The remaining arguments are of the configuration options described on the Initialization Config page. The first two are required.
-
--width- the number of pixels wide the total panel is. 128px is for a 2x2 matrix panel. -
--height- the number of pixels tall the total panel is. 64px is for a 2x2 matrix panel.
The rest are optional if you're using default the configuration for everything.
-
--orientation- the rotation angle, must be one ofNormal,CW,ÂCCW,R180. -
--pinout- the pinout to use for the panel(s) e.g.AdafruitMatrixBonnet -
--num-address-lines- the number of address lines. The 64x64 panels we stock useÂ5, all smaller panels use4 -
--num-planes- the color depth, defaults to10. Lower values can improve refresh rate speed. -
--serpentine- the organization of multiple panels, serpentine is default. Use--no-serpentineif your panels are wired in the non-serpentine configuration.
The command should look as follows with appropriate values filled in for your panel:
python fbmirror_scaled.py --scale [scale] --width [width] --height [height] --orientation [orientation]
The values for a 2x2 matrix panel made from 64x32 panels rotated 180 are shown below, adjust them for your panel(s).
python fbmirror_scaled.py --scale 3 --width 128 --height 64 --orientation R180
The code for this example is embedded below. You can download it, or copy/paste the text into a file.
#!/usr/bin/python3
"""
Mirror a scaled copy of the framebuffer to RGB matrices,
A portion of the framebuffer is displayed until the user hits ctrl-c.
Control scale, matrix size, and orientation with command line arguments.
Usage: fbmirror_scaled.py [OPTIONS]
Options:
--x-offset INTEGER The x offset of top left corner of the
region to mirror
--y-offset INTEGER The y offset of top left corner of the
region to mirror
--scale INTEGER The scale factor to reduce the display down
by.
--num-address-lines INTEGER The number of address lines used by the
panels
--num-planes INTEGER The number of bit planes (color depth. Lower
values can improve refresh rate in frames
per second
--orientation [Normal|R180|CCW|CW]
The overall orientation (rotation) of the
panels
--pinout [AdafruitMatrixBonnet|AdafruitMatrixBonnetBGR|AdafruitMatrixHat|AdafruitMatrixHatBGR]
The details of the electrical connection to
the panels
--serpentine / --no-serpentine The organization of multiple panels
--height INTEGER The panel height in pixels
--width INTEGER The panel width in pixels
--help Show this message and exit.
The `/dev/fb0` special file will exist if a monitor is plugged in at boot time,
or if `/boot/firmware/cmdline.txt` specifies a resolution such as
`... video=HDMI-A-1:640x480M@60D`.
"""
import click
import numpy as np
import PIL.Image as Image
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(",")]
with open("/sys/class/graphics/fb0/bits_per_pixel") as f:
bits_per_pixel = int(f.read())
assert bits_per_pixel == 16
bytes_per_pixel = bits_per_pixel // 8
dtype = {2: np.uint16, 4: np.uint32}[bytes_per_pixel]
with open("/sys/class/graphics/fb0/stride") as f:
stride = int(f.read())
linux_framebuffer = np.memmap('/dev/fb0',mode='r', shape=(screeny, stride // bytes_per_pixel), dtype=dtype)
@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)
@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_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, serpentine=serpentine)
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)
while True:
tmp = linux_framebuffer[yoffset:yoffset + height * scale, xoffset:xoffset + width * scale]
# Convert the RGB565 framebuffer into RGB888Packed (so that we can use PIL image operations to rescale it)
r = (tmp & 0xf800) >> 8
r = r | (r >> 5)
r = r.astype(np.uint8)
g = (tmp & 0x07e0) >> 3
g = g | (g >> 6)
g = g.astype(np.uint8)
b = (tmp & 0x001f) << 3
b = b | (b >> 5)
b = b.astype(np.uint8)
img = Image.fromarray(np.stack([r, g, b], -1))
img = img.resize((width, height))
matrix_framebuffer[:, :] = np.array(img)
matrix.show()
if __name__ == '__main__':
main()
Code Explanation
After initializing the matrix panel(s), this example mirrors the /dev/fb0 framebuffer using numpy.memmap. Inside the main loop it copies the current values from the mirrored instance into a PIL Image, scales it down to the specified size, then copies it into matrix_framebuffer to be shown on the panel.
Page last edited March 12, 2025
Text editor powered by tinymce.