Run: Micropython + LVGL on CYD (Cheap Yellow Display
git clone https://github.com/lvgl-micropython/lvgl_micropython.git
cd lvgl_micropython
python3 make.py esp32 clean \
  --flash-size=4 \
  BOARD=ESP32_GENERIC \
  DISPLAY=ili9341 \
  INDEV=xpt2046

esptool.py --chip esp32 \
     -b 460800 \
     --before default_reset \
     --after hard_reset \
     write_flash --flash_mode dio \
     --flash_size 4MB --flash_freq 40m \
     --erase-all 0x0 build/lvgl_micropy_ESP32_GENERIC-4.bin
import lcd_bus
from micropython import const
import machine


# display settings
_WIDTH = const(240)
_HEIGHT = const(320)
_BL = const(21)
_RST = const(17)
_DC = const(2)

_MOSI = const(13)
#_MISO = const(12)
_SCK = const(14)
_HOST = const(1)  # SPI2

_LCD_CS = const(15)
_LCD_FREQ = const(40000000)

#_TOUCH_CS = const(9)
#_TOUCH_FREQ = const(1000000)

spi_bus = machine.SPI.Bus(
    host=_HOST,
    mosi=_MOSI,
    #miso=_MISO,
    sck=_SCK
)

display_bus = lcd_bus.SPIBus(
    spi_bus=spi_bus,
    freq=_LCD_FREQ,
    dc=_DC,
    cs=_LCD_CS,
)


import ili9341  # NOQA
import lvgl as lv  # NOQA


display = ili9341.ILI9341(
    data_bus=display_bus,
    display_width=_WIDTH,
    display_height=_HEIGHT,
     reset_pin=_RST,
    reset_state=ili9341.STATE_LOW,
    backlight_pin=_BL,
    backlight_on_state=ili9341.STATE_HIGH,
    color_space=lv.COLOR_FORMAT.RGB565,
    color_byte_order=ili9341.BYTE_ORDER_BGR,
    rgb565_byte_swap=True,
)

import task_handler  # NOQA
import xpt2046  # NOQA

display.set_power(True)
display.init(1)
# display.set_color_inversion(True)
display.set_rotation(lv.DISPLAY_ROTATION._90)
display.set_backlight(100)

#touch_dev = machine.SPI.Device(
#    spi_bus=spi_bus,
#    freq=_TOUCH_FREQ,
#    cs=_TOUCH_CS
#)

#indev = xpt2046.XPT2046(touch_dev,debug=False,startup_rotation=lv.DISPLAY_ROTATION._0)

#indev.calibrate()

th = task_handler.TaskHandler()

scrn = lv.screen_active()
#scrn.set_style_bg_color(lv.color_hex(0xFFFFFF), 0)

#btnm = lv.buttonmatrix(scrn)
#btnm.add_event_cb(lambda e: btnm_event_handler(e,scrn),lv.EVENT.VALUE_CHANGED, None)
#btnm.set_size(230,120)
#btnm.align(1,5,5)

tabview = lv.tabview(scrn)
tabview.set_tab_bar_size(30)

tab1 = tabview.add_tab("Tab 1")
tab2 = tabview.add_tab("Tab 2")
tab3 = tabview.add_tab("Tab 3")

# Add content to the tabs
label1 = lv.label(tab1)
label1.set_text("This is the content of Tab 1")

#label2 = lv.label(tab2)
#label2.set_text("This is the content of Tab 2")

label3 = lv.label(tab3)
label3.set_text("This is the content of Tab 3")




btn = lv.button(tab1)
btn.center()
btn.set_size(100,50)
btn.set_style_bg_color(lv.color_make(255, 0, 0), 0)  # RGB: Red=255, Green=0, Blue=0
lbl = lv.label(btn)
lbl.set_text('Start')
lbl.center()

# Add second button
btn2 = lv.button(tab1)
btn2.set_size(100,50)
btn2.align(lv.ALIGN.CENTER, 0, 60)  # Position below the first button
lbl2 = lv.label(btn2)
lbl2.set_text('Stop')
lbl2.center()

tab2.set_flex_flow(lv.FLEX_FLOW.COLUMN)
lab21 = lv.label(tab2)
lab21.set_text('Group 1')
chk21 = lv.checkbox(tab2)
chk21.set_text('Option 1')
chk22 = lv.checkbox(tab2)
chk22.set_text('Option 2')
chk23 = lv.checkbox(tab2)
chk23.set_text('Option 3')
chk24 = lv.checkbox(tab2)
chk24.set_text('Option 4')
lab22 = lv.label(tab2)
lab22.set_text('Group 2')
chk25 = lv.checkbox(tab2)
chk25.set_text('Option 5')
chk26 = lv.checkbox(tab2)
chk26.set_text('Option 6')
chk27 = lv.checkbox(tab2)
chk27.set_text('Option 7')
chk28 = lv.checkbox(tab2)
chk28.set_text('Option 8')

o = 1
def btnm_event_handler(e,ta):
    global o
    obj = e.get_target()
    o=obj
    print("Toggled")

Example: Clock

import lcd_bus
from micropython import const
import machine
import time  # Add time module for clock functionality

# Try to import ntptime for NTP sync
try:
    import ntptime
    NTP_AVAILABLE = True
except ImportError:
    NTP_AVAILABLE = False
    print("ntptime not available")

# Try to import network for WiFi
try:
    import network
    NETWORK_AVAILABLE = True
except ImportError:
    NETWORK_AVAILABLE = False
    print("network not available")

# WiFi Configuration - CHANGE THESE TO YOUR WIFI CREDENTIALS
WIFI_SSID = "Quantr 2.4G"        # Replace with your WiFi name
WIFI_PASSWORD = "quantrwi"  # Replace with your WiFi password

# Timezone Configuration
TIMEZONE_OFFSET = 8  # Hong Kong is UTC+8 hours
TIMEZONE_NAME = "Hong Kong"
 
 
# display settings
_WIDTH = const(240)
_HEIGHT = const(320)
_BL = const(21)
_RST = const(17)
_DC = const(2)
 
_MOSI = const(13)
#_MISO = const(12)
_SCK = const(14)
_HOST = const(1)  # SPI2
 
_LCD_CS = const(15)
_LCD_FREQ = const(40000000)
 
#_TOUCH_CS = const(9)
#_TOUCH_FREQ = const(1000000)
 
spi_bus = machine.SPI.Bus(
    host=_HOST,
    mosi=_MOSI,
    #miso=_MISO,
    sck=_SCK
)
 
display_bus = lcd_bus.SPIBus(
    spi_bus=spi_bus,
    freq=_LCD_FREQ,
    dc=_DC,
    cs=_LCD_CS,
)
 
 
import ili9341  # NOQA
import lvgl as lv  # NOQA
 
 
display = ili9341.ILI9341(
    data_bus=display_bus,
    display_width=_WIDTH,
    display_height=_HEIGHT,
     reset_pin=_RST,
    reset_state=ili9341.STATE_LOW,
    backlight_pin=_BL,
    backlight_on_state=ili9341.STATE_HIGH,
    color_space=lv.COLOR_FORMAT.RGB565,
    color_byte_order=ili9341.BYTE_ORDER_BGR,
    rgb565_byte_swap=True,
)
 
import task_handler  # NOQA
import xpt2046  # NOQA
 
display.set_power(True)
display.init(1)
# display.set_color_inversion(True)
display.set_rotation(lv.DISPLAY_ROTATION._90)
display.set_backlight(100)
 
#touch_dev = machine.SPI.Device(
#    spi_bus=spi_bus,
#    freq=_TOUCH_FREQ,
#    cs=_TOUCH_CS
#)
 
#indev = xpt2046.XPT2046(touch_dev,debug=False,startup_rotation=lv.DISPLAY_ROTATION._0)
 
#indev.calibrate()
 
th = task_handler.TaskHandler()

# WiFi connection functions
def connect_wifi():
    """Connect to WiFi network"""
    if not NETWORK_AVAILABLE:
        print("Network module not available")
        return False
    
    try:
        wlan = network.WLAN(network.STA_IF)
        wlan.active(True)
        
        if wlan.isconnected():
            print("WiFi already connected")
            print(f"IP address: {wlan.ifconfig()[0]}")
            return True
        
        print(f"Connecting to WiFi: {WIFI_SSID}")
        wlan.connect(WIFI_SSID, WIFI_PASSWORD)
        
        # Wait for connection with timeout
        timeout = 10  # 10 seconds timeout
        while not wlan.isconnected() and timeout > 0:
            time.sleep(1)
            timeout -= 1
            print(".", end="")
        
        if wlan.isconnected():
            print(f"\nWiFi connected successfully!")
            print(f"IP address: {wlan.ifconfig()[0]}")
            return True
        else:
            print(f"\nWiFi connection failed!")
            return False
            
    except Exception as e:
        print(f"WiFi connection error: {e}")
        return False

def disconnect_wifi():
    """Disconnect from WiFi"""
    if not NETWORK_AVAILABLE:
        return
    
    try:
        wlan = network.WLAN(network.STA_IF)
        wlan.disconnect()
        wlan.active(False)
        print("WiFi disconnected")
    except Exception as e:
        print(f"WiFi disconnect error: {e}")

# Time setting functions
def get_local_time():
    """Get time adjusted for Hong Kong timezone (UTC+8)"""
    # Get UTC time
    utc_time = time.time()
    # Add timezone offset (8 hours = 8 * 3600 seconds)
    local_timestamp = utc_time + (TIMEZONE_OFFSET * 3600)
    # Convert to local time structure
    return time.localtime(local_timestamp)

def sync_time_ntp():
    """Try to sync time with NTP server (requires WiFi)"""
    if not NTP_AVAILABLE:
        print("NTP not available")
        return False
    
    if not NETWORK_AVAILABLE:
        print("Network not available")
        return False
    
    try:
        # Check if WiFi is connected
        wlan = network.WLAN(network.STA_IF)
        if wlan.isconnected():
            print("Syncing time with NTP server...")
            ntptime.settime()
            print(f"Time synced successfully! (UTC time will be converted to {TIMEZONE_NAME})")
            return True
        else:
            print("WiFi not connected, cannot sync NTP time")
            return False
    except Exception as e:
        print(f"NTP sync failed: {e}")
        return False

def set_manual_time():
    """Set time manually - modify the values as needed"""
    # Format: (year, month, day, weekday, hour, minute, second, microsecond)
    rtc = machine.RTC()
    # Set to July 4, 2025, 14:30:00 Hong Kong time
    # Note: This sets the RTC to UTC time, but we'll display Hong Kong time
    # So if we want to display 14:30 HK time, we set RTC to 06:30 UTC
    utc_hour = 14 - TIMEZONE_OFFSET  # Convert HK time to UTC
    if utc_hour < 0:
        utc_hour += 24
    rtc.datetime((2025, 7, 4, 5, utc_hour, 30, 0, 0))
    print(f"Manual time set to: 2025-07-04 14:30:00 {TIMEZONE_NAME} time")

def setup_time():
    """Setup the system time"""
    print("Setting up time...")
    
    # First try to connect to WiFi
    if connect_wifi():
        # If WiFi connected, try NTP sync
        if sync_time_ntp():
            print(f"Time setup complete via NTP (displaying {TIMEZONE_NAME} time)")
            return
    
    # If WiFi or NTP failed, use manual time
    print("Using manual time setting")
    set_manual_time()
    
    # Print current time to verify (Hong Kong time)
    current = get_local_time()
    print(f"Current {TIMEZONE_NAME} time: {current[0]}-{current[1]:02d}-{current[2]:02d} {current[3]:02d}:{current[4]:02d}:{current[5]:02d}")

# Initialize time
setup_time()
 
scrn = lv.screen_active()
scrn.set_style_bg_color(lv.color_hex(0xFFFFFF), 0)  # White background

#btnm = lv.buttonmatrix(scrn)
#btnm.add_event_cb(lambda e: btnm_event_handler(e,scrn),lv.EVENT.VALUE_CHANGED, None)
#btnm.set_size(230,120)
#btnm.align(1,5,5)

# SemiBlock label at the top
semiblock_label = lv.label(scrn)
semiblock_label.set_text("SemiBlock")
semiblock_label.align(lv.ALIGN.TOP_MID, -45, 20)
semiblock_label.set_style_text_color(lv.color_hex(0xFF80C0), 0)  # Pinkly blue color
semiblock_label.set_style_transform_scale(600, 0)  # Scale text to 200% (2x bigger)

# Digital clock display - large font
clock_label = lv.label(scrn)
clock_label.set_text("00:00:00")
clock_label.align(lv.ALIGN.LEFT_MID, 50, -15)
clock_label.set_style_transform_scale(500, 0)  # Scale text to 300% (3x bigger)
clock_label.set_style_text_color(lv.color_hex(0x000000), 0)  # Black color

# Date display
date_label = lv.label(scrn)
date_label.set_text("2025-01-01")
date_label.align(lv.ALIGN.LEFT_MID, 50, 30)
date_label.set_style_transform_scale(500, 0)
date_label.set_style_text_color(lv.color_hex(0x0000FF), 0)  # Blue color

# Day of week display
day_label = lv.label(scrn)
day_label.set_text("Monday")
day_label.align(lv.ALIGN.LEFT_MID, 50, 75)
day_label.set_style_transform_scale(500, 0)
day_label.set_style_text_color(lv.color_hex(0x0000FF), 0)  # Blue color

# Clock frame/border
clock_frame = lv.obj(scrn)
clock_frame.set_size(280, 140)
clock_frame.align(lv.ALIGN.CENTER, 0, 40)
clock_frame.set_style_border_width(2, 0)
clock_frame.set_style_border_color(lv.color_hex(0xFF80C0), 0)  # Gray border
clock_frame.set_style_bg_opa(lv.OPA.TRANSP, 0)  # Transparent background
clock_frame.set_style_radius(10, 0)  # Rounded corners

# Don't move labels to frame - keep them on main screen for proper updates
# Don't move labels to frame - keep them on main screen for proper updates

def update_clock():
    """Update the clock display with current time in Hong Kong timezone"""
    # Use Hong Kong local time instead of system time
    current_time = get_local_time()
    
    # Format time as HH:MM:SS
    time_str = "{:02d}:{:02d}:{:02d}".format(
        current_time[3],  # hour
        current_time[4],  # minute
        current_time[5]   # second
    )
    
    # Format date as YYYY-MM-DD
    date_str = "{:04d}-{:02d}-{:02d}".format(
        current_time[0],  # year
        current_time[1],  # month
        current_time[2]   # day
    )
    
    # Get day of week
    days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
    day_str = days[current_time[6]]
    
    clock_label.set_text(time_str)
    date_label.set_text(date_str)
    day_label.set_text(day_str)
    
    # Debug print to verify time is being read correctly
    print(f"{TIMEZONE_NAME} Time: {time_str}, Date: {date_str}, Day: {day_str}")

# Create a timer to update the clock every second
clock_timer = lv.timer_create(lambda timer: update_clock(), 1000, None)

# Initial clock update
update_clock()

Cheap Yellow Display Pins

Connector types

ConnectorTypeNote
P14P 1.25mm JSTSerial
P34P 1.25mm JSTGPIO
P42P 1.25mm JSTSpeaker
CN14P 1.25mm JSTGPIO (I2C)

What pins are available on the CYD?

There are 3 easily accessible GPIO pins

PinLocationNote
IO35P3 JST connectorInput only pin, no internal pull-ups available
IO22P3 and CN1 JST connector
IO27CN1 JST connector

If you need more than that, you need to start taking them from something else. An SD Card sniffer like mentioned in the Add-ons is probably the next easiest.

After that you're probably de-soldering something!

Broken Out Pins

There are three 4P 1.25mm JST connectors on the board

P3

PinUseNote
GND
IO35Input only pin, no internal pull-ups available
IO22Also on the CN1 connector
IO21Used for the TFT Backlight, so not really usable

CN1

This is a great candidate for I2C devices

PinUseNote
GND
IO22Also on P3 connector
IO27
3.3V

P1

PinUseNote
VIN
IO1(?)TXMaybe possible to use as a GPIO?
IO3(?)RXMaybe possible to use as a GPIO?
GND

Buttons

The CYD has two buttons, reset and boot.

PinUseNote
IO0BOOTCan be used as an input in sketches

Speaker

The speaker connector is a 2P 1.25mm JST connector that is connected to the amplifier, so not usable as GPIO at the speaker connector

PinUseNote
IO26Connected to ampi2s_set_dac_mode(I2S_DAC_CHANNEL_LEFT_EN);

RGB LED

If your project requires additional pins to what is available elsewhere, this might be a good candidate to sacrifice.

Note: LEDs are "active low", meaning HIGH == off, LOW == on

PinUseNote
IO4Red LED
IO16Green LED
IO17Blue LED

SD Card

Uses the VSPI Pin names are predefined in SPI.h

PinUseNote
IO5SS
IO18SCK
IO19MISO
IO23MOSI

Touch Screen

PinUseNote
IO25XPT2046_CLK
IO32XPT2046_MOSI
IO33XPT2046_CS
IO36XPT2046_IRQ
IO39XPT2046_MISO

LDR (Light Sensor)

PinUseNote
IO34

Display

Uses the HSPI

PinUseNote
IO2TFT_RSAKA: TFT_DC
IO12TFT_SDOAKA: TFT_MISO
IO13TFT_SDIAKA: TFT_MOSI
IO14TFT_SCK
IO15TFT_CS
IO21TFT_BLAlso on P3 connector, for some reason

Test points

PadUseNote
S1GNDnear USB-SERIAL
S23.3vfor ESP32
S35vnear USB-SERIAL
S4GNDfor ESP32
S53.3vfor TFT
JP0 (pad nearest USB socket)5vTFT LDO
JP03.3vTFT LDO
JP3 (pad nearest USB socket)5vESP32 LDO
JP33.3vESP32 LDO

More examples on https://github.com/quantrpeter/cheap-yellow-display-micropython-lvgl-example