"""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] + "..."