Skip to content
14 changes: 12 additions & 2 deletions docs/io_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ The table below uses standard 40-pin physical numbering and highlights:

## Waveshare SPI display pin notes

For the Waveshare 1.3" LCD HAT on a GPIO40 header:
The Waveshare 1.3" LCD HAT (ST7789, 240×240) and 1.44" LCD HAT (ST7735S,
128×128) share the same GPIO40 header pinout:
- `SPI0_MOSI`: pin `19` (`GPIO10`)
- `SPI0_SCLK`: pin `23` (`GPIO11`)
- `CS` / `LCD-CS` (`SPI0_CE0`): pin `24` (`GPIO8`)
Expand All @@ -50,7 +51,16 @@ For the Waveshare 1.3" LCD HAT on a GPIO40 header:
- Power: pin `1` (`3V3`)
- Ground: e.g. pin `6` (`GND`)

These are the standard Waveshare/RPi-style assignments that the `RPI_40` profile follows.
Both HATs use the same `RPI_40` hardware profile — the only difference is the
display driver setting:

| HAT | Display setting | Driver |
|-----|----------------|--------|
| 1.3" LCD HAT (240×240) | `st7789_240x240` (default) | `ST7789.py` |
| 1.44" LCD HAT (128×128) | `st7735_128x128` | `ST7735.py` |

To use the 1.44" HAT, change the **Display type** setting to `st7735 128x128`
(or use a SettingsQR with `disp_conf=st7735_128x128`).

### CS and the three wiring options

Expand Down
6 changes: 5 additions & 1 deletion src/seedsigner/gui/keyboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def __init__(self,
selected_char="a",
rows=4,
cols=10,
rect=(0,40, 240,240),
rect=None,
additional_keys=[KEY_BACKSPACE],
auto_wrap=[WRAP_TOP, WRAP_BOTTOM, WRAP_LEFT, WRAP_RIGHT],
render_now=True,
Expand All @@ -192,6 +192,10 @@ def __init__(self,
self.charset = charset
self.rows = rows
self.cols = cols
if rect is None:
from seedsigner.gui.renderer import Renderer
renderer = Renderer.get_instance()
rect = (0, GUIConstants.TOP_NAV_HEIGHT, renderer.canvas_width, renderer.canvas_height)
self.rect = rect
self.font = Fonts.get_font(font_name, font_size)

Expand Down
30 changes: 27 additions & 3 deletions src/seedsigner/gui/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
DISPLAY_TYPE__ILI9341,
DISPLAY_TYPE__ILI9486,
DISPLAY_TYPE__ST7789,
DISPLAY_TYPE__ST7735,
DISPLAY_TYPE__DESKTOP,
DisplayDriver,
)
Expand All @@ -23,6 +24,8 @@ class Renderer(ConfigurableSingleton):
draw: ImageDraw.ImageDraw = None
disp = None
lock = Lock()
_needs_resize = False
_display_size = (0, 0)


@classmethod
Expand Down Expand Up @@ -60,21 +63,42 @@ def initialize_display(self):
self.canvas_width = self.disp.width
self.canvas_height = self.disp.height

elif self.display_type == DISPLAY_TYPE__ST7735:
# The UI is designed for 240×240; render at that resolution
# and downscale to the physical 128×128 display in
# show_image().
self.canvas_width = 240
self.canvas_height = 240

elif self.display_type in [DISPLAY_TYPE__ILI9341, DISPLAY_TYPE__ILI9486]:
# Swap for the natively portrait-oriented displays
self.canvas_width = self.disp.height
self.canvas_height = self.disp.width

self._needs_resize = (
self.canvas_width != self.disp.width
or self.canvas_height != self.disp.height
)
self._display_size = (self.disp.width, self.disp.height)

self.canvas = Image.new('RGB', (self.canvas_width, self.canvas_height))
self.draw = ImageDraw.Draw(self.canvas)
finally:
self.lock.release()


def _resize_for_display(self, image):
"""Downscale *image* to the physical display size when the canvas is
larger than the display (e.g. 240×240 canvas on a 128×128 ST7735)."""
if self._needs_resize:
return image.resize(self._display_size, Image.LANCZOS)
return image


def show_image(self, image=None, alpha_overlay=None, show_direct=False):
if show_direct:
# Use the incoming image as the canvas and immediately render
self.disp.show_image(image, 0, 0)
self.disp.show_image(self._resize_for_display(image), 0, 0)
return

if alpha_overlay:
Expand All @@ -86,7 +110,7 @@ def show_image(self, image=None, alpha_overlay=None, show_direct=False):
# Always write to the current canvas, rather than trying to replace it
self.canvas.paste(image)

self.disp.show_image(self.canvas, 0, 0)
self.disp.show_image(self._resize_for_display(self.canvas), 0, 0)


def show_image_pan(self, image, start_x, start_y, end_x, end_y, rate, alpha_overlay=None):
Expand Down Expand Up @@ -120,7 +144,7 @@ def show_image_pan(self, image, start_x, start_y, end_x, end_y, rate, alpha_over
# Always keep a copy of the current display in the canvas
self.canvas.paste(crop)

self.disp.show_image(crop, 0, 0)
self.disp.show_image(self._resize_for_display(crop), 0, 0)



Expand Down
4 changes: 2 additions & 2 deletions src/seedsigner/gui/screens/screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -866,7 +866,7 @@ def run(self):
# Display the brightness tips toast
duration = 10 ** 9 * 1.2 # 1.2 seconds
if is_brightness_tip_enabled and time.time_ns() - self.tips_start_time.cur_count < duration:
image = self.qr_encoder.part_to_image(self.qr_encoder.cur_part(), 240, 240, border=2, background_color=hex_color)
image = self.qr_encoder.part_to_image(self.qr_encoder.cur_part(), self.renderer.canvas_width, self.renderer.canvas_height, border=2, background_color=hex_color)
self.render_brightness_tip(image)
pending_encoder_restart = True
else:
Expand All @@ -876,7 +876,7 @@ def run(self):
# brightness tip is stowed.
self.qr_encoder.restart()
pending_encoder_restart = False
image = self.qr_encoder.next_part_image(240, 240, border=2, background_color=hex_color)
image = self.qr_encoder.next_part_image(self.renderer.canvas_width, self.renderer.canvas_height, border=2, background_color=hex_color)

with self.renderer.lock:
self.renderer.show_image(image)
Expand Down
Loading
Loading