Color dashboard (ST7735 TFT)
A full-color 1.8″ TFT on the bench: the ESP32 drives a real ST7735 over SoftSPI, sets an address window (CASET/RASET) and streams a 160×128 RGB565 framebuffer per frame — exactly like the MicroPython st7735 driver. It fetches live weather from the sandbox internet and paints a colored card: location, temperature, humidity, and conditions. Edit the colors or layout in the Code tab.

How it works
The ESP32 drives a 1.8″ ST7735 color TFT over SoftSPI, decoded cycle-exactly by the emulator just like a real panel. After the init sequence (software reset, sleep-out, COLMOD 16-bit RGB565, display on), the script builds a 160×128 framebuffer with MicroPython's framebuf in RGB565 format and blits it in one shot: set the column and row address windows with CASET (0x2A) and RASET (0x2B), send RAMWR (0x2C), then stream two bytes per pixel. The controller's address counter wraps across the window exactly as hardware does, so the whole frame lands. Content is a live weather card fetched from the sandbox internet (api.weather.test over a real TCP socket), redrawn every couple of seconds.
What's on the bench
- Battery
- ESP32-C3
- TFT 1.8″ 160×128 (ST7735)
How it's wired
- Battery · pos→ESP32-C3 · vin
- Battery · neg→ESP32-C3 · gnd
- ESP32-C3 · 3v3→TFT 1.8″ 160×128 (ST7735) · vcc
- ESP32-C3 · gnd2→TFT 1.8″ 160×128 (ST7735) · gnd
- ESP32-C3 · g4→TFT 1.8″ 160×128 (ST7735) · din
- ESP32-C3 · g6→TFT 1.8″ 160×128 (ST7735) · clk
- ESP32-C3 · g7→TFT 1.8″ 160×128 (ST7735) · cs
- ESP32-C3 · g3→TFT 1.8″ 160×128 (ST7735) · dc
- ESP32-C3 · g10→TFT 1.8″ 160×128 (ST7735) · rst
The code
This MicroPython script runs on the emulated board every boot; edit it in the Code tab.
from machine import Pin, SoftSPI import framebuf, time, network, socket, json W, H = 160, 128 spi = SoftSPI(baudrate=8000000, sck=Pin(6), mosi=Pin(4), miso=Pin(5)) dc = Pin(3, Pin.OUT); cs = Pin(7, Pin.OUT, value=1); rst = Pin(10, Pin.OUT) def wr(c, d=None): cs(0); dc(0); spi.write(bytes([c])) if d is not None: dc(1); spi.write(bytes(d)) cs(1) rst(0); time.sleep_ms(20); rst(1); time.sleep_ms(50) wr(0x01); time.sleep_ms(120) # SWRESET wr(0x11); time.sleep_ms(120) # SLPOUT wr(0x3A, [0x05]) # COLMOD 16-bit RGB565 wr(0x36, [0x00]) # MADCTL: 160x128 window wr(0x29) # DISPON buf = bytearray(W * H * 2) fb = framebuf.FrameBuffer(buf, W, H, framebuf.RGB565) def rgb(r, g, b): # framebuf.RGB565 stores pixels little-endian, but the ST7735 wire is # big-endian (MSB first) — byte-swap so colors land right (the classic # TFT gotcha every real driver handles). v = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3) return ((v & 0xFF) << 8) | (v >> 8) def show(): wr(0x2A, [0, 0, 0, W - 1]) # CASET wr(0x2B, [0, 0, 0, H - 1]) # RASET cs(0); dc(0); spi.write(bytes([0x2C])); dc(1); spi.write(buf); cs(1) w = network.WLAN(network.STA_IF); w.active(True) w.connect('BreadboardNet') while not w.isconnected(): time.sleep_ms(100) def weather(): addr = socket.getaddrinfo('api.weather.test', 80)[0][-1] s = socket.socket(); s.connect(addr) s.send(b'GET /weather HTTP/1.0\r\nHost: api.weather.test\r\n\r\n') r = b'' while True: c = s.recv(256) if not c: break r += c s.close() return json.loads(r.split(b'\r\n\r\n', 1)[1]) SKY = rgb(30, 90, 170) CARD = rgb(20, 28, 40) n = 0 while True: wx = weather(); n += 1 fb.fill(SKY) fb.fill_rect(6, 6, W - 12, H - 12, CARD) fb.rect(6, 6, W - 12, H - 12, rgb(90, 130, 200)) fb.text('INTERNET WEATHER', 14, 14, rgb(140, 200, 255)) fb.text(wx['location'][:18], 14, 34, 0xFFFF) fb.text('%s C' % wx['temperature_c'], 14, 58, rgb(120, 255, 150)) fb.text('humidity %d%%' % wx['humidity'], 14, 80, rgb(230, 210, 90)) fb.text(wx['conditions'][:18], 14, 100, rgb(255, 150, 120)) show() print('drew', n, wx['temperature_c'], 'C') time.sleep_ms(2000)
Try this
- Watch the colored card update — the temperature and conditions come from a real HTTP fetch each cycle.
- Change the colors: edit the rgb(...) calls or the SKY/CARD fills in the Code tab and re-run.
- Open the 📡 Sniffer to see the weather fetch cross the air, then the SPI-driven redraw follow.
- Swap the data source — point weather() at /time or /api and reformat the card.



