Merge pull request #6 from adafruit/clocking-fix
Some checks failed
Pip / build (ubuntu-latest, 3.11) (push) Has been cancelled
Pip / build (ubuntu-latest, 3.12) (push) Has been cancelled
Pip / build (ubuntu-latest, 3.13) (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Wheels / Build SDist (push) Has been cancelled
Wheels / Wheels on ubuntu-latest (push) Has been cancelled
Wheels / Upload release (push) Has been cancelled
Some checks failed
Pip / build (ubuntu-latest, 3.11) (push) Has been cancelled
Pip / build (ubuntu-latest, 3.12) (push) Has been cancelled
Pip / build (ubuntu-latest, 3.13) (push) Has been cancelled
pre-commit / pre-commit (push) Has been cancelled
Wheels / Build SDist (push) Has been cancelled
Wheels / Wheels on ubuntu-latest (push) Has been cancelled
Wheels / Upload release (push) Has been cancelled
Fix clocking & add framebuffer mirroring examples
This commit is contained in:
commit
101a7963b0
10 changed files with 180 additions and 55 deletions
43
examples/fbmirror.py
Normal file
43
examples/fbmirror.py
Normal file
|
|
@ -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
examples/fbmirror_scaled.py
Normal file
58
examples/fbmirror_scaled.py
Normal file
|
|
@ -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()
|
||||
|
|
@ -32,4 +32,4 @@ while True:
|
|||
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}]")
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 <class pinout = adafruit_matrix_bonnet_pinout,
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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]);
|
||||
|
|
@ -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;
|
||||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ struct PyPiomatter {
|
|||
std::unique_ptr<piomatter::piomatter_base> matter;
|
||||
|
||||
void show() { matter->show(); }
|
||||
double fps() const { return matter->fps; }
|
||||
};
|
||||
|
||||
template <typename pinout, typename colorspace>
|
||||
|
|
@ -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<piomatter::adafruit_matrix_bonnet_pinout,
|
||||
piomatter::colorspace_rgb565>,
|
||||
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<piomatter::adafruit_matrix_bonnet_pinout,
|
||||
piomatter::colorspace_rgb888>,
|
||||
|
|
|
|||
Loading…
Reference in a new issue