Overview
The ESP32 LVGL 2.8" Smart Display — better known in the maker community as the Sunton ESP32-2432S028R or simply the "Cheap Yellow Display" (CYD) — is one of the best value all-in-one ESP32 dev boards you can buy. You get an ESP-WROOM-32 module, a 2.8" 240x320 ILI9341 TFT, a resistive touch panel driven by an XPT2046, a microSD slot, a small speaker output, and a USB programming port, all on a single board for about the price of a bare ESP32 plus a discrete display.
What makes it especially attractive is software support. The board is fully compatible with the LVGL graphics library, TFT_eSPI, and LovyanGFX on the Arduino IDE side, plus various MicroPython display drivers. If you want a Wi-Fi connected smart-home control panel, a portable instrument display, or a small dashboard for sensor data, this board cuts your bring-up time dramatically.
The trade-off is that you have to use the specific GPIOs the board manufacturer chose — the display, touch, and SD share the same SPI bus but have separate chip-select lines, and the backlight is on GPIO21. Once you have the right pin map plugged into TFT_eSPI's User_Setup.h (or your LVGL config), everything works flawlessly.
At a Glance
Specifications
| Parameter | Value |
| MCU | ESP32-WROOM-32, 240 MHz dual-core Xtensa |
| Flash | 4 MB |
| SRAM | 520 KB |
| Display Size | 2.8 inch diagonal |
| Resolution | 240 x 320 pixels (portrait) |
| Display Driver | ILI9341 over SPI |
| Touch Controller | XPT2046 resistive, SPI |
| Connectivity | Wi-Fi 802.11 b/g/n, Bluetooth 4.2 + BLE |
| USB / Power | Micro-USB (CH340 USB-UART) and 4P 1.25mm JST 5V input |
| Audio | Mono speaker output via onboard amp |
| Expansion | Extended IO header + 4-pin I2C temperature/humidity port |
| Backlight Control | GPIO21 (active HIGH) |
Pinout Diagram
Wiring Guide
Display Driver (ILI9341, hardwired on the board):
- CS → GPIO15
- DC / RS → GPIO2
- RST → GPIO12
- SCK → GPIO14
- MOSI → GPIO13
- MISO → GPIO12 (shared, rarely used for display)
- Backlight → GPIO21 (set HIGH or PWM for brightness)
The display SPI bus (HSPI) is shared with the touch controller and the SD slot. Each peripheral has its own chip-select, so as long as you only assert one CS at a time, they coexist happily.
Heads up: if your screen stays dark even though your sketch is running, you almost certainly forgot to set GPIO21 HIGH for the backlight. This is the #1 gotcha on this board.
Touch Controller (XPT2046 resistive, hardwired):
- T_CS → GPIO33
- T_IRQ → GPIO36 (input only, optional)
- T_CLK → GPIO25
- T_DIN (MOSI) → GPIO32
- T_DO (MISO) → GPIO39 (input only)
Touch uses a separate set of SPI pins from the display on most CYD variants — do not assume they share the bus until you cross-reference the silkscreen. The XPT2046 is slow (typical < 2 MHz SPI), so keep that bus speed low or you will get noisy readings.
Resistive touch always needs calibration. The first time you flash an LVGL example, you tap three corners and the offsets are stored to flash. Plan for that.
microSD Slot (SPI mode, shares display bus):
- SD_CS → GPIO5
- SD_SCK → GPIO18
- SD_MOSI → GPIO23
- SD_MISO → GPIO19
The SD slot uses VSPI by default while the display uses HSPI, so they are physically separate buses on this board. Format your card as FAT32 with a 32KB allocation unit and the standard Arduino SD.h library works out of the box. Cards larger than 32GB usually still work if formatted as FAT32 manually (Windows won't do it for you above 32GB — use guiformat or mkfs.fat).
Programming & Power options:
- Micro-USB — CH340 USB-to-UART, used for flashing and serial monitor. Provides 5V power.
- 4P 1.25mm JST connector (power base) — 5V external supply. Useful for enclosure builds where USB isn't exposed.
- RESET button — pulls EN low to reboot the ESP32.
- BOOT button — hold while pressing RESET to force download mode. Most upload tools handle this automatically via DTR/RTS, but it's there as a backup.
- Extended IO header — breaks out spare GPIOs (typically GPIO22, GPIO27, GPIO35) and 3.3V/GND for adding sensors or buttons.
- 4-pin I2C temperature/humidity port — 3.3V, GND, SDA, SCL on a 4-pin connector for plugging in something like an SHT3x or AHT10 without soldering.
Driver tip: on Windows install the CH340 driver from WCH; on macOS recent versions install it automatically; on Linux it shows up as /dev/ttyUSB0 with no extra setup.
Code Examples
Arduino IDE — TFT_eSPI setup for the CYD (User_Setup.h snippet):
In User_Setup.h inside the TFT_eSPI library folder, comment out the default setup and use this:
#define ILI9341_DRIVER
#define TFT_WIDTH 240
#define TFT_HEIGHT 320
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST -1 // tied to ESP32 reset
#define TFT_BL 21
#define TFT_BACKLIGHT_ON HIGH
#define TOUCH_CS 33
#define SPI_FREQUENCY 55000000
#define SPI_READ_FREQUENCY 20000000
#define SPI_TOUCH_FREQUENCY 2500000
#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_GFXFF
#define SMOOTH_FONT
Then a minimal sketch to verify the panel is alive:
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
void setup() {
pinMode(21, OUTPUT);
digitalWrite(21, HIGH); // backlight ON
tft.init();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setTextSize(2);
tft.setCursor(20, 100);
tft.println("CYD is alive!");
}
void loop() {}
ESP32 + LVGL — Hello World with a button:
#include <lvgl.h>
#include <TFT_eSPI.h>
static const uint16_t SCREEN_W = 320;
static const uint16_t SCREEN_H = 240;
TFT_eSPI tft = TFT_eSPI();
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[SCREEN_W * 10];
void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) {
uint32_t w = area->x2 - area->x1 + 1;
uint32_t h = area->y2 - area->y1 + 1;
tft.startWrite();
tft.setAddrWindow(area->x1, area->y1, w, h);
tft.pushColors((uint16_t *)&color_p->full, w * h, true);
tft.endWrite();
lv_disp_flush_ready(disp);
}
void setup() {
pinMode(21, OUTPUT); digitalWrite(21, HIGH);
tft.begin();
tft.setRotation(1);
lv_init();
lv_disp_draw_buf_init(&draw_buf, buf, NULL, SCREEN_W * 10);
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = SCREEN_W;
disp_drv.ver_res = SCREEN_H;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
lv_disp_drv_register(&disp_drv);
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_center(btn);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "Hello CYD");
}
void loop() {
lv_timer_handler();
delay(5);
}
MicroPython — quick check using ili9341 driver:
from machine import Pin, SPI
import ili9341
# Backlight ON
Pin(21, Pin.OUT).value(1)
spi = SPI(1, baudrate=40000000, sck=Pin(14), mosi=Pin(13), miso=Pin(12))
display = ili9341.Display(spi, dc=Pin(2), cs=Pin(15), rst=Pin(12))
display.clear(ili9341.color565(0, 0, 0))
display.draw_text8x8(60, 100, "Hello CYD", ili9341.color565(255, 220, 0))
Frequently Asked Questions
pinMode(21, OUTPUT); digitalWrite(21, HIGH); at the top of setup() and you should see the display light up immediately.SD.begin(5) for the card and TFT_eSPI's defaults for the display — and they will coexist without conflict.tft.setRotation(1) or tft.setRotation(3) for landscape, and in LVGL set disp_drv.hor_res = 320; disp_drv.ver_res = 240; to match. Remember to also rotate touch coordinates accordingly — the helper functions in most touch drivers take a rotation argument.