Add native USB HID boot setup and status display
This commit is contained in:
+136
@@ -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] + "..."
|
||||
Reference in New Issue
Block a user