Skip to content

Commit

Permalink
Merge pull request #6 from adafruit/clocking-fix
Browse files Browse the repository at this point in the history
Fix clocking & add framebuffer mirroring examples
  • Loading branch information
jepler authored Jan 24, 2025
2 parents b015674 + feb6462 commit 101a796
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 55 deletions.
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

0 comments on commit 101a796

Please sign in to comment.