Add native USB HID boot setup and status display

This commit is contained in:
2026-08-26 14:10:31 +02:00
parent d0c926abd6
commit 0e9637c474
5 changed files with 218 additions and 40 deletions
+136
View File
@@ -0,0 +1,136 @@
"""Status display support for the LILYGO T-Dongle-S3.
The board's 160x80 ST7735 panel is wired to SPI2. This driver deliberately uses
only MicroPython's built-in ``machine`` and ``framebuf`` modules.
"""
import framebuf
import time
from machine import Pin, SPI
WIDTH = 160
HEIGHT = 80
# Verified against LILYGO's T-Dongle-S3 factory-screen example.
_PIN_MOSI = 3
_PIN_SCK = 5
_PIN_CS = 4
_PIN_DC = 2
_PIN_RST = 1
_PIN_BACKLIGHT = 38
_X_OFFSET = 1
_Y_OFFSET = 26
_SWRESET = 0x01
_SLPOUT = 0x11
_DISPON = 0x29
_CASET = 0x2A
_RASET = 0x2B
_RAMWR = 0x2C
_MADCTL = 0x36
_COLMOD = 0x3A
_INVON = 0x21
_BLACK = 0x0000
_WHITE = 0xFFFF
_CYAN = 0x07FF
_GREEN = 0x07E0
_YELLOW = 0xFFE0
_GRAY = 0x8410
class StatusDisplay:
"""Render the configured SSID and current DHCP address on the LCD."""
def __init__(self):
self._cs = Pin(_PIN_CS, Pin.OUT, value=1)
self._dc = Pin(_PIN_DC, Pin.OUT, value=0)
self._reset = Pin(_PIN_RST, Pin.OUT, value=1)
# The T-Dongle-S3 backlight enable is active-low.
self._backlight = Pin(_PIN_BACKLIGHT, Pin.OUT, value=1)
self._spi = SPI(2, baudrate=20_000_000, polarity=0, phase=0,
sck=Pin(_PIN_SCK), mosi=Pin(_PIN_MOSI))
self._buffer = bytearray(WIDTH * HEIGHT * 2)
self._framebuffer = framebuf.FrameBuffer(
self._buffer, WIDTH, HEIGHT, framebuf.RGB565
)
self._shown = None
self._initialise()
self._backlight.off()
def _command(self, command, data=None):
self._cs.off()
self._dc.off()
self._spi.write(bytes((command,)))
if data is not None:
self._dc.on()
self._spi.write(data)
self._cs.on()
def _initialise(self):
self._reset.off()
time.sleep_ms(20)
self._reset.on()
time.sleep_ms(120)
self._command(_SWRESET)
time.sleep_ms(150)
self._command(_SLPOUT)
time.sleep_ms(120)
self._command(_COLMOD, b"\x05") # 16-bit RGB565.
# Landscape, BGR colour order, matching the vendor panel setup.
self._command(_MADCTL, b"\xA8")
self._command(_INVON)
self._command(_DISPON)
time.sleep_ms(20)
def update(self, ssid, address=None):
"""Redraw only when the displayed network state changed."""
state = (ssid, address)
if state == self._shown:
return
self._shown = state
framebuffer = self._framebuffer
framebuffer.fill(_BLACK)
framebuffer.text("PolterHID", 3, 3, _CYAN)
framebuffer.text("WiFi", 3, 23, _GRAY)
framebuffer.text(_fit(ssid, 19), 42, 23, _WHITE)
framebuffer.text("IP", 3, 47, _GRAY)
if address:
framebuffer.text(_fit(address, 19), 42, 47, _GREEN)
else:
framebuffer.text("connecting...", 42, 47, _YELLOW)
self.show()
def show(self):
"""Transfer the RGB565 framebuffer, converting its byte order for SPI."""
# framebuf stores RGB565 words in native little-endian order, while the
# ST7735 expects the most-significant byte first on the SPI bus.
for index in range(0, len(self._buffer), 2):
low = self._buffer[index]
self._buffer[index] = self._buffer[index + 1]
self._buffer[index + 1] = low
self._command(_CASET, _coordinates(_X_OFFSET, _X_OFFSET + WIDTH - 1))
self._command(_RASET, _coordinates(_Y_OFFSET, _Y_OFFSET + HEIGHT - 1))
self._cs.off()
self._dc.off()
self._spi.write(bytes((_RAMWR,)))
self._dc.on()
self._spi.write(self._buffer)
self._cs.on()
# Restore native byte order before subsequent framebuf drawing.
for index in range(0, len(self._buffer), 2):
low = self._buffer[index]
self._buffer[index] = self._buffer[index + 1]
self._buffer[index + 1] = low
def _coordinates(start, end):
return bytes((start >> 8, start & 0xFF, end >> 8, end & 0xFF))
def _fit(text, maximum):
if len(text) <= maximum:
return text
return text[:maximum - 3] + "..."