Add hermes_face ROS package and verify the display on the Pi
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
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
#!/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")
|
||||
Reference in New Issue
Block a user