Breadboard
docs
Example project · Displays & sensors

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.

The Color dashboard (ST7735 TFT) circuit as rendered by the simulator

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 · posESP32-C3 · vin
  • Battery · negESP32-C3 · gnd
  • ESP32-C3 · 3v3TFT 1.8″ 160×128 (ST7735) · vcc
  • ESP32-C3 · gnd2TFT 1.8″ 160×128 (ST7735) · gnd
  • ESP32-C3 · g4TFT 1.8″ 160×128 (ST7735) · din
  • ESP32-C3 · g6TFT 1.8″ 160×128 (ST7735) · clk
  • ESP32-C3 · g7TFT 1.8″ 160×128 (ST7735) · cs
  • ESP32-C3 · g3TFT 1.8″ 160×128 (ST7735) · dc
  • ESP32-C3 · g10TFT 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.

Related projects