这次我们来看一个嵌入式 UI 工程:esp32s31_3.97寸axs15260_lvgl9。项目命名非常直白,对应的是 ESP32-S3 主控、3.97 寸 AXS15260 驱动芯片的 LCD 模组,再配合 LVGL 9 图形库做界面渲染。这套组合在小型 HMI、桌面摆件、智能家居控制面板里很常见:主控成本低、屏幕尺寸适中,LVGL9 又提供了完整的控件、动画和事件机制,适合做“本地小屏交互”类产品。
很多第一次接触 ESP32-S3 接屏的同学,最常踩的坑并不是 LVGL 本身,而是屏幕初始化序列、颜色格式、buffer 分配和lv_display_flush_ready这类回调没接对。结果就是编译能过,屏幕白屏、花屏或者点不亮。这篇文章会从环境准备、工程创建、LVGL9 初始化、刷屏实现、功能验证到常见排错完整走一遍。如果你正打算把一块 3.97 寸 AXS15260 屏接到 ESP32-S3,或者想把旧工程从 LVGL v8 迁移到 v9,这篇可以直接对照操作。
先说几个重点:第一,LVGL9 的 API 相比 v8 改动不小,显示对象叫lv_display_t,很多lv_disp_xxx接口已经改名;第二,显示屏的 buffer 大小直接影响能否稳定运行,推荐优先买带 PSRAM 的 ESP32-S3 模组;第三,AXS15260 初始化命令不能靠猜,必须按屏幕模组自带的 datasheet 或厂家示例来写。下面的内容会围绕这些展开。
1. 核心能力速览
从项目命名看,这套工程的能力点集中在“屏幕点亮 + UI 渲染”这两个环节。工程本身不涉及云平台,也不是一个完整产品,更像是一个底层显示基座。把这个基座跑通之后,再去叠加 WiFi、传感器、触摸、MQTT 等上层逻辑会顺很多。
| 能力项 | 说明 |
|---|---|
| 项目名称 | esp32s31_3.97寸axs15260_lvgl9 |
| 主控平台 | ESP32-S3 系列芯片,项目名里的 esp32s31 对应同一平台 |
| 显示模组 | 3.97 寸 TFT 液晶屏,显示控制芯片为 AXS15260 |
| 图形库 | LVGL 9.x |
| 推荐开发框架 | ESP-IDF v5.x,也支持 Arduino/PlatformIO 接入 |
| 显示回调 | 自定义disp_flush_cb将 LVGL 渲染数据写入 AXS15260 |
| 触摸支持 | 是否支持取决于模组上是否集成触摸 IC,未集成时需要外接 |
| 帧缓冲 | 推荐使用 PSRAM 动态分配,具体尺寸取决于屏幕分辨率 |
| 批量任务 | 不涉及模型推理型批量任务,但适合作为同一 UI 框架批量移植 |
| 适合人群 | 嵌入式入门到进阶,HMI 面板、小屏交互、桌面信息屏开发者 |
从上面这张表能看出,这个工程更适合“先跑通再扩展”的开发方式。先确认屏幕能显示 LVGL 默认界面,再逐步加业务代码。
2. 适用场景与使用边界
AXS15260 这颗显示控制芯片配合 3.97 寸屏幕,通常是低成本中尺寸显示方案。适用场景集中在几类:
- 智能家居面板:温控器、开关面板、空气质量检测器,用 LVGL 做卡片式界面。
- 桌面信息设备:桌面时钟、番茄钟、通知提醒屏、电子相册。
- 轻量 HMI:小型设备的状态显示与参数设置界面,按钮、进度条、下拉列表。
- 学习验证平台:熟悉 ESP32-S3 的 LCD 接口驱动,学习 LVGL9 的新 API。
不适合的场景同样存在。如果要做高分辨率视频播放、复杂 3D 界面、或者需要长时间以最高亮度显示,那么 ESP32-S3 加 AXS15260 的组合并不合适。LVGL 适合 UI 渲染,不适合视频解码。另外,屏幕刷新速率和内部/PSRAM 带宽会限制动画流畅度,大量半透明阴影和复杂图形也会增加 CPU 负载。
开发前也要注意接口边界。AXS15260 屏幕的具体分辨率、接口类型、背光控制方式,必须严格按照模组厂家提供的规格书确认。不同开发板引出的引脚不一样,不能拿别人的接线图直接照抄。显示驱动初始化命令涉及厂商资料,使用时需要确认授权范围,属于正常硬件开发,没有额外合规风险。但如果下一步接入摄像头、人脸识别或联网采集数据,就需要注意隐私和数据合规。
3. 环境准备与前置条件
先把硬件和软件环境列清楚,避免后续排查时不知道是代码问题还是环境问题。
3.1 硬件清单
- ESP32-S3 开发板。优先选择带 PSRAM 的方案,比如 ESP32-S3-DevKitC-1 的 8MB PSRAM 版本。
- 3.97 寸 AXS15260 LCD 模组。确认屏幕是否带触摸、接口是 SPI、RGB 还是 8080 并口。
- 杜邦线或定制排线。如果屏幕接口很密,建议直接焊接到转接板。
- 3.3V 稳压电源。屏幕背光瞬间电流比较大,尽量不要用开发板 USB 口同时带动背光和 WiFi。
- USB 数据线。用来烧录和查看串口日志,注意部分数据线只供电不能传数据。
ESP32-S3 的引脚兼容性需要特别留意。AXS15260 模组一般会引出几个关键信号:SCL/SCK、SDA/DC、RES、CS、背光控制脚等。如果屏幕走 RGB 接口,需要的 GPIO 数量会更多。建议先把屏幕规格书里的引脚定义和开发板丝印对应一遍,再开始接线。
3.2 软件环境
推荐用 ESP-IDF v5.x 作为开发环境。LVGL 9 对较新的编译器和工具链依赖更强,ESP-IDF v4.4 虽然也能跑,但组件版本比较老,遇到 API 不兼容问题反而浪费时间。Windows、Linux、macOS 都支持,Linux 下编译体验最好。
需要准备的工具:
- 乐鑫 ESP-IDF 工具链,版本不低于 v5.0。
- Python 3.x,IDF 自动安装依赖时会用到。
- 一个趁手的编辑器,VSCode 配合 ESP-IDF 插件不错。
- Git,用来拉取 LVGL 官方组件和示例。
- 串口工具,Windows 下可以是 ESP-IDF PowerShell,Linux 下可以用
idf.py monitor。
这里建议不要把 LVGL 源码手动复制到工程目录,而是通过 ESP-IDF 的组件管理器声明依赖。这样版本可控、升级方便,也能避免网上旧教程里的源码结构和 LVGL9 不匹配。
4. 安装部署与启动方式
下面以 ESP-IDF 和 LVGL 官方组件的方式演示。如果你的工程已经存在,可以直接在main/idf_component.yml里增加依赖;如果还没有工程,可以先创建。
4.1 创建工程
idf.py create-project axs15260_lvgl9 cd axs15260_lvgl9 idf.py set-target esp32s3set-target esp32s3会把工程目标设置为 ESP32-S3。此时工程目录下还没有显示驱动,接下来添加 LVGL9 组件。
4.2 通过组件管理器添加 LVGL9
在main/idf_component.yml中写入以下内容:
dependencies: lvgl/lvgl: "^9"版本号^9表示使用 9.x 系列里的较新版本。实际构建时,组件管理器会解析出具体版本并下载。如果你追求稳定,建议在第一次构建成功后,把dependencies.lock文件固定到版本管理里,避免后续别人拉代码时版本漂移。
4.3 配置工程参数
编译前先执行:
idf.py menuconfig进入 LVGL 配置目录,按实际屏幕调整几个关键项:
Color depth:常见选 16 bit RGB565。Swap the 2 bytes of RGB565 color:如果颜色显示偏蓝偏红,需要打开或关闭。Default font:如果没有特殊需求,先用内置 14px 字体起步。Buffer相关配置:可以在代码里动态分配,也可以在 menuconfig 里固定。
不要一次性把字号、字体、动画全部打开,先保持最小配置跑通,再逐项增加功能。menuconfig 里的具体路径会随 LVGL 版本略有变化,以实际界面为准。
4.4 LVGL9 主程序框架
这是一个最小可运行的代码结构,重点是把 LVGL 的显示 buffer 和 flush 回调挂接好:
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_log.h" #include "lvgl.h" #define DISPLAY_HOR_RES 800 // 按实际 AXS15260 分辨率修改 #define DISPLAY_VER_RES 480 // 按实际分辨率修改 static const char *TAG = "axs15260"; // 显示刷屏回调:LVGL 渲染完一块区域后把数据交给屏幕 static void disp_flush_cb(lv_display_t *display, const lv_area_t *area, uint8_t *px_map) { // TODO: 调用 AXS15260 的写入接口,将 px_map 数据送到屏幕。 // 不同屏幕驱动对 DMA、地址自增、颜色字节序有不同要求,这里按实际驱动补充。 // 注意:LVGL9 中必须调用 flush_ready,否则渲染流程会卡住 lv_display_flush_ready(display); } void app_main(void) { lv_init(); // 创建 LVGL 显示对象 lv_display_t *display = lv_display_create(DISPLAY_HOR_RES, DISPLAY_VER_RES); lv_display_set_flush_cb(display, disp_flush_cb); // 分配显示 buffer。先用内部 SRAM 小 buffer 验证,后续再调整到 PSRAM static lv_color_t buf1[DISPLAY_HOR_RES * 40]; static lv_color_t buf2[DISPLAY_HOR_RES * 40]; lv_display_set_buffers(display, buf1, buf2, sizeof(buf1), LV_DISPLAY_RENDER_MODE_PARTIAL); while (1) { lv_timer_handler(); vTaskDelay(pdMS_TO_TICKS(5)); } }这里lv_display_set_buffers用两个 buffer,可以在 LVGL9 中实现简单双缓冲,减少撕裂感。但前提是流程里刷屏回调不会覆盖正在使用的 buffer。如果屏幕驱动程序比较简单,先用一个 buffer 稳定跑起来,再考虑双缓冲。
4.5 AXS15260 初始化
AXS15260 的初始化代码无法从一个空工程里凭空生成,必须依赖模组厂家提供的初始化命令序列。代码结构一般是这样的:
static void axs15260_init(void) { // 1. 复位屏幕,拉低 RES 再拉高 // 2. 延时等待电源稳定 // 3. 通过 SPI/并口发送初始化命令序列 // 4. 打开显示,配置背光 PWM }在app_main()中先调用axs15260_init(),再执行lv_init(),顺序不能反。如果屏幕没有点亮,首先检查初始化命令是否完整、复位时序是否正确、背光引脚是否被配置为 PWM 输出。
5. 功能测试与效果验证
工程能编译烧录不代表屏幕驱动正常,建议按下面的顺序逐项验证。
5.1 基础显示验证
先不用写复杂 UI,直接显示一个带颜色的背景和一个实体矩形,判断屏幕是否真正被点亮。
void ui_demo(void) { lv_obj_t *scr = lv_screen_active(); lv_obj_set_style_bg_color(scr, lv_color_hex(0x112233), 0); lv_obj_t *rect = lv_obj_create(scr); lv_obj_set_size(rect, 200, 100); lv_obj_set_style_bg_color(rect, lv_color_hex(0xFF8800), 0); lv_obj_align(rect, LV_ALIGN_CENTER, 0, 0); }判断标准:
- 屏幕背景是否变成了
0x112233。 - 屏幕中央是否出现橙色矩形。
- 颜色有没有通道错乱,例如红色对象显示成蓝色。
如果背景不显示,先查背光和初始化命令;如果背景显示但颜色错乱,先查 RGB565 字节序和屏线接法;如果矩形边界有毛刺,可能是扫描方向和刷新区域设置不对。
5.2 文本显示验证
显示一张文本是确认字库和渲染是否正常的重要手段。在刚才的画面上加一个 label:
lv_obj_t *label = lv_label_create(lv_screen_active()); lv_label_set_text(label, "AXS15260 + LVGL9 OK"); lv_obj_set_style_text_color(label, lv_color_white(), 0); lv_obj_align(label, LV_ALIGN_BOTTOM_MID, 0, -20);判断标准:
- 英文字母显示是否清晰。
- 如果显示成方块或空白,检查默认字体是否已启用。
- 如果中文字符显示异常,是因为内置字体没有中文字形,需要额外中添加字库文件。
5.3 动画与控件测试
LVGL9 的动画 API 和 v8 有差异,建议先跑一个官方 demo 或简单动画验证刷新稳定性。你可以先创建按钮,再绑定点击事件,确认事件回调正常:
static void btn_event_cb(lv_event_t *event) { if (lv_event_get_code(event) == LV_EVENT_CLICKED) { ESP_LOGI(TAG, "button clicked"); } } void ui_demo(void) { lv_obj_t *btn = lv_button_create(lv_screen_active()); lv_obj_set_size(btn, 120, 40); lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0); 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, "Click"); lv_obj_center(label); }如果按钮点击后的串口日志没有打印,可能是输入设备没有接入,也可能是事件回调被 LVGL 内部某些逻辑阻塞,优先查输入设备层。没触摸屏时,想验证点击也可以先用 LVGL 的lv_indev_create接一个模拟指针设备,或者跳过按钮验证,先用定时器触发 UI 变化。
5.4 长时间稳定性验证
屏幕点亮、UI 正常后,建议让界面跑 30 分钟以上。重点观察:
- 是否偶然出现黑屏、闪屏。
- 刷新一段时间后是否出现花屏。
- 串口是否打印
esp_restart或者内存申请失败。 - 屏幕颜色是否逐渐变色。
长时间花屏往往不是 UI 代码问题,而是刷屏回调里 buffer 生命周期或 DMA 传输时序问题。遇到这类问题,可以把双缓冲改成单缓冲,或者把显示 buffer 从 PSRAM 换到内部 SRAM 再做对比。
6. 接口服务与事件处理
这里的“接口”不是 HTTP API,而是 LVGL9 对外暴露的显示接口、输入设备接口和 tick 接口。把这些接口理解清楚,才能排查为什么界面不刷新、触摸没反应。
6.1 tick 接口
LVGL 需要知道时间流逝,才能驱动动画、超时和控件状态。最简单的方法是在app_main之外另起一个 FreeRTOS 任务,每毫秒调用一次lv_tick_inc(1):
static void lv_tick_task(void *arg) { while (1) { lv_tick_inc(1); vTaskDelay(pdMS_TO_TICKS(1)); } }如果 tick 不工作,时钟页面的秒针不动、进度条动画卡住,但静态界面正常,基本上就是这个问题。
6.2 显示接口
LVGL9 通过lv_display_set_flush_cb注册刷屏回调。回调收到的是渲染好的像素块,你需要在回调里把它写到 AXS15260。写完必须调用lv_display_flush_ready(display),否则 LVGL 认为这次刷新没有完成,整个渲染任务会阻塞。
6.3 输入设备接口
如果模组带触摸,需要实现一个输入设备回调:
static void touchpad_read_cb(lv_indev_t *indev, lv_indev_data_t *data) { // 从触摸控制芯片读取坐标 >lv_indev_t *indev = lv_indev_create(); lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); lv_indev_set_read_cb(indev, touchpad_read_cb);触摸坐标可能需要做旋转和镜像处理。比如屏幕竖屏、横屏切换后,x、y要互换,或者屏幕扫描方向不同导致坐标轴反向。这个映射最好放在驱动层,不要污染 UI 逻辑。
6.4 控件事件
LVGL9 控件事件通过LV_EVENT_*宏触发,常用的有LV_EVENT_CLICKED、LV_EVENT_VALUE_CHANGED、LV_EVENT_SCROLL_END。事件回调里尽量只做轻量逻辑,不要在回调里执行长时间阻塞函数,否则会卡住lv_timer_handler。
7. 资源占用与性能观察
ESP32-S3 是双核 Xtensa LX7 处理器,主频最高 240 MHz。单纯跑 LVGL 界面是够用的,但显存/帧缓冲的分配和刷新方式决定流畅度。
7.1 帧缓冲怎么算
LVGL 需要一块 buffer 来存放当前渲染区域的颜色数据。以 RGB565 颜色格式为例,每个像素占用 2 字节。如果屏幕分辨率是 800x480,那么一整帧大小是:
800 * 480 * 2 = 768000 字节 ≈ 750 KB这个尺寸放在内部 SRAM 非常紧张,ESP32-S3 常规版本的内部 SRAM 没那么多,因此更常见的是使用部分刷新 buffer。比如一次只刷新 40 行:
800 * 40 * 2 = 64000 字节 ≈ 64 KB工程示例里lv_display_set_buffers分配了两个800 * 40的数组,总共约 128 KB。这个量级在带 PSRAM 的板子上通常没有压力。但如果你的屏幕分辨率不是 800x480,需要按实际宽高重新计算。
7.2 查看内存余量
调试时先看内部 SRAM 和 PSRAM 的剩余空间:
ESP_LOGI(TAG, "internal free: %d", heap_caps_get_free_size(MALLOC_CAP_INTERNAL)); ESP_LOGI(TAG, "psram free: %d", heap_caps_get_free_size(MALLOC_CAP_SPIRAM));如果internal free在创建 UI 后已经低于 20 KB,说明 buffer 太大或 LVGL 内存池配置过高。此时优先把较大块 buffer 放到 PSRAM。不过需要注意,PSRAM 读取速度和内部 SRAM 有差距,高频刷新场景下可能要测试稳定性。
7.3 性能观察
可以用高精度定时器测量一次lv_timer_handler的耗时:
int64_t start = esp_timer_get_time(); lv_timer_handler(); int64_t cost = esp_timer_get_time() - start; ESP_LOGI(TAG, "lv_timer_handler cost %lld us", cost);这个日志不适合常开,否则刷屏很厉害,但它能快速判断“动画卡是因为 UI 太重还是 LCD 写屏太慢”。如果cost在几毫秒到十几毫秒,说明 LVGL 渲染本身正常;如果cost高达几十毫秒,就要检查是不是在回调里加了延时,或者屏幕写入方式太慢。
7.4 降低资源占用的方法
如果资源紧张,按优先级做这几件事:
- 把显示 buffer 从整帧缓冲改成部分刷新模式。
- 关闭不需要的控件阴影、圆角模糊这些高开销效果。
- 减小
LV_MEM_SIZE或者使用自定义内存分配方式。 - 删除不需要的字体和图片资源。
- 把动画帧率从 60 fps 降到 30 fps。
提升性能的另一个思路是减少刷新面积。LVGL 本身只刷新脏区域,但如果你在 UI 里频繁整屏重绘,性能就会明显下降。合理的布局和局部刷新比一味提高芯片主频更有效。
8. 常见问题与排查方法
AXS15260 + LVGL9最常见的报错和现象,集中在下面这张表里。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 编译时找不到 lvgl 头文件 | 组件依赖没有生效或版本不对 | 查看dependencies.lock和构建日志 | 确认lvgl/lvgl依赖已写入idf_component.yml |
| 下载时串口连接失败 | 驱动没装或 boot 引脚状态不对 | 检查设备管理器,按住 BOOT 再下电 | 安装 USB-UART 驱动,进入下载模式 |
| 屏幕背光亮但没有任何内容 | AXS15260 初始化命令未发送或显示未打开 | 检查初始化函数是否在lv_init前调用 | 按 datasheet 补全初始化序列并打开显示 |
| 屏幕花屏 | 刷屏时序、DMA 或 buffer 重叠 | 观察是否从某一区域开始花 | 改用单缓冲,确认 DMA 传输完成后再复用 buffer |
| 颜色整体偏色 | RGB565 字节序、颜色通道顺序反了 | 显示纯红、纯绿、纯蓝测试 | 在 LVGL color swap 配置或驱动层做交换 |
| 标点符号正常但中文全是方块 | 字体没包含中文字形 | 检查当前默认字体 | 使用中文字体文件或字体转换工具生成字库 |
| 动画卡顿 | UI 渲染太重或刷屏回调太慢 | 测量lv_timer_handler耗时 | 缩小刷新区域,使用部分刷新,减少半透明效果 |
| 反复重启 | 内存不足或栈溢出 | 查看复位原因和 panic 日志 | 减小 buffer,改用 PSRAM,增大任务栈 |
这几个问题占了实际开发中的大部分。其中“屏幕背光亮但没有任何内容”是最容易卡住新手的问题。背光亮说明供电和基本 IO 正常,此时大概率是初始化命令序列没生效。可以先尝试只发一条显示开关命令,看屏幕有没有反应,再逐步增加初始化命令。初始化命令一次不要全贴进去,分几段验证,能更快定位问题。
9. 最佳实践与开发建议
工程跑通之后,不要急着往一个app_main.c里堆业务代码。嵌入式 UI 项目最怕的就是“什么都能跑,但改一处全崩”。这里给一套更稳的项目结构思路。
9.1 把显示驱动和 UI 逻辑分开
建议将 AXS15260 初始化和数据写入封装成一个独立的底层层,只提供初始化函数和刷屏回调给上层。UI 代码放在另一层,不直接接触屏幕寄存器。这样即使以后换屏幕驱动芯片,也不需要重写 UI 代码。
推荐目录结构:
main/ ├── CMakeLists.txt ├── idf_component.yml ├── app_main.c ├── lvgl_app.c ├── lvgl_app.h ├── axs15260.c └── axs15260.haxs15260.c负责屏幕初始化、背光、像素写入;lvgl_app.c负责创建界面和事件;app_main.c只做系统初始化和任务创建。这种分层在调 WiFi、调外设时会让问题定位快很多。
9.2 固定版本和配置
LVGL9 的 API 还在持续完善,不同小版本之间也可能有不兼容变化。建议在dependencies.lock里固定版本,而不是每次都拉最新版。同时把必要的 menuconfig 配置固化到sdkconfig.defaults,这样别人拉代码后执行idf.py build就能得到一致的构建结果,不用手动点无数层菜单。
9.3 先在模拟器或官方 demo 上验证 UI
如果 UI 比较复杂,先在 PC 上用 LVGL 模拟器把布局、字体、交互调好,再把代码原样搬到 ESP32-S3 上。模拟器可以节省大量烧录时间,也能避免在真机上频繁刷写损耗 Flash。真机只验证驱动相关的问题,例如颜色格式、刷新率、触摸坐标,不要把真机当成 UI 调试器用。
9.4 注意 PSRAM 和 DMA 的限制
PSRAM 空间大,但外设 DMA 和缓存一致性需要测试。刷屏 buffer 可以放在 PSRAM,但需要确认当前 ESP32-S3 的访问路径和 DMA 是否可靠。出问题时,先用内部 SRAM 的小 buffer 跑一遍,再逐步调大 buffer,这样能判断出是否和 PSRAM 访问有关。不能简单认为“PSRAM 内存大就一定适合放显示缓冲”。
9.5 电源设计不要省
屏幕刷新和背光驱动会在瞬间拉高电流。开发阶段用开发板供电没问题,但产品化时要用独立稳压模块给背光和逻辑供电,ESP32-S3 的 GPIO 输出能力不能直接作为大电流背光驱动。屏幕复位脚如果没有上拉电阻,建议加一个 10K 上拉到 3.3V,避免上电瞬间误触发复位。
9.6 合规与资料使用
AXS15260 的寄存器手册、初始化例程多数来自模组厂或芯片原厂。使用时注意确认获取渠道是否合法,不要传播未授权导出的内部资料。如果项目后续量产,还要核对屏幕型号、接口定义、焊接方式是否与设计一致。涉及产品 UI 素材时,使用开源字体或自绘素材,避免字体版权纠纷。
10. 总结与下一步
esp32s31_3.97寸axs15260_lvgl9这类工程,本质上是在解决“ESP32-S3 如何把 LVGL9 渲染结果稳定显示到 AXS15260 屏”的问题。最值得先验证的不是复杂 UI,而是三件事:屏幕初始化能不能点亮、lv_timer_handler循环跑起来之后刷新是否稳定、显示 buffer 的内存分配是否合理。这三个点通了,后续叠加 WiFi、定时器、传感器、OAuth 登录联调都只是时间问题。
最容易踩的坑也很集中:LVGL9 的 API 和旧教程对不上、AXS15260 初始化命令缺失导致白屏、buffer 分配太大导致反复重启。建议第一次部署时直接用组件管理器依赖 LVGL9,先跑一个 16 bit 颜色、单缓冲或小区域双缓冲的最小工程,确认屏幕亮起来后再逐步加 UI。这样即使出问题,也能快速区分是驱动问题还是界面代码问题。
下一步可以考虑在这个底板上接入 WiFi 和 NTP 做一个天气时钟,或者加触摸屏做控制面板,再往后可以用 OTA 远程更新界面。也可以把工程代码整理成多个模块,把 AXS15260 驱动作为可复用的组件,后续换其他 ESP32-S3 屏幕型号时直接替换底层驱动即可。总的来说,这个项目是 ESP32-S3 小型图形界面开发里很典型的一步,跑通它,就等于掌握了 LVGL9 在 ESP32 平台上的核心接入流程。