
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
Connector | Type | Note |
---|---|---|
P1 | 4P 1.25mm JST | Serial |
P3 | 4P 1.25mm JST | GPIO |
P4 | 2P 1.25mm JST | Speaker |
CN1 | 4P 1.25mm JST | GPIO (I2C) |
What pins are available on the CYD?
There are 3 easily accessible GPIO pins
Pin | Location | Note |
---|---|---|
IO35 | P3 JST connector | Input only pin, no internal pull-ups available |
IO22 | P3 and CN1 JST connector | |
IO27 | CN1 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
Pin | Use | Note |
---|---|---|
GND | ||
IO35 | Input only pin, no internal pull-ups available | |
IO22 | Also on the CN1 connector | |
IO21 | Used for the TFT Backlight, so not really usable |
CN1
This is a great candidate for I2C devices
Pin | Use | Note |
---|---|---|
GND | ||
IO22 | Also on P3 connector | |
IO27 | ||
3.3V |
P1
Pin | Use | Note |
---|---|---|
VIN | ||
IO1(?) | TX | Maybe possible to use as a GPIO? |
IO3(?) | RX | Maybe possible to use as a GPIO? |
GND |
Buttons
The CYD has two buttons, reset and boot.
Pin | Use | Note |
---|---|---|
IO0 | BOOT | Can 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
Pin | Use | Note |
---|---|---|
IO26 | Connected to amp | i2s_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
Pin | Use | Note |
---|---|---|
IO4 | Red LED | |
IO16 | Green LED | |
IO17 | Blue LED |
SD Card
Uses the VSPI Pin names are predefined in SPI.h
Pin | Use | Note |
---|---|---|
IO5 | SS | |
IO18 | SCK | |
IO19 | MISO | |
IO23 | MOSI |
Touch Screen
Pin | Use | Note |
---|---|---|
IO25 | XPT2046_CLK | |
IO32 | XPT2046_MOSI | |
IO33 | XPT2046_CS | |
IO36 | XPT2046_IRQ | |
IO39 | XPT2046_MISO |
LDR (Light Sensor)
Pin | Use | Note |
---|---|---|
IO34 |
Display
Uses the HSPI
Pin | Use | Note |
---|---|---|
IO2 | TFT_RS | AKA: TFT_DC |
IO12 | TFT_SDO | AKA: TFT_MISO |
IO13 | TFT_SDI | AKA: TFT_MOSI |
IO14 | TFT_SCK | |
IO15 | TFT_CS | |
IO21 | TFT_BL | Also on P3 connector, for some reason |
Test points
Pad | Use | Note |
---|---|---|
S1 | GND | near USB-SERIAL |
S2 | 3.3v | for ESP32 |
S3 | 5v | near USB-SERIAL |
S4 | GND | for ESP32 |
S5 | 3.3v | for TFT |
JP0 (pad nearest USB socket) | 5v | TFT LDO |
JP0 | 3.3v | TFT LDO |
JP3 (pad nearest USB socket) | 5v | ESP32 LDO |
JP3 | 3.3v | ESP32 LDO |
More examples on https://github.com/quantrpeter/cheap-yellow-display-micropython-lvgl-example