Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions examples/fbmirror.py
Original file line number Diff line number Diff line change
@@ -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()
58 changes: 58 additions & 0 deletions examples/fbmirror_scaled.py
Original file line number Diff line number Diff line change
@@ -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()
2 changes: 1 addition & 1 deletion examples/playframes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}]")
25 changes: 11 additions & 14 deletions src/assemble.py
Original file line number Diff line number Diff line change
@@ -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()
27 changes: 26 additions & 1 deletion src/include/piomatter/piomatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -35,6 +41,8 @@ struct piomatter_base {

virtual ~piomatter_base() {}
virtual void show() = 0;

double fps;
};

template <class pinout = adafruit_matrix_bonnet_pinout,
Expand Down Expand Up @@ -111,11 +119,21 @@ struct piomatter : piomatter_base {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + protomatter_wrap_target,
offset + protomatter_wrap);
// 1 side-set pin
sm_config_set_sideset(&c, 2, true, false);
sm_config_set_out_shift(&c, /* shift_right= */ false,
/* auto_pull = */ true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
sm_config_set_clkdiv(&c, 1.0);
// Due to https://github.com/raspberrypi/utils/issues/116 it's not
// possible to keep the RP1 state machine fed at high rates. This target
// frequency is approximately the best sustainable clock with current
// FW & kernel.
constexpr double target_freq =
2700000 * 2; // 2.7MHz pixel clock, 2 PIO cycles per pixel
double div = clock_get_hz(clk_sys) / target_freq;
sm_config_set_clkdiv(&c, div);
sm_config_set_out_pins(&c, 0, 28);
sm_config_set_sideset_pins(&c, pinout::PIN_CLK);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);

Expand Down Expand Up @@ -146,6 +164,8 @@ struct piomatter : piomatter_base {
size_t datasize = 0;
int old_buffer_idx = buffer_manager::no_buffer;
int buffer_idx;
uint64_t t0, t1;
t0 = monotonicns64();
while ((buffer_idx = manager.get_filled_buffer()) !=
buffer_manager::exit_request) {
if (buffer_idx != buffer_manager::no_buffer) {
Expand All @@ -160,6 +180,11 @@ struct piomatter : piomatter_base {
if (datasize) {
pio_sm_xfer_data_large(pio, sm, PIO_DIR_TO_SM, datasize,
(uint32_t *)databuf);
t1 = monotonicns64();
if (t0 != t1) {
fps = 1e9 / (t1 - t0);
}
t0 = t1;
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
Expand Down
13 changes: 8 additions & 5 deletions src/include/piomatter/protomatter.pio.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,27 @@

const int protomatter_wrap = 4;
const int protomatter_wrap_target = 0;
const int protomatter_sideset_pin_count = 0;
const bool protomatter_sideset_enable = 0;
const int protomatter_sideset_pin_count = 1;
const bool protomatter_sideset_enable = true;
const uint16_t protomatter[] = {
// ; data format (out-shift-right):
// ; MSB ... LSB
// ; 0 ddd......ddd: 31-bit delay
// ; 1 ccc......ccc: 31 bit data count
// .side_set 1 opt
// .wrap_target
// top:
0x6021, // out x, 1
0x605f, // out y, 31
0x0025, // jmp !x delay_loop
0x0025, // jmp !x do_delay
// data_loop:
0x6000, // out pins, 32
0x0083, // jmp y--, data_loop
0x1883, // jmp y--, data_loop side 1 ; assert clk bit
// .wrap
// do_delay:
0x6000, // out pins, 32
// delay_loop:
0x0085, // jmp y--, delay_loop
0x0086, // jmp y--, delay_loop
0x0000, // jmp top
// ;; fill program out to 32 instructions so nothing else can load
0xa042, // nop
Expand Down
47 changes: 16 additions & 31 deletions src/include/piomatter/render.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@

namespace piomatter {

constexpr unsigned DATA_OVERHEAD = 3;
// this is ... flatly wrong!? but it's the number that makes the ramp intensity
// correct to my eye
constexpr unsigned CLOCKS_PER_DATA = 128;
constexpr unsigned DELAY_OVERHEAD = 5;
constexpr unsigned CLOCKS_PER_DELAY = 1;
constexpr int DATA_OVERHEAD = 3;
constexpr int CLOCKS_PER_DATA = 2;
constexpr int DELAY_OVERHEAD = 5;
constexpr int CLOCKS_PER_DELAY = 1;

constexpr uint32_t command_data = 1u << 31;
constexpr uint32_t command_delay = 0;
Expand Down Expand Up @@ -139,12 +137,12 @@ void protomatter_render_rgb10(std::vector<uint32_t> &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) {
Expand All @@ -155,27 +153,15 @@ void protomatter_render_rgb10(std::vector<uint32_t> &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) {
Expand All @@ -197,9 +183,9 @@ void protomatter_render_rgb10(std::vector<uint32_t> &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]);
Expand All @@ -214,8 +200,7 @@ void protomatter_render_rgb10(std::vector<uint32_t> &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;
Expand Down Expand Up @@ -245,7 +230,7 @@ void protomatter_render_rgb10(std::vector<uint32_t> &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());
Expand Down
1 change: 0 additions & 1 deletion src/protodemo.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
7 changes: 5 additions & 2 deletions src/protomatter.pio
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading