Skip to content

Commit 101a796

Browse files
authored
Merge pull request #6 from adafruit/clocking-fix
Fix clocking & add framebuffer mirroring examples
2 parents b015674 + feb6462 commit 101a796

File tree

10 files changed

+180
-55
lines changed

10 files changed

+180
-55
lines changed

examples/fbmirror.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#!/usr/bin/python3
2+
"""
3+
Mirror a scaled copy of the framebuffer to a 64x32 matrix
4+
5+
The upper left corner of the framebuffer is displayed until the user hits ctrl-c.
6+
7+
The `/dev/fb0` special file will exist if a monitor is plugged in at boot time,
8+
or if `/boot/firmware/cmdline.txt` specifies a resolution such as
9+
`... video=HDMI-A-1:640x480M@60D`.
10+
"""
11+
12+
13+
import adafruit_raspberry_pi5_piomatter
14+
import numpy as np
15+
16+
yoffset = 0
17+
xoffset = 0
18+
19+
with open("/sys/class/graphics/fb0/virtual_size") as f:
20+
screenx, screeny = [int(word) for word in f.read().split(",")]
21+
22+
with open("/sys/class/graphics/fb0/bits_per_pixel") as f:
23+
bits_per_pixel = int(f.read())
24+
25+
assert bits_per_pixel == 16
26+
27+
bytes_per_pixel = bits_per_pixel // 8
28+
dtype = {2: np.uint16, 4: np.uint32}[bytes_per_pixel]
29+
30+
with open("/sys/class/graphics/fb0/stride") as f:
31+
stride = int(f.read())
32+
33+
linux_framebuffer = np.memmap('/dev/fb0',mode='r', shape=(screeny, stride // bytes_per_pixel), dtype=dtype)
34+
35+
width = 64
36+
height = 32
37+
geometry = adafruit_raspberry_pi5_piomatter.Geometry(width=width, height=height, n_addr_lines=4, rotation=adafruit_raspberry_pi5_piomatter.Orientation.Normal)
38+
matrix_framebuffer = np.zeros(shape=(geometry.height, geometry.width), dtype=dtype)
39+
matrix = adafruit_raspberry_pi5_piomatter.AdafruitMatrixBonnetRGB565(matrix_framebuffer, geometry)
40+
41+
while True:
42+
matrix_framebuffer[:,:] = linux_framebuffer[yoffset:yoffset+height, xoffset:xoffset+width]
43+
matrix.show()

examples/fbmirror_scaled.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/bin/python3
2+
"""
3+
Mirror a scaled copy of the framebuffer to a 64x32 matrix
4+
5+
The upper left corner of the framebuffer is displayed until the user hits ctrl-c.
6+
7+
The `/dev/fb0` special file will exist if a monitor is plugged in at boot time,
8+
or if `/boot/firmware/cmdline.txt` specifies a resolution such as
9+
`... video=HDMI-A-1:640x480M@60D`.
10+
"""
11+
12+
13+
import adafruit_raspberry_pi5_piomatter
14+
import numpy as np
15+
import PIL.Image as Image
16+
17+
with open("/sys/class/graphics/fb0/virtual_size") as f:
18+
screenx, screeny = [int(word) for word in f.read().split(",")]
19+
20+
with open("/sys/class/graphics/fb0/bits_per_pixel") as f:
21+
bits_per_pixel = int(f.read())
22+
23+
assert bits_per_pixel == 16
24+
25+
bytes_per_pixel = bits_per_pixel // 8
26+
dtype = {2: np.uint16, 4: np.uint32}[bytes_per_pixel]
27+
28+
with open("/sys/class/graphics/fb0/stride") as f:
29+
stride = int(f.read())
30+
31+
linux_framebuffer = np.memmap('/dev/fb0',mode='r', shape=(screeny, stride // bytes_per_pixel), dtype=dtype)
32+
33+
xoffset = 0
34+
yoffset = 0
35+
width = 64
36+
height = 32
37+
scale = 3
38+
39+
geometry = adafruit_raspberry_pi5_piomatter.Geometry(width=width, height=height, n_addr_lines=4, rotation=adafruit_raspberry_pi5_piomatter.Orientation.Normal)
40+
matrix_framebuffer = np.zeros(shape=(geometry.height, geometry.width, 3), dtype=np.uint8)
41+
matrix = adafruit_raspberry_pi5_piomatter.AdafruitMatrixBonnetRGB888Packed(matrix_framebuffer, geometry)
42+
43+
while True:
44+
tmp = linux_framebuffer[yoffset:yoffset+height*scale, xoffset:xoffset+width*scale]
45+
# Convert the RGB565 framebuffer into RGB888Packed (so that we can use PIL image operations to rescale it)
46+
r = (tmp & 0xf800) >> 8
47+
r = r | (r >> 5)
48+
r = r.astype(np.uint8)
49+
g = (tmp & 0x07e0) >> 3
50+
g = g | (g >> 6)
51+
g = g.astype(np.uint8)
52+
b = (tmp & 0x001f) << 3
53+
b = b | (b >> 5)
54+
b = b.astype(np.uint8)
55+
img = Image.fromarray(np.stack([r, g, b], -1))
56+
img = img.resize((width, height))
57+
matrix_framebuffer[:,:] = np.asarray(img)
58+
matrix.show()

examples/playframes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@
3232
t1 = time.monotonic()
3333
dt = t1 - t0
3434
fps = nimages/dt
35-
print(f"{nimages} frames in {dt}s, {fps}fps")
35+
print(f"{nimages} frames in {dt}s, {fps}fps [{matrix.fps}]")

src/assemble.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,26 @@
1-
import sys
2-
from contextlib import contextmanager
1+
import io
2+
import pathlib
3+
from contextlib import redirect_stdout
34

45
import adafruit_pioasm
56
import click
67

78

8-
@contextmanager
9-
def temporary_stdout(filename):
10-
old_stdout = sys.stdout
11-
try:
12-
with open(filename, "w", encoding="utf-8") as sys.stdout:
13-
yield sys.stdout
14-
finally:
15-
sys.stdout = old_stdout
16-
179
@click.command
1810
@click.argument("infile")
1911
@click.argument("outfile")
2012
def main(infile, outfile):
21-
program_name = infile.rpartition("/")[2].partition(".")[0]
22-
print(program_name)
13+
program_name = pathlib.Path(infile).stem
2314
program = adafruit_pioasm.Program.from_file(infile, build_debuginfo=True)
2415

25-
with temporary_stdout(outfile):
16+
c_program = io.StringIO()
17+
with redirect_stdout(c_program):
2618
program.print_c_program(program_name)
2719

20+
with open(outfile, "w", encoding="utf-8") as out:
21+
print("#pragma once", file=out)
22+
print("", file=out)
23+
print(c_program.getvalue().rstrip().replace("True", "true"), file=out)
24+
2825
if __name__ == '__main__':
2926
main()

src/include/piomatter/piomatter.h

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212

1313
namespace piomatter {
1414

15+
static uint64_t monotonicns64() {
16+
struct timespec tp;
17+
clock_gettime(CLOCK_MONOTONIC, &tp);
18+
return tp.tv_sec * UINT64_C(1000000000) + tp.tv_nsec;
19+
}
20+
1521
constexpr size_t MAX_XFER = 65532;
1622

1723
void pio_sm_xfer_data_large(PIO pio, int sm, int direction, size_t size,
@@ -35,6 +41,8 @@ struct piomatter_base {
3541

3642
virtual ~piomatter_base() {}
3743
virtual void show() = 0;
44+
45+
double fps;
3846
};
3947

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

@@ -146,6 +164,8 @@ struct piomatter : piomatter_base {
146164
size_t datasize = 0;
147165
int old_buffer_idx = buffer_manager::no_buffer;
148166
int buffer_idx;
167+
uint64_t t0, t1;
168+
t0 = monotonicns64();
149169
while ((buffer_idx = manager.get_filled_buffer()) !=
150170
buffer_manager::exit_request) {
151171
if (buffer_idx != buffer_manager::no_buffer) {
@@ -160,6 +180,11 @@ struct piomatter : piomatter_base {
160180
if (datasize) {
161181
pio_sm_xfer_data_large(pio, sm, PIO_DIR_TO_SM, datasize,
162182
(uint32_t *)databuf);
183+
t1 = monotonicns64();
184+
if (t0 != t1) {
185+
fps = 1e9 / (t1 - t0);
186+
}
187+
t0 = t1;
163188
} else {
164189
std::this_thread::sleep_for(std::chrono::milliseconds(1));
165190
}

src/include/piomatter/protomatter.pio.h

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,27 @@
22

33
const int protomatter_wrap = 4;
44
const int protomatter_wrap_target = 0;
5-
const int protomatter_sideset_pin_count = 0;
6-
const bool protomatter_sideset_enable = 0;
5+
const int protomatter_sideset_pin_count = 1;
6+
const bool protomatter_sideset_enable = true;
77
const uint16_t protomatter[] = {
88
// ; data format (out-shift-right):
99
// ; MSB ... LSB
1010
// ; 0 ddd......ddd: 31-bit delay
1111
// ; 1 ccc......ccc: 31 bit data count
12+
// .side_set 1 opt
1213
// .wrap_target
1314
// top:
1415
0x6021, // out x, 1
1516
0x605f, // out y, 31
16-
0x0025, // jmp !x delay_loop
17+
0x0025, // jmp !x do_delay
1718
// data_loop:
1819
0x6000, // out pins, 32
19-
0x0083, // jmp y--, data_loop
20+
0x1883, // jmp y--, data_loop side 1 ; assert clk bit
2021
// .wrap
22+
// do_delay:
23+
0x6000, // out pins, 32
2124
// delay_loop:
22-
0x0085, // jmp y--, delay_loop
25+
0x0086, // jmp y--, delay_loop
2326
0x0000, // jmp top
2427
// ;; fill program out to 32 instructions so nothing else can load
2528
0xa042, // nop

src/include/piomatter/render.h

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,10 @@
77

88
namespace piomatter {
99

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

1715
constexpr uint32_t command_data = 1u << 31;
1816
constexpr uint32_t command_delay = 0;
@@ -139,12 +137,12 @@ void protomatter_render_rgb10(std::vector<uint32_t> &result,
139137

140138
int data_count = 0;
141139

142-
auto do_delay = [&](uint32_t delay) {
143-
if (delay == 0)
144-
return;
140+
auto do_data_delay = [&](uint32_t data, int32_t delay) {
141+
delay = std::max((delay / CLOCKS_PER_DELAY) - DELAY_OVERHEAD, 1);
145142
assert(delay < 1000000);
146143
assert(!data_count);
147144
result.push_back(command_delay | (delay ? delay - 1 : 0));
145+
result.push_back(data);
148146
};
149147

150148
auto prep_data = [&data_count, &result](uint32_t n) {
@@ -155,27 +153,15 @@ void protomatter_render_rgb10(std::vector<uint32_t> &result,
155153
data_count = n;
156154
};
157155

158-
auto do_data = [&](uint32_t d) {
159-
assert(data_count);
160-
data_count--;
161-
result.push_back(d);
162-
};
163-
164156
int32_t active_time;
165157

166-
auto do_data_active = [&active_time, &do_data](uint32_t d) {
158+
auto do_data_clk_active = [&active_time, &data_count, &result](uint32_t d) {
167159
bool active = active_time > 0;
168160
active_time--;
169161
d |= active ? pinout::oe_active : pinout::oe_inactive;
170-
do_data(d);
171-
};
172-
173-
auto do_data_delay = [&prep_data, &do_data, &do_delay](uint32_t d,
174-
int32_t delay) {
175-
prep_data(1);
176-
do_data(d);
177-
if (delay > 0)
178-
do_delay(delay);
162+
assert(data_count);
163+
data_count--;
164+
result.push_back(d);
179165
};
180166

181167
auto calc_addr_bits = [](int addr) {
@@ -197,9 +183,9 @@ void protomatter_render_rgb10(std::vector<uint32_t> &result,
197183
return data;
198184
};
199185

200-
auto add_pixels = [&do_data_active, &result](uint32_t addr_bits, bool r0,
201-
bool g0, bool b0, bool r1,
202-
bool g1, bool b1) {
186+
auto add_pixels = [&do_data_clk_active,
187+
&result](uint32_t addr_bits, bool r0, bool g0, bool b0,
188+
bool r1, bool g1, bool b1) {
203189
uint32_t data = addr_bits;
204190
if (r0)
205191
data |= (1 << pinout::PIN_RGB[0]);
@@ -214,8 +200,7 @@ void protomatter_render_rgb10(std::vector<uint32_t> &result,
214200
if (b1)
215201
data |= (1 << pinout::PIN_RGB[5]);
216202

217-
do_data_active(data);
218-
do_data_active(data | pinout::clk_bit);
203+
do_data_clk_active(data);
219204
};
220205

221206
int last_bit = 0;
@@ -245,7 +230,7 @@ void protomatter_render_rgb10(std::vector<uint32_t> &result,
245230
active_time = 1 << last_bit;
246231
last_bit = bit;
247232

248-
prep_data(2 * pixels_across);
233+
prep_data(pixels_across);
249234
auto mapiter = matrixmap.map.begin() + 2 * addr * pixels_across;
250235
for (size_t x = 0; x < pixels_across; x++) {
251236
assert(mapiter != matrixmap.map.end());

src/protodemo.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ void test_pattern(int offs) {
9090
}
9191

9292
static uint64_t monotonicns64() {
93-
9493
struct timespec tp;
9594
clock_gettime(CLOCK_MONOTONIC, &tp);
9695
return tp.tv_sec * UINT64_C(1000000000) + tp.tv_nsec;

src/protomatter.pio

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
; 0 ddd......ddd: 31-bit delay
44
; 1 ccc......ccc: 31 bit data count
55

6+
.side_set 1 opt
67
.wrap_target
78
top:
89
out x, 1
910
out y, 31
10-
jmp !x delay_loop
11+
jmp !x do_delay
1112

1213
data_loop:
1314
out pins, 32
14-
jmp y--, data_loop
15+
jmp y--, data_loop side 1 ; assert clk bit
1516
.wrap
1617

18+
do_delay:
19+
out pins, 32
1720
delay_loop:
1821
jmp y--, delay_loop
1922
jmp top

0 commit comments

Comments
 (0)