Skip to content

2.4_2.8inch_Lesson04_DHT20_Temperature_and_Humidity_Display

1. Course Introduction

This lesson brings together the I2C interface, the DHT20 temperature and humidity sensor, touch input, and the LVGL 9.1.0 interface. The program periodically reads temperature and humidity and updates the values onto on-screen labels, allowing learners to see how sensor data drives the UI.

This lesson's project runs in ESP-IDF 5.5.4, with the target chip being esp32s3. The local project is:

2.4_2.8_ESP-IDF/Lesson04-DHT_Screen_24_28/

Reference materials:

2. Learning Objectives

  • Understand the I2C address conflict between the 2.4-inch / 2.8-inch touch chip and the DHT20.
  • Complete the DHT20 sensor connection, initialization, and temperature/humidity readings.
  • Write sensor data into an LVGL label and observe the on-screen changes.
  • Understand the FreeRTOS tasks, mutex, and lvgl_port_lock() in the upgraded ESP-IDF project.
  • Judge whether the sensor, UI, and display refresh are working correctly based on the on-screen values and serial logs.

3. What You Need to Prepare

  • CrowPanel Advance 2.4-inch Version 1.1 / 1.2 development board.
  • A USB data cable that supports data transfer.
  • DHT20 temperature and humidity sensor.
  • VS Code, the ESP-IDF extension, and ESP-IDF 5.5.4 installed in Lesson01.
  • SquareLine Studio, for recreating or exporting the temperature/humidity UI.
  • This lesson's project: Lesson04-DHT_Screen_24_28

Code and Resource Download

Code download: - Lesson04-DHT_Screen_24_28

Resource download: - 320x240 assets - source material

4. ESP-IDF Software Operation Steps

  1. In VS Code, select File -> Open Folder... and open the root directory of this lesson's ESP-IDF project.
2.4_2.8_ESP-IDF/Lesson04-DHT_Screen_24_28/

Select Open Folder

image-20260729201457486

  1. After opening the project, first run Build -> Delete to clear the old project's generated build cache and path records. This step is recommended after switching computers, changing the ESP-IDF version, or copying the project.

image-20260729201535684

  1. Confirm that VS Code has loaded ESP-IDF 5.5.4. If the version shown in the status bar or the ESP-IDF extension is incorrect, go back to the Lesson01 installation steps and reselect the 5.5.4 environment.

Confirm ESP-IDF Version

  1. Connect the DHT20 to the UART1-OUT interface and use a USB cable that supports data transfer to connect the development board to the computer.

DHT20 Hardware Connection

  1. Click the serial port location in the VS Code bottom status bar and select the actual COM port recognized by the computer.

image-20260729201639679

  1. Click the target chip location in the status bar, or run the command ESP-IDF: Set Espressif Device Target, and select esp32s3 as the target chip. The image is provided only to illustrate the entry point; the actual selection should follow this course's esp32s3.

image-20260729201728864

image-20260729201747790

  1. When selecting the debug configuration, choose the one that matches the ESP32-S3 development board, such as the ESP32-S3 built-in USB-JTAG or the corresponding ESP32-S3 OpenOCD configuration. A normal UART flash mostly depends on having the serial port and target chip set correctly.

image-20260729182506501

  1. Click the Build button in the status bar to start compiling, or run the following in the ESP-IDF terminal:
idf.py build

The first build needs to download and generate dependencies, so it will take longer; continue to flashing only after you see the Build success message.

image-20260729192536042

image-20260729201922579

image-20260729202057664

  1. Confirm that the flashing method is UART, then click the lightning icon Flash, or run the following in the ESP-IDF terminal:
idf.py -p COMx flash

image-20260729202147353

image-20260729190620727

image-20260729202530331

5. Hardware Operation Steps

On the 2.4-inch and 2.8-inch versions, the onboard touch chip's I2C address is 0x38, and the DHT20's default I2C address is also 0x38. Connecting the DHT20 directly to IIC-OUT would cause an address conflict. Therefore, this section reconfigures IO17 and IO18 on UART1-OUT as I2C lines to connect the DHT20 temperature and humidity sensor.

  1. Connect the DHT20 to the UART1-OUT interface, with IO17 as SDA and IO18 as SCL, and connect power and GND as well.

DHT20 Hardware Connection

  1. After flashing completes, observe the screen display; the temperature and humidity values should keep refreshing.

Temperature and Humidity Display Effect

6. Key Code Explanation

6.1 Shared Data and Mutex

TaskHandle_t read_data_h;
static SemaphoreHandle_t data_mutex = NULL;

typedef struct {
    float temperature;
    float humidity;
} sensor_data_t;

static sensor_data_t measurements = {0};

static lv_obj_t *label_temp;
static lv_obj_t *label_humid;

measurements holds the latest temperature and humidity. The sensor task writes to it, and the display task reads from it. When two tasks access the same data simultaneously, the data_mutex mutex is needed to protect it and avoid reading half-written data that another task is modifying. label_temp and label_humid are two LVGL label objects created dynamically in the current code, used to display values on top of the background UI.

6.2 Screen Initialization and Dynamic Labels

void init_screen(void)
{
    gpio_set_direction(GPIO_NUM_38, GPIO_MODE_OUTPUT);
    gpio_set_level(GPIO_NUM_38, 1);

    setup_gpio_init();
    setup_spi_init();
    device_lcd_init();
    soft_drv_lvgl_port_init();

    lv_ui_init();

    if (lvgl_port_lock(0)) {
        label_temp = lv_label_create(lv_scr_act());
        lv_obj_set_style_text_font(label_temp, &lv_font_montserrat_14, LV_STATE_DEFAULT);
        lv_obj_set_style_text_color(label_temp, lv_color_hex(0x000000), LV_STATE_DEFAULT);
        lv_obj_align(label_temp, LV_ALIGN_TOP_LEFT, 130, 60);

        label_humid = lv_label_create(lv_scr_act());
        lv_obj_set_style_text_font(label_humid, &lv_font_montserrat_14, LV_STATE_DEFAULT);
        lv_obj_set_style_text_color(label_humid, lv_color_hex(0x000000), LV_STATE_DEFAULT);
        lv_obj_align(label_humid, LV_ALIGN_TOP_LEFT, 130, 135);
        lvgl_port_unlock();
    }
}

init_screen() first pulls GPIO38 high, then initializes GPIO, SPI, LCD, and the LVGL port, and then calls lv_ui_init() to load the background interface exported from SquareLine Studio. The current Lesson 4 code does not use touch input, so the touch registration related code has been removed from soft_drv_lvgl_port.c.

After the background interface is loaded, the program creates two new LVGL labels: the temperature label aligned to (130, 60) and the humidity label aligned to (130, 135). These coordinates must correspond to the number positions reserved on the UI background image. Because labels are LVGL objects, creation must be wrapped with lvgl_port_lock() first, then lvgl_port_unlock() after creation is complete.

6.3 LVGL Port Display Configuration

lvgl_port_display_cfg_t disp_cfg = {};
disp_cfg.io_handle = lcd_io;
disp_cfg.panel_handle = lcd_panel;
disp_cfg.buffer_size = LCD_H_RES * LCD_DRAW_BUFF_HEIGHT;
disp_cfg.double_buffer = LCD_DEAW_BUFF_DOUBLE;
disp_cfg.hres = LCD_H_RES;
disp_cfg.vres = LCD_V_RES;
disp_cfg.color_format = LV_COLOR_FORMAT_RGB565;
disp_cfg.rotation.swap_xy = true;
disp_cfg.rotation.mirror_x = false;
disp_cfg.rotation.mirror_y = true;
disp_cfg.flags.buff_dma = true;
disp_cfg.flags.swap_bytes = true;
lvgl_disp = lvgl_port_add_disp(&disp_cfg);

This code is located in main/soft_drv/soft_drv_lvgl_port.c. It registers the LCD panel handle with LVGL and specifies the screen resolution, RGB565 color format, rotation direction, and DMA screen refresh. flags.swap_bytes = true adapts to the byte order of RGB565 data under LVGL 9.x and prevents abnormal color display.

6.4 DHT20 Initialization

void dht20_begin(void)
{
    static esp_err_t err = ESP_OK;
    err = i2c_init();
    if (err != ESP_OK) {
        DHT20_ERROR("i2c init: [ %s ]", esp_err_to_name(err));
    }
    vTaskDelay(200/portTICK_PERIOD_MS);
    dht20_reset_sensor();
}

The DHT20's I2C is initialized by the peripheral/i2c component, not the setup_i2c_init() used for touch in Lesson 3. The current sdkconfig.defaults configures the DHT20 to use SCL=GPIO18, SDA=GPIO17, at 100 kHz, and the sensor address is provided by CONFIG_I2C_ADDRESS. After initializing I2C, dht20_begin() waits 200 ms, then checks and resets the DHT20 status register so the sensor enters a readable state.

6.5 DHT20 Data Conversion

float dht20_read_data(dht20_data_t *data)
{
    static uint8_t txbuf[3] = {0xAC, 0x33, 0x00};
    static uint8_t status_byte[1] = { 0 };
    static uint8_t rxdata[7] = {0};

    i2c_write(DHT20_I2C_ADDRESS, txbuf, 3);
    vTaskDelay(80 / portTICK_PERIOD_MS);
    i2c_read(DHT20_I2C_ADDRESS, status_byte, 1);

    i2c_read(DHT20_I2C_ADDRESS, rxdata, 7);

    uint8_t get_crc = dht20_crc8(rxdata, 6);
    if(rxdata[6] == get_crc)
    {
        uint32_t raw_humid = rxdata[1];
        raw_humid <<= 8;
        raw_humid += rxdata[2];
        raw_humid <<= 4;
        raw_humid += rxdata[3] >> 4;
        data->humidity = (float)(raw_humid / 1048576.0f) * 100.0f;

        uint32_t raw_temp = (rxdata[3] & 0x0F);
        raw_temp <<= 8;
        raw_temp += rxdata[4];
        raw_temp <<= 8;
        raw_temp += rxdata[5];
        data->temperature = (float)(raw_temp / 1048576.0f) * 200.0f - 50.0f;
    }
    else
    {
        return DHT20_ERROR_CHECKSUM;
    }
    return 0.0f;
}

0xAC, 0x33, 0x00 is the DHT20 measurement trigger command. After completing the measurement, the sensor returns 7 bytes of data, with the last byte being the CRC check. Once the check passes, the code converts the 20-bit raw humidity value into a percentage and the 20-bit raw temperature value into degrees Celsius. If the CRC does not match, the function returns an error and the main task will not update the displayed data.

6.6 Sensor Reading Task

void dht20_read_task(void *arg)
{
    (void)arg;
    dht20_data_t raw_data;

    while(1) {
        if(dht20_read_data(&raw_data) >= 0.0f) {
            if(xSemaphoreTake(data_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
                measurements.temperature = raw_data.temperature;
                measurements.humidity = raw_data.humidity;
                xSemaphoreGive(data_mutex);
            }
        }
        vTaskDelay(pdMS_TO_TICKS(2000));
    }
}

The sensor task reads the DHT20 once every 2 seconds. After a successful read, the task takes data_mutex, writes the latest temperature and humidity into measurements, and then releases the mutex. The DHT20 does not require high-frequency reads; a 2-second interval is more stable and also reduces I2C bus occupancy.

6.7 Display Refresh Task

void update_display_task(void *arg)
{
    (void)arg;
    char buffer[16];

    while(1) {
        if(xSemaphoreTake(data_mutex, pdMS_TO_TICKS(100)) == pdTRUE) {
            snprintf(buffer, sizeof(buffer), "%.1f", measurements.temperature);
            char humid_buf[16];
            snprintf(humid_buf, sizeof(humid_buf), "%.1f", measurements.humidity);
            xSemaphoreGive(data_mutex);

            if (lvgl_port_lock(100)) {
                lv_label_set_text(label_temp, buffer);
                lv_label_set_text(label_humid, humid_buf);
                lvgl_port_unlock();
            }
        }
        vTaskDelay(pdMS_TO_TICKS(500));
    }
}

The display task refreshes the interface every 500 ms.

It first reads measurements under the protection of a mutex and formats it into a string with one decimal place using snprintf(); it then releases the data lock, acquires the LVGL graphics lock, and calls lv_label_set_text() to update the two labels. Using the data lock and the LVGL lock separately reduces the lock hold time and makes scheduling between tasks easier.

6.8 app_main() Startup Sequence

void app_main(void)
{
    init_screen();
    dht20_begin();

    data_mutex = xSemaphoreCreateMutex();

    xTaskCreate(dht20_read_task, "sensor", 2048, NULL, 3, &read_data_h);
    xTaskCreate(update_display_task, "display", 2048, NULL, 2, NULL);

    while(1) {
        vTaskDelay(portMAX_DELAY);
    }
}

The main function executes in the order shown in the current source code: it first initializes the screen, then initializes the DHT20, then creates the mutex, and finally creates the two FreeRTOS tasks. The sensor task has a priority of 3 and is responsible for data acquisition; the display task has a priority of 2 and is responsible for refreshing the interface. The main task then enters an infinite delay to keep the application from exiting.

Note: The current code does not call the setup_i2c_init() and device_touch_init() functions from Lesson 3 inside init_screen(), because this lesson only displays temperature and humidity and does not require touch input. If touch interaction is added later, the touch device will need to be re-registered following the structure from Lesson 3.

7. UI Asset Creation Process

Next, we use the example of connecting a temperature and humidity sensor to the UART1-OUT interface. To make the experiment results easier to observe, this section uses LVGL to build an interface that displays temperature and humidity.

For SquareLine Studio download and installation instructions, refer to: https://www.elecrow.com/wiki/Create_LVGL_UI_with_SquareLine_Studio.html

  1. Open SquareLine Studio.

After launching SquareLine Studio, you will enter the main interface. If the software is not yet installed, refer to Lesson 3 or the link above to complete the installation first. This section uses it to generate LVGL's ui.c, ui.h, image assets, and widget object files.

Open SquareLine Studio

  1. Create a SquareLine Studio UI project template.

On the welcome screen, first click Create at the top. When creating a new project, you must select LVGL 9.1.0. Then select the TFT_eSPI-compatible category on the left, and choose the TFT_eSPI template in the middle. This template generates UI files that will be copied into the ESP-IDF project.

Note: When selecting this framework template, SquareLine Studio generates template code suitable for TFT_eSPI. SquareLine Studio also supports other graphics libraries; when switching to different hardware, the display code must be modified to match the actual library.

 ![19](./assets/images/2.4_2.8_ESP-IDF-04-DHT20_Temperature_and_Humidity_Display/2.4_2.8_ESP-IDF-04_18.webp)
  1. Confirm the resolution based on the screen size. The 2.4-inch and 2.8-inch course projects use 320 x 240.

Modify the resolution

  1. In the 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.

Enterprise WeChat screenshot_17852958807489

  1. Prepare the background image assets provided in the course.

This section's interface requires the background images provided in the course. Different screen sizes require assets of the corresponding resolution; the 2.4-inch and 2.8-inch products can use the images in the 320x240 folder. Add the images to the SquareLine Studio project's assets so they can be selected on the canvas later.

Image asset download link: 320x240 assets

Add the background image

Asset link:

CrowPanel-Advance-2.4-HMI-ESP32-S3-AI-Powered-IPS-Touch-Screen-320x240/example/V1.1_and_V1.2/Arduino_Code/lesson-05/source material/320x240

  1. Place the background image onto the interface canvas.

In the widget or asset area on the left, select the image widget and add the background image to the current Screen. After adding it, select the just-imported background image in the properties panel on the right, and adjust its position and size so it fills the entire 320*240 canvas.

Add the background image to the canvas 1

Add the background image to the canvas 2

  1. Add the temperature value text label.

Since the screen needs to display the temperature value, a text label must be added to the temperature display area. After selecting the Text or Label widget, drag it into the temperature box so the value appears at the position reserved in the background image.

Add the text label

  1. Set an object name for the temperature label.

In the properties panel on the right, change the label's name. It is recommended to keep it consistent with the object name used in the code, for example TempLabel1. The ESP-IDF project code will later locate this label through the exported object name and write the temperature value read from the DHT20 into it.

Set the label name

  1. Fill in the default display value for the temperature label.

    Enter a default temperature value in the label's text content, for example 25. This value is only placeholder content during the UI design phase; once the program runs, it will be replaced by the temperature value read in real time from the sensor.

    Fill in the default temperature value

  2. Set the font style of the temperature label.

    Set the label's font color and size based on the background image color. To make the values clearly visible on the screen, you can set the font color to white and choose an appropriate font size, for example using larger digits to display the temperature value in the screenshot.

    Set the font color and size

  3. Duplicate the temperature label as the humidity label.

    The temperature and humidity display styles are essentially the same, so you can duplicate the temperature label you just created and then move the duplicated label to the humidity display area. This keeps the font size, color, and alignment of the two values consistent.

    Duplicate the label

    Humidity label position

  4. Modify the humidity label name and default value.

    Select the duplicated label, change its name in the properties panel on the right to the humidity label object name, for example HumiLabel2; then change the default text to a sample humidity value, for example 50. The code will update the humidity display through this object name.

    Modify the humidity label

  5. Check the overall effect of the UI interface.

    After the background image, temperature label, and humidity label are all set up, check whether the two values are located in their respective areas, whether the fonts are clear, and whether there is any offset or obstruction. Once the effect is confirmed correct, the UI interface required for this section is complete.

    UI interface completed

  6. Open the project export settings.

    Click Project Setting in SquareLine Studio to enter the project export configuration page. The export settings determine where the generated UI files are placed and which LVGL header file is included in the code.

    Project settings

  7. Set the UI file export path.

    In the export path, select the directory used when creating the project earlier, or customize a location dedicated to storing the exported files. It is recommended that the path not contain too many nested levels, for ease of copying files later.

    Set the export path

  8. Create an Output folder to receive the exported files.

    Under the path set earlier, create a new Output folder. The ui.c, ui.h, ui_helpers.*, image .c files, and so on exported by SquareLine Studio will all be placed in this folder.

    Create the Output folder

  9. Set the LVGL header file and export method.

    In the project settings, set the LVGL include to lvgl.h and select Flat export. Flat export places the exported UI files in the same directory level, making them easier to copy into the project.

    Select Flat export

  10. Confirm the export settings.

    After checking that the export path, lvgl.h, and Flat export are all set correctly, click the confirm button to save the project settings.

    Confirm the settings

  11. Export the UI files.

    After returning to the main interface, perform the export operation to generate the UI interface files designed in this section. Once exporting is complete, go to the Output folder to view the generated results.

    Export the UI files

  12. Copy the generated UI files.

    Open the Output folder you just created and select the exported UI-related files. Typically you need to copy ui.c, ui.h, ui_helpers.c, ui_helpers.h, the interface files, image asset .c files, and so on.

    image-20260729145025449

  13. Paste into this section's project directory.

    Paste the copied UI files into this section's code project directory, overwriting or replacing the original UI files.

    image-20260729203256808

8. Experimental Observations

Observe the screen display; the temperature and humidity values should refresh continuously.

24_image_05_02

9. Code Download

Code download link: Lesson04-DHT_Screen_24_28

Image asset download link: source material