1. LVGL基础显示与触摸初始化

LVGL基础显示与触摸初始化 学习卡片

任务目标

在ESP32开发板上完成LVGL图形库的基础初始化,配置显示驱动(ILI9341 SPI屏)和触摸驱动(XPT2046电阻触摸),最终实现屏幕点亮并显示一个可点击的按钮,触摸按钮时LED状态翻转,以此验证显示与触摸链路均正常工作。

环境与依赖

  • 硬件:ESP32 DevKitC、ILI9341 2.4寸SPI屏(含XPT2046触摸)、LED(GPIO2)
  • 软件:ESP-IDF v5.x,LVGL v8.3.x(作为ESP-IDF组件)
  • 接线:ILI9341_SCLK=GPIO18, MOSI=GPIO23, CS=GPIO5, DC=GPIO4, RST=GPIO16, BL=GPIO17;XPT2046_SCLK=GPIO18, MOSI=GPIO23, MISO=GPIO19, CS=GPIO21, IRQ=GPIO22

关键完整代码

// main.c — LVGL显示与触摸初始化最小实现
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "lvgl.h"
#include "ili9341.h"      // 需在components中提供
#include "xpt2046.h"      // 需在components中提供

#define PIN_LED 2
#define LVGL_TICK_MS 5

static const char *TAG = "lvgl_demo";
static spi_device_handle_t spi_dev;

// 显示缓冲(LVGL推荐双缓冲)
static lv_disp_draw_buf_t disp_buf;
static lv_color_t buf1[240*10];
static lv_color_t buf2[240*10];

// 触摸状态
static bool touch_pressed = false;
static lv_coord_t touch_x = 0, touch_y = 0;

// ---- SPI初始化 ----
static void spi_init(void) {
    spi_bus_config_t buscfg = {
        .mosi_io_num = 23,
        .miso_io_num = 19,
        .sclk_io_num = 18,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
        .max_transfer_sz = 240*10*2
    };
    spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO);

    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = 40*1000*1000,
        .mode = 0,
        .spics_io_num = 5,
        .queue_size = 7,
    };
    spi_bus_add_device(SPI2_HOST, &devcfg, &spi_dev);
}

// ---- 显示驱动回调(LVGL调用)----
static void disp_flush(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p) {
    ili9341_draw_bitmap(area->x1, area->y1, area->x2, area->y2, (uint16_t*)color_p);
    lv_disp_flush_ready(drv);
}

// ---- 触摸驱动回调(LVGL调用)----
static void touch_read(lv_indev_drv_t *drv, lv_indev_data_t *data) {
    data->state = touch_pressed ? LV_INDEV_STATE_PR : LV_INDEV_STATE_REL;
    data->point.x = touch_x;
    data->point.y = touch_y;
}

// ---- 触摸中断/轮询更新(简化:轮询读取)----
static void touch_update_task(void *arg) {
    while (1) {
        uint16_t x, y;
        if (xpt2046_read(&x, &y)) {   // 返回true表示触摸按下
            touch_pressed = true;
            touch_x = x;
            touch_y = y;
        } else {
            touch_pressed = false;
        }
        vTaskDelay(pdMS_TO_TICKS(10));
    }
}

// ---- 按钮事件回调 ----
static void btn_event_cb(lv_event_t *e) {
    static bool led_on = false;
    led_on = !led_on;
    gpio_set_level(PIN_LED, led_on);
}

// ---- LVGL初始化 ----
static void lvgl_init(void) {
    lv_init();

    // 显示缓冲
    lv_disp_draw_buf_init(&disp_buf, buf1, buf2, 240*10);

    // 显示驱动
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = 240;
    disp_drv.ver_res = 320;
    disp_drv.flush_cb = disp_flush;
    disp_drv.draw_buf = &disp_buf;
    lv_disp_drv_register(&disp_drv);

    // 触摸驱动
    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touch_read;
    lv_indev_drv_register(&indev_drv);
}

// ---- LVGL心跳与任务处理 ----
static void lvgl_task(void *arg) {
    while (1) {
        lv_tick_inc(LVGL_TICK_MS);
        lv_timer_handler();
        vTaskDelay(pdMS_TO_TICKS(LVGL_TICK_MS));
    }
}

void app_main(void) {
    // GPIO
    gpio_set_direction(PIN_LED, GPIO_MODE_OUTPUT);
    gpio_set_level(PIN_LED, 0);

    // 硬件初始化
    spi_init();
    ili9341_init(spi_dev, 16, 17);   // DC=GPIO4, RST=GPIO16, BL=GPIO17
    xpt2046_init(spi_dev, 21, 22);   // CS=GPIO21, IRQ=GPIO22

    // LVGL
    lvgl_init();

    // 创建UI:一个按钮
    lv_obj_t *btn = lv_btn_create(lv_scr_act());
    lv_obj_set_size(btn, 120, 50);
    lv_obj_center(btn);
    lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);

    lv_obj_t *label = lv_label_create(btn);
    lv_label_set_text(label, "Toggle LED");
    lv_obj_center(label);

    // 启动任务
    xTaskCreate(touch_update_task, "touch", 4096, NULL, 5, NULL);
    xTaskCreate(lvgl_task, "lvgl", 8192, NULL, 5, NULL);

    ESP_LOGI(TAG, "LVGL initialized. Touch button to toggle LED.");
}

运行与验证

  1. 编译烧录:在ESP-IDF终端执行 idf.py set-target esp32 → idf.py build flash monitor。
  2. 屏幕显示:上电后屏幕应点亮,中央显示一个带“Toggle LED”文字的按钮。
  3. 触摸验证:用手指或触控笔点击按钮,观察GPIO2外接LED状态翻转(亮→灭→亮)。同时串口监视器应输出日志 LVGL initialized...。
  4. 通过标准:屏幕无花屏/闪烁,按钮点击响应灵敏(每次点击LED翻转一次),触摸位置与视觉按钮位置基本吻合。

常见问题

  • 屏幕白屏:检查SPI接线(SCLK/MOSI/CS/DC)及背光引脚是否使能(BL接GPIO17并拉高)。
  • 触摸无响应:确认XPT2046的IRQ引脚是否接对(GPIO22),并检查触摸驱动初始化时CS引脚号是否正确。
  • 编译报错:确保LVGL组件已通过 idf.py add-dependency "lvgl/lvgl^8.3.0" 添加,且ili9341.h和xpt2046.h头文件路径已包含。