Skip to content

2.4_2.8inch_Lesson03_PWM_and_LVGL_Light_Control

1. Course Introduction

This lesson consists of two experimental segments. The main thread of the course is the 2.4_2.8_LVGL.ino lamp-control interface: it connects the LCD, touch input, and LVGL together, displays the UI exported from SquareLine Studio, and uses the ON / Off buttons to control GPIO18 on the UART1 interface. pwm.ino is a supplementary example used to demonstrate PWM duty cycle variation and backlight brightness control.

This lesson marks the transition from "peripherals can move" to "the interface can interact." The key focus is understanding how UI events call Lamp_on / Lamp_off to ultimately control GPIO18 output; the PWM portion supplements the understanding of backlight brightness adjustment.

Reference materials:

2. Learning Objectives

  • Be able to understand the relationship between PWM frequency, resolution, and duty cycle.
  • Be able to complete LVGL display, touch, and UI initialization.
  • Be able to flash the PWM project and the LVGL project separately, following the Arduino IDE configuration method from Lesson 01.
  • Be able to explain the relationship between Lamp_on / Lamp_off and GPIO18, the main control pin in this lesson.
  • Be able to determine, based on on-screen behavior, whether the problem lies in the backlight, screen refresh, or touch stage.

3. What You Need

  • CrowPanel Advance 2.4-inch development board.
  • A USB data cable that supports data transfer.
  • Optional: a measurement tool for observing PWM output, used to view GPIO38 backlight/PWM changes.
  • A lamp/LED module for the lamp-control experiment, connected to the UART1 interface of the development board; the course code uses GPIO18 to control on/off.
  • Arduino IDE, with esp32 by Espressif Systems 3.3.8 installed via Boards Manager.
  • LVGL 9.1.0, LovyanGFX, and the project's bundled UI files.
  • SquareLine Studio, for recreating or exporting UI files.
  • PWM project: pwm.ino
  • LVGL project file: 2.4_2.8_LVGL.ino

Code and Resource Download

Code download: - 2.4_2.8_LVGL

Resource download: - lamp_28.png

4. Software Operation Steps

  1. Open the project 2.4_2.8_LVGL.ino using Arduino IDE.

image-20260728185245194

image-20260728185226241

Note: Check that the UI files such as ui.c, ui.h, ui_Screen1.c, ui_events.c, and ui_img_lamp_28_png.c are all in the same project directory.

  1. Confirm that Lamp_on and Lamp_off in ui_events.c control GPIO18.

image-20260728190140812

  1. Connect the external LED, and use a USB cable that supports data transfer to connect the development board to the computer.

IMG_8117

  1. Complete the development environment configuration in the Tools menu in order: first confirm that the version of esp32 by Espressif Systems in Boards Manager is 3.3.8; then set Board to ESP32S3 Dev Module, select the serial port corresponding to the board in Port, set Flash Size to 16MB (128Mb), set PSRAM to OPI PSRAM, and set Partition Scheme to Huge APP (3MB No OTA/1MB SPIFFS). After completing the above configuration, you can proceed with compiling and uploading the program.

image-20260728185637086

Note: If Arduino IDE reports a compilation error, first return to Lesson 01 to check whether the ESP32 Core is 3.3.8, and confirm that the board model, PSRAM, Flash Size, and the course library files are consistent.

  1. Click Upload to compile and wait for the upload to complete. If it gets stuck at Connecting..., check for port occupation and use the BOOT/RESET download mode as required by the board.

image-20260728185836555

image-20260728190200609

  1. Open Serial Monitor in the top-right corner, set the baud rate to 115200, and observe the log.

image-20260728190243524

4.1 Flashing the PWM Output Project

  1. Open the project pwm.ino, and confirm that LovyanGFX_Driver.h is in the same folder. The PWM example initializes the LCD, so this display driver file is required.

image-20260729092055399

  1. Check whether pwmPin in the code is 38, pwmFreq is 5000, and pwmResolution is 8.

image-20260729092148780

  1. Click Upload in the top-left corner of Arduino IDE and wait for compilation and upload to complete.

image-20260729092247164

  1. After the upload is complete, the screen should display PWM Backlight, GPIO38, Duty: x / 255, and a blue progress bar. The progress bar will keep changing, and the Serial Monitor will also output PWM duty = .... This project does not display the LVGL lamp-control interface; it is a supplementary example mainly used to understand the PWM output of GPIO38.

image-20260729093849385

Note: The main lamp-control pin in this lesson is GPIO18. The PWM example uses GPIO38 to demonstrate backlight/PWM adjustment; it is not the main control pin for the lamp-control UI in this lesson.

Note: If you still cannot see backlight changes after flashing, first confirm whether the screen shows a Duty value and a blue progress bar. As long as the progress bar changes, the program is running; if the backlight change is not obvious, then check whether GPIO38 is consistent with the current hardware version.

5. Hardware Operation Steps

  1. Connect the external LED, and use a USB cable that supports data transfer to connect the development board to the computer.

IMG_8117

  1. After flashing is complete, wait for the board to reset; the screen should display the lamp-control UI you just created.

Official screenshot: UI displayed after flashing

  1. Tap the ON and Off buttons on the screen, and observe whether the lamp/LED on the UART1 interface toggles on and off.

Official screenshot: tap On to turn the light on

Official screenshot: tap Off to turn the light off

Note: The PWM project and the LVGL project are two independent Arduino projects. When testing PWM, open pwm.ino; when testing touch-based lamp control, open 2.4_2.8_LVGL/2.4_2.8_LVGL.ino. Do not mix the two project files in a single window and compile them together. GPIO18 is the main control pin for the LVGL lamp-control UI in this lesson; GPIO38 is used for the PWM backlight/duty-cycle supplementary example. When troubleshooting lamp-control issues, prioritize checking GPIO18.

6. Key Code Explanation

6.1 UI Project Code

The complete touch UI project for this lesson mainly accomplishes five things: initialize the LCD, initialize the touch, register the LVGL screen-refresh callback, register the touch input callback, and create the UI exported from SquareLine.

LGFX gfx;
static lv_color_t *buf;
static lv_color_t *buf1;

gfx is the LovyanGFX display object, responsible for actually writing pixels to the LCD. buf and buf1 are LVGL draw buffers; the course code places them in PSRAM to store screen refresh data.

void my_disp_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) {
  gfx.pushImageDMA(
    area->x1,
    area->y1,
    area->x2 - area->x1 + 1,
    area->y2 - area->y1 + 1,
    (lgfx::rgb565_t *)px_map
  );

  lv_display_flush_ready(disp);
}

my_disp_flush() is the LVGL screen-refresh callback. LVGL first renders the interface content into the buffer, then hands it off through this function to LovyanGFX, and finally gfx.pushImageDMA() pushes it to the LCD.

void my_touchpad_read(lv_indev_t *indev, lv_indev_data_t *data) {
  data->state = LV_INDEV_STATE_REL;
  if (gfx.getTouch(&touchX, &touchY)) {
    data->state = LV_INDEV_STATE_PR;
    data->point.x = LCD_H_RES - touchX;
    data->point.y = touchY;
  }
}

my_touchpad_read() is the LVGL touch-read callback. When the touchscreen is pressed, the function converts the touch coordinates into coordinates under the current screen orientation, then hands them to the LVGL widget system.

lv_display_set_flush_cb(disp, my_disp_flush);
lv_indev_set_read_cb(indev, my_touchpad_read);
ui_init();

The first line is responsible for sending the LVGL rendering result to the LCD, the second line is responsible for handing the touch coordinates to LVGL, and the third line is responsible for creating the interface objects. If any one of the three is missing, the screen may still light up, but a complete, interactive UI will not appear.

pinMode(18, OUTPUT);
pinMode(38, OUTPUT);
digitalWrite(38, HIGH);

GPIO18 is used for the lamp-control output of the UI in this lesson; Lamp_on / Lamp_off will control its high and low levels. GPIO38 is used to turn on the screen backlight; if GPIO38 is not pulled high, the LCD may have initialized successfully, but the screen will still appear black.

void loop()
{
  lv_timer_handler();
  delay(5);
}

lv_timer_handler() must be executed continuously for LVGL to refresh animations, process touch events, and respond to button clicks. The 5 ms delay keeps the main loop responsive while giving the system a little scheduling time.

7. UI Creation Steps

The UI in this section is created using SquareLine Studio. The official workflow is to first create an Arduino + TFT_eSPI type project, then add the lamp image, an On button, and an Off button, and finally bind the Lamp_on and Lamp_off callback functions to the two buttons respectively and export the UI files.

For SquareLine Studio download and basic installation, refer to:

7.1 Creating the SquareLine Project

  1. Open SquareLine Studio and click Create at the top. When creating a new project, you must select LVGL 9.1.0.
  2. In the board category, select Arduino.
  3. Select the Arduino with TFT_eSPI project template.

19

  1. Confirm the resolution based on the screen size. The course projects for 2.4-inch and 2.8-inch use 320 x 240.

Official screenshot: screen resolution reference for different sizes

  1. In Project Settings on the right, set the project name and path, and confirm the key parameters:
Resolution: 320 x 240
Color depth: 16 bit
LVGL version: 9.1.0
Theme: Light
Multilanguage: Disable

20

  1. After confirming the parameters, click CREATE to create the project.

WeCom screenshot_17852958807489

7.2 Importing the Lamp Image Asset

  1. After entering the project editing interface, switch to the Assets area at the bottom.
  2. Click ADD FILE INTO ASSETS to import the lamp image asset into the project resources.
  3. Of course, you can also choose other images you prefer to use.
  4. Image asset download link: lamp_28.png

Official screenshot: open Assets and add image asset

  1. After the import is complete, drag the lamp image asset onto the Screen1 canvas. Adjust the position of the image on the canvas so that it is on the left side of the screen, leaving space for the button area on the right.

Official screenshot: place the lamp image onto the canvas

7.3 Adding the On Button

  1. In the left Widgets > BASIC, select Button.
  2. Drag the Button onto the right side of the canvas as the On button.

Official screenshot: add a Button widget

  1. Select the button and adjust its position and size in the Inspector on the right.

Official screenshot: adjust button size and position

  1. Expand STYLE (MAIN) > Background and set the button background color to orange-red; the official example color is FF5529.

Official screenshot: set the On button background color

  1. Add a Label from the left Widgets > BASIC and drag it into the button.

Official screenshot: add a Label to the button

  1. Select the Label and change the text to On in the text box on the right.

Official screenshot: set the Label text to On

  1. Adjust the Label style, set the text color to white, and choose an appropriate font size. Here montserrat 40 is used.

Official screenshot: set the On text color and font size

  1. In the Hierarchy panel on the right, confirm that Label is a child control of Button.

Official screenshot: confirm the Label is under the Button

7.4 Duplicating and Creating the Off Button

  1. In the Hierarchy panel, select the already-created Button1. 2. Open the More menu and select Copy.

Screenshot: Copy the On button

  1. Paste it as a new button on the canvas or in the hierarchy. Use Paste as child to complete the copy.

Screenshot: Paste the copied button

  1. Move the copied button below the On button.

Screenshot: Move the copied button

  1. Select the Label inside the new button and change its text to Off.

Screenshot: Set the Label text to Off

  1. Adjust the position and background color of the two buttons as needed so that the On and Off buttons are arranged vertically on the right side.

Screenshot: Finish the On and Off button styles

7.5 Configuring Button Events

  1. Select the On button and scroll the Inspector on the right to the EVENTS section.
  2. Click ADD EVENT to add an event.

Screenshot: Add an event for the On button

  1. Set Trigger to RELEASED.
  2. Set Action to CALL FUNCTION.
  3. Enter the following for Function name:
Lamp_on

Screenshot: Bind Lamp_on to the On button

  1. Select the Off button and add an event in the same way.
  2. Set Trigger to RELEASED, set Action to CALL FUNCTION, and enter the following for Function name:
Lamp_off

Screenshot: Bind Lamp_off to the Off button

  1. Click the play button in the top-right corner to enter preview mode and check that the On and Off buttons can be clicked normally.

Screenshot: Click the play button to enter preview

Screenshot: Play mode active

Note: The function name set in Call function must match the function name in the Arduino project exactly. In this lesson, the ON button uses Lamp_on and the Off button uses Lamp_off; any error in capitalization or letter case will make the button clickable but unable to control GPIO18.

7.6 Setting the Export Path

  1. Click File > Project Settings to open the project settings.

Screenshot: Open Project Settings

  1. Set the export path in FILE EXPORT. In the example, Project Export Root points to the SquareLine project directory, and UI Files Export Path points to the Output folder.

Screenshot: Set the UI file export path

  1. Set LVGL Include Path to:
lvgl.h
  1. Check Flat export (exports all files to one folder), then click APPLY CHANGES.

Screenshot: Set lvgl.h, Flat export, and apply

Note: It is recommended to check Flat export so that the exported UI files are consolidated into a single folder, making it less likely to omit files when copying them into the Arduino project later.

7.7 Exporting the UI Files and Copying Them to the Arduino Project

  1. Click the top menu Export > Export UI Files.

Screenshot: Export UI Files

  1. Open the exported Output folder and confirm that it contains the UI source files, event files, helper files, and image resource files. Common files include:
ui.c
ui.h
ui_events.c
ui_events.h
ui_helpers.c
ui_helpers.h
ui_Screen1.c
ui_comp_hook.c
ui_img_lamp_28_png.c

Screenshot: UI files in the Output folder

  1. Copy the exported UI files into the Arduino project directory 2.4_2.8_LVGL, placing them at the same level as 2.4_2.8_LVGL.ino, LovyanGFX_Driver.h, pins_config.h, and touch.h.

image-20260729121352218

Note: The 2.4-inch / 2.8-inch displays must use a 320 x 240 resolution; if you mistakenly select 480 x 320 or 800 x 480, the exported UI coordinates and image dimensions will not match the screen. 2. The UI files in the local course code are exported from a SquareLine Studio / LVGL 9.1.0 project; confirm the destination path before exporting the UI files; when copying files, do not omit image resource files such as ui_img_lamp_28_png.c, otherwise the compilation will report undefined image symbols.

8. Observations

Click the ON and Off buttons on the screen and observe whether the lamp/LED on the UART1 interface toggles on and off.

Screenshot: Click On to turn the lamp on

Screenshot: Click Off to turn the lamp off

9. Code Download