diff --git a/examples/fbmirror.py b/examples/fbmirror.py new file mode 100644 index 0000000..20a4fea --- /dev/null +++ b/examples/fbmirror.py @@ -0,0 +1,43 @@ +#!/usr/bin/python3 +""" +Mirror a scaled copy of the framebuffer to a 64x32 matrix + +The upper left corner of the framebuffer is displayed until the user hits ctrl-c. + +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 adafruit_raspberry_pi5_piomatter +import numpy as np + +yoffset = 0 +xoffset = 0 + +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) + +width = 64 +height = 32 +geometry = adafruit_raspberry_pi5_piomatter.Geometry(width=width, height=height, n_addr_lines=4, rotation=adafruit_raspberry_pi5_piomatter.Orientation.Normal) +matrix_framebuffer = np.zeros(shape=(geometry.height, geometry.width), dtype=dtype) +matrix = adafruit_raspberry_pi5_piomatter.AdafruitMatrixBonnetRGB565(matrix_framebuffer, geometry) + +while True: + matrix_framebuffer[:,:] = linux_framebuffer[yoffset:yoffset+height, xoffset:xoffset+width] + matrix.show() diff --git a/examples/fbmirror_scaled.py b/examples/fbmirror_scaled.py new file mode 100644 index 0000000..aee0832 --- /dev/null +++ b/examples/fbmirror_scaled.py @@ -0,0 +1,58 @@ +#!/usr/bin/python3 +""" +Mirror a scaled copy of the framebuffer to a 64x32 matrix + +The upper left corner of the framebuffer is displayed until the user hits ctrl-c. + +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 adafruit_raspberry_pi5_piomatter +import numpy as np +import PIL.Image as Image + +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) + +xoffset = 0 +yoffset = 0 +width = 64 +height = 32 +scale = 3 + +geometry = adafruit_raspberry_pi5_piomatter.Geometry(width=width, height=height, n_addr_lines=4, rotation=adafruit_raspberry_pi5_piomatter.Orientation.Normal) +matrix_framebuffer = np.zeros(shape=(geometry.height, geometry.width, 3), dtype=np.uint8) +matrix = adafruit_raspberry_pi5_piomatter.AdafruitMatrixBonnetRGB888Packed(matrix_framebuffer, 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.asarray(img) + matrix.show() diff --git a/examples/playframes.py b/examples/playframes.py index 7609b2b..f3af2f3 100644 --- a/examples/playframes.py +++ b/examples/playframes.py @@ -32,4 +32,4 @@ t1 = time.monotonic() dt = t1 - t0 fps = nimages/dt - print(f"{nimages} frames in {dt}s, {fps}fps") + print(f"{nimages} frames in {dt}s, {fps}fps [{matrix.fps}]") diff --git a/src/assemble.py b/src/assemble.py index 645d656..400898d 100644 --- a/src/assemble.py +++ b/src/assemble.py @@ -1,29 +1,26 @@ -import sys -from contextlib import contextmanager +import io +import pathlib +from contextlib import redirect_stdout import adafruit_pioasm import click -@contextmanager -def temporary_stdout(filename): - old_stdout = sys.stdout - try: - with open(filename, "w", encoding="utf-8") as sys.stdout: - yield sys.stdout - finally: - sys.stdout = old_stdout - @click.command @click.argument("infile") @click.argument("outfile") def main(infile, outfile): - program_name = infile.rpartition("/")[2].partition(".")[0] - print(program_name) + program_name = pathlib.Path(infile).stem program = adafruit_pioasm.Program.from_file(infile, build_debuginfo=True) - with temporary_stdout(outfile): + c_program = io.StringIO() + with redirect_stdout(c_program): program.print_c_program(program_name) + with open(outfile, "w", encoding="utf-8") as out: + print("#pragma once", file=out) + print("", file=out) + print(c_program.getvalue().rstrip().replace("True", "true"), file=out) + if __name__ == '__main__': main() diff --git a/src/include/piomatter/piomatter.h b/src/include/piomatter/piomatter.h index 4ae67a0..4f91fc3 100644 --- a/src/include/piomatter/piomatter.h +++ b/src/include/piomatter/piomatter.h @@ -12,6 +12,12 @@ namespace piomatter { +static uint64_t monotonicns64() { + struct timespec tp; + clock_gettime(CLOCK_MONOTONIC, &tp); + return tp.tv_sec * UINT64_C(1000000000) + tp.tv_nsec; +} + constexpr size_t MAX_XFER = 65532; void pio_sm_xfer_data_large(PIO pio, int sm, int direction, size_t size, @@ -35,6 +41,8 @@ struct piomatter_base { virtual ~piomatter_base() {} virtual void show() = 0; + + double fps; }; template &result, int data_count = 0; - auto do_delay = [&](uint32_t delay) { - if (delay == 0) - return; + auto do_data_delay = [&](uint32_t data, int32_t delay) { + delay = std::max((delay / CLOCKS_PER_DELAY) - DELAY_OVERHEAD, 1); assert(delay < 1000000); assert(!data_count); result.push_back(command_delay | (delay ? delay - 1 : 0)); + result.push_back(data); }; auto prep_data = [&data_count, &result](uint32_t n) { @@ -155,27 +153,15 @@ void protomatter_render_rgb10(std::vector &result, data_count = n; }; - auto do_data = [&](uint32_t d) { - assert(data_count); - data_count--; - result.push_back(d); - }; - int32_t active_time; - auto do_data_active = [&active_time, &do_data](uint32_t d) { + auto do_data_clk_active = [&active_time, &data_count, &result](uint32_t d) { bool active = active_time > 0; active_time--; d |= active ? pinout::oe_active : pinout::oe_inactive; - do_data(d); - }; - - auto do_data_delay = [&prep_data, &do_data, &do_delay](uint32_t d, - int32_t delay) { - prep_data(1); - do_data(d); - if (delay > 0) - do_delay(delay); + assert(data_count); + data_count--; + result.push_back(d); }; auto calc_addr_bits = [](int addr) { @@ -197,9 +183,9 @@ void protomatter_render_rgb10(std::vector &result, return data; }; - auto add_pixels = [&do_data_active, &result](uint32_t addr_bits, bool r0, - bool g0, bool b0, bool r1, - bool g1, bool b1) { + 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]); @@ -214,8 +200,7 @@ void protomatter_render_rgb10(std::vector &result, if (b1) data |= (1 << pinout::PIN_RGB[5]); - do_data_active(data); - do_data_active(data | pinout::clk_bit); + do_data_clk_active(data); }; int last_bit = 0; @@ -245,7 +230,7 @@ void protomatter_render_rgb10(std::vector &result, active_time = 1 << last_bit; last_bit = bit; - prep_data(2 * pixels_across); + prep_data(pixels_across); auto mapiter = matrixmap.map.begin() + 2 * addr * pixels_across; for (size_t x = 0; x < pixels_across; x++) { assert(mapiter != matrixmap.map.end()); diff --git a/src/protodemo.c b/src/protodemo.c index 319b386..4dab4ec 100644 --- a/src/protodemo.c +++ b/src/protodemo.c @@ -90,7 +90,6 @@ void test_pattern(int offs) { } static uint64_t monotonicns64() { - struct timespec tp; clock_gettime(CLOCK_MONOTONIC, &tp); return tp.tv_sec * UINT64_C(1000000000) + tp.tv_nsec; diff --git a/src/protomatter.pio b/src/protomatter.pio index c4f082a..1aef1f2 100644 --- a/src/protomatter.pio +++ b/src/protomatter.pio @@ -3,17 +3,20 @@ ; 0 ddd......ddd: 31-bit delay ; 1 ccc......ccc: 31 bit data count +.side_set 1 opt .wrap_target top: out x, 1 out y, 31 - jmp !x delay_loop + jmp !x do_delay data_loop: out pins, 32 - jmp y--, data_loop + jmp y--, data_loop side 1 ; assert clk bit .wrap +do_delay: + out pins, 32 delay_loop: jmp y--, delay_loop jmp top diff --git a/src/pymain.cpp b/src/pymain.cpp index 998bdfa..4d79d42 100644 --- a/src/pymain.cpp +++ b/src/pymain.cpp @@ -18,6 +18,7 @@ struct PyPiomatter { std::unique_ptr matter; void show() { matter->show(); } + double fps() const { return matter->fps; } }; template @@ -151,8 +152,19 @@ Update the displayed image After modifying the content of the framebuffer, call this method to update the data actually displayed on the panel. Internally, the 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", + make_piomatter, + py::arg("buffer"), py::arg("geometry")) + //.doc() = "Drive panels connected to an Adafruit Matrix Bonnet using + // the RGB565 memory layout (4 bytes per pixel)" + ; + m.def("AdafruitMatrixBonnetRGB888", make_piomatter,