第11章 实战派ESP32-S3驱动LVGL:从零移植到5大Demo运行详解

发布时间:2026/7/13 14:23:45

第11章 实战派ESP32-S3驱动LVGL:从零移植到5大Demo运行详解 第11章 实战派ESP32-S3驱动LVGL从零移植到5大Demo运行详解最近有不少朋友在问怎么给咱们的实战派ESP32-S3开发板配上漂亮的图形界面。今天我就来手把手带你从零开始把LVGL这个强大的图形库移植到开发板上并且一口气跑通5个官方Demo。整个过程我会把每一步的原理和踩过的坑都讲清楚让你不仅能跑起来更能理解背后的门道。1. 准备工作获取例程与开发环境咱们先从最基础的开始。你需要把开发板资料包里的【08-lcd_lvgl】这个例程复制到你自己的实验文件夹里然后用VSCode打开整个工程。接下来用USB线把开发板连接到电脑。在VSCode的ESP-IDF插件里选择正确的串口号目标芯片选esp32s3下载方式选“串口下载”。这些都设置好后点击那个“一键三联”按钮编译、下载、打开终端一气呵成等待程序烧录完成。程序跑起来后你会看到屏幕上开始播放一个“跑分”动画这就是LVGL的基准测试Demolv_demo_benchmark。它会依次展示LVGL的所有UI组件并实时显示每秒渲染的帧数FPS。等所有组件都跑一遍后会出一个总结表格你可以用手指在触摸屏上上下滑动来查看详细结果。如果你想试试其他Demo也很简单。打开工程里的main.c文件找到app_main函数你会看到下面这段代码void app_main(void) { bsp_i2c_init(); // I2C初始化 pca9557_init(); // IO扩展芯片初始化 bsp_lvgl_start(); // 初始化液晶屏lvgl接口 /* 下面5个demos 只打开1个运行 */ lv_demo_benchmark(); // lv_demo_keypad_encoder(); // lv_demo_music(); // lv_demo_stress(); // lv_demo_widgets(); }这里注释了4个Demo函数只运行了lv_demo_benchmark。你想跑哪个就把对应的函数取消注释然后把其他行注释掉再重新“一键三联”下载就行。这里有个小提示lv_demo_music音乐播放器Demo在咱们这块320*240分辨率的屏幕上显示效果不是最佳因为它推荐的最佳分辨率是480x272。其他几个Demo都没问题。2. 核心代码解析LVGL是如何驱动起来的光会跑Demo还不够咱们得知道它是怎么工作的。核心就在bsp_lvgl_start()这个函数里。咱们点开main.c仔细看看app_main函数。前两行bsp_i2c_init()和pca9557_init()是初始化I2C总线和IO扩展芯片的为后续的屏幕和触摸屏通信铺路。重头戏是第三行bsp_lvgl_start()它一口气完成了液晶屏初始化、LVGL接口绑定和触摸屏初始化。2.1 剖析 bsp_lvgl_start() 函数咱们找到这个函数的定义看看它到底干了啥// 开发板显示初始化 void bsp_lvgl_start(void) { /* 初始化LVGL */ lvgl_port_cfg_t lvgl_cfg ESP_LVGL_PORT_INIT_CONFIG(); lvgl_port_init(lvgl_cfg); /* 初始化液晶屏 并添加LVGL接口 */ disp bsp_display_lcd_init(); /* 初始化触摸屏 并添加LVGL接口 */ disp_indev bsp_display_indev_init(disp); /* 打开液晶屏背光 */ bsp_display_backlight_on(); }这个函数结构非常清晰分四步走初始化LVGL环境调用lvgl_port_init参数用默认配置ESP_LVGL_PORT_INIT_CONFIG()就行乐鑫已经帮我们配好了基础设置。初始化屏幕并绑定LVGL调用bsp_display_lcd_init()这个函数不仅启动了屏幕驱动更重要的是创建了一个LVGL能识别的“显示设备”。初始化触摸屏并绑定LVGL调用bsp_display_indev_init(disp)并把上一步得到的屏幕句柄disp传给它这样触摸事件就能和正确的屏幕关联起来。打开背光最后点亮屏幕。接下来咱们钻进最关键的bsp_display_lcd_init()函数里看看。2.2 屏幕驱动与LVGL接口适配这个函数位于esp32_s3_szp.c文件中它做了两件大事初始化硬件屏幕然后把屏幕“介绍”给LVGL。// 液晶屏初始化添加LVGL接口 static lv_disp_t *bsp_display_lcd_init(void) { /* 初始化液晶屏 */ bsp_display_new(); // 液晶屏驱动初始化 app_lcd_set_color(0xffff); // 设置整屏背景白色 esp_lcd_panel_disp_on_off(panel_handle, true); // 打开液晶屏显示 /* 液晶屏添加LVGL接口 */ ESP_LOGD(TAG, Add LCD screen); const lvgl_port_display_cfg_t disp_cfg { .io_handle io_handle, .panel_handle panel_handle, .buffer_size BSP_LCD_H_RES * BSP_LCD_DRAW_BUF_HEIGHT, // LVGL缓存大小 .double_buffer true, // 是否开启双缓存 .hres BSP_LCD_H_RES, // 液晶屏的宽 .vres BSP_LCD_V_RES, // 液晶屏的高 .monochrome false, // 是否单色显示器 /* Rotation的值必须和液晶屏初始化里面设置的 翻转 和 镜像 一样 */ .rotation { .swap_xy true, // 是否翻转 .mirror_x true, // x方向是否镜像 .mirror_y false, // y方向是否镜像 }, .flags { .buff_dma false, // 是否使用DMA 注意dma与spiram不能同时为true .buff_spiram true, // 是否使用PSRAM 注意dma与spiram不能同时为true } }; return lvgl_port_add_disp(disp_cfg); }前半部分3-5行是纯硬件初始化调用底层驱动点亮屏幕并把背景设为白色防止刚上电时出现花屏。后半部分是配置LVGL显示接口的关键我挑两个最容易出错的地方重点讲第一处屏幕旋转与镜像配置 (rotation)这个配置必须和之前硬件初始化时的设置严格一致。在bsp_display_new()函数里有这样两行esp_lcd_panel_swap_xy(panel_handle, true); // 显示翻转 esp_lcd_panel_mirror(panel_handle, true, false); // 镜像esp_lcd_panel_swap_xy(..., true)表示交换X和Y轴也就是旋转。所以我们在LVGL配置里.swap_xy也要设为true。esp_lcd_panel_mirror(..., true, false)表示X轴镜像开启Y轴镜像关闭。所以LVGL配置里.mirror_x true,.mirror_y false。如果这里配错了显示内容的方向或镜像就会出错。第二处显示缓存配置 (flags)这里决定了LVGL的绘图缓存放在哪里怎么传输。.buff_dma false不使用DMA直接内存访问来传输数据。.buff_spiram true将LVGL的绘图缓存放到外部的PSRAM扩展内存里。这里有个非常重要的限制buff_dma和buff_spiram只能有一个为true不能同时开启因为DMA通常要求内存是连续的并且位于内部RAM而PSRAM是外部内存访问方式不同。在LVGL的底层代码里会检查这个如果两个都设为true程序会直接报错。.buffer_size决定了缓存区的大小。这里设置的是BSP_LCD_H_RES * BSP_LCD_DRAW_BUF_HEIGHT。BSP_LCD_H_RES是屏幕宽度320BSP_LCD_DRAW_BUF_HEIGHT是一次渲染的高度。因为我们用了PSRAM内存充足可以把这个值设成240也就是全屏高度这样一次就能渲染一整行速度最快。如果不用PSRAM只用内部RAM就得设小点比如20至少是屏幕高度的1/10LVGL官方建议。2.3 触摸屏驱动适配屏幕能显示了还得能触摸。触摸屏的初始化在bsp_display_indev_init()函数里它同样在esp32_s3_szp.c文件中。// 触摸屏初始化添加LVGL接口 static lv_indev_t *bsp_display_indev_init(lv_disp_t *disp) { /* 初始化触摸屏 */ if (tp NULL) { BSP_ERROR_CHECK_RETURN_NULL(bsp_touch_new(NULL, tp)); } assert(tp); /* 添加LVGL接口 */ const lvgl_port_touch_cfg_t touch_cfg { .disp disp, .handle tp, }; return lvgl_port_add_touch(touch_cfg); }这个函数逻辑很简单先检查触摸屏句柄tp是否已经初始化如果没有就调用bsp_touch_new()初始化咱们的开发板用的是FT6336触摸芯片。然后把初始化好的触摸屏句柄tp和之前创建的屏幕句柄disp打包通过lvgl_port_add_touch()注册给LVGL。真正的硬件初始化在bsp_touch_new()函数里这里也有两个配置要点esp_err_t bsp_touch_new(esp_lcd_touch_handle_t *ret_touch) { /* Initialize touch */ esp_lcd_touch_config_t tp_cfg { .x_max BSP_LCD_V_RES, // 注意这里是高度240 .y_max BSP_LCD_H_RES, // 注意这里是宽度320 .rst_gpio_num GPIO_NUM_NC, // 与LCD复位引脚共用 .int_gpio_num GPIO_NUM_NC, // 未使用中断引脚 .levels { .reset 0, .interrupt 0, }, .flags { .swap_xy 1, // 与屏幕旋转设置保持一致 .mirror_x 1, // 与屏幕镜像设置保持一致 .mirror_y 0, }, }; ... ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)BSP_I2C_NUM, tp_io_config, tp_io_handle), TAG, ); ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, tp_cfg, ret_touch)); ... }第一处坐标最大值 (x_max,y_max)这里容易搞反。因为我们的屏幕初始化时做了swap_xy交换XY轴所以触摸屏的X方向最大值应该对应屏幕的高度BSP_LCD_V_RES240Y方向最大值对应屏幕的宽度BSP_LCD_H_RES320。如果这里设反了触摸坐标就不准。第二处旋转与镜像标志 (flags)和屏幕一样这里的.swap_xy、.mirror_x、.mirror_y必须和屏幕初始化时的.rotation设置完全一致否则你手指点的位置和屏幕上响应的位置会对不上。初始化函数里最后两行是固定套路先用esp_lcd_new_panel_io_i2c初始化I2C通信再用esp_lcd_touch_new_i2c_ft5x06初始化FT5x06系列的触摸芯片FT6336兼容这个驱动。3. 从零移植手把手改造你的工程如果你不是直接用现成的例程而是想在自己已有的工程里添加LVGL那可以跟着下面的步骤来。我们假设你有一个基于之前章节比如第7章LCD摄像头例程的工程要把它改造成支持LVGL。3.1 工程文件清理与准备把上一章的例程文件夹复制一份重命名为08-lcd_lvgl。进入文件夹删除与摄像头相关的、不必要的源文件只保留最核心的工程文件。用VSCode打开工程修改顶层CMakeLists.txt文件中的项目名称比如改成project(lcd_lvgl)。3.2 移植屏幕显示接口我们需要参考乐鑫官方esp-bsp组件库中的代码。找到bsp/esp32_s3_eye/esp32_s3_eye.c文件里的bsp_display_lcd_init函数把它复制到我们工程的esp32_s3_szp.c文件中。复制过来的函数需要大改目标是改成我们前面章节分析的那个最终形态。主要修改点包括替换初始化部分删除原函数里初始化屏幕的复杂代码直接用我们自己的bsp_display_new()等三个函数。配置缓存大小.buffer_size设为BSP_LCD_H_RES * BSP_LCD_DRAW_BUF_HEIGHT。记得在esp32_s3_szp.h里定义BSP_LCD_DRAW_BUF_HEIGHT如果用PSRAM就设为240用内部RAM就设小点比如20。配置双缓存.double_buffer true开启双缓存提升刷新流畅度。匹配旋转参数根据我们屏幕实际的初始化调用swap_xy和mirror正确设置.rotation里的三个布尔值。选择缓存位置在.flags里根据你的硬件选择.buff_dma或.buff_spiram之一为true另一个为false。调整函数签名删除入口参数返回类型从lv_display_t*改为lv_disp_t*这是LVGL v8和v9的版本差异。3.3 移植触摸屏驱动触摸屏驱动参考esp-bsp里bsp/esp32_s3_korvo_2/esp32_s3_korvo_2.c的bsp_touch_new函数。复制过来后需要修改交换坐标最大值将.x_max和.y_max的值对调。禁用中断引脚开发板硬件上触摸屏中断引脚未连接所以.int_gpio_num GPIO_NUM_NC。匹配旋转标志同样根据屏幕设置调整.flags中的swap_xy和mirror_x。更换驱动型号把ESP_LCD_TOUCH_IO_I2C_TT21100_CONFIG和esp_lcd_touch_new_i2c_tt21100分别改为ESP_LCD_TOUCH_IO_I2C_FT5x06_CONFIG和esp_lcd_touch_new_i2c_ft5x06以匹配我们的FT6336芯片。移除重复初始化删除函数里的bsp_i2c_init()调用因为主函数里已经初始化过了。简化函数签名删除第一个入口参数。接着把同一个文件里的bsp_display_indev_init函数也复制过来并做简化去掉条件判断直接调用修改后的bsp_touch_new并修正参数类型。最后在esp32_s3_szp.c中定义三个必要的全局变量static esp_lcd_touch_handle_t tp; // 触摸屏句柄 static lv_disp_t *disp; // LVGL显示设备句柄指向屏幕 static lv_indev_t *disp_indev NULL; // LVGL输入设备句柄指向触摸屏3.4 编写整合函数并修改主程序现在我们可以编写最终的bsp_lvgl_start()函数了就是把前面做好的屏幕和触摸屏初始化函数组合起来再加上LVGL环境初始化和背光控制。别忘了在esp32_s3_szp.h头文件中声明这个函数void bsp_lvgl_start(void);同时还需要包含必要的头文件#include esp_lcd_touch_ft5x06.h #include esp_lvgl_port.h回到main.c的app_main函数把原来初始化LCD和摄像头的代码删掉换成我们的新函数void app_main(void) { bsp_i2c_init(); // I2C初始化 pca9557_init(); // IO扩展芯片初始化 bsp_lvgl_start(); // 初始化lvgl显示 }3.5 管理工程依赖组件LVGL不是ESP-IDF自带的我们需要通过组件管理器来添加。修改工程根目录下的idf_component.yml文件## IDF Component Manager Manifest File dependencies: # espressif/esp32-camera: ^2.0.10 # 如果不用摄像头就注释掉 lvgl/lvgl: ~8.3.0 # LVGL图形库本体 espressif/esp_lvgl_port: ~1.4.0 # 乐鑫提供的LVGL与ESP32硬件适配层 espressif/esp_lcd_touch_ft5x06: ~1.0.6 # FT5x06/FT6336触摸芯片驱动版本号符号小知识~8.3.0会下载8.3.x系列中的最高版本如8.3.11只更新补丁号。^8.3.0会下载8.x.x系列中的最高版本如8.9.0更新次版本号。如果你希望锁定某个确切的、经过测试的版本直接写8.3.11即可。添加esp_lvgl_port组件后它会自动拉取匹配的lvgl组件。同样esp_lcd_touch_ft5x06会拉取esp_lcd_touch基础组件。由于我们移除了摄像头组件原来工程里调用摄像头相关的代码会报错。一个干净的做法是用条件编译宏把它控制起来。在esp32_s3_szp.h和.c文件中给所有摄像头相关的代码加上#if CAMERA_EN ... #endif。然后在头文件里定义#define CAMERA_EN 0来关闭它。以后需要用摄像头时把这个宏改成1就行。4. 配置Menuconfig与运行5大Demo组件添加好后在VSCode里选择目标芯片为esp32s3。一定要等右下角的进度条跑完组件下载完成再点击小齿轮图标进入menuconfig进行配置。4.1 关键配置项进入menuconfig后有几个地方需要关注LVGL颜色格式由于我们的屏幕驱动可能需要的颜色字节顺序Endian问题有时需要设置颜色反转。在menuconfig中找到Component config - LVGL - Color inversion相关的选项根据实际情况勾选。如果屏幕颜色显示异常比如红色变成蓝色可以尝试切换这个设置。启用DemoLVGL源码自带5个演示程序。我们需要在menuconfig中使能它们。一路往下翻找到Component config - LVGL - Demos。把下面这5个选项勾选上只勾选主选项不要勾选它们的子选项LV_USE_DEMO_BENCHMARKLV_USE_DEMO_KEYPAD_AND_ENCODERLV_USE_DEMO_MUSICLV_USE_DEMO_STRESSLV_USE_DEMO_WIDGETS为Music Demo启用字体lv_demo_music需要额外的字体支持。在Component config - LVGL - Font usage下确保LV_FONT_MONTSERRAT_12和LV_FONT_MONTSERRAT_16被勾选LV_FONT_MONTSERRAT_14通常是默认勾选的。配置完成后点击Save保存然后退出menuconfig。4.2 在代码中调用Demo在main.c文件的开头包含Demo的总头文件#include demos/lv_demos.h然后你就可以在bsp_lvgl_start()之后调用任何一个Demo函数了。记得一次只运行一个把其他的注释掉void app_main(void) { bsp_i2c_init(); pca9557_init(); bsp_lvgl_start(); // 初始化液晶屏lvgl接口 /* 下面5个demos 只打开1个运行 */ lv_demo_benchmark(); // 性能基准测试 // lv_demo_keypad_encoder(); // 键盘与编码器交互演示 // lv_demo_music(); // 音乐播放器界面 (320*240分辨率下效果一般) // lv_demo_stress(); // 压力测试疯狂创建删除对象 // lv_demo_widgets(); // 所有控件/widgets展示 }4.3 编译、下载与测试一切就绪在VSCode里选择好串口号和下载方式点击“一键三联”。编译完成后程序会自动下载到开发板并打开串口终端。如果一切顺利你选择的Demo就会在屏幕上流畅运行。你可以逐个体验Benchmark全面测试图形性能看帧率。Widgets展示按钮、滑块、列表等所有UI控件适合学习。Keypad Encoder演示如何用物理按键和编码器控制界面。Stress压力测试看看LVGL的稳定性。Music一个模拟的音乐播放器界面注意分辨率适配问题。跑通所有Demo后如果你希望保存当前的配置比如那些menuconfig选项可以在项目终端里执行命令idf.py save-defconfig。这会生成一个sdkconfig.defaults文件里面只保存了你修改过的配置方便以后复用。好了到这里你已经成功在实战派ESP32-S3开发板上移植了LVGL并且能运行所有官方Demo了。这套代码就是一个很好的LVGL项目模板接下来你就可以基于它开发属于自己的炫酷图形界面了。如果在移植过程中遇到显示方向不对、触摸不准或者颜色异常的问题回头重点检查一下屏幕和触摸屏的rotation/flags配置以及menuconfig里的颜色设置这几个地方是新手最容易出错的。

相关新闻