Skip to content

Lesson09--- LVGL Lighting Control

Introduction


In previous courses, we separately lit an LED, implemented touch testing, and lit up the screen.In this lesson, we will use LVGL to create two buttons to control the LED connected to the UART1 interface for turning on and off operations.

Pressing the ON button can turn on the LED, and pressing the OFF button can turn off the LED.

Hardware Used in This Lesson


The UART1 interface on the Advance-P4 is connected to an LED.

image27

Operation Effect Diagram


After running the code, when you press the "LED ON" button on the Advance-P4, you will be able to turn on the LED; when you press the "LED OFF" button, you will be able to turn off the LED.

image34

image35

Key Explanations

Now, the focus of this lesson is on how to use LVGL to create button objects and display the LVGL interface on the screen to achieve interactive effects.

First, click the GitHub link below to download the code for this lesson.

Then drag the code for this lesson into VS Code and open the project file.

Once opened, you can see the framework of this project.

image4

It can be seen that the components we use in this lesson are all those explained in previous sessions:

  • bsp_display: Touch-related driver.

  • bsp_i2c: Provides I2C driver support required for touch functionality.

  • bsp_extra: Used to control the LED connected to the UART1 interface.

  • bsp_illuminate: Responsible for screen initialization, screen lighting, and LVGL initialization.

LVGL Initialization Code


The components used in this lesson have been explained in detail in previous courses.

Here, we will only describe the LVGL initialization in detail.

lvgl_init() is the core initialization function of the entire graphic display system.

It mainly completes the following tasks:

  • Initializes the LVGL operating task environment (task/timer)

  • Registers and binds the display driver (Display) with LVGL's rendering layer

  • Registers and binds the touch input driver (Touch) to the LVGL input system

The purpose of doing this is to ensure that LVGL's graphic rendering, screen refreshing, and touch event handling are correctly linked with the underlying hardware.

image5

This part starts the LVGL task and timer through lvgl_port_init(), completing the following:

  • Allocating stack space for the LVGL main task (LVGL task);

  • Setting the task priority;

  • Configuring LVGL's periodic refresh timer;

  • Defining the maximum sleep time (i.e., the time the LVGL main loop sleeps when idle);

Significance:

The LVGL task continuously calls lv_timer_handler() to refresh the UI, process animations, and respond to events.

image6

This step registers the display screen with LVGL through lvgl_port_add_disp_dsi(), serving as a bridge between "LVGL" and the "screen".

The initialization content includes:

  • io_handle: The physical communication interface of the screen (such as "MIPI", "SPI", "RGB", etc.)

  • panel_handle: Screen panel driver handle

  • buffer_size: Frame buffer size (used for rendering images)

  • double_buffer: Whether to use double buffering (prevents tearing and improves refresh smoothness)

  • hres/vres: Screen resolution

  • color_format: Color format (e.g., "RGB565")

  • rotation: Screen rotation/mirror configuration

  • flags:

  • buff_dma, buff_spiram: Whether the buffer is placed in internal memory or external "PSRAM"

  • full_refresh: Whether to enable full-frame refresh mode

  • direct_mode: Whether to directly output LVGL rendering results to the screen (reducing intermediate layers)

Significance:

All LVGL drawing operations will ultimately be updated to your screen through this display interface.

image7

Register the touch input device with LVGL so that it can receive finger touch events.

The initialization content includes:

  • disp: The bound display object (the touch area corresponds to the screen)

  • handle: Touch driver handle (such as "FT5x06", "GT911", "CST816", etc.)

Significance:

Only in this way can LVGL's internal event system (such as button clicks, swipes) obtain touch coordinate data. After this part of the initialization, clicking buttons on the screen will produce visible effects.

This concludes our explanation of the components.

Main Function


The main folder is the core directory for program execution, which contains the main function executable file main.c.

Add the main folder to the "CMakeLists.txt" file of the build system.

image8

This is the entry file of the entire application. In ESP-IDF, there is no int main(), and execution starts from void app_main(void).

Let's first explain main.c to see how the interfaces in these four components are called to achieve the LVGL lighting effect. It creates a simple interface on the touch screen, containing two buttons labeled "LED ON" and "LED OFF" to control the LED on GPIO48.

image9

image10

Function:

"LDO" (Low Dropout Regulator) is a low dropout regulator used to supply power to devices such as screens and touch chips.

Two channels are enabled here:

"LDO3" outputs 2.5V (to power the screen)

"LDO4" outputs 3.3V (to power logic circuits or other peripherals)

After successful initialization:

Provides stable power for subsequent LCD and touch modules.

image11

Initialize the "I2C" bus for communication with the touch chip.

The touch input part of LVGL usually needs to read coordinates via I2C.

After successful initialization:

The system can obtain touch event coordinate data through I2C.

image12

Function:

Initialize the touch driver and register touch interrupts or polling read mechanisms.

Enable LVGL to receive touch events (clicks, swipes, etc.).

After successful initialization:

User clicks on the screen can trigger LVGL events.

image13

Function:

"display_init()": Initialize the LCD hardware interface and initialize the LVGL library;

"set_lcd_blight(100)": Turn on the screen backlight brightness (100 indicates maximum brightness).

After successful initialization:

The LVGL graphics system starts running, and the screen can display UI elements.

image14

Function:

Configure "GPIO48" as an output pin;

Control the LED switch through "gpio_extra_set_level(true/false)".

After successful initialization:

The system can turn the LED on or off through button clicks.

image15

Function:

Create a concise interface using LVGL:

  • Background: white;

  • Title: "LED Controller";

  • Two buttons:

  • "LED ON": Triggers btn_on_click_event() to turn on the LED;

  • "LED OFF": Triggers btn_off_click_event() to turn off the LED.

Now let's take a look inside this function.

image16

lv_scr_act()

Obtains the currently active screen object (LVGL has only one main screen by default).

You can understand it as "I want to place things on the current screen".

lv_obj_set_style_bg_color()

Sets the background color of this screen to white (0xFFFFFF).

image17

This section creates and configures a title text:

'lv_label_create(scr)': Creates a text label object on the main screen.

'lv_label_set_text()': Sets the text content to "LED Controller".

'lv_obj_align()': Sets the alignment to top-center, with a downward offset of 50 pixels.

'lv_obj_set_style_text_font()': Sets the font size to 24pt.

Result: A large-sized title "LED Controller" is displayed centered at the top of the screen.

image18

'lv_btn_create(scr)': Creates a button object and places it on the main screen.

'lv_obj_set_size()': Sets the button size to 120×50 pixels.

'lv_obj_align()': Aligns the button to the center, with an upward offset of 40 pixels.

'lv_obj_add_event_cb()': Binds a button event---when the button is "clicked", it calls the 'btn_on_click_event()' function.

Within this function, 'gpio_extra_set_level(true);' is executed → turning on the LED.

Result: A button is created slightly above the center of the screen, used for "turning on the light".

image19

This label is a child object of the button (created within the button).

Its text will be automatically displayed in the center of the button.

Result: The text "LED ON" is displayed on the button.

image20

The "OFF" button is created using the same logic.

Now let's look at the events bound to these two buttons after they are clicked.

image21

image22

Here are the event handlers triggered when the buttons are clicked, which turn the LED on or off with immediate response.

Next is the main function app_main:

Role: Serves as the program entry point, prints startup logs;

Calls system_init() to complete all initializations;

Enters a loop to keep the program running (LVGL's own tasks execute in the background).

image23

Finally, let's understand the "CMakeLists.txt" file in the main directory.

The role of this CMake configuration is:

Collects all .c source files in the main/ directory as the component's source files;

Registers the "main" component with the ESP-IDF build system and declares that it depends on "bsp_extra", "bsp_display", "bsp_illuminate", "bsp_i2c", and "esp_timer".

This way, during the build process, ESP-IDF knows to build these five components first, then build the "main" component.

image24

Meanwhile, the header file references in main.h are also kept in sync.

image25

Note:

In subsequent courses, we will not create a new "CMakeLists.txt" file from scratch. Instead, we will make minor modifications to this existing file to integrate other drivers into the main function.

Complete Code


Kindly click the link below to view the full code implementation.

Programming Steps


Now the code is ready. Next, we need to flash it to the ESP32-P4 to see the actual effect.

First, connect the Advance-P4 device to our computer via a USB cable.

image26

Also, remember to connect an LED to the UART1 interface.

image27

Before starting the flashing preparation, delete all compiled files to restore the project to its initial "unbuilt" state. (This ensures that subsequent compilations are not affected by your previous operations.)

image28

Here, follow the steps from the first lesson to first select the ESP-IDF version, code upload method, serial port number, and the target chip (ESP32-P4).

Next, we need to configure the SDK.

Click on the icon shown in the figure below.

image29

Wait for a moment while the configuration loads, and then you can proceed with the relevant SDK configuration.

image30

Then, search for "flash" in the search box.

(Make sure your flash configuration matches mine.)

image31

After completing the configuration, remember to save your settings.

Next, we will compile and flash the code (detailed in the first lesson).

Here, we also want to share a very convenient feature: there is a single button that can execute compilation, uploading, and opening the monitor in one go. (This works on the premise that the entire code is error-free.)

image32

After waiting for a while, the code compilation and upload will be completed, and the monitor will open automatically.

At this point, please remember to use an additional Type-C cable to connect your Advance-P4 via the USB 2.0 interface. This is because the maximum current provided by a computer's USB-A interface is generally 500mA, and the Advance-P4 requires a sufficient power supply when using multiple peripherals---especially the screen. (It is recommended to connect it to a charger.)

image33

After running the code, when you tap the "LED ON" button on the Advance-P4's touchscreen, you will be able to turn on the LED; tapping the "LED OFF" button will allow you to turn off the LED.

image34

image35