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.2
or4
are 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-serpentine
if 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.