Skip to content

PlatformIO_LVGL_7.0: 7.0-inch ESP32-S3 Dual Frame Buffer and Touch Control

1. Course Introduction

This lesson uses VS Code, PlatformIO, the Arduino framework, LVGL 9.1.0, LovyanGFX (in-project version), and SquareLine Studio 1.6.1 to drive the CrowPanel ESP32-S3 7.0-inch display. The program reads temperature and humidity from the DHT20 via the I2C bus on GPIO19/GPIO20, displays the values on the 800 × 480 screen through LVGL, and controls the LED on GPIO38 via an ON/OFF touch button.

Learners should first prepare the development environment and hardware, open the course project, then compile, select the serial port, and flash the firmware, before observing 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 a button press, 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 role of platformio.ini, src/, include/, and lib/ in this project.
  • Be able to complete the connection of the DHT20, LED, USB, and display, and compile, flash, and open the serial monitor following the PlatformIO workflow.
  • Be able to use SquareLine Studio 1.6.1 to create an 800 × 480 LVGL 9.1.0 UI 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. What You Need to Prepare

  • One CrowPanel ESP32-S3 7.0-inch development board.
  • One DHT20 temperature and humidity module, connected to GPIO19 (SDA) and GPIO20 (SCL).
  • One LED module, connected to GPIO38.
  • A 4-pin connector cable and a USB data cable that supports data transfer.
  • The first build requires network access to download the pinned platform packages and libraries.

4. Software Operation Steps

  1. Open VS Code, search for and install PlatformIO IDE in Extensions. If it is already installed, make sure the extension is enabled.

Install PlatformIO IDE

  1. After installation, restart VS Code; the PlatformIO icon should appear on the left sidebar. Click the icon to enter the PIO Home main page.

Open PIO Home

  1. In the PIO Home Quick Access area, click Open Project.

Click Open Project

  1. In the dialog, browse to the path and select the project root folder PlatformIO70. This folder must contain platformio.ini, then click the blue Open "PlatformIO70" button.

image-20260727104508098

  1. Wait for the Explorer on the left to load the project, then open src/main.cpp and platformio.ini to review the project contents. src/main.cpp is the main program code, and platformio.ini is the configuration file for the platform, dependencies, serial port, and build scripts; the include, src, patches, scripts, and other directories should be kept intact.

image-20260727104553809

  1. Connect the 7.0-inch HMI development board to a USB port on your computer using a USB data cable.

IMG_7942

  1. Select the device serial port: check 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. If upload_port and monitor_port are fixed in platformio.ini, change them to the actual port on the current computer, or delete these two lines to let PlatformIO auto-detect.

image-20260727104841940

  1. Click the right-arrow icon (PlatformIO: Upload) on the bottom status bar of VS Code. Wait for the compilation, dependency check, patch application, and upload process to run automatically. The first build will download dependencies such as Arduino-ESP32, LVGL, LovyanGFX, and DHT20, so it will take longer.

image-20260727104931841

image-20260727105520548

5. Hardware Operation Steps

Wire the connections as shown in the diagram:

  • DHT20 temperature and humidity sensor → board I2C interface.

  • LED module → board GPIO_D interface.

IMG_7944

Reconnect the USB cable that supports data transfer. After the upload completes and the board auto-resets, the screen backlight should turn on, and the interface should show temperature, humidity, and the two buttons ON and OFF.

16

Tap ON with your finger, and observe the interface response and the GPIO38 output state; do not touch the screen with sharp or conductive objects.

IMG_7937

Tap OFF and confirm that the output returns to a low level and the LED turns off.

IMG_7938

6. Key Code Explanation

6.1 RGB Display Configuration

/*---------------------------------------------------------------
 * RGB display configuration
 * Bind the ESP32-S3 RGB signals and panel timing to LovyanGFX.
 *--------------------------------------------------------------*/

class LGFX : public lgfx::LGFX_Device
{
public:
    lgfx::Bus_RGB bus;
    lgfx::Panel_RGB panel;

    /**
     * @brief Configure the RGB bus and the 800 by 480 panel.
     * @param None.
     * @return A configured LGFX device through the global lcd object.
     * @note Runs automatically while lcd is constructed before setup().
     */
    LGFX()
    {
        auto bus_config = bus.config();
        bus_config.pin_henable = GPIO_NUM_41;
        bus_config.pin_vsync = GPIO_NUM_40;
        bus_config.pin_hsync = GPIO_NUM_39;
        bus_config.pin_pclk = GPIO_NUM_0;
        bus_config.freq_write = 24000000;
        bus_config.hsync_front_porch = 40;
        bus_config.hsync_pulse_width = 48;
        bus_config.hsync_back_porch = 40;
        bus_config.vsync_front_porch = 1;
        bus_config.vsync_pulse_width = 31;
        bus_config.vsync_back_porch = 13;
    }
};

This section comes from the annotated src/main.cpp and is responsible for binding the synchronization signals, pixel clock, and porch parameters of the 7.0-inch 800×480 RGB screen to LovyanGFX. The PlatformIO project also patches LovyanGFX's RGB bus implementation via scripts/patch_lovyangfx.py; if the patch is not applied, common problems include VSYNC switching failure, the screen not refreshing, or abnormal double-buffer addresses.

6.2 Full-Screen Double Buffering

// Owns the panel and the two full-screen frame buffers used by LVGL.
LGFX lcd;

/**
 * @brief Switch the RGB panel to the buffer rendered by LVGL.
 * @param display LVGL display requesting the transfer.
 * @param area Unused because full-frame rendering is configured.
 * @param pixel_map Frame buffer selected by LVGL.
 * @return Nothing.
 * @note Called by LVGL after each rendered frame.
 */
void display_flush(lv_display_t *display, const lv_area_t *area, uint8_t *pixel_map)
{
    (void)area;
    if(!lcd.bus.presentFrameBuffer(pixel_map)) {
        Serial.println("LovyanGFX VSYNC frame switch timeout");
    }
    lv_display_flush_ready(display);
}

lv_color_t *frame_buffer_0 = reinterpret_cast<lv_color_t *>(lcd.bus.getFrameBuffer(0));
lv_color_t *frame_buffer_1 = reinterpret_cast<lv_color_t *>(lcd.bus.getFrameBuffer(1));
lv_display_set_buffers(display, frame_buffer_0, frame_buffer_1,
                       SCREEN_WIDTH * SCREEN_HEIGHT * sizeof(lv_color_t),
                       LV_DISPLAY_RENDER_MODE_FULL);

The two full-screen buffers alternately handle rendering and display; display_flush() calls presentFrameBuffer() to wait for the VSYNC switch after each frame is completed. If the serial port outputs RGB double frame buffer allocation failed, first check the PSRAM, board JSON, partition, and LovyanGFX patch; if a VSYNC timeout is output, focus on whether the RGB timing and the patch are in effect.

6.3 GT911 Touch Input

/**
 * @brief Supply the current touch state and coordinates to LVGL.
 * @param indev LVGL input device requesting a sample.
 * @param data Destination for pointer state and coordinates.
 * @return Nothing.
 * @note Called periodically after the input device is registered.
 */
void touchpad_read(lv_indev_t *indev, lv_indev_data_t *data)
{
    (void)indev;
    data->state = LV_INDEV_STATE_RELEASED;

    if(touch_touched()) {
        data->state = LV_INDEV_STATE_PRESSED;
        data->point.x = touch_last_x;
        data->point.y = touch_last_y;
    }
}

lv_indev_t *touchpad = lv_indev_create();
lv_indev_set_type(touchpad, LV_INDEV_TYPE_POINTER);
lv_indev_set_read_cb(touchpad, touchpad_read);

The touch driver maintains the latest coordinates in touch.h, and touchpad_read() only hands "whether pressed" and coordinates to LVGL. When the UI displays normally but the buttons do not respond, first confirm that touch_init() has been called, then check the GT911's SDA GPIO19, SCL GPIO20, coordinate orientation, and whether touch_last_x/touch_last_y fall within the range of 0–799 and 0–479.

6.4 Sensor and Output

/*---------------------------------------------------------------
 * Runtime sensor and UI processing
 * Read the DHT20 at a safe interval and service LVGL continuously.
 *--------------------------------------------------------------*/

// Read the sensor on a fixed interval so the UI remains responsive.
if(now - last_sensor_update >= 2000) {
    const int status = dht20.read();
    if(status == DHT20_OK) {
        lv_label_set_text(ui_TempLabel, sensor_text);
        lv_label_set_text(ui_HumiLabel, sensor_text);
    }
}

// The generated UI event callbacks update led; the loop applies it to GPIO38.
digitalWrite(RELAY_PIN, led == 1 ? HIGH : LOW);

lv_timer_handler();

The DHT20 is read proactively once every two seconds; on failure it only outputs an error code and does not write invalid values to the UI. The button events exported by SquareLine are only responsible for changing the global led, and the main loop writes that state to GPIO38 each iteration. lv_timer_handler() must run continuously; otherwise the interface refresh, touch events, and label updates will all stop.

7. UI Resource Creation and Integration

  1. This section uses SquareLine Studio 1.6.1 to demonstrate the UI creation process again. The course already provides the exported UI files, so beginners can first read this section to understand the workflow and then directly use the files in the project to complete the build. 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.

  1. Open SquareLine Studio 1.6.1, click Create at the top to create a new project, select 9.1 as the LVGL major version, and choose Elecrow as the vendor category. In the template list, select DIS08070H - ESP32 7inch HMI Display 800x480 RGB - Arduino-IDE, which is designed for the Elecrow CrowPanel 7-inch capacitive touch screen, with preset parameters of 800×480 resolution and LVGL 9.1, then complete the project creation.

Note: This template is an Elecrow CrowPanel-specific Arduino project template that directly generates LVGL UI code matching the hardware; if you switch to a different screen model, you need to select the corresponding hardware template and verify the resolution and color depth configuration.

image-20260724111759549

  1. Enter the project name, set the LVGL version to 9.1.0, the resolution to width 800 and height 480, and the color depth to 16 bit, then click CREATE. After creation, the canvas should be a landscape 800×480.

image-20260724111904548

Note: A 16 bit color depth can represent 65,536 colors, using the RGB 5:6:5 pixel representation. Keep it consistent with the project's color configuration.

  1. After the project opens, select Screen1 in the Screens panel on the left, and confirm that the central canvas is blank and the Inspector on the right shows the Screen properties. Subsequent widgets are all added to this page.

Confirm Screen1 canvas

  1. In the Assets area, click ADD FILE TO ASSETS and import LVGL-Assets-800x480/background.png, on.png, and off.png in order. After importing, you should see three thumbnails.

Import image assets

Note: Image assets only support the PNG format; the pixel dimensions of an image should be smaller than the project screen size; a single image should not exceed 100 KB, and preferably be kept under 30 KB, to avoid affecting display smoothness.

  1. Select Screen1, expand STYLE SETTINGS > STYLE (MAIN) > Background on the right, and enable the background image setting.

Enable background image setting

  1. Select background under Bg Image. The canvas should immediately display the course background image, and the background should fully cover the entire page.

Set background image

  1. Click Label in the Widgets panel on the left to add a temperature label to Screen1. This label will be updated by the program with the temperature value.

Add temperature label

  1. Select the temperature label, and adjust its X and Y position, width, and height in Transform so that it sits within the temperature display area of the background image. You can also drag it first and then fine-tune with values.

Adjust temperature label position

  1. In the label's STYLE (MAIN), change the text color to white and set an appropriate font size. The text on the canvas should be clearly visible and not exceed the background frame.

Set label text style

  1. Right-click Label1 and select Duplicate to create Label2, then move it to the humidity display area. Keep the names Label1 and Label2, because the main program updates them by name.

    Duplicate label

  2. Select the two Labels separately, and enter default numbers in Text for easy preview. These are only placeholder values at design time and will be replaced by DHT20 readings once the development board runs.

    Fill in label default values

  3. Click Button in Widgets to add the first button, and drag it to the ON area on the right side of the interface. Add ON Button

  4. In Transform, set the size of the first button and adjust its position so that the button aligns with the ON area in the background.

    Adjust the ON button size and position

  5. Expand the button's STYLE (MAIN) > Background, and select on.png as the background image. The ON icon should appear on the canvas.

    Set the ON button image

  6. Duplicate Button1 to get Button2, move it to the OFF area, and change the background image to off.png. Keep the names Button1 and Button2 so they correspond to the exported event functions.

    Duplicate the button

    Set the OFF button image

  7. In the STATE settings of both buttons, check the DEFAULT and PRESSED states. The pressed state can use an obvious color change so that you can tell in the preview whether the button is pressed. In the Inspector → Style Settings → State panel, set the displayed background color to white, and to red when in the Checked state. Set the same parameters for the OFF button.

    Check the button default state

    Set the button pressed state

  8. Select the ON button and click ADD EVENT.

    Apply the button state settings

  9. Select CLICKED as the trigger condition, and choose the triggered event in Action. The generated program will be modified later to implement the GPIO38 output control function.

    38

    Note: Since the button will ultimately control the LED on and 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.

  10. Complete this event. In the example, you can first choose to switch to the Screen1 screen to generate the event structure.

    Set the event trigger condition

  11. Add an event to the OFF button in the same way.

    Complete the ON button event

  12. Click Run to preview the interface display and button states.

    Add an event to the OFF button

  13. Open File > Project Settings, then configure the relevant settings for the exported files.

    Run the preview

    image-20260724113200377

  14. Set the export directory to an easy-to-find path consisting only of English characters, create a new output folder, confirm that the LVGL Include Path is lvgl.h, and click APPLY CHANGES to confirm.

    image-20260724113251380

    image-20260724113402379

    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 fail to recognize them automatically, which usually requires manually modifying the paths; therefore it is recommended to keep it checked.

  15. Click Export > Export UI Files. After the export is complete, ui.c, ui.h, ui_Screen1.c, the event file, helper files, and the image array file should appear in the target directory.

    Export the UI files

    image-20260724113447185

  16. Add the UI files to the PlatformIO project. We need to add the UI files from SquareLine Studio to the PlatformIO project. The .c files of the user interface should be placed in the /scr folder of the project files, while the .h files should be placed in the /include folder.

    image-20260727110158597

    image-20260727110227278

    image-20260727110258899

8. Expected Behavior

After the firmware is uploaded and reset, the LCD is first cleared to black, and about 300 ms later the backlight turns on and the 800×480 UI loads. The background image should display completely; 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 according to the temperature and humidity readings, respectively.

16

Tap ON with your finger and observe the interface response and the GPIO38 output state; do not touch the screen with sharp or conductive objects.

IMG_7937

Tap OFF and confirm that the output returns to a low level and the LED turns off.

IMG_7938

9. Download Code

PlatformIO Project: PlatformIO70.