先交代个背景:我手头那块ESP32S3开发板吃灰了小半年,最近要做一个带触摸屏的桌面时钟和传感器数据面板,这才把ESP32S3+LVGL+SquareLine Studio+TFT_eSPI这条链路彻底走通。这套组合几乎是当前DIY图形界面最省心的方案,但网上教程大多只讲一半,要么只讲SquareLine Studio画界面,要么只讲LVGL裸机移植,中间隔着一道“UI导出的代码到底怎么接到ESP32S3和屏幕上”的鸿沟。这篇把我从画界面到上屏显示的全流程、踩过的坑、改过的配置一次说清楚。
这条链路适合这几类人:想给ESP32S3作品加个好看交互界面的嵌入式爱好者,想用图形化方式做LVGL界面而不想手写一大堆控件代码的入门玩家,以及对TFT_eSPI驱动不熟、总在屏幕初始化上翻车的朋友。不需要你会多少LVGL的API,能装软件、会复制粘贴改参数,就能跑起来。
1. 整体方案设计:为什么是这四件套组合
1.1 这套组合各自扮演什么角色
先说结论:ESP32S3是大脑,TFT_eSPI是屏幕的翻译官,LVGL是界面框架,SquareLine Studio是画界面的工具。四者分工明确,缺一不可。
ESP32S3选它的原因很直接——双核240MHz、带512KB SRAM、支持Octal SPI和大量GPIO,跑LVGL完全无压力,价格还便宜。LVGL本身不挑硬件,但ESP32S3的性价比和生态让它成为当前LVGL方案的首选平台之一。
TFT_eSPI则是一个专门为ESP系列优化的屏幕驱动库。它最厉害的地方是支持帧缓冲(frame buffer)方式,可以先把一帧画面画在内存里,再一次性刷到屏幕,避免了逐像素传输的闪烁问题。相比你直接用ESP-IDF的SPI驱动裸写屏幕初始化,TFT_eSPI帮你把ILI9341、ST7789这些常见驱动IC的初始化序列、像素格式、翻转方向全封装好了,省掉大量调试时间。
LVGL(Light and Versatile Graphics Library)是当前嵌入式领域用的最多的开源图形库。它的核心思想是“控件树”加“事件驱动”——你界面上每一个按钮、标签、进度条都是一个对象,对象之间有父子层级,对象能响应点击、滑动等事件。版本上我用的LVGL 8.4,别用9.x,原因是SquareLine Studio 1.4.x版本导出的代码默认匹配LVGL 8.x,9.x的API改动比较大,新手折腾起来容易卡住。
SquareLine Studio则是LVGL官方出的可视化UI设计工具,类似PC上的Qt Designer。你在软件里拖拽控件、设置样式、绑定事件,它直接导出C语言代码,底层调用LVGL API生成界面。它解决的核心痛点是:LVGL原生开发需要手写大量控件创建、布局、样式代码,一个简单的页面可能要上百行,用SquareLine Studio十分钟就能画完。
1.2 这个方案能规避哪些坑
我最初想过纯手写LVGL代码,写了一个页面就放弃了——光是一个带背景、圆角、阴影的按钮就需要十几行样式设置,页面多了以后代码量爆炸。SquareLine Studio让UI设计和C代码割裂开,界面改起来不用动逻辑代码,这是它最大的价值。
另一个考虑是TFT_eSPI的选型。市面上存在LovyanGFX这个库,性能也很强,但TFT_eSPI的社区资料更多,SquareLine Studio生成的示例代码默认对接TFT_eSPI,踩坑时搜索解决方案容易得多。对新手来说,用别人踩过最多坑的路径,才是最快路径。
再补充一个很多人忽视的点:ESP32S3的SPI引脚可以和LCD的CS/DC/RST任意映射,但背光引脚(BL)最好不要随意接。我见过很多人在背光上翻车,屏幕死活不亮,其实就是BL引脚没配置对或者PWM频率太低了。
2. 开发环境搭建与硬件连接细节
2.1 开发环境选型:Arduino IDE还是ESP-IDF
做ESP32S3图形界面,第一道选择题是用Arduino IDE还是ESP-IDF。我的建议是:新手和想快速出效果的人直接选Arduino IDE,有一定经验或者产品化需求再考虑ESP-IDF。
理由有三:一是Arduino生态下TFT_eSPI和LVGL的例程最多,复制粘贴就能跑;二是SquareLine Studio生成的代码是纯C文件,Arduino工程的.ino文件里能直接#Include调用,集成起来几乎没有障碍;三是Arduino的库管理器一键安装,省去ESP-IDF组件配置的麻烦。
当然Arduino IDE也有短板——编译速度慢、内存分配不够精细。但做界面原型完全够用,性能瓶颈不会出现在IDE上,而是出现在你代码的帧缓冲和触摸扫描设置上。如果你之后要上量产固件,再用ESP-IDF重写不迟,逻辑代码基本可以平迁。
在Arduino IDE里安装ESP32板卡支持时,我建议装2.0.14或更高版本。选板子时不要选ESP32S3 Dev Module选错了会可能导致后续串口不识别。Port选择对应你开发板的COM口,Upload Speed选921600就行,如果下载失败降到460800。
2.2 屏幕型号选择与引脚连接参考
屏幕我用的是一块2.8寸ILI9341电阻触摸屏模块,带SPI接口,这款在淘宝上最普遍,驱动IC资料也最全。其它如ST7789、ST7735的逻辑基本一致,区别只在初始化命令和部分参数,TFT_eSPI里对应改一下就行。
接线遵循“同组信号线尽量靠近”的原则。以下是我实测稳定的一组接法:
| 屏幕引脚 | ESP32S3引脚 | 说明 |
|---|---|---|
| VCC | 3V3 | 注意别接5V |
| GND | GND | 共地 |
| CS | GPIO10 | 片选,低电平有效 |
| RESET | GPIO11 | 复位,低电平有效 |
| DC/RS | GPIO12 | 数据/命令选择 |
| SDI/MOSI | GPIO11? | 注意,看下方说明 |
| SCK | GPIO12? | 注意,看下方说明 |
| LED/BL | GPIO21 | 背光控制,可PWM |
| T_CLK | GPIO7 | 触摸SPI时钟 |
| T_CS | GPIO6 | 触摸片选 |
| T_DIN | GPIO5 | 触摸数据输入 |
| T_DO | GPIO4 | 触摸数据输出 |
| T_IRQ | GPIO3 | 触摸中断 |
上表里SDI和SCK的引脚位我故意没写死,因为这取决于你用的TFT_eSPI配置里SPI的映射。TFT_eSPI默认使用ESP32的VSPI/FSPI硬件外设,一般默认是SCK=GPIO12、MOSI=GPIO11,但不同开发板的丝印标注可能不同。最可靠的方法是在User_Setup.h里显式定义,后文我会给你完整配置。
还有一点必须提醒:如果你的屏幕模块板载了稳压和电平转换电路,VCC可以接5V,但很多没有稳压的裸屏模块接5V会直接烧掉。不确定时一律接3V3,屏幕亮度不够再接5V试,但别在没查清楚模块原理图之前贸然上5V。
2.3 触摸芯片的一个常被忽略的点
触摸屏上常见的芯片是XPT2046(电阻式)或FT6236(电容式),两者接线逻辑完全不同。我用的这块是XPT2046,接线参考上表。FT6236通常是I2C接口,SCL/SDA两个引脚连到ESP32S3的I2C引脚上,然后在TFT_eSPI的触摸配置里选对应驱动。
如果屏幕触摸没反应,应该先确认你的模块是哪颗触摸芯片,再去看库里面是不是启用了正确的触摸驱动,这个点很容易被忽略。
3. SquareLine Studio界面设计与代码导出实操
3.1 新建项目时的参数选择
打开SquareLine Studio(我用的是1.4.1版),点击Create新建项目,这里有几个参数直接决定后续代码能不能跑起来。
Screen Width和Screen Height填你屏幕的实际分辨率,比如320x240。有些屏幕模块是240x320竖屏的,建议先在TFT_eSPI里设置为横屏(rotation=1),然后SquareLine Studio里也按横屏尺寸320x240建项目,两边统一,不然导出来的界面偏一半,很麻烦。
Color Depth选16 bit(RGB565),这个对应TFT_eSPI的色彩格式,TFT_eSPI默认也是RGB565。如果你选24 bit或32 bit,颜色效果是好了,但内存占用翻倍,很多情况下没必要。
选中“LVGL version 8.3”或8.x版本。如果你看到有9.x选项,不要选,后文解释为什么不建议。
Font选择你需要的字体。中文显示是个大坑,后文单独说。
3.2 拖控件、设置样式和绑定事件的思路
SquareLine Studio的操作逻辑是:左侧面板是控件库,中间是画布,右侧是属性面板。你把按钮、标签、弧形指示器、图片这些控件拖到画布上,然后在右侧属性面板里改大小、位置、背景色、边框、阴影,就和做PPT一样。
绑定事件时,选中控件,点击右侧的“Events”标签,在Clicked那一行点“+”号,然后选择新建一个事件函数。SquareLine Studio会自动生成类似void Button1_clicked(lv_event_t * e)的空函数,你导出后在代码里往这个函数里填业务逻辑就行。
个人经验:别在SquareLine Studio里面写太复杂的业务代码,它生成的ui_events.c文件每次重新生成会被覆盖。它的定位是纯UI层,逻辑代码放主程序文件里,通过全局变量或函数指针访问UI对象。SquareLine Studio在“UI"详情页里可以勾选控件是否“Export”,没勾的商品导出后会引用不到,要注意。
3.3 导出代码的正确姿势
界面画好后,点右上角Export,选择Create New Project,选择LVGL version 8.x,路径选一个空文件夹。生成的目录结构大致如下:
project/ ├── ui/ │ ├── ui.c │ ├── ui.h │ ├── ui_events.c │ ├── ui_events.h │ ├── ui_helpers.c │ ├── ui_helpers.h │ ├── ui_theme.c │ ├── ui_theme.h │ ├── fonts/ │ └── images/ ├── generated/ │ └── ... └── ...关键入口是ui_init()函数,它负责创建所有控件和页面。在你的主程序里调用它,就能创建整个界面。
如果你不需要SquareLine Studio帮你创建模板工程,也可以在Export时选择“Export UI Files Only”,只导出ui文件夹的源码,自己集成到现有工程里。这样更干净,我推荐后者。
3.4 中文字体处理的独家技巧
中文字体在SquareLine Studio里是个老大难。默认字体只有ASCII字符,中文全显示成方框。解决思路是:在Font Gauge里导入中文字体文件(ttf/otf),然后勾选Custom characters或直接指定需要的字符集范围,让它重新生成一个包含汉字的子集字体文件。
更实用的做法是:程序启动时从SD卡或Flash加载一个完整的字库(比如全角GB2312字库),通过LVGL的字体回调动态取字模。但这对新手来说太复杂了。折中方案是:只包含你想用的几百个常用字,生成字体子集。SquareLine Studio可以手动输入需要包含的字符,把界面上用到的中文字全敲进去,生成出的字体体积小,也够用。
这一步我踩过最深的坑是:用ttf直接生成中文渲染没问题,但刷新率严重下降,因为取字模是动态渲染的。后来改用预生成字体子集,画屏速度立刻正常了。
4. TFT_eSPI驱动配置:最容易被坑的一环
4.1 安装与路径定位
在Arduino IDE里,通过库管理器搜索TFT_eSPI并安装。注意安装后要去库所在的路径找到User_Setup.h,用文本编辑器打开来改。库目录一般在C:\Users\你的用户名\Documents\Arduino\libraries\TFT_eSPI下。
为什么强调这个:TFT_eSPI把硬件配置全部集中在这个头文件里,编译时通过宏开关选择不同屏幕驱动和引脚映射,跟Arduino的板级文件完全独立。
4.2 User_Setup.h的具体配置参考
我的配置修改如下,直接抄作业注意改成你自己的引脚。User_Setup.h开头有大量注释和例程,把不相关的屏幕驱动全部注释掉,只保留ILI9341:
#define ILI9341_DRIVER #define TFT_CS 10 #define TFT_DC 12 #define TFT_RST 11 #define TFT_MISO 13 #define TFT_MOSI 11 #define TFT_SCLK 12等一下,这里要特别注意,上面的写法里MOSI和SCLK与你前面接线的表格存在冲突。实际上,ESP32S3的硬件SPI外设的默认引脚是可以重新映射的,关键是你的User_Setup.h里写的引脚编号要和实际硬件接线的编号保持一致。我在实际项目里最终采用的稳定组合是:
#define TFT_CS 10 #define TFT_DC 12 #define TFT_RST 11 #define TFT_MOSI 11 #define TFT_SCLK 12 #define TFT_MISO 13也就是说,屏幕上MOSI接的是ESP32S3的GPIO11,SCLK接GPIO12,这与默认FSPI映射一致。前提是你的实际接线要跟着这个配置走。强烈建议先看你的板子丝印,把引脚确认清楚再接线,别照抄网上的图。
背光引脚设置,TFT_eSPI允许通过#define TFT_BL定义,并在初始化后调用pinMode(TFT_BL, OUTPUT)和digitalWrite(TFT_BL, HIGH)点亮:
#define TFT_BL 214.3 SPI频率、缓冲区与色彩设置
在User_Setup.h里找以下几项并修改:
#define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 16000000 #define SPI_TOUCH_FREQUENCY 2500000SPI频率我实测40MHz稳定,如果你屏幕排线质量好,可以试到54MHz或更高,但超出后可能出现雪花或颜色错位。读取频率用于读屏幕像素内容,一般用不了那么高。触摸频率XPT2046建议2.5MHz以下,太高反而读到乱码。
还应确认#define LOAD_GLCD、LOAD_FONT2这些字体宏有没有打开,TFT_eSPI自带的GLCD字体和Font2对于调试打印很有用。如果你不需要,可以注释掉节省Flash,但第一次调试建议先留着,后面再精简。
4.4 触摸配置的User_Setup对应项
如果你的屏幕带XPT2046触摸,在User_Setup.h的触摸部分,应该保持或开启:
#define TOUCH_CS 6 #define TOUCH_SCLK 7 #define TOUCH_DIN 5 #define TOUCH_DOUT 4 #define TOUCH_IRQ 3同时在代码中初始化触摸时调用tft.setTouch(calData)之类的校准函数。注意TFT_eSPI的触摸校准数据存放在一个数组里,不同屏幕批次有差异,建议首次上电做一次校准或写入固定校准值。
5. 代码集成与LVGL移植:从UI文件到点亮屏幕
5.1 目录结构与源码导入
把SquareLine Studio导出的ui文件夹整个拷贝到你的Arduino工程目录下。如果你的工程叫esp32s3_dashboard.ino,那么整体结构是:
esp32s3_dashboard/ ├── esp32s3_dashboard.ino └── ui/ ├── ui.c ├── ui.h └── ...在.ino文件里添加包含头文件:
#include <TFT_eSPI.h> #include <lvgl.h> #include "ui/ui.h"5.2 LVGL初始化与显示驱动对接
LVGL是一个需要“心跳”和“刷屏回调”配合的库。所谓心跳,就是LVGL需要一个周期性调用的函数lv_tick_inc(1)来计时,我放在ESP32S3的hw_timer中断服务函数里,每1ms调用一次。所谓刷屏回调,就是LVGL内部需要把一帧图像数据通过SPI输出到屏幕时,通过函数指针调用你注册的my_disp_flush函数。
在setup()中,主要代码如下:
#include <TFT_eSPI.h> #include <lvgl.h> #include "ui/ui.h" static TFT_eSPI tft = TFT_eSPI(); static lv_disp_draw_buf_t draw_buf; static lv_color_t buf[TFT_WIDTH * 40]; // 40行缓冲 void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p) { uint32_t w = (area->x2 - area->x1 + 1); uint32_t h = (area->y2 - area->y1 + 1); tft.startWrite(); tft.setAddrWindow(area->x1, area->y1, w, h); tft.pushColors(&color_p->full, w * h, true); tft.endWrite(); lv_disp_flush_ready(disp); }这里tft.pushColors的最后一个参数true表示颜色字节序在LVGL里已经是RGB565小端,TFT_eSPI直接推走就行。如果你发现颜色红蓝反了,或者偏色,就把这个参数改成false,或者检查LVGL里LV_COLOR_16_SWAP宏。
初始化LVGL的标准流程:
void setup() { Serial.begin(115200); lv_init(); tft.begin(); tft.setRotation(1); // 横屏 tft.fillScreen(TFT_BLACK); lv_disp_draw_buf_init(&draw_buf, buf, NULL, TFT_WIDTH * 40); static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.hor_res = TFT_WIDTH; disp_drv.ver_res = TFT_HEIGHT; disp_drv.flush_cb = my_disp_flush; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv); ui_init(); // 创建SquareLine Studio生成的界面 }static关键字不能漏,因为LVGL的驱动结构体需要在注册后一直存在,如果写成局部变量会造成悬垂指针,系统随机崩溃。这种崩溃最难调,我当初折腾到凌晨才定位到问题。
触摸对接在大同小异,在setup()里注册输入设备驱动,写一个my_touchpad_read回调,从XPT2046读取坐标,通过lv_indev_drv_register注册到LVGL:
static lv_indev_drv_t indev_drv; lv_indev_drv_init(&indev_drv); indev_drv.type = LV_INDEV_TYPE_POINTER; indev_drv.read_cb = my_touchpad_read; lv_indev_drv_register(&indev_drv);5.3 主循环与任务调度写法
在loop()中要调用lv_timer_handler(),这个函数是LVGL的心脏,所有事件处理和重绘都在它内部完成。千万不能加delay(),它会阻塞心跳和屏幕刷新。
void loop() { lv_timer_handler(); delay(5); }如果你的界面中需要周期性刷新数据(比如时间、温湿度),一般有几种写法。我简单说比较实用的一种:在ui_events.c里用lv_timer_create创建周期性回调函数,在回调里更新标签内容。记得方块指针别用局部变量,要用全局或静态。
5.4 FreeRTOS任务化移植的进阶经验
有些工程把LVGL运行在FreeRTOS独立任务里,这样主任务可以干别的事而不影响屏幕响应。ESP32S3的Arduino环境本身跑着FreeRTOS,你可以这样简单开启一个独立任务:
void lvgl_task(void *pvParameters) { while (1) { lv_timer_handler(); vTaskDelay(pdMS_TO_TICKS(5)); } } // 在setup中 xTaskCreatePinnedToCore(lvgl_task, "lvgl_task", 8192, NULL, 1, NULL, 1);这里有个坑:LVGL不是线程安全的,LVGL 8.x在启用LV_USE_OS后可以支持多线程访问,但默认配置下所有操作都要在同一个任务里执行。因此我建议所有控件的修改都通过lv_timer_handler所在任务去处理,不要在其它任务里直接操作控件对象。你可以用lv_msg_send或队列把数据传给LVGL任务。
任务栈大小,8KB起步。如果你的界面比较复杂,可能要加到16KB,栈溢出会导致随机重启。用uxTaskGetStackHighWaterMark函数可以监控剩余栈空间。
6. 常见问题排查与性能优化实录
6.1 问题速查表
以下是我调试过程中实际踩过的坑,整理成表格方便查阅:
| 现象 | 可能原因 | 解决措施 |
|---|---|---|
| 屏幕全白或全黑 | 背光没点亮;RESET没拉高;SPI引脚错误 | 检查TFT_BL配置和接线;确认User_Setup.h引脚编号与接线一致 |
| 颜色呈负片/红蓝反色 | RGB565字节序不对 | 在lv_conf.h中开启或关闭LV_COLOR_16_SWAP;pushColors最后一个参数调true/false |
| 界面卡顿,刷屏慢 | 无帧缓冲;SPI频率太低;占用内存太大 | 使用TFT_eSPI的frame buffer或至少40行行缓冲;SPI频率提到40MHz以上;降低LVGL缓存占用 |
| 触摸点击位置偏移 | 触摸校准数据不对;旋转方向不一致 | 用TFT_eSPI的校准例程重新校准;让setRotation与LVGL的旋转设置保持一致 |
| 编译报错:lv_conf.h找不到 | LVGL的配置头文件路径没包含 | 在Arduino工程中把lv_conf.h放在src目录或工程根目录,确保编译能找到 |
| 程序不定时重启 | 任务栈溢出;内存不足;LVGL 16bit颜色缓冲过大 | 增大任务栈;调整LVGL内存池大小;减少draw_buf行数 |
| 中文字体全是方框 | 字体不包含中文字符集 | 用SquareLine Studio导入中文字体并指定字符集;或运行期加载外部字库 |
| 屏幕有横纹/雪花 | SPI频率过高;电源供电不足;排线干扰 | 降低SPI_FREQUENCY到26MHz或更低;检查电源纹波,加个大电容 |
6.2 关于内存和缓冲的取舍
LVGL在ESP32S3上最需要关心的是花多少内存。一个320x240的16位色全屏缓冲是3202402=153600字节,约150KB,ESP32S3有512KB SRAM,看起来够用但实际可分配大块连续内存有时不够。Frame buffer会占用连续内存,所以LVGL默认用draw_buf分段画法——比如我设40行,那么缓冲大小是320402=25600字节,约25KB,既保证刷新速度又不至于内存告急。
如果你用液晶屏需要Flash存储字体和图片,这些资源都放在Flash里,ESP32S3的Flash从4MB到16MB都有,一般够用。如果图片太多导致编译后固件超区,注意Tools->Partition Scheme选Huge APP (3MB No OTA),这个坑特别普遍,默认分区表APP区只有1.2MB,UI资源一多直接编译不过。
6.3 提升刷新率的一些验证过的做法
从我的测试来看,影响刷新率最明显的几个因素依次是:SPI频率、LVGL绘制缓冲大小、是否开启DMA、是否用双缓冲。
TFT_eSPI可以通过DMA传输减少CPU等待,pushColors里如果使用DMA通道,可以极大提高刷新吞吐量。但DMA在部分ESP32S3开发板上需要正确配置,而且很多便宜的模块排线质量不好,传输太快会导致数据错乱。我实测40MHz+DMA效果好于80MHz无DMA,所以不要盲目拉高SPI频率。
另外,UI中如果用了大量图片资源,建议在SquareLine Studio导出时选择压缩图片格式(如LV_IMG_CF_RAW或LV_IMG_CF_INDEXED),不要用ARGB8888,否则内存占用和刷图速度都吃不消。能用纯色或简单样式就别加阴影和圆角的大半径模糊,LVGL绘制这些特效时会消耗额外时间。
6.4 一个能救急的经验:RGB565字节序和显示方向
ESP32S3的SPI外设是大端还是小端?TFT_eSPI在给ILI9341喂数据时,会遇到字节序问题。最典型的现象是颜色间隙性错乱,尤其在渐变和图片显示时最明显。我在lv_conf.h里开启LV_COLOR_16_SWAP之后,图片颜色基本正常了。
显示方向问题更是高发。如果你在TFT_eSPI里设置了setRotation(1),但SquareLine Studio生成的UI是竖屏布局,那么界面的上下会反或左右往一边偏。这个需要通过重新设置tft.setRotation(0)或重新生成UI来解决。原则就是:屏幕物理方向和逻辑分辨率必须一致。
7. 从“能跑”到“好用”:我的三个关键体会
调试完这套方案,我最大的感受是:折腾这个组合最大的成本不是学习LVGL的API,而是把工具生成物的各种隐含约定理清楚。SquareLine Studio生成的代码默认LVGL 8.x,TFT_eSPI也对LVGL 8.x做了适配,但版本不匹配时出现的问题根本想不到。所以我的第一条建议是:全部锁定版本。LVGL用8.4.x,SquareLine Studio用1.4.x,TFT_eSPI用最新版但不要用开发分支,Arduino ESP32核心用2.0.14以上,锁定之后整个编译链基本不会翻车。
第二点建议是:准备好串口日志。LVGL和TFT_eSPI都可以打开调试宏,在lv_conf.h里把LV_USE_LOG设为1,通过定制lv_log_register_print_cb把日志重定向到Serial。这个方法能帮你快速定位崩溃位置。我在解决触摸坐标偏移问题时就是靠日志发现,XPT2046的原始坐标范围与屏幕分辨率换算关系没处理好。
第三点是:不要一开始就追求炫酷效果。先跑通一个只有背景和按钮的界面,再叠加动画、图片、中文显示。这套技术栈的复杂性不在单个环节,而在多个环节的衔接——UI工具导出、LVGL初始化、显示驱动对接、触摸输入处理,每一步拆开都可能跑通,合在一起就会产生各种边缘问题,所以循序渐进最省时间。
最后分享一个小技巧:如果你用ESP32S3的PSRAM(外部RAM),可以在lv_conf.h里把LVGL内存池指向PSRAM:LV_MEM_POOL_ALLOC换成ps_malloc分配的空间。PSRAM容量大,可以放心开大绘制缓冲,刷新率会有明显提升。但PSRAM的访问速度比内部SRAM慢,如果缓冲太大反而会变慢,具体多大合适,要以实测为准。这个优化留给想进一步榨干性能的朋友去试。