The Pi (hermes-bot) is flashed, upgraded and running ROS 2 Jazzy. The ST7789 works through Blinka on lgpio: pass cs=None (the kernel owns CE0), and the glass sits at y_offset=80 in the controller RAM for every rotation, since the driver rotates in software. The mounted pose needs rotation=270. hermes_face is a numpy port of the flat face from docs/face-prototype.html, with phosphor and vector styles, plus a new `sleeping` pose for "no agent connected". The renderer has no ROS or hardware imports. The tone map is a lookup table and the glow runs at half resolution, so the face reaches ~28 fps on the panel at 24% of a core (20 fps) to 40% (30 fps) with the CPU at full clock. CONTEXT.md now records the Pi, the cloud-init first-boot-only gotcha, and what is left. pi/50-cloud-init.yaml is a gitignored Wi-Fi template. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AFN8nRRCiDiMN7ChBpHJhs
65 lines
2.3 KiB
Python
65 lines
2.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Draw a test pattern on the BrainCraft HAT's ST7789 (240x240, SPI CE0, DC=GPIO25, BL=GPIO26).
|
|
|
|
display_test.py [ROTATION] one pattern, then a frame-rate probe
|
|
|
|
The driver rotates in software (PIL, counter-clockwise), and the panel's 80-row RAM offset belongs
|
|
to the glass, so y_offset=80 holds for every rotation. 270 is the mounted pose (microSD end up).
|
|
A correct picture has the frame complete on all four edges, no noise, the white triangle in the
|
|
top-left corner as you look at the panel in its mounted pose, and upright text.
|
|
"""
|
|
import sys
|
|
import time
|
|
|
|
import board
|
|
import digitalio
|
|
from adafruit_rgb_display import st7789
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
W = H = 240
|
|
|
|
|
|
def make_display(rotation, x_offset, y_offset, baudrate=48_000_000):
|
|
return st7789.ST7789(
|
|
board.SPI(),
|
|
cs=None, # CE0 is driven by the kernel SPI driver; claiming GPIO8 fails with 'GPIO busy'
|
|
dc=digitalio.DigitalInOut(board.D25),
|
|
rst=None,
|
|
baudrate=baudrate,
|
|
width=W,
|
|
height=H,
|
|
x_offset=x_offset,
|
|
y_offset=y_offset,
|
|
rotation=rotation,
|
|
)
|
|
|
|
|
|
def pattern(label):
|
|
img = Image.new("RGB", (W, H), "black")
|
|
d = ImageDraw.Draw(img)
|
|
for i, c in enumerate(["red", "lime", "blue", "yellow", "cyan", "magenta"]):
|
|
d.rectangle([i * 40, 90, i * 40 + 39, 150], fill=c)
|
|
d.rectangle([0, 0, W - 1, H - 1], outline="white") # edge frame: shows offset/clipping
|
|
d.rectangle([3, 3, W - 4, H - 4], outline="gray")
|
|
d.polygon([(8, 8), (58, 8), (8, 58)], fill="white") # top-left marker: shows rotation
|
|
d.text((70, 12), label[0], fill="white", font=ImageFont.load_default(size=48))
|
|
d.text((70, 66), label[1], fill="white", font=ImageFont.load_default(size=14))
|
|
for x in range(0, W, 30):
|
|
d.line([(x, 160), (x, H - 8)], fill="gray")
|
|
for y in range(160, H - 8, 20):
|
|
d.line([(0, y), (W, y)], fill="gray")
|
|
return img
|
|
|
|
|
|
backlight = digitalio.DigitalInOut(board.D26)
|
|
backlight.switch_to_output(value=True)
|
|
|
|
rotation = int(sys.argv[1]) if len(sys.argv) > 1 else 270
|
|
disp = make_display(rotation, 0, 80)
|
|
img = pattern((str(rotation), f"rot={rotation} y_off=80"))
|
|
disp.image(img)
|
|
t0, n = time.monotonic(), 30
|
|
for _ in range(n):
|
|
disp.image(img)
|
|
print(f"pattern shown; full-frame push rate {n / (time.monotonic() - t0):.1f} fps at 48 MHz")
|