137 lines
4.1 KiB
Python
137 lines
4.1 KiB
Python
"""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] + "..."
|