In this tutorial, we’ll show you how to program the ESP32 CYD (Cheap Yellow Display) using VS Code with the PlatformIO extension. We’ll show you how to load and configure the TFT_eSPI library, the XPT2046_Touchscreen library, and set up the LVGL library on VS Code with PlatformIO.
New to the ESP32 Cheap Yellow Display? Get started here: Getting Started with ESP32 Cheap Yellow Display Board – CYD (ESP32-2432S028R)
This tutorial also applies if you use an ESP32 with a separate 2.8 inch ILI9341 240×320 TFT LCD touchscreen.
Prerequisites
Before proceeding with this tutorial:
- You need VS Code and the PlatformIO extension installed on your computer.
- We assume you are familiar with programming the ESP32 using VS Code and the platformIO extension.
- You should be familiar with the ESP32 Cheap Yellow Display Board—start here.
- We recommend that you have programmed the ESP32 CYD board using Arduino IDE before and you are familiar with the TFT_eSPI, XPT2046_Touchscreen library, and LVGL libraries installation procedure on Arduino IDE.
Installing the XPT2046_Touchscreen library on VS Code
To install the XPT2046_Touchscreen library on VS Code, you must add the following line to the platformio.ini file of your current project.
lib_deps = https://github.com/PaulStoffregen/XPT2046_Touchscreen.git
There’s currently an issue in VS Code when loading this library from the Libraries interface. So, you must load it like this using the library Gihub link, so that it uses the latest version.
Most of your projects will use a 115200 baud rate, so also add the following line to the platformio.ini file.
monitor_speed = 115200
Installing the TFT_eSPI Library on VS Code
To install the TFT_eSPI library, you can click on the Home icon, select the Libraries tab at the left, and then search for TFT_eSPI.
Select the TFT_eSPI by Bodmer and then click Add to Project. Select the project you’re currently working on.
After that, the library will be automatically added to your platformio.ini file. The lib_deps directive will look as follows:
lib_deps =
https://github.com/PaulStoffregen/XPT2046_Touchscreen.git
bodmer/TFT_eSPI@^2.5.43
Configuring the TFT_eSPI Library on VS Code – the User_Setup.h config file
To properly use the TFT_eSPI library, you need a configuration file called User_Setup.h with the right definitions for the display model you’re using.
We’ve already prepared that file so that you don’t have any configuration issues following our examples. Follow the next instructions to learn how to do it.
Note: we tested this on the ESP32-2432S028R and the 2.8 inch ILI9341 240×320 TFT LCD touchscreen. If you have different models with different sizes, you’ll probably need to adjust the User_Setup.h file.
1) On the Explorer tab at the left sidebar, under the project folder, navigate to .pio > libdeps\[you_board_model] > TFT_eSPI > User_Setup.h.
2) Open the User_Setup.h file.
3) Replace the code in the User_Setup.h file with the code provided in our User_Setup.h file. You can get our User_Setup.h file below.
- Click here to download .zip folder with the User_Setup.h config file (view raw file)
4) Save the User_Setup.h file.
Testing the Installation
To test if you’ve installed and configured the libraries properly, you can run the following code on your board. It displays text and tests the touchscreen.
/* Rui Santos & Sara Santos - Random Nerd Tutorials
THIS EXAMPLE WAS TESTED WITH THE FOLLOWING HARDWARE:
1) ESP32-2432S028R 2.8 inch 240×320 also known as the Cheap Yellow Display (CYD): https://makeradvisor.com/tools/cyd-cheap-yellow-display-esp32-2432s028r/
SET UP INSTRUCTIONS: https://RandomNerdTutorials.com/cyd/
2) REGULAR ESP32 Dev Board + 2.8 inch 240x320 TFT Display: https://makeradvisor.com/tools/2-8-inch-ili9341-tft-240x320/ and https://makeradvisor.com/tools/esp32-dev-board-wi-fi-bluetooth/
SET UP INSTRUCTIONS: https://RandomNerdTutorials.com/esp32-tft/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
#include <SPI.h>
/* Install the "TFT_eSPI" library by Bodmer to interface with the TFT Display - https://github.com/Bodmer/TFT_eSPI
*** IMPORTANT: User_Setup.h available on the internet will probably NOT work with the examples available at Random Nerd Tutorials ***
*** YOU MUST USE THE User_Setup.h FILE PROVIDED IN THE LINK BELOW IN ORDER TO USE THE EXAMPLES FROM RANDOM NERD TUTORIALS ***
FULL INSTRUCTIONS AVAILABLE ON HOW CONFIGURE THE LIBRARY: https://RandomNerdTutorials.com/cyd/ or https://RandomNerdTutorials.com/esp32-tft/ */
#include <TFT_eSPI.h>
// Install the "XPT2046_Touchscreen" library by Paul Stoffregen to use the Touchscreen - https://github.com/PaulStoffregen/XPT2046_Touchscreen
// Note: this library doesn't require further configuration
#include <XPT2046_Touchscreen.h>
TFT_eSPI tft = TFT_eSPI();
// Touchscreen pins
#define XPT2046_IRQ 36 // T_IRQ
#define XPT2046_MOSI 32 // T_DIN
#define XPT2046_MISO 39 // T_OUT
#define XPT2046_CLK 25 // T_CLK
#define XPT2046_CS 33 // T_CS
SPIClass touchscreenSPI = SPIClass(VSPI);
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ);
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 240
#define FONT_SIZE 2
// Touchscreen coordinates: (x, y) and pressure (z)
int x, y, z;
// Print Touchscreen info about X, Y and Pressure (Z) on the Serial Monitor
void printTouchToSerial(int touchX, int touchY, int touchZ) {
Serial.print("X = ");
Serial.print(touchX);
Serial.print(" | Y = ");
Serial.print(touchY);
Serial.print(" | Pressure = ");
Serial.print(touchZ);
Serial.println();
}
// Print Touchscreen info about X, Y and Pressure (Z) on the TFT Display
void printTouchToDisplay(int touchX, int touchY, int touchZ) {
// Clear TFT screen
tft.fillScreen(TFT_WHITE);
tft.setTextColor(TFT_BLACK, TFT_WHITE);
int centerX = SCREEN_WIDTH / 2;
int textY = 80;
String tempText = "X = " + String(touchX);
tft.drawCentreString(tempText, centerX, textY, FONT_SIZE);
textY += 20;
tempText = "Y = " + String(touchY);
tft.drawCentreString(tempText, centerX, textY, FONT_SIZE);
textY += 20;
tempText = "Pressure = " + String(touchZ);
tft.drawCentreString(tempText, centerX, textY, FONT_SIZE);
}
void setup() {
Serial.begin(115200);
// Start the SPI for the touchscreen and init the touchscreen
touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
touchscreen.begin(touchscreenSPI);
// Set the Touchscreen rotation in landscape mode
// Note: in some displays, the touchscreen might be upside down, so you might need to set the rotation to 3: touchscreen.setRotation(3);
touchscreen.setRotation(1);
// Start the tft display
tft.init();
// Set the TFT display rotation in landscape mode
tft.setRotation(1);
// Clear the screen before writing to it
tft.fillScreen(TFT_WHITE);
tft.setTextColor(TFT_BLACK, TFT_WHITE);
// Set X and Y coordinates for center of display
int centerX = SCREEN_WIDTH / 2;
int centerY = SCREEN_HEIGHT / 2;
tft.drawCentreString("Hello, world!", centerX, 30, FONT_SIZE);
tft.drawCentreString("Touch screen to test", centerX, centerY, FONT_SIZE);
}
void loop() {
// Checks if Touchscreen was touched, and prints X, Y and Pressure (Z) info on the TFT display and Serial Monitor
if (touchscreen.tirqTouched() && touchscreen.touched()) {
// Get Touchscreen points
TS_Point p = touchscreen.getPoint();
// Calibrate Touchscreen points with map function to the correct width and height
x = map(p.x, 200, 3700, 1, SCREEN_WIDTH);
y = map(p.y, 240, 3800, 1, SCREEN_HEIGHT);
z = p.z;
printTouchToSerial(x, y, z);
printTouchToDisplay(x, y, z);
delay(100);
}
}
Demonstration
Upload the code to your board.
After uploading the code to your board, it should display the sample “Hello, world!” text centered at the top. Press the touchscreen with your finger to test it. It should print the coordinates: (x, y) and pressure (z) in the TFT display.
Important note: you need to do all the installation procedure and setting up the configuration file for each new project in VS Code. You’ll also need to repeat this if you update the libraries in your project.
Installing and Configuring the LVGL Library in VS Code
1) After creating or opening a project in VS Code, click on the Home icon and select the Libraries tab. Search for LVGL.
2) Add LVGL Version 9 to your project.
3) At the left sidebar, open the Explorer tab. Go the the following path .pio > libdeps.
4) Create a new file under the libdeps folder called lv_conf.h.
5) Add the following code to the lv_conf.h file.
- Click here to download .zip folder with the lv_conf.h config file (view raw file)
6) Then, open the lvgl folder. Move the demos and examples folders to the src folder.
Now, you have everything set up to use the LVGL library in VS Code.
To use the LVGL library you also need to install the TFT_eSPI and XPT2046_Touchscreen libraries as mentioned earlier.
Important note: you need to do all the installation procedure and setting up the configuration files for each new project in VS Code.
To test if all the libraries were properly set up, you can test the following code.
/* Rui Santos & Sara Santos - Random Nerd Tutorials
THIS EXAMPLE WAS TESTED WITH THE FOLLOWING HARDWARE:
1) ESP32-2432S028R 2.8 inch 240×320 also known as the Cheap Yellow Display (CYD): https://makeradvisor.com/tools/cyd-cheap-yellow-display-esp32-2432s028r/
SET UP INSTRUCTIONS: https://RandomNerdTutorials.com/cyd-lvgl/
2) REGULAR ESP32 Dev Board + 2.8 inch 240x320 TFT Display: https://makeradvisor.com/tools/2-8-inch-ili9341-tft-240x320/ and https://makeradvisor.com/tools/esp32-dev-board-wi-fi-bluetooth/
SET UP INSTRUCTIONS: https://RandomNerdTutorials.com/esp32-tft-lvgl/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*/
/* Install the "lvgl" library version 9.2 by kisvegabor to interface with the TFT Display - https://lvgl.io/
*** IMPORTANT: lv_conf.h available on the internet will probably NOT work with the examples available at Random Nerd Tutorials ***
*** YOU MUST USE THE lv_conf.h FILE PROVIDED IN THE LINK BELOW IN ORDER TO USE THE EXAMPLES FROM RANDOM NERD TUTORIALS ***
FULL INSTRUCTIONS AVAILABLE ON HOW CONFIGURE THE LIBRARY: https://RandomNerdTutorials.com/cyd-lvgl/ or https://RandomNerdTutorials.com/esp32-tft-lvgl/ */
#include <lvgl.h>
/* Install the "TFT_eSPI" library by Bodmer to interface with the TFT Display - https://github.com/Bodmer/TFT_eSPI
*** IMPORTANT: User_Setup.h available on the internet will probably NOT work with the examples available at Random Nerd Tutorials ***
*** YOU MUST USE THE User_Setup.h FILE PROVIDED IN THE LINK BELOW IN ORDER TO USE THE EXAMPLES FROM RANDOM NERD TUTORIALS ***
FULL INSTRUCTIONS AVAILABLE ON HOW CONFIGURE THE LIBRARY: https://RandomNerdTutorials.com/cyd-lvgl/ or https://RandomNerdTutorials.com/esp32-tft-lvgl/ */
#include <TFT_eSPI.h>
// Install the "XPT2046_Touchscreen" library by Paul Stoffregen to use the Touchscreen - https://github.com/PaulStoffregen/XPT2046_Touchscreen - Note: this library doesn't require further configuration
#include <XPT2046_Touchscreen.h>
// Touchscreen pins
#define XPT2046_IRQ 36 // T_IRQ
#define XPT2046_MOSI 32 // T_DIN
#define XPT2046_MISO 39 // T_OUT
#define XPT2046_CLK 25 // T_CLK
#define XPT2046_CS 33 // T_CS
SPIClass touchscreenSPI = SPIClass(VSPI);
XPT2046_Touchscreen touchscreen(XPT2046_CS, XPT2046_IRQ);
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 320
// Touchscreen coordinates: (x, y) and pressure (z)
int x, y, z;
#define DRAW_BUF_SIZE (SCREEN_WIDTH * SCREEN_HEIGHT / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];
// If logging is enabled, it will inform the user about what is happening in the library
void log_print(lv_log_level_t level, const char * buf) {
LV_UNUSED(level);
Serial.println(buf);
Serial.flush();
}
// Get the Touchscreen data
void touchscreen_read(lv_indev_t * indev, lv_indev_data_t * data) {
// Checks if Touchscreen was touched, and prints X, Y and Pressure (Z)
if(touchscreen.tirqTouched() && touchscreen.touched()) {
// Get Touchscreen points
TS_Point p = touchscreen.getPoint();
// Calibrate Touchscreen points with map function to the correct width and height
x = map(p.x, 200, 3700, 1, SCREEN_WIDTH);
y = map(p.y, 240, 3800, 1, SCREEN_HEIGHT);
z = p.z;
data->state = LV_INDEV_STATE_PRESSED;
// Set the coordinates
data->point.x = x;
data->point.y = y;
// Print Touchscreen info about X, Y and Pressure (Z) on the Serial Monitor
/* Serial.print("X = ");
Serial.print(x);
Serial.print(" | Y = ");
Serial.print(y);
Serial.print(" | Pressure = ");
Serial.print(z);
Serial.println();*/
}
else {
data->state = LV_INDEV_STATE_RELEASED;
}
}
int btn1_count = 0;
// Callback that is triggered when btn1 is clicked
static void event_handler_btn1(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
if(code == LV_EVENT_CLICKED) {
btn1_count++;
LV_LOG_USER("Button clicked %d", (int)btn1_count);
}
}
// Callback that is triggered when btn2 is clicked/toggled
static void event_handler_btn2(lv_event_t * e) {
lv_event_code_t code = lv_event_get_code(e);
lv_obj_t * obj = (lv_obj_t*) lv_event_get_target(e);
if(code == LV_EVENT_VALUE_CHANGED) {
LV_UNUSED(obj);
LV_LOG_USER("Toggled %s", lv_obj_has_state(obj, LV_STATE_CHECKED) ? "on" : "off");
}
}
static lv_obj_t * slider_label;
// Callback that prints the current slider value on the TFT display and Serial Monitor for debugging purposes
static void slider_event_callback(lv_event_t * e) {
lv_obj_t * slider = (lv_obj_t*) lv_event_get_target(e);
char buf[8];
lv_snprintf(buf, sizeof(buf), "%d%%", (int)lv_slider_get_value(slider));
lv_label_set_text(slider_label, buf);
lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
LV_LOG_USER("Slider changed to %d%%", (int)lv_slider_get_value(slider));
}
void lv_create_main_gui(void) {
// Create a text label aligned center on top ("Hello, world!")
lv_obj_t * text_label = lv_label_create(lv_screen_active());
lv_label_set_long_mode(text_label, LV_LABEL_LONG_WRAP); // Breaks the long lines
lv_label_set_text(text_label, "Hello, world!");
lv_obj_set_width(text_label, 150); // Set smaller width to make the lines wrap
lv_obj_set_style_text_align(text_label, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(text_label, LV_ALIGN_CENTER, 0, -90);
lv_obj_t * btn_label;
// Create a Button (btn1)
lv_obj_t * btn1 = lv_button_create(lv_screen_active());
lv_obj_add_event_cb(btn1, event_handler_btn1, LV_EVENT_ALL, NULL);
lv_obj_align(btn1, LV_ALIGN_CENTER, 0, -50);
lv_obj_remove_flag(btn1, LV_OBJ_FLAG_PRESS_LOCK);
btn_label = lv_label_create(btn1);
lv_label_set_text(btn_label, "Button");
lv_obj_center(btn_label);
// Create a Toggle button (btn2)
lv_obj_t * btn2 = lv_button_create(lv_screen_active());
lv_obj_add_event_cb(btn2, event_handler_btn2, LV_EVENT_ALL, NULL);
lv_obj_align(btn2, LV_ALIGN_CENTER, 0, 10);
lv_obj_add_flag(btn2, LV_OBJ_FLAG_CHECKABLE);
lv_obj_set_height(btn2, LV_SIZE_CONTENT);
btn_label = lv_label_create(btn2);
lv_label_set_text(btn_label, "Toggle");
lv_obj_center(btn_label);
// Create a slider aligned in the center bottom of the TFT display
lv_obj_t * slider = lv_slider_create(lv_screen_active());
lv_obj_align(slider, LV_ALIGN_CENTER, 0, 60);
lv_obj_add_event_cb(slider, slider_event_callback, LV_EVENT_VALUE_CHANGED, NULL);
lv_slider_set_range(slider, 0, 100);
lv_obj_set_style_anim_duration(slider, 2000, 0);
// Create a label below the slider to display the current slider value
slider_label = lv_label_create(lv_screen_active());
lv_label_set_text(slider_label, "0%");
lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
}
void setup() {
String LVGL_Arduino = String("LVGL Library Version: ") + lv_version_major() + "." + lv_version_minor() + "." + lv_version_patch();
Serial.begin(115200);
Serial.println(LVGL_Arduino);
// Start LVGL
lv_init();
// Register print function for debugging
lv_log_register_print_cb(log_print);
// Start the SPI for the touchscreen and init the touchscreen
touchscreenSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
touchscreen.begin(touchscreenSPI);
// Set the Touchscreen rotation in landscape mode
// Note: in some displays, the touchscreen might be upside down, so you might need to set the rotation to 0: touchscreen.setRotation(0);
touchscreen.setRotation(2);
// Create a display object
lv_display_t * disp;
// Initialize the TFT display using the TFT_eSPI library
disp = lv_tft_espi_create(SCREEN_WIDTH, SCREEN_HEIGHT, draw_buf, sizeof(draw_buf));
lv_display_set_rotation(disp, LV_DISPLAY_ROTATION_270);
// Initialize an LVGL input device object (Touchscreen)
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
// Set the callback function to read Touchscreen input
lv_indev_set_read_cb(indev, touchscreen_read);
// Function to draw the GUI (text, buttons and sliders)
lv_create_main_gui();
}
void loop() {
lv_task_handler(); // let the GUI do its work
lv_tick_inc(5); // tell LVGL how much time has passed
delay(5); // let this time pass
}
Testing the Installation
Upload the code to your board.
After uploading the code to your board, it should display the sample “Hello, world!” text centered at the top, two buttons, and one slider.
Interact with the widgets and see the results in the Serial Monitor. If the example is working as expected, all the libraries were successfully added and configured.
Wrapping Up
In this quick tutorial, we’ve shown you how to set up the TFT, touchscreen, and LVGL libraries on VS Code to program the ESP32 CYD Display.
We hope you’ve found this guide useful. If you have a better or different approach to set up the libraries, please share it below.
Other projects you may like:
- LVGL with ESP32 Cheap Yellow Display Board (ESP32-2432S028R) (Arduino IDE)
- ESP32 CYD with LVGL: Display Temperature with DS18B20 Sensor (Text and Arc)
- ESP32 Cheap Yellow Display (CYD) Pinout (ESP32-2432S028R)
- ESP32 Touchscreen On/Off Button – Cheap Yellow Display (ESP32-2432S028R)
Learn more about building GUIs using LVGL with our eBook:
But there are issues with PlatformIO and arduino-esp32 core versions 3 and greater. PIO has dropped support for those versions. There is a community project trying to get core 3+ implemented but it is not yet ready for prime time.
I know… Unfortunately I don’t really recommend this method to program the ESP32 CYD, but this was a very popular request so we wrote about it. It works, but it’s better to use the Arduino IDE for the time being.
It was a popular request because Platform IO is so fast in Compiling compared to Arduino, if you make small changes and want to test your Display output its very practical.
Arduino took around 8 mins and Platform IO around 1 min
Arduino uses 1 core whereas VS Code uses all cores while compiling
I also strongly prefer VSCode as a dev environment, both for the project build speed on W10 and the project/library code exploration tools such as Show Definition.
However, the combination of PlatformIO, VSCode and Arduino is proving to be fragile, particularly with the comparatively complex TFT/LVGL library setup. I just discovered that doing a PIO “Full Clean”, which I use to reduce file counts and temp storage prior to project backup, somehow reloads the TFT and LVGL libraries in such a way that undefined symbol warnings occur in the next build and the resulting execution on the ESP32 CYD gets hung up somewhere down in lv_indev.c.
And this is with simple/beginner code exercises so it’s likely to get worse when we get into the really hairy stuff.
Thanks for trying, RUI ….
Turns out the VSCode “Full Clean” reloads the lib_deps libraries from the network. In the case of TFT_eSPI, this replaces the RNT User_Setup.h with the default from the TFT_eSPI repository. Hence, the build error. Replacing User_Setup.h with the RNT issued copy after the Full Clean fixes the build problem.
Hi Rui & Sara
Just a line to say thanks for your website.
Very valuable resource.
I have bought one of your books, might buy more, Keep it up.
Cheers
Vincent
Hi.
That’s great.
Thank you so much for supporting our work.
Regards,
Sara
Rui & Sara,
I’ve been trying for some weeks to get up from ground zero on the CYD. I’ve found various already-built projects — some work and others fail in mystifying ways. While I sort of prefer PIO with Arduino framework, some of these offerings have had me building in the espidf framework. It has been something of an adventure and largely frustrating. For instance, a crash at lvgl_touch_init() tells me where it happened, but I’m not given much guidance on where to look for the underlying problem.
By carefully following your instructions in this tutorial, I got your demo code to work successfully. Thank you for being so careful in explaining how to go about it.
One suggestion I have is to change the tone of the warnings about using your exact User_Setup.h and lv_conf.h files. As one can see by comparing, the differences between the files amount to a rather few lines that set the values needed for the CYD amongst the vast sea of other settings covered by these files (to handle the equally vast sea of other board offerings). Make it less mysterious – we’re setting up pin defs for communicating with this particular CYD board’s display SPI interface, the touchscreen interface, and giving the screen dimensions.
Hi.
Thanks for your feedback.
We’ll take a look into it.
Regards,
Sara
2nd the motion. I spent a number of days trying to get the RNT CYD code running on a DIYUSER module – ESP32, 2.4″ ILI9341 320×240 display, resistive touch. In the end, all that needed changing were the XPT2046 SPI pin assignments.
That said, this whole LVGL/TFT/XPT world can be bewilderingly complex with bezillions of options which should be politely hidden from those new to micro-controllers. Perhaps the “Important Tuning Parameters” note(s) could be offered as an “Advanced User” note.