PlatformIO_LVGL_2.8: 2.8-inch Temperature and Humidity Display and Touch-Controlled LED on PlatformIO¶
1. Course Introduction¶
This lesson uses VS Code, PlatformIO, the Arduino framework, LVGL 9.1.0, TFT_eSPI 2.5.31, and SquareLine Studio 1.6.1 to drive the CrowPanel ESP32 2.8-inch display. The program reads temperature and humidity from the DHT20 via the I2C bus on GPIO22/GPIO21, displays the values on a 320×240 screen through LVGL, and controls the LED on GPIO25 via an ON/OFF touch button.
Learners first need to prepare the development environment and hardware, open the course project, complete compilation, select the serial port, and flash the firmware, then observe the screen, LED, and serial monitor.
During normal operation, the LCD backlight turns on, the temperature and humidity labels display values, the LED state changes after the button is pressed, and the serial port outputs coordinates when touched. This lesson uses the same set of UI resources as the Arduino version; the difference is that the project is managed by PlatformIO for dependencies and the build process.
Reference materials:
2. Learning Objectives¶
- Be able to install PlatformIO in VS Code and open the course project.
- Be able to explain the roles of
platformio.ini,src/,include/, andlib/in this project. - Be able to complete the connections for the DHT20, LED, USB, and screen, and compile, flash, and open the serial monitor following the PlatformIO workflow.
- Be able to use SquareLine Studio 1.6.1 to create a 320×240 UI in LVGL 9.1.0 and integrate the exported files into the PlatformIO project.
- Be able to determine whether the experiment is successful based on the screen display, button response, LED state, and serial coordinates.
3. Preparations¶
3.1 Hardware¶
- One CrowPanel ESP32 2.8-inch HMI development board.
- One DHT20 temperature and humidity module, connected to GPIO22 (SDA) and GPIO21 (SCL).
- One LED module, connected to GPIO25.
- A 4-pin connection cable and a USB data cable that supports data transfer.
3.2 Software and Versions¶
- Visual Studio Code.
- PlatformIO IDE extension.
- LVGL
9.1.0. - SquareLine Studio
1.6.1.
4. Software Operation Steps¶
- Open VS Code, search for and install PlatformIO IDE in Extensions.
- After installation, restart VS Code; the PlatformIO icon should appear on the left side. Click the icon to enter the PIO Home main interface.
- In the PIO Home
Quick Accessarea, click Open Project.
- In the pop-up dialog, browse to the path and select the project root folder
PlatformIO28(the folder must containplatformio.ini), then click the blue button Open "PlatformIO28".
- The project loads in the left-side Explorer
src/main.cpp: main program code
platformio.ini: platform configuration file
include and lib are dependency library directories
Open main.cpp to review the project code
- Connect the CrowPanel development board to a USB port on your computer using a USB data cable.
- Select the device serial port: Look at the bottom status bar of VS Code; the dropdown on the left defaults to
Auto. Click the dropdown and select the serial port corresponding to the development board (example: COM13).
- Click the right-arrow icon (PlatformIO: Upload) in the bottom status bar of VS Code. Wait for the compilation and upload process to run automatically.
5. Hardware Operation Steps¶
Connect the wires as shown in the diagram:
-
DHT20 temperature and humidity sensor → main board I2C interface
-
LED module → main board GPIO_D interface
Displays real-time temperature and humidity (data collected by the DHT20) and provides ON/OFF buttons to turn the external LED on/off.
ON→LED lights up
OFF→LED turns off
6. Key Code Explanation¶
6.1 PlatformIO Version and Pin Configuration¶
[env:denky32]
; Use the pioarduino ESP32 platform package verified by this course.
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.38-1/platform-espressif32.zip
; Select the ESP32 board definition and Arduino framework.
board = denky32
framework = arduino
; Keep the monitor speed aligned with Serial.begin(115200).
monitor_speed = 115200
build_flags =
; Let LVGL load include/lv_conf.h from this project.
-DLV_CONF_INCLUDE_SIMPLE
-Iinclude
-include soc/gpio_struct.h
; Configure TFT_eSPI for the 2.8-inch ILI9341 panel and resistive touch.
-DUSER_SETUP_LOADED=1
-DILI9341_DRIVER=1
-DTFT_WIDTH=240
-DTFT_HEIGHT=320
-DTFT_MISO=12
-DTFT_MOSI=13
-DTFT_SCLK=14
-DTFT_CS=15
-DTFT_DC=2
-DTFT_RST=-1
-DTFT_BL=27
-DTOUCH_CS=33
-DSPI_FREQUENCY=15999999
-DSPI_TOUCH_FREQUENCY=600000
; Lock the GUI libraries to the versions used by the lesson code.
lib_deps =
lvgl/lvgl@9.1.0
bodmer/TFT_eSPI@2.5.31
When it runs and what it does: Before compiling, PlatformIO reads platformio.ini and prepares the ESP32 Arduino build environment based on board and framework, passing the display parameters to TFT_eSPI through build_flags. monitor_speed = 115200 stays consistent with Serial.begin(115200) in the source code.
Key parameters: The LCD uses ILI9341, the backlight is on GPIO27, the touch chip-select is on GPIO33, and SPI uses GPIO12/13/14/15/2. TFT_WIDTH=240 and TFT_HEIGHT=320 are the driver-layer portrait dimensions; the program then rotates them to a 320×240 landscape orientation via lcd.setRotation(1).
6.2 Initialize LCD, Backlight, and Touch¶
/**
* @brief Initialize the display, touch device, sensor, and generated UI.
*
* PlatformIO's Arduino framework calls this function once after reset. The
* callbacks are registered before ui_init() creates and loads Screen1.
*/
void setup() {
Serial.begin(115200);
/*---------------------------------------------------------------
* Initialize application I/O and the DHT20 sensor
* GPIO25 starts low so the controlled LED is off at boot.
*--------------------------------------------------------------*/
pinMode(25, OUTPUT);
digitalWrite(25, LOW);
Wire.begin(22, 21);
dht20.begin();
/*---------------------------------------------------------------
* Initialize LVGL and LCD hardware
* The explicit inversion setting matches the panel configuration.
*--------------------------------------------------------------*/
lv_init();
lv_tick_set_cb(lv_tick_get_ms);
lcd.begin();
lcd.fillScreen(TFT_BLACK);
lcd.invertDisplay(false);
delay(300);
pinMode(27, OUTPUT);
digitalWrite(27, HIGH);
lcd.setRotation(1);
lcd.setTouch(calData);
/*---------------------------------------------------------------
* Register LVGL display and input callbacks
*--------------------------------------------------------------*/
lv_display_t *disp = lv_display_create(screenWidth, screenHeight);
lv_display_set_flush_cb(disp, my_disp_flush);
lv_display_set_buffers(disp, buf1, NULL, sizeof(buf1), LV_DISPLAY_RENDER_MODE_PARTIAL);
lv_indev_t *indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(indev, my_touchpad_read);
ui_init();
}
When it runs and what it does: This code executes once in setup(). The comments explain that it first initializes LVGL and the LCD, then turns on the backlight, sets the landscape orientation, and performs touch calibration. If digitalWrite(27, HIGH) is commented out, the LCD may already be refreshing, but what you see with the naked eye is a black screen.
6.3 Register LVGL Display and Input Callbacks¶
/**
* @brief Transfer an LVGL-rendered area to the LCD.
*/
void my_disp_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) {
uint32_t w = (area->x2 - area->x1 + 1);
uint32_t h = (area->y2 - area->y1 + 1);
lcd.startWrite();
lcd.setAddrWindow(area->x1, area->y1, w, h);
lcd.pushColors(reinterpret_cast<uint16_t *>(px_map), w * h, true);
lcd.endWrite();
lv_display_flush_ready(disp);
}
/**
* @brief Convert a panel touch into an LVGL pointer event.
*/
void my_touchpad_read(lv_indev_t *indev, lv_indev_data_t *data) {
(void)indev;
bool touched = lcd.getTouch(&touchX, &touchY, 600);
if ([touched) {
data->state = LV_INDEV_STATE_RELEASED;
} else {
data->state = LV_INDEV_STATE_PRESSED;
data->point.x = screenWidth - touchX;
data->point.y = touchY;
Serial.print("Data x ");
Serial.println(touchX);
Serial.print("Data y ");
Serial.println(touchY);
}
}
When it runs and what it does: While lv_timer_handler() runs, LVGL polls the touch callback. The PlatformIO project mirrors the X coordinate using screenWidth - touchX to match the current landscape interface orientation. The serial output of coordinates is used to verify that touch input is working correctly.
6.4 Read DHT20 and Update Labels¶
/**
* @brief Update sensor labels, apply the LED state, and service LVGL.
*
* PlatformIO's Arduino framework calls this function repeatedly after setup().
* The short delay keeps the UI responsive while allowing sensor labels to
* follow the current DHT20 readings.
*/
void loop() {
Serial.print(led);
char DHT_buffer[12];
int a = (int)dht20.getTemperature();
int b = (int)dht20.getHumidity();
snprintf(DHT_buffer, sizeof(DHT_buffer), "%d", a);
lv_label_set_text(ui_Label1, DHT_buffer);
snprintf(DHT_buffer, sizeof(DHT_buffer), "%d", b);
lv_label_set_text(ui_Label2, DHT_buffer);
if (led == 1) {
digitalWrite(25, HIGH);
}
if (led == 0) {
digitalWrite(25, LOW);
}
lv_timer_handler();
delay(10);
}
When it runs and what it does: The PlatformIO Arduino framework calls loop() repeatedly. The DHT20 readings are converted into integer strings and written to ui_Label1 and ui_Label2. If the label names are changed in SquareLine Studio, compilation will fail to find the corresponding objects, or the correct positions cannot be updated at runtime.
6.5 Button Events and GPIO25¶
// Shares the LED state with the PlatformIO application loop.
extern int led;
/**
* @brief Set the shared LED state when the ON button is clicked.
*
* LVGL calls this handler for events from ui_Button1. The application loop
* performs the physical GPIO write after the state changes.
*/
void ui_event_Button1(lv_event_t * e)
{
lv_event_code_t event_code = lv_event_get_code(e);
if(event_code == LV_EVENT_CLICKED) {
led = 1;
}
}
/**
* @brief Clear the shared LED state when the OFF button is clicked.
*
* LVGL calls this handler for events from ui_Button2. The application loop
* performs the physical GPIO write after the state changes.
*/
void ui_event_Button2(lv_event_t * e)
{
lv_event_code_t event_code = lv_event_get_code(e);
if(event_code == LV_EVENT_CLICKED) {
led = 0;
}
}
When it runs and what it does: The button events in ui_Screen1.c only modify the shared variable led; loop() performs the GPIO25 output uniformly based on this variable. The source comments emphasize this division of labor: the UI callback updates the state, and the application loop handles the physical GPIO.
7. UI Asset Creation and Integration¶
This section uses SquareLine Studio 1.6.1 to demonstrate the UI creation method again. The course already provides the exported UI files; beginners can first read this section to understand the workflow, then directly use the files in the project to complete compilation. When creating a new project, you must select LVGL 9.1.0.
How to download SquareLine Studio:https://www.elecrow.com/wiki/Create_LVGL_UI_with_SquareLine_Studio.html
- Launch SquareLine Studio 1.6.1, click Create, and select the Arduino with TFT_eSPI template for the Arduino platform. This template is responsible for generating the UI file framework suitable for Arduino and TFT_eSPI projects.
Note: When the Arduino framework is selected, SquareLine Studio displays only the Arduino with TFT_eSPI option. It generates template code suitable for TFT_eSPI, but SquareLine Studio also supports other graphics libraries; when switching to other hardware, you need to modify the display code according to the actual library.
2. Enter a project name, set the LVGL version to 9.1.0, set the resolution to width 320 and height 240, set the color depth to 16 bit, then click CREATE. After creation, the canvas should be in landscape orientation at 320×240.
-
Note: A
16 bitcolor depth can represent 65,536 colors using the RGB 5:6:5 pixel format. Keep this consistent with the project's color configuration. -
After the project opens, select
Screen1in the Screens panel on the left, and confirm that the central canvas is blank and that the Inspector on the right displays the Screen properties. All subsequent widgets will be added to this page.
- In the Assets area, click ADD FILE TO ASSETS, and import
background.png,on.png, andoff.pngfromLVGL-Assets-320x240/in sequence. After importing, you should see three thumbnails.
Caution: Image assets support the PNG format only; the pixel dimensions of an image should be smaller than the project's screen size; keep each image under 100 KB, preferably within 30 KB, to avoid affecting display smoothness.
- Select
Screen1, expand STYLE SETTINGS > STYLE (MAIN) > Background on the right, and enable the background image setting.
- In Bg Image, select
background. The canvas should immediately display the course background image, and the background should fully cover the 320×240 page.
- In the Widgets panel on the left, click Label to add
Label1toScreen1. This label will be updated by the program to show the temperature value.
- Select
Label1, and in Transform adjust the X and Y position and the width and height so that it sits in the temperature display area of the background image. You can also drag it first and then fine-tune it with the numeric values.
- In
Label1's STYLE (MAIN), change the text color to white and set an appropriate font size. The text in the canvas should be clearly visible and should not extend beyond the background frame.
- Right-click
Label1and choose Duplicate to createLabel2, then move it to the humidity display area. Keep the namesLabel1andLabel2, because the main program updates them by name.
- Select the two labels one by one, and in Text enter default numbers that are convenient for preview. These are only placeholder values at design time and will be replaced by DHT20 readings once the development board runs.
- In Widgets, click Button to add
Button1, and drag it to the ON area on the right side of the interface.
- In Transform, set the size of
Button1and adjust its position so that the button aligns with the ON area in the background.
- Expand
Button1's STYLE (MAIN) > Background and selecton.pngas the background image. The ON icon should appear in the canvas.
- Duplicate
Button1to getButton2, move it to the OFF area, and change the background image tooff.png. Keep the namesButton1andButton2so they correspond to the exported event functions.
- In the STATE settings of both buttons, check the
DEFAULTandPRESSEDstates. The pressed state may use a noticeable color change so that, during preview, you can tell whether the button is pressed. In Inspector -> Style Settings -> State, set the background color to white, and when in the CHECKED state show red. Set the same parameters for the "OFF" button.
- Select
Button1and click ADD EVENT.
- Select "CLICKED" as the trigger condition, and in "Action" choose the trigger event. It will be modified later in the generated program to implement LED control.
Caution: Because the buttons will ultimately control the LED on/off, you can add any event here first so that the exported UI file generates the button event code framework; the LED control code will be modified in a later step.
- Complete this event. Here, I chose to switch screens, that is, switch to the Screen1 screen.
- Add the event to Button2 in the same way (state is "OFF").
- Click Run.
- Open File > Project Settings, then configure the relevant settings for the exported files.
- Set the export directory to an easy-to-find path consisting of English characters only, create a new
outputfolder, fill in and confirm that the LVGL Include Path islvgl.h, then click APPLY CHANGES.
Tip: After selecting Flat export, all output files are placed in the same folder, so the program does not need to modify file paths. If Flat export is not selected, files are scattered across different folders and the compiler may not recognize them automatically, usually requiring manual path changes; therefore it is recommended to keep it checked.
- Click Export > Export UI Files. After the export finishes, the destination directory should contain
ui.c,ui.h,ui_Screen1.c, the event files, helper files, and the image array file.
- Add the UI files to the PlatformIO project. We need to add the UI files from SquareLine Studio to the PlatformIO project. The
.cfiles of the user interface should be placed in the/scrfolder of the project, and the.hfiles should be placed in the/includefolder.
8. Experimental Results¶
After the firmware is uploaded and reset, the LCD is first cleared to black; about 300 ms later the backlight turns on and the 320×240 UI loads. The background image should be displayed in full, two white integers appear slightly left of center on the screen, and the ON and OFF icons appear on the right. When the DHT20 is connected properly, the two integers update with the temperature and humidity readings respectively.
ON -> LED on
OFF -> LED off
9. Code Download¶
- PlatformIO project: PlatformIO28















































