这块带2.4寸触摸屏的ESP32S3开发板在工位上亮了半个月了,从最开始屏幕只出白光,到用TFT_eSPI刷出一块块色块,再到SquareLine Studio里拖出来的仪表指针在LVGL界面上跟着数据转动——这条“设计工具出UI、LVGL跑界面、TFT_eSPI点亮屏幕”的链路算是完整打通了。整个过程踩的坑不算少,但走通之后回头再看,这套组合其实非常适合快速做出一块带图形界面的智能硬件原型。
先给没接触过的朋友理一下关系:ESP32S3是主控芯片,负责跑逻辑和图形渲染;LVGL是一个专为嵌入式设备设计的轻量级图形库,负责控件绘制、事件分发和动画;SquareLine Studio是LVGL官方推出的可视化UI设计工具,拖拽控件之后能直接导出C代码;TFT_eSPI则是ESP32生态里最流行的屏幕驱动库,负责通过SPI总线把像素点真正刷到屏上。简单说,SquareLine Studio让你像做PPT一样完成界面设计,导出代码后由LVGL解释渲染,最后由TFT_eSPI完成最底层的像素输出。
这篇东西适合谁看?如果手上正好有一块ESP32S3开发板和SPI屏幕,想给项目加个触摸界面,但又不想从裸机驱动一点点摸,那这篇文章基本可以照着抄。我也会把每个环节里容易翻车的细节单独拎出来讲,尽量让你少走弯路。
1. 方案选型与整体流程解析
1.1 为什么是ESP32S3而不是STM32或者Linux小板子
做图形界面,很多人第一反应是STM32,但实际用下来,除非只是做一两个静态页面,否则STM32的体验会比较憋屈。以常见的STM32F103RCT6为例,主频72MHz,SRAM 64KB,跑一个稍微复杂点的LVGL界面,刷屏速度和动态加载都会捉襟见肘,尤其开了动画之后CPU占用会很夸张。
ESP32S3的优势主要在这几点:双核Xtensa LX7 240MHz,算力对于LVGL来说是够用的;内置和外部扩展PSRAM之后,可以一次性分配几十KB甚至几百KB的帧缓冲,对界面流畅度的影响是决定性的;再加上芯片本身就带Wi-Fi和蓝牙,做出来的界面可以直接跟云平台或手机联动,这是STM32方案要额外加模块才能实现的。
如果你的项目预算和体积允许,直接用带PSRAM的ESP32S3模组,是最省心的选择。至于嵌入式Linux加Qt的方案,性能和扩展毫无疑问更强,但启动速度、功耗和成本都会上去,对一个几寸小屏幕的交互设备来说属于杀鸡用牛刀。
1.2 LVGL和SquareLine Studio的任务划分
LVGL在嵌入式图形界面领域基本是事实标准,核心原因是它专门为资源受限的MCU设计,对内存和CPU的占用都可以精确控制,而且提供了丰富的控件库,比如按钮、滑块、仪表、图表、列表、标签页等。它不依赖任何操作系统,裸机可以跑,FreeRTOS下也可以跑,这也是它在ST、ESP、NXP等平台都通用的原因。
但LVGL本身有一个学习曲线:把控件一个个用代码创建出来、设置坐标、绑定事件,虽然不难,但很琐碎。尤其当你需要反复调整界面布局时,每次都要改坐标编译烧录,效率非常低。
SquareLine Studio解决的就是这个问题。你在工具里拖控件、调颜色、设动画、写交互逻辑,导出之后它就是标准的LVGL C代码。换句话说,SquareLine Studio管界面设计,LVGL管运行渲染,二者互为表里。用这套流程最大的收益是,后期改界面不用动主逻辑代码,导出一份新UI文件替换进去就行,对迭代原型非常友好。
1.3 TFT_eSPI在这条链路里的角色
TFT_eSPI是一个面向ESP系列芯片的TFT液晶屏驱动库,支持的驱动芯片很全,常见的有ST7789、ILI9341、ILI9488等。它比Adafruit GFX的优势在于专门为ESP32优化过,支持DMA传输,刷屏速度有明显提升。
在整条链路中,TFT_eSPI是离硬件最近的一层。LVGL不关心你用的是哪颗屏幕驱动芯片,它只负责计算出每一块需要刷新的区域的颜色数据,然后调用你在显示驱动回调里提供的函数。这个函数内部做的事情,就是告诉TFT_eSPI“我要在这块屏幕的这个区域写这么多像素”,然后TFT_eSPI把数据通过SPI总线送到屏幕控制器。
这里有一个很容易让新手困惑的点:LVGL和TFT_eSPI并不“内置对接”,你需要自己写一个分发回调。好在代码量很小,核心就二三十行,后面我会给出可以直接用的实现。
1.4 全流程主线:从设计到点亮的五步
把整套流程拆开看,主线是非常清晰的:
- 在SquareLine Studio里创建工程,选好分辨率、色深、LVGL版本,然后拖拽控件设计界面。
- 设计完成后导出C代码,得到一个包含界面初始化、事件绑定、图片和字体资源的文件夹。
- 在Arduino或者PlatformIO里配置好TFT_eSPI,确认屏幕能正常点亮和显示颜色。
- 将SquareLine Studio导出的文件加入工程,并写一个LVGL显示驱动回调,把LVGL的绘制指令翻译成TFT_eSPI的像素操作。
- 接入触摸屏,把触摸坐标上报给LVGL的输入设备驱动,界面就能点按交互了。
每一步之间都有明确的输入输出关系,所以排查问题时也方便:屏幕没有显示,先查第3步;界面出来了但点不动,先查第5步;界面显示错乱,往往在缓存或者色彩格式上。后面几个小节我会按这个顺序逐一展开。
2. 动手准备:硬件接线与开发环境搭建
2.1 我用的硬件清单和相关建议
先列一下我这套实际用到的硬件,不一定照抄,但规格可以作为参考:
- 主控板:ESP32-S3-DevKitC-1兼容开发板,双核240MHz,带8MB PSRAM,型号是ESP32-S3-N8R8。
- 屏幕:2.4英寸320x240分辨率TFT屏,SPI接口,驱动芯片ST7789,带电阻触摸屏XPT2046。
- 连接线:杜邦线若干,建议用短一点且质量好一些的,因为SPI时钟频率提上去之后长飞线很容易出现数据错乱。
- 电源:显示屏背光耗电不大,直接从开发板的3.3V引脚取电就行,但如果屏幕尺寸较大,建议使用外接稳压模块,避免背光开启时电压跌落导致花屏或重启。
PSRAM是我的强烈建议。LVGL界面一旦涉及图片、较多样化的字体或更大的帧缓冲,没有PSRAM会非常局促。如果你买的是无PSRAM的ESP32S3,界面简单还行,复杂一点就会频繁卡顿。
2.2 接线表:SPI引脚映射和触摸引脚
我这边使用的引脚定义如下(不同开发板丝印可能不同,但GPIO编号是可配置的):
| 功能 | 屏幕引脚 | GPIO |
|---|---|---|
| SPI时钟 | SCLK | GPIO12 |
| SPI数据 | MOSI | GPIO11 |
| 片选 | CS | GPIO10 |
| 数据/命令 | DC | GPIO9 |
| 复位 | RST | GPIO8 |
| 背光 | BLK | GPIO7 |
| 触摸片选 | TOUCH_CS | GPIO6 |
| 触摸中断 | TOUCH_IRQ | GPIO5 |
有两个地方要特别说明。第一,SPI屏幕一般不需要接MISO,除非屏幕支持回读,大多数情况下不需要;第二,背光引脚BLK很多教程会直接接3.3V,这样屏幕会常亮,但如果你希望由程序控制亮度或开关,需要引到GPIO。此外,DC引脚接错会导致屏幕亮但画面全部错乱,这是比CS接错更隐蔽的问题,建议接线后用万用表再对一遍。
2.3 开发环境:Arduino框架就够了
很多从MCU转过来的朋友习惯用Keil,在ESP32S3上其实没必要,Arduino框架已经把底层大部分封装好了,直接用起来效率最高,尤其是配合TFT_eSPI和LVGL这两套库,生态非常成熟。
安装步骤我先快速带一下。打开Arduino IDE,进入“开发板管理器”,搜索esp32,选择官方ESP32 Arduino Core安装。我自己用的是2.0.x版本,建议不要一看到最新版就装,有些新版本和第三方库的兼容性不一定稳。在“开发板”里选择ESP32S3 Dev Module即可。
如果你习惯用PlatformIO,也可以用ESP-IDF框架,但整套流程会复杂不少,对本主题来说没必要。Arduino框架的实时性对这个场景完全够用,我用下来最大的好处是库管理方便,SquareLine Studio导出的代码往src目录一放就能编译。
2.4 库安装的两个重点
在Arduino库管理器里搜索并安装以下库:
- TFT_eSPI(作者Bodmer)
- LVGL(注意安装8.3.x版本,不要装9.x)
- XPT2046_Touchscreen(用于电阻触摸屏)
这里有三个容易踩的坑。第一个坑是LVGL版本,SquareLine Studio较新的1.4.x版本默认导出LVGL 8.3,如果你在Arduino里装了LVGL 9.x,编译时会出现大量函数名不存在的报错,因为9.x改了不少API;第二个坑是TFT_eSPI安装后默认没有开启任何屏幕驱动,必须手动改User_Setup.h,不改的话编译会提示找不到驱动配置;第三个坑是XPT2046_Touchscreen库有不少分支版本,我建议选PaulStoffregen的原版,兼容性最稳。
3. 屏幕驱动层:TFT_eSPI深入配置
3.1 User_Setup.h里必须搞清楚的几个宏
TFT_eSPI的全部驱动配置都集中在User_Setup.h里,这个文件在库文件夹的根目录。官方示例默认支持的是M5Stack等开发板自带的屏幕,所以拿到新屏之后第一件事就是改配置。
需要关注的核心宏有这么几个:
- 屏幕驱动芯片:比如
#define ST7789_DRIVER或#define ILI9341_DRIVER。不同的芯片初始化命令序列不同,选错会导致黑屏或者颜色异常。 - 屏幕尺寸:
#define TFT_WIDTH 240和#define TFT_HEIGHT 320。需要提醒的是,部分屏幕虽然是2.4寸,但实际驱动芯片内部坐标可能是240x320,如果设置反了,画面会出现只显示一部分的情况。 - SPI引脚:
TFT_MOSI、TFT_SCLK、TFT_CS、TFT_DC、TFT_RST,这些用上一节表格里的GPIO号填入即可。 - 背光引脚:
#define TFT_BL后面的注释已经写得很清楚,默认情况下TFT_eSPI不一定初始化它,如果屏幕不亮,先确认是不是背光没拉高。 - SPI频率:
SPI_FREQUENCY默认是27MHz,我在实际项目中设置为40MHz。不建议一开始就上80MHz,长线传输时会非常容易出现花屏或干扰。
还有一个比较隐蔽的宏是SPI_TOUCH_FREQUENCY,这个值专门给触摸屏通信使用,不要和屏幕刷新频率混为一谈。触摸响应不需要太高,2.5MHz就足够了,太高反而容易增加噪声。
3.2 一份能直接用的TFT_eSPI配置片段
以下是我这块ST7789屏幕用的User_Setup.h精简片段,你可以按自己的硬件替换引脚:
#define ST7789_DRIVER #define TFT_WIDTH 240 #define TFT_HEIGHT 320 #define TFT_MOSI 11 #define TFT_SCLK 12 #define TFT_CS 10 #define TFT_DC 9 #define TFT_RST 8 #define TFT_BL 7 #define LOAD_GLCD #define LOAD_FONT2 #define LOAD_FONT4 #define LOAD_FONT6 #define LOAD_FONT7 #define LOAD_FONT8 #define LOAD_GFXFF #define SMOOTH_FONT #define SPI_FREQUENCY 40000000 #define SPI_READ_FREQUENCY 20000000 #define SPI_TOUCH_FREQUENCY 2500000配置好之后,编译下载一个最简单的TFT_eSPI内置示例,比如TFT_Print_Test,如果能看到屏幕滚动输出文字,说明驱动层已经通了。这里提醒一句:每次修改User_Setup.h之后,最好执行一次Clean编译,这个库的配置是通过宏嵌入编译的,Arduino有时候不够聪明,不会自动重新编译被改动的库文件。
3.3 用矩形填充测试屏幕是否真正正常
在开始LVGL之前,我强烈建议先写一段简单的测试代码,逐块填充屏幕颜色。直接背光一亮就上LVGL,如果屏幕上花屏你会很难判断是屏幕驱动问题还是LVGL配置问题。
测试代码思路很简单,分四个循环画四条垂直线,颜色分别是红、绿、蓝、白:
#include <TFT_eSPI.h> TFT_eSPI tft; void setup() { tft.init(); tft.setRotation(1); tft.fillScreen(TFT_BLACK); tft.fillRect(0, 0, 60, 320, TFT_RED); tft.fillRect(60, 0, 60, 320, TFT_GREEN); tft.fillRect(120, 0, 60, 320, TFT_BLUE); tft.fillRect(180, 0, 60, 320, TFT_WHITE); } void loop() {}如果四条色带显示清晰、颜色正常,再进入下一步。如果颜色通道混乱,比如红色和蓝色对调了,基本是RGB与BGR颜色排序问题,在TFT_eSPI里可以尝试打开或关闭#define TFT_RGB_ORDER TFT_BGR这种宏,具体取决于屏幕硬件。如果画面出现水平方向的条纹或撕裂,先降SPI频率再重新测试。
3.4 方向设置和坐标系理解
屏幕有一个“原生方向”的概念。ST7789这类驱动芯片内部的坐标系以屏幕的某个角为原点,当屏幕被物理安装成不同方向时,你需要通过setRotation()来调整。
setRotation(0)到setRotation(3)分别对应四个旋转方向。对于320x240分辨率,旋转后的逻辑宽高也会跟着变。这里有个知识点:在LVGL中,屏幕的宽度和高度必须与setRotation()的结果保持一致,否则控件会画到屏幕外面去。
我建议在初始化阶段先固定一个旋转方向,不要频繁切换。因为在SquareLine Studio里设计界面时,你选定的分辨率和方向,最终是硬编码进导出代码里的。如果中途改了屏幕旋转,UI坐标会对不上,排查起来很痛苦。
4. SquareLine Studio里的UI设计与代码导出
4.1 创建工程时的三处关键设置
SquareLine Studio是安装即用的图形化工具,创建新工程时会让你选择设备模板和分辨率。这里有三处必须注意:
第一,屏幕分辨率。我的屏幕是320x240横向使用,所以工程分辨率选择320x240即可,工具里预设了很多常见型号,没有的话可以手动输入。第二,色彩深度。选择RGB565(16bit),因为TFT_eSPI默认就是16bit颜色输出,选错会出现颜色偏差。第三,LVGL版本。新版本SquareLine Studio创建工程时会有一个LVGL版本选项,务必选择与Arduino库匹配的版本,我是统一用8.3。
创建完成之后,工作区会有一个默认的Screen,这就是我们在LVGL里的主屏幕对象,对应代码里的ui_Screen1。
4.2 拖控件、调属性、绑事件
SquareLine Studio的界面操作非常直观。左侧是控件面板,按钮、标签、滑块、仪表、图表、开关等等都用拖拽的方式添加。右侧是属性面板,可以调整控件的坐标、尺寸、颜色、圆角、字体、阴影,以及在LVGL里非常重要的align属性。
align属性决定了控件相对屏幕或其他控件的对齐方式,比如CENTER、LEFT_MID、TOP_RIGHT等。为什么非要强调这个?因为LVGL布局跟HTML的流式布局不一样,如果你全部用绝对坐标,屏幕旋转之后控件位置就乱了。建议养成用align布局的习惯,在SquareLine Studio里选择好对齐方式后,导出的代码会自动生成相对布局逻辑。
事件绑定是另一个重点。选中任意控件,点击右上角的“交互”标签,选择对应的事件,比如“点击”、“数值变化”等,工具会自动创建一个事件回调占位函数,并在ui.c里自动注册好。你需要做的就是在这个回调函数里实现界面逻辑。这样做的好处是,事件绑定和界面初始化完全分离,后面如果只是改界面布局,不需要动逻辑代码。
4.3 字体、图片资源的处理方式
SquareLine Studio对字体的处理有自己的一套。它可以把TTF字体文件导入并转换成LVGL的字体数据,支持选择需要包含的字号和字符范围。如果你UI上只需要数字和少量英文,字符范围可以只选ASCII,这样生成的字体文件会小很多;如果必须显示中文,建议使用“逐个添加字符”的方式,只把界面用到的字加进去,否则全量中文字库会让Flash空间瞬间膨胀。
图片资源也是同样的逻辑。SquareLine Studio支持导入PNG、JPG等格式,导出时会转换成C语言的像素数组,但你导入的图不能太大。我的习惯是所有图标控制在32x32以内,色彩简单一点,因为图片在内存中是RGB565格式,尺寸每翻一倍,内存占用是成平方级增长的。
如果你要做动图或者大量图片素材,建议先压缩再导入。LVGL在8.3版本对图片解码有硬件加速接口,但ESP32S3配上TFT_eSPI没有额外的图像协处理器,最终还是靠优化素材撑性能。
4.4 导出项目:生成文件结构和必须保留的文件
界面设计、事件函数写完,点击右上角的Export导出。导出时要选择平台为Arduino或通用C,工具会生成一个以工程名命名的文件夹。
文件夹里面最重要的内容是ui子目录,通常包括:
ui.c:所有界面控件的创建和初始化逻辑ui.h:对外暴露的ui_init()声明ui_events.c:事件回调函数的空实现,你的逻辑写在这里ui_events.h:事件回调函数声明ui_helpers.c/.h:一些辅助函数images目录:图片资源的C数组源文件fonts目录:字体资源的C数组源文件
记住一点:SquareLine Studio生成的ui_init()只能调用一次。它会创建所有屏幕和控件,如果你在代码里写了一个复位界面或者重刷逻辑,不能直接再调一遍ui_init(),否则会重复创建控件导致内存泄漏或者LVGL断言报错。正确的做法是针对具体控件做属性更新,而不是整套界面重新初始化。
5. 将UI工程接入ESP32S3的完整移植过程
5.1 显示桥接:LVGL如何把数据交给TFT_eSPI
LVGL本身不直接操作屏幕,它通过lv_disp_drv_t这个显示驱动结构体向“外部显示设备”发送绘制指令。我们在Arduino代码里要做的,就是把TFT_eSPI的实例绑定到这个结构体上。
核心流程如下:
#include <lvgl.h> #include <TFT_eSPI.h> TFT_eSPI tft; lv_disp_draw_buf_t draw_buf; lv_disp_drv_t disp_drv; 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((uint16_t *)&color_p->full, w * h, true); tft.endWrite(); lv_disp_flush_ready(disp); }这段代码的关键理解点是:LVGL会告诉你一个矩形区域,也就是area,并给你这个区域内所有像素的颜色数组color_p。你要做的就是用TFT_eSPI设置屏幕窗口,然后把这个颜色数组一次性推过去。
pushColors的最后一个参数传true表示启用DMA传输,前提是TFT_eSPI初始化时调用了tft.initDMA()。DMA的好处是数据搬运不占CPU,LVGL在上一批像素还没刷完时,可以继续计算下一帧画面,这对动画流畅度影响极大。
5.2 显示缓冲区:决定流畅度的核心
LVGL的绘制不是一块像素一块像素往屏上写的,而是先绘制到一个内存缓冲区,再由flush回调整块刷出去。缓冲区的大小直接决定刷新效率。
在setup()里初始化buffer:
static lv_color_t buf1[240 * 32]; static lv_color_t buf2[240 * 32]; void setup() { lv_init(); lv_disp_draw_buf_init(&draw_buf, buf1, buf2, 240 * 32); disp_drv.hor_res = 320; disp_drv.ver_res = 240; disp_drv.flush_cb = my_disp_flush; disp_drv.draw_buf = &draw_buf; lv_disp_drv_register(&disp_drv); }buf1和buf2构成双缓冲,LVGL在显示buf1内容的同一时间,可以往buf2里绘制下一帧。如果你内存充足,可以直接分配全屏大小的缓冲,也就是320x240x2字节,约150KB。ESP32S3有PSRAM的话,用heap_caps_malloc分配在PSRAM里就行,这样LVGL渲染时不会挤占内部SRAM空间,内存压力小很多。
区分一下两个概念:部分缓冲和全帧缓冲。部分缓冲在复杂界面上容易出现刷新区块多、刷屏断层的情况,全帧缓冲则能最大程度避免闪烁。代价是内存。我实测下来,240x32的双缓冲在2.4寸屏幕上已经有不俗的表现,如果追求丝滑,再上到全帧缓冲。
5.3 触摸接入:坐标采集和回调注册
触摸屏的物理信息由XPT2046芯片读取,Arduino里用XPT2046_Touchscreen库,然后通过LVGL的lv_indev_drv_t输入设备驱动,把坐标上报给界面。
我用的初始化代码:
#include <XPT2046_Touchscreen.h> XPT2046_Touchscreen ts(6, 5); void my_touchpad_read(lv_indev_drv_t *indev, lv_indev_data_t *data) { if (ts.touched()) { TS_Point p = ts.getPoint(); >void setup() { Serial.begin(115200); tft.init(); tft.setRotation(1); tft.initDMA(); lv_init(); lv_disp_draw_buf_init(&draw_buf, buf1, buf2, 240 * 32); lv_disp_drv_register(&disp_drv); lv_indev_drv_register(&indev_drv); ui_init(); } void loop() { lv_timer_handler(); delay(5); }需要解释两点。第一,tft.init()必须在lv_init()之前调用,因为LVGL初始化后可能立即就会查询屏幕参数;第二,ui_init()必须放在所有LVGL驱动注册之后,因为SquareLine Studio生成的代码在创建控件时依赖显示器已经存在。
loop()里维护LVGL心跳也算一个关键点。lv_timer_handler()是LVGL的定时器处理函数,负责动画、刷新、触摸检测等所有事务。不要在里面做复杂计算或者长延时,否则界面会出现肉眼可见的卡顿。
5.5 用FreeRTOS还是裸机loop?
ESP32S3默认的Arduino框架在loop()之下其实已经跑着FreeRTOS,但我们暂时不需要手动创建任务。LVGL官方推荐的做法是,如果项目里有耗时操作,比如网络请求、传感器读取,应该把它们放到独立任务里,不要阻塞lv_timer_handler()的执行。
举个例子,我需要定时获取传感器数据并更新仪表指针,于是创建了一个update_data_task,每隔500ms更新一次,界面渲染放在主循环。这样做的好处是数据更新和界面渲染完全解耦,不会互相拖累。
如果你真的想在FreeRTOS线程中独立跑LVGL,可以参考LVGL官方文档里lv_port的示例,核心思路是把lv_timer_handler()放到一个优先级的任务中,并设置合适的Tick计数方式,比如让LVGL使用esp_timer的微秒计时。
6. 实测经验:花屏、卡顿、触摸漂移的排查流程
6.1 现象对照速查表
我把这半个月遇到的各种问题整理成了下面的速查表,基本覆盖了90%的常见故障:
| 现象 | 最常见原因 | 排查顺序 |
|---|---|---|
| 屏幕完全不亮 | 背光没控制 | 先测BLK引脚电压,再接3.3V测试 |
| 屏幕白光,无内容 | SPI引脚、初始化顺序 | 检查DC/CS引脚,检查TFT_eSPI配置 |
| 画面花屏 | SPI频率过高、接线过长 | 降频率到27MHz,换短杜邦线 |
| 颜色错乱 | RGB/BGR顺序不对 | User_Setup.h里切换TFT_RGB_ORDER |
| 只显示一半图像 | 分辨率或旋转设置不对 | 检查TFT_WIDTH/HEIGHT及setRotation |
| 界面能显示但点不动 | 触摸驱动未注册或坐标方向不对 | 检查ts.begin()和触摸回调 |
| 点击有反馈但位置偏移 | 触摸坐标映射范围不对 | 打印原始值,重新map |
| 动画掉帧明显 | 缓冲区太小或未开DMA | 开双缓冲、加大缓冲区、打开DMA |
| 编译报大量LVGL错误 | 版本不匹配 | Arduino库换成LVGL 8.3.x |
| 运行一段时间后崩溃 | 内存不足或控件重复初始化 | 检查堆内存,避免重复调用ui_init |
6.2 花屏问题:先把频率降下来再谈优化
我一开始把SPI频率设置成80MHz,结果屏幕刷新时总会有细微的横纹,尤其在纯色背景下特别明显。一开始怀疑是屏幕本身的问题,后来把频率降到40MHz,现象消失了。
很多朋友一遇到花屏就想换屏幕,其实是冤枉了硬件。SPI传输在长杜邦线、主板供电纹波大的情况下,频率越高越容易出错。建议先用短线和稳定供电确认屏幕体质,再把频率慢慢加回合理范围。40MHz是性能和稳定性的一个平衡点,对于240x320这种小屏,刷新率已经足够。
还有一个容易被忽略的因素是开发板的USB供电。如果USB线质量差,屏幕刷新时电流瞬时增大,电压跌到4.5V以下,SPI信号就不稳定了。碰见花屏,先换一根粗线供电试一下,能省很多排查时间。
6.3 触摸漂移:用地图和翻转解决
触摸漂移意味着你按的位置和界面响应位置不一致。先做一个定位测试:在屏幕上画几个固定圆点,依次点按,通过串口打印触摸坐标和屏幕坐标进行对比。
如果发现X轴反了,就在my_touchpad_read里修正为>