esp32开发与应用(3.5寸触摸屏+lvgl)

📅 2026/7/10 23:15:35 👁️ 阅读次数 📝 编程学习
esp32开发与应用(3.5寸触摸屏+lvgl)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】

既然屏幕的驱动、触控的驱动也好了,那么下面要做的就是移植lvgl。其实不用lvgl,自己直接操作屏幕开发也是可以的,但就是比较麻烦,开发起来也不是很美观,看上去并不象一个产品。所以,一般驱动ok了,就会移植lvgl。

1、lvgl vs qt

由于版权的关系,lvgl用的比较多。而目前我们开发的环境是mcu平台,所以基本上还是lvgl为主。

2、lvgl版本

这里选用的版本不是最新的,而是8.3.11,但已经可以满足我们需求了。

3、更新idf_component.yml文件

这部分,主要是加上后面我们需要引入的库,

## IDF Component Manager Manifest File dependencies: ## Required IDF version idf: version: '>=4.1.0' # # Put list of dependencies here # # For components maintained by Espressif: # component: "~1.0.0" # # For 3rd party components: # username/component: ">=1.0.0,<2.0.0" # username2/component2: # version: "~1.0.0" # # For transient dependencies `public` flag can be set. # # `public` flag doesn't have an effect dependencies of the `main` component. # # All dependencies of `main` are public by default. # public: true lvgl/lvgl: version: "^8.3.11" espressif/esp_lvgl_port: version: "^2.3.0"

4、删除不必要的CMakeLists.txt内容

和大家想的不一样,这里要尽量保证不引入额外的库,

idf_component_register(SRCS "main.c" INCLUDE_DIRS ".")

5、用ai编写demo文件

因为是lvgl demo,我们可以要求ai编写一个进度条的demo,同时在进度条更新的时候更新label。实际测试的时候,可能会出现各种问题,比如lcd屏幕拿错、接线接错、频率设置错、函数位置放错等等。所以,实际开发的时候,反反复复是常有的事情,大家还是需要耐心一点。注意,触摸的pin都要连上。

#include <stdio.h> #include <stdlib.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/spi_master.h" #include "driver/gpio.h" #include "esp_log.h" #include "esp_timer.h" #include "lvgl.h" // ================= CONFIG ================= #define LCD_W 480 #define LCD_H 320 #define PIN_MOSI 13 #define PIN_CLK 14 #define PIN_CS 15 #define PIN_DC 2 #define PIN_RST 4 #define PIN_BL 12 #define TP_MOSI 23 #define TP_MISO 19 #define TP_CLK 18 #define TP_CS 5 #define TP_IRQ 27 static spi_device_handle_t spi_lcd; static spi_device_handle_t spi_tp; static const char *TAG = "ILI9488_LVGL"; // ====================================================== // GPIO control // ====================================================== static inline void dc_cmd(void) { gpio_set_level(PIN_DC, 0); } static inline void dc_data(void) { gpio_set_level(PIN_DC, 1); } static void lcd_reset(void) { gpio_set_level(PIN_RST, 0); vTaskDelay(pdMS_TO_TICKS(100)); gpio_set_level(PIN_RST, 1); vTaskDelay(pdMS_TO_TICKS(150)); } // ====================================================== // SPI CMD / DATA // ====================================================== static void lcd_cmd(uint8_t cmd) { spi_transaction_t t = { .length = 8, .tx_buffer = &cmd, }; dc_cmd(); spi_device_polling_transmit(spi_lcd, &t); } static void lcd_data(const void *data, int len) { spi_transaction_t t = { .length = len * 8, .tx_buffer = data, }; dc_data(); spi_device_polling_transmit(spi_lcd, &t); } // ====================================================== // ILI9488 INIT (stable version) // ====================================================== static void ili9488_init(void) { lcd_reset(); lcd_cmd(0x01); // Software reset vTaskDelay(pdMS_TO_TICKS(120)); lcd_cmd(0x11); // Sleep out vTaskDelay(pdMS_TO_TICKS(120)); // RGB565 mode (important for stability) lcd_cmd(0x3A); uint8_t pix = 0x66; lcd_data(&pix, 1); // MADCTL (display orientation) lcd_cmd(0x36); uint8_t mad = 0x28; // change to 0x28 if upside-down lcd_data(&mad, 1); lcd_cmd(0x29); // Display ON vTaskDelay(pdMS_TO_TICKS(50)); ESP_LOGI(TAG, "LCD init OK"); } // ====================================================== // Set drawing window // ====================================================== static void set_window(int x1,int y1,int x2,int y2) { uint8_t d[4]; lcd_cmd(0x2A); d[0]=x1>>8; d[1]=x1; d[2]=x2>>8; d[3]=x2; lcd_data(d,4); lcd_cmd(0x2B); d[0]=y1>>8; d[1]=y1; d[2]=y2>>8; d[3]=y2; lcd_data(d,4); lcd_cmd(0x2C); } // ====================================================== // Touch read (XPT2046 style) // ====================================================== static uint16_t tp_read(uint8_t cmd) { uint8_t tx[3] = {cmd,0,0}; uint8_t rx[3] = {0}; spi_transaction_t t = { .length = 24, .tx_buffer = tx, .rx_buffer = rx, }; spi_device_polling_transmit(spi_tp, &t); return ((rx[1] << 8) | rx[2]) >> 3; } static void touch_read(lv_indev_drv_t *drv, lv_indev_data_t *data) { if (gpio_get_level(TP_IRQ) == 0) { >6、编译和测试

实际开发的时候,还有一种debug方法比较好,那就是可以参考之前2.8寸屏时的demo。有的时候对比测试,反而效率更高。至于编译、烧录,这都是基本操作了。

7、lvgl继续引入触摸机制

和一般屏幕相比较,触摸屏的优势显而易见。所以lvglport之后,我们可以快速把触摸屏移植到lvgl,主要就是一个touch_read回调函数。这个回调函数里面,难的不是电压计算,而是如何把电压换算成坐标。毕竟屏幕的旋转,是经常遇到的事情。

搞定了屏幕位置读取,以及位置换算之后,就可以写两个按钮,看看触摸屏和lvgl结合起来,应用效果如何。当然,这里面主要的代码,还是ai来写。

#include <stdio.h> #include <stdlib.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/spi_master.h" #include "driver/gpio.h" #include "esp_log.h" #include "esp_timer.h" #include "lvgl.h" static void lvgl_task(void *arg); // ================= CONFIG ================= #define LCD_W 480 #define LCD_H 320 #define PIN_MOSI 13 #define PIN_CLK 14 #define PIN_CS 15 #define PIN_DC 2 #define PIN_RST 4 #define PIN_BL 12 #define TP_MOSI 23 #define TP_MISO 19 #define TP_CLK 18 #define TP_CS 5 #define TP_IRQ 27 static spi_device_handle_t spi_lcd; static spi_device_handle_t spi_tp; static const char *TAG = "ILI9488_LVGL"; // ====================================================== // GPIO control // ====================================================== static inline void dc_cmd(void) { gpio_set_level(PIN_DC, 0); } static inline void dc_data(void) { gpio_set_level(PIN_DC, 1); } static void lcd_reset(void) { gpio_set_level(PIN_RST, 0); vTaskDelay(pdMS_TO_TICKS(100)); gpio_set_level(PIN_RST, 1); vTaskDelay(pdMS_TO_TICKS(150)); } // ====================================================== // SPI CMD / DATA // ====================================================== static void lcd_cmd(uint8_t cmd) { spi_transaction_t t = { .length = 8, .tx_buffer = &cmd, }; dc_cmd(); spi_device_polling_transmit(spi_lcd, &t); } static void lcd_data(const void *data, int len) { spi_transaction_t t = { .length = len * 8, .tx_buffer = data, }; dc_data(); spi_device_polling_transmit(spi_lcd, &t); } // ====================================================== // ILI9488 INIT (stable version) // ====================================================== static void ili9488_init(void) { lcd_reset(); lcd_cmd(0x01); // Software reset vTaskDelay(pdMS_TO_TICKS(120)); lcd_cmd(0x11); // Sleep out vTaskDelay(pdMS_TO_TICKS(120)); // RGB565 mode (important for stability) lcd_cmd(0x3A); uint8_t pix = 0x66; lcd_data(&pix, 1); // MADCTL (display orientation) lcd_cmd(0x36); uint8_t mad = 0x28; // change to 0x28 if upside-down lcd_data(&mad, 1); lcd_cmd(0x29); // Display ON vTaskDelay(pdMS_TO_TICKS(50)); ESP_LOGI(TAG, "LCD init OK"); } // ====================================================== // Set drawing window // ====================================================== static void set_window(int x1,int y1,int x2,int y2) { uint8_t d[4]; lcd_cmd(0x2A); d[0]=x1>>8; d[1]=x1; d[2]=x2>>8; d[3]=x2; lcd_data(d,4); lcd_cmd(0x2B); d[0]=y1>>8; d[1]=y1; d[2]=y2>>8; d[3]=y2; lcd_data(d,4); lcd_cmd(0x2C); } // ====================================================== // Touch read (XPT2046 style) // ====================================================== static uint16_t tp_read(uint8_t cmd) { uint8_t tx[3] = {cmd,0,0}; uint8_t rx[3] = {0}; spi_transaction_t t = { .length = 24, .tx_buffer = tx, .rx_buffer = rx, }; spi_device_polling_transmit(spi_tp, &t); return ((rx[1] << 8) | rx[2]) >> 3; } static void touch_read(lv_indev_drv_t *drv, lv_indev_data_t *data) { static bool touched = false; if (gpio_get_level(TP_IRQ) == 0) { data->state = LV_INDEV_STATE_PRESSED; // Read raw touch coordinates uint16_t x_raw = tp_read(0xD0); uint16_t y_raw = tp_read(0x90); // Calibrate and convert to screen coordinates // Swap X and Y, and invert Y axis for correct orientation // Adjust these values based on your touch panel calibration int x = (LCD_W * (3900 - y_raw)) / 3900; // very critical, by feixiaoxing int y = LCD_H - 1 - (LCD_H * (x_raw - 100)) / 3900; // Clamp values if (x < 0) x = 0; if (x >= LCD_W) x = LCD_W - 1; if (y < 0) y = 0; if (y >= LCD_H) y = LCD_H - 1; data->point.x = x; data->point.y = y; if (!touched) { ESP_LOGI(TAG, "Touch: x=%d, y=%d (raw: x=%d, y=%d)", data->point.x, data->point.y, x_raw, y_raw); touched = true; } } else { data->state = LV_INDEV_STATE_RELEASED; touched = false; } } // ====================================================== // LVGL flush (stable version) // ====================================================== static uint8_t line_buf[480 * 3]; static void lcd_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) { int w = area->x2 - area->x1 + 1; int h = area->y2 - area->y1 + 1; set_window(area->x1, area->y1, area->x2, area->y2); dc_data(); spi_transaction_t t = { .length = w * 3 * 8, .tx_buffer = line_buf, }; for (int y = 0; y < h; y++) { for (int x = 0; x < w; x++) { uint16_t c = color_p[y * w + x].full; line_buf[x*3+0] = ((c >> 11) & 0x1F) << 3; line_buf[x*3+1] = ((c >> 5) & 0x3F) << 2; line_buf[x*3+2] = (c & 0x1F) << 3; } spi_device_polling_transmit(spi_lcd, &t); } lv_disp_flush_ready(disp); } // ====================================================== // UI // ====================================================== static lv_obj_t *bar; static lv_obj_t *label; static lv_obj_t *btn_inc; static lv_obj_t *btn_dec; static int progress = 0; // Button callback functions static void btn_inc_event_cb(lv_event_t *e) { lv_event_code_t code = lv_event_get_code(e); if (code == LV_EVENT_CLICKED) { if (progress < 100) { progress++; char buf[32]; sprintf(buf, "progress: %d%%", progress); lv_label_set_text(label, buf); lv_bar_set_value(bar, progress, LV_ANIM_ON); } } } static void btn_dec_event_cb(lv_event_t *e) { lv_event_code_t code = lv_event_get_code(e); if (code == LV_EVENT_CLICKED) { if (progress > 0) { progress--; char buf[32]; sprintf(buf, "progress: %d%%", progress); lv_label_set_text(label, buf); lv_bar_set_value(bar, progress, LV_ANIM_ON); } } } static void ui_create(void) { // Create progress bar bar = lv_bar_create(lv_scr_act()); lv_obj_set_size(bar, 300, 30); lv_obj_align(bar, LV_ALIGN_CENTER, 0, -60); lv_bar_set_range(bar, 0, 100); lv_bar_set_value(bar, 0, LV_ANIM_OFF); // Create label to show percentage label = lv_label_create(lv_scr_act()); lv_label_set_text(label, "progress: 0%"); lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); lv_obj_set_style_text_font(label, &lv_font_montserrat_14, LV_PART_MAIN); // Create increment button btn_inc = lv_btn_create(lv_scr_act()); lv_obj_set_size(btn_inc, 100, 40); lv_obj_align(btn_inc, LV_ALIGN_CENTER, -80, 60); lv_obj_add_event_cb(btn_inc, btn_inc_event_cb, LV_EVENT_ALL, NULL); lv_obj_t *label_inc = lv_label_create(btn_inc); lv_label_set_text(label_inc, "+"); lv_obj_center(label_inc); // Create decrement button btn_dec = lv_btn_create(lv_scr_act()); lv_obj_set_size(btn_dec, 100, 40); lv_obj_align(btn_dec, LV_ALIGN_CENTER, 80, 60); lv_obj_add_event_cb(btn_dec, btn_dec_event_cb, LV_EVENT_ALL, NULL); lv_obj_t *label_dec = lv_label_create(btn_dec); lv_label_set_text(label_dec, "-"); lv_obj_center(label_dec); } // ================= LVGL Tick Timer ================= static void lv_tick_cb(void *arg) { lv_tick_inc(1); // LVGL 1ms tick } // ====================================================== // Backlight control // ====================================================== static void backlight_init(void) { gpio_config_t io = { .pin_bit_mask = 1ULL << PIN_BL, .mode = GPIO_MODE_OUTPUT }; gpio_config(&io); gpio_set_level(PIN_BL, 1); } // ====================================================== // MAIN ENTRY // ====================================================== void app_main(void) { gpio_set_direction(PIN_DC, GPIO_MODE_OUTPUT); gpio_set_direction(PIN_RST, GPIO_MODE_OUTPUT); // ================= LCD SPI ================= spi_bus_config_t bus_lcd = { .mosi_io_num = PIN_MOSI, .miso_io_num = -1, .sclk_io_num = PIN_CLK, .max_transfer_sz = 1024 * 10 }; spi_bus_initialize(SPI2_HOST, &bus_lcd, SPI_DMA_CH_AUTO); spi_device_interface_config_t dev_lcd = { .clock_speed_hz = 20 * 1000 * 1000, // 20MHz .mode = 0, .spics_io_num = PIN_CS, .queue_size = 7, }; spi_bus_add_device(SPI2_HOST, &dev_lcd, &spi_lcd); // ================= TOUCH SPI ================= spi_bus_config_t bus_tp = { .mosi_io_num = TP_MOSI, .miso_io_num = TP_MISO, .sclk_io_num = TP_CLK, .max_transfer_sz = 32 }; spi_bus_initialize(SPI3_HOST, &bus_tp, SPI_DMA_CH_AUTO); spi_device_interface_config_t dev_tp = { .clock_speed_hz = 2 * 1000 * 1000, .mode = 0, .spics_io_num = TP_CS, .queue_size = 3, }; spi_bus_add_device(SPI3_HOST, &dev_tp, &spi_tp); gpio_set_direction(TP_IRQ, GPIO_MODE_INPUT); // ================= LCD INIT ================= ili9488_init(); backlight_init(); // ================= LVGL INIT ================= lv_init(); static lv_color_t *buf1; buf1 = heap_caps_malloc(LCD_W * 20 * sizeof(lv_color_t), MALLOC_CAP_DMA); static lv_disp_draw_buf_t draw_buf; lv_disp_draw_buf_init(&draw_buf, buf1, NULL, LCD_W * 20); // LVGL tick timer (1ms) esp_timer_handle_t timer; const esp_timer_create_args_t tick_args = { .callback = &lv_tick_cb, .name = "lv_tick" }; esp_timer_create(&tick_args, &timer); esp_timer_start_periodic(timer, 1000); // 1ms static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.hor_res = LCD_W; disp_drv.ver_res = LCD_H; disp_drv.flush_cb = lcd_flush; disp_drv.draw_buf = &draw_buf; disp_drv.full_refresh = 1; 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); ui_create(); // Create LVGL UI // Create LVGL task handler in separate task xTaskCreate(lvgl_task, "lvgl_task", 4096, NULL, 5, NULL); // Keep main task alive while (1) { vTaskDelay(pdMS_TO_TICKS(100)); } } // ================= LVGL Task ================= static void lvgl_task(void *arg) { while (1) { lv_timer_handler(); // LVGL main loop vTaskDelay(pdMS_TO_TICKS(10)); } }