ESP32驱动1.44寸TFT彩屏:SPI接口与LVGL图形库实战指南

发布时间:2026/9/2 11:44:06
ESP32驱动1.44寸TFT彩屏:SPI接口与LVGL图形库实战指南 简介本资源是一份面向嵌入式初学者与物联网开发者的ESP32图像显示实践方案聚焦于在资源受限的单片机平台上实现摄像头数据采集与1.44英寸TFT屏幕实时显示这一典型嵌入式视觉应用。压缩包共含2个Python源文件总计4KB核心为MicroPython环境下SPI驱动协同设计其中lcd_spi.py封装了TFT屏与OV2640等摄像头的底层SPI通信配置含时钟极性、相位、波特率及引脚映射test_lcd_cam.py则整合图像捕获、JPEG解码适配、RGB565格式转换与帧缓冲刷新逻辑支持低延迟显示。已有1756人学习下载代码结构清晰、注释完整无需额外依赖库可直接烧录运行是理解ESP32硬件加速外设协同、嵌入式图像处理流程与微型GUI显示机制的优质入门范例。1. 项目概述让ESP32“看见”并“展示”图像如果你手头有一块ESP32开发板和一块小巧的1.44寸TFT彩屏是不是总想让它做点比显示文字和简单图形更酷的事情比如让它显示一张照片、一个图标甚至是动态的传感器数据可视化图表。这个想法实现起来远比想象中要直接和有趣。今天要聊的就是如何驱动ESP32从获取图像数据源开始一步步让这些数据在1.44寸的TFT屏幕上完美呈现出来。这不仅仅是一个简单的“点亮屏幕”实验。它涉及从数据源头可能是内存数组、文件系统或者网络获取原始的像素信息理解TFT屏幕的驱动原理再到通过SPI或并行接口高效地将这些数据“刷”到屏幕上。整个过程就像是为ESP32构建了一套微型的“视觉系统”。无论是想做一个迷你相框、一个传感器数据的图形化终端还是一个简单游戏的显示界面这个核心流程都是必经之路。接下来我会结合自己多次调试的经验把从硬件连接到软件实现的每一个环节掰开揉碎特别是那些容易踩坑的地方让你能快速复现并应用到自己的项目中。2. 核心思路与方案选型为什么是SPI与LVGL拿到ESP32和1.44寸TFT屏第一反应可能是直接找最通用的库。但市面上库那么多选哪个怎么搭配这里面的门道直接决定了后续开发的效率和最终显示的效果。2.1 硬件接口的抉择SPI为何成为首选绝大多数1.44寸TFT屏如常见的ST7735S、ILI9341驱动芯片的变种都支持SPI串行外设接口和8位并行接口。对于ESP32而言我强烈推荐使用SPI模式原因有三点。第一是引脚资源占用少。一个标准的SPI接口通常只需要4-6根线SCLK时钟、MOSI主出从入、DC数据/命令选择、CS片选、RST复位有时再加一个背光控制。而8位并行接口动辄需要十几根数据线和控制线对于引脚资源并不算极度充裕的ESP32尤其是某些只有较少GPIO的型号来说SPI能节省出大量引脚用于连接其他传感器或外设。第二是软件生态成熟。Arduino社区和ESP-IDF框架下针对SPI TFT屏的驱动库如TFT_eSPI经过多年优化其稳定性和效率都非常高。这些库底层通常利用ESP32的硬件SPI控制器配合DMA直接内存访问技术能在极低的CPU占用率下完成屏幕刷新。第三是布线简单抗干扰能力相对较好。SPI线少在面包板或洞洞板上连接更清晰减少了飞线混乱导致的接触不良或信号串扰问题。虽然理论上并行接口的刷新率更高但对于1.44寸这种分辨率常见128x128或128x160的小屏幕SPI的刷新速度已经完全足够流畅显示图像和简单动画。注意在购买屏幕时务必确认其驱动芯片型号如ST7735, ILI9163, ILI9341等并留意屏幕模块上是否有电阻配置来选择SPI或并行模式。很多模块默认就是SPI模式需要通过焊接电阻来切换。2.2 软件库的黄金组合TFT_eSPI LVGL确定了硬件接口软件栈的选择就清晰了。我的方案是TFT_eSPI作为底层硬件驱动LVGL作为上层图形库。TFT_eSPI是一个专为ESP32、ESP8266等平台优化的TFT屏幕驱动库。它的强大之处在于高度可配置性。你需要在一个名为User_Setup.h的头文件里根据你的屏幕型号、驱动芯片、引脚连接方式、SPI频率等进行精确配置。一旦配好它就能提供一套非常高效的底层API用于画点、线、矩形、填充、显示图片等基本操作。它直接操作ESP32的硬件SPI效率是很多通用Adafruit库无法比拟的。那么为什么还要引入LVGLLight and Versatile Graphics Library呢因为如果你需要显示复杂的用户界面比如按钮、滑块、图表、动画或者需要从文件系统加载多张图片纯靠TFT_eSPI的基础API来手撸代码工作量巨大且难以维护。LVGL是一个开源的高性能嵌入式图形库它提供了完整的UI组件Widgets、样式系统、动画引擎和事件处理机制。你可以用类似“创建按钮-设置坐标-绑定回调函数”这样的高级抽象来构建界面LVGL底层会调用TFT_eSPI或其他驱动来实际渲染。简单来说TFT_eSPI是“发动机”负责把像素数据高速送到屏幕LVGL是“车身和内饰设计”让你能轻松构建出美观易用的界面。两者通过一个“显示驱动回调函数”接口连接配合非常默契。2.3 图像数据来源的规划图像数据从哪里来这决定了项目的应用方向。主要有三种来源程序内数组C数组最简单的方式将小尺寸的图标、Logo转换成C语言数组直接编译进程序。适用于固定不变的静态小图。工具可以用Image2Lcd或在线转换工具。SPIFFS/LittleFS文件系统将图片文件如BMP、PNG需解码存入ESP32的Flash中划分出的文件系统分区。运行时动态读取并显示。这种方式更灵活可以更换图片而不需重新编译程序。需要用到LVGL的图片解码器或额外的库。网络获取ESP32通过Wi-Fi从服务器下载图片数据如JPEG在内存中解码后显示。这可以实现天气信息图、远程监控画面等高级功能。这对ESP32的内存RAM和解码能力是较大考验。对于入门和大多数应用我建议从程序内数组和SPIFFS文件系统开始它们稳定且易于调试。3. 硬件连接与驱动库配置详解理论清楚了现在开始动手。第一步是把硬件正确地连接起来并配置好软件环境。3.1 ESP32与1.44寸TFT屏的接线指南我以一款常见的驱动芯片为ST7735S的1.44寸SPI屏幕为例。你需要准备杜邦线母对母或公对母并找到ESP32开发板的引脚定义图。以下是经典的接线方式我推荐使用ESP32的默认硬件SPI引脚HSPI以获得最佳性能TFT屏引脚功能ESP32引脚推荐备注VCC电源正极3.3V绝对不要接5V大部分1.44寸屏是3.3V逻辑。GND电源地GND共地。SCL(SCK)SPI时钟GPIO 18(HSPI CLK)硬件SPI时钟线。SDA(MOSI)SPI数据线GPIO 23(HSPI MOSI)硬件SPI主设备输出线。RES(RST)复位GPIO 4可自定义上电或软件复位用。DC(A0)数据/命令选择GPIO 2可自定义高电平写数据低电平写命令。CS(SS)片选GPIO 15可自定义低电平选中该SPI从设备。BL背光控制GPIO 12(或接VCC)可自定义PWM控制可实现调光。实操心得GPIO 2在ESP32上电时会有个短暂的电平变化有时会导致屏幕初始化乱码。如果遇到可以换到GPIO 0等其他引脚但注意GPIO 0涉及启动模式。最稳妥的方法是在程序初始化时先确保DC引脚为低电平延迟片刻后再执行屏幕初始化序列。接线时务必断电操作。接好后检查一遍VCC和GND是否接反、接错这是烧毁模块最常见的原因。3.2 TFT_eSPI库的精准配置在Arduino IDE中安装好TFT_eSPI库后找到其配置文件。它位于你的Arduino库目录下例如文档\Arduino\libraries\TFT_eSPI\User_Setup.h。用文本编辑器打开它我们需要修改关键部分。首先注释掉其他所有驱动芯片的定义只启用你屏幕对应的那一个。对于ST7735S通常是// #define ILI9341_DRIVER // 常见2.4/2.8寸屏 #define ST7735_DRIVER // 常见1.44寸屏 // #define ILI9163_DRIVER // 另一种1.44寸屏然后根据你的屏幕分辨率设置。常见的1.44寸屏是128x128但也有128x160的。必须设对否则显示会错位。#define TFT_WIDTH 128 #define TFT_HEIGHT 128 // 或 160接下来是最关键的引脚定义部分必须与你刚才的接线一一对应#define TFT_CS PIN_D8 // GPIO 15对应你的CS引脚 #define TFT_DC PIN_D4 // GPIO 2 对应你的DC引脚 #define TFT_RST PIN_D2 // GPIO 4 对应你的RST引脚 // 如果你的背光控制接了GPIO可以定义否则注释掉或接VCC常亮 #define TFT_BL PIN_D1 // GPIO 12然后是SPI设置我们使用ESP32的硬件SPIHSPI#define USE_HSPI_PORT // 注释掉则使用默认VSPI但HSPI更常用 #define TFT_SPI_FREQUENCY 27000000 // SPI时钟频率27MHz对于ST7735通常很稳定可尝试4000000040MHz追求速度但可能不稳定。最后有些屏幕初始化时需要特殊的偏移Offset或颜色顺序RGB/BGR。如果显示颜色不对比如红色和蓝色反了可以调整#define TFT_RGB_ORDER TFT_RGB // 或 TFT_BGR // 如果显示内容整体偏移调整这些值 #define ST7735_OFFSETX 0 #define ST7735_OFFSETY 0保存文件。现在TFT_eSPI库就针对你的硬件准备好了。3.3 LVGL库的引入与基础集成在Arduino库管理中搜索并安装lvgl。LVGL的集成稍复杂因为它本身是一个框架需要你写一些“胶水代码”将其与TFT_eSPI和ESP32的系统对接。你需要完成三个核心任务显示驱动Display Driver告诉LVGL如何初始化屏幕、刷新指定区域。输入设备驱动如不需要触摸可跳过。任务调度Task Handler在loop()函数中定期调用lv_timer_handler()以处理LVGL的内部动画、事件等。网上有很多现成的集成示例。一个最简化的流程是在setup()中初始化TFT_eSPI对象然后调用一个自定义的lvgl_init()函数。在这个函数里你需要分配一块绘图缓冲区draw buffer。对于ESP32建议使用其PSRAM如果板子有的话因为LVGL的缓冲区消耗内存较大。#include lvgl.h #include TFT_eSPI.h TFT_eSPI tft TFT_eSPI(); static lv_disp_draw_buf_t draw_buf; // 声明绘图缓冲区 static lv_color_t buf[TFT_WIDTH * 10]; // 分配一个缓冲区大小为例屏幕宽度 * 10行像素 void my_disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) { // 这个函数由LVGL在需要刷新屏幕时调用 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_drv); // 通知LVGL刷新完成 } void lvgl_init() { lv_init(); // 初始化LVGL核心 lv_disp_draw_buf_init(draw_buf, buf, NULL, TFT_WIDTH * 10); // 初始化绘图缓冲区 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); // 注册显示驱动 } void setup() { Serial.begin(115200); tft.init(); // 初始化TFT_eSPI tft.setRotation(1); // 根据需要设置旋转方向0-3 lvgl_init(); // 初始化LVGL // ... 创建你的LVGL界面对象按钮、标签等 } void loop() { lv_timer_handler(); // 必须定期调用延迟5ms左右 delay(5); }这段代码是集成的骨架。my_disp_flush函数是性能关键它利用TFT_eSPI的pushColors函数进行区域刷新效率很高。4. 图像数据的获取与显示实战硬件和底层驱动就绪后我们进入核心环节获取图像数据并显示。我将分两种最实用的场景来讲解。4.1 场景一显示编译到程序内部的图标C数组这是最轻量级的方式适合显示启动Logo、固定图标等。你需要先将图片转换成C语言数组。可以使用工具如Image2Lcd或在线转换网站搜索“image to c array”。转换时关键参数设置输出数据类型选择C Array或Bin File。扫描模式选择水平扫描通常对应TFT_eSPI的格式。输出灰度16位真彩色RGB565。这是TFT屏幕最常用的格式一个像素用2字节16位表示R(5位)、G(6位)、B(5位)。包含头文件可选会生成宽度和高度的定义。转换后会得到一个.c和.h文件。将数组声明复制到你的Arduino项目中。使用TFT_eSPI显示非常简单// 假设你有一个名为 gImage_test 的数组定义了宽度和高度 IMAGE_WIDTH, IMAGE_HEIGHT #include image.h // 你的图片头文件 void showEmbeddedImage() { // 方法1使用pushImage函数速度最快 tft.pushImage(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, (uint16_t *)gImage_test); // 方法2如果需要先绘制到Sprite精灵图进行一些处理 TFT_eSprite spr TFT_eSprite(tft); // 创建精灵图 spr.createSprite(IMAGE_WIDTH, IMAGE_HEIGHT); spr.pushImage(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, (uint16_t *)gImage_test); // ... 可以在spr上进行绘图操作 spr.pushSprite(10, 10); // 将精灵图绘制到屏幕指定位置 spr.deleteSprite(); // 释放内存 }pushImage函数是阻塞式的会一次性将整个数组发送到屏幕。对于大图可能会暂时阻塞主循环。对于LVGL你需要使用LVGL的图片对象lv_img并将数组设置为其数据源这需要遵循LVGL特定的图片数据格式通常是lv_img_dsc_t结构体。4.2 场景二从SPIFFS文件系统读取并显示图片这种方式更灵活。首先你需要准备一张图片并将其上传到ESP32的SPIFFS分区中。步骤1准备Arduino IDE的SPIFFS上传工具在Arduino IDE中通常通过“项目文件夹”下的data目录来管理SPIFFS文件。你需要安装一个插件如ESP32FS它会将一个菜单项添加到“工具”中用于将data目录下的所有文件上传到ESP32的Flash。步骤2转换并存放图片将你的图片如test.jpg或test.bmp放到项目目录下的data文件夹里。对于LVGL它支持直接读取并解码BMP、PNG等格式需要启用相应的解码器。为了简单起见我们可以先用TFT_eSPI显示BMP格式因为TFT_eSPI内置了BMP解码函数。步骤3编写读取与显示代码#include SPIFFS.h void showBMPFromSPIFFS(const char *filename, int x, int y) { fs::File bmpFS SPIFFS.open(filename, r); // 以只读方式打开文件 if (!bmpFS) { Serial.println(File not found); return; } // 使用TFT_eSPI的drawBmp函数直接绘制 // 注意此函数会解析BMP文件头并绘制但仅支持特定的RGB565格式BMP bool success tft.drawBmpFile(SPIFFS, filename, x, y); if (!success) { Serial.println(BMP format not supported or draw failed.); } bmpFS.close(); } void setup() { Serial.begin(115200); if (!SPIFFS.begin(true)) { // 初始化SPIFFStrue表示如果失败则格式化 Serial.println(SPIFFS Mount Failed); return; } tft.init(); showBMPFromSPIFFS(/test.bmp, 0, 0); // 显示根目录下的test.bmp }TFT_eSPI的drawBmpFile函数非常方便但限制较多主要是BMP格式和位深度。对于更通用的图片显示尤其是在LVGL中更好的做法是使用LVGL的文件系统接口将SPIFFS挂载到LVGL。启用LVGL的PNG或JPG解码器需要额外的库如lodepng或tjpgd。创建一个LVGL图片对象lv_img_set_src(img, “S:/path/to/image.png”)LVGL会自动处理解码和显示。这种方式更强大但初始配置更复杂。它允许你在UI中动态加载和切换图片。4.3 图像显示的性能优化技巧在ESP32上显示图像尤其是动态更新时性能是关键。以下是几个实测有效的优化点使用双缓冲Double Buffering在LVGL中可以分配两个绘图缓冲区。当LVGL在其中一个缓冲区draw buffer中渲染下一帧时DMA可以同时将另一个缓冲区render buffer的数据发送到屏幕。这能极大提高刷新流畅度避免撕裂感。前提是你的ESP32有足够的PSRAM来分配两个缓冲区。// 在PSRAM中分配两个缓冲区 static lv_color_t *buf1 (lv_color_t *)ps_malloc(TFT_WIDTH * 20 * sizeof(lv_color_t)); static lv_color_t *buf2 (lv_color_t *)ps_malloc(TFT_WIDTH * 20 * sizeof(lv_color_t)); lv_disp_draw_buf_init(draw_buf, buf1, buf2, TFT_WIDTH * 20);提高SPI时钟频率在User_Setup.h中逐步提高TFT_SPI_FREQUENCY例如从27MHz到40MHz观察屏幕是否稳定。频率越高刷新越快。但过高的频率可能导致显示花屏或通信失败。减少单次刷新区域无论是直接使用TFT_eSPI还是通过LVGL都应避免全屏频繁刷新。只更新需要变化的区域。在LVGL中这由库自动管理。如果自己写驱动可以使用setAddrWindow和pushColors来指定更新区域。图片预处理确保图片尺寸不超过屏幕分辨率128x128/128x160。在电脑上提前将图片缩放并转换为正确的格式RGB565的BMP可以节省ESP32上宝贵的内存和解码时间。5. 常见问题排查与实战心得即使按照步骤操作也难免会遇到问题。这里我整理了几个最常遇到的“坑”及其解决方案。5.1 屏幕白屏、花屏或不亮这是最常见的问题排查思路如下电源检查首先确认VCC接3.3VGND共地。用万用表测量屏幕VCC和GND之间电压是否为稳定的3.3V。背光BL是否已使能接高电平或PWM信号。接线检查逐根检查SPI线是否接触不良尤其是CLK和MOSI这两根数据线。可以尝试更换引脚比如换到另一组硬件SPIVSPI的引脚是GPIO 18, 23, 19, 5? 注意MISO不用接。复位时序在setup()的最开始手动控制一下RST引脚复位屏幕。pinMode(TFT_RST, OUTPUT); digitalWrite(TFT_RST, LOW); delay(50); digitalWrite(TFT_RST, HIGH); delay(150); // 等待屏幕复位稳定驱动配置反复核对User_Setup.h中的驱动芯片型号、分辨率、引脚定义。一个字符错误都会导致失败。可以尝试将SPI频率先调低比如到10MHz看是否正常。库冲突确保只包含了必要的库。有时多个图形库存在冲突。5.2 显示颜色异常红蓝反色、颜色失真这几乎百分百是颜色顺序RGB/BGR设置错误。症状红色显示为蓝色绿色正常。解决在User_Setup.h中修改TFT_RGB_ORDER定义。如果原来是TFT_RGB就改为TFT_BGR反之亦然。更深层原因有些屏幕驱动芯片的初始化命令需要单独设置颜色格式。除了宏定义可能还需要在初始化代码中发送特定的命令。TFT_eSPI库的init()函数里通常已经处理但某些山寨屏幕可能需要你查找其数据手册在setup()中额外用tft.sendCommand()来配置。5.3 显示位置偏移或镜像偏移显示的内容整体向左/右/上/下偏移屏幕边缘有黑边或内容被截断。调整User_Setup.h中的ST7735_OFFSETX和ST7735_OFFSETY参数。也可以尝试tft.setRotation()函数它会影响偏移的基准。镜像或旋转直接使用tft.setRotation(0-3)来设置0、90、180、270度旋转。这个函数同时会影响绘图坐标的原点位置。5.4 内存不足导致崩溃当加载大图片或使用LVGL复杂界面时ESP32的片上RAM约520KB可能很快耗尽程序崩溃重启。症状在打开文件、解码图片或创建大量UI对象时ESP32不断重启串口提示“Alloc failed!”或“Brownout detector was triggered”。解决方案使用PSRAM如果你的ESP32模块带有外部PSRAM如ESP32-WROVER务必启用它。在Arduino IDE的“工具”菜单下选择“PSRAM”为“Enabled”。然后在代码中使用ps_malloc来分配图片缓冲区或LVGL的绘图缓冲区。优化内存使用减少LVGL的缓冲区大小避免同时加载多张大图片使用更简单的UI样式将字符串常量存储在Flash中用F()宏包裹。监控内存定期使用ESP.getFreeHeap()和ESP.getFreePsram()打印剩余内存定位内存泄漏点。5.5 LVGL界面卡顿、刷新慢原因1lv_timer_handler()调用间隔过长或不规律。确保它在loop()中尽可能频繁且稳定地被调用延迟不要超过10ms。避免在loop()中执行长时间的阻塞操作如delay(100)。如果需要延时使用LVGL自带的定时器lv_timer_create。原因2绘图缓冲区太小。LVGL的缓冲区大小决定了其最大可更新的区域面积。如果缓冲区高度只有10行那么刷新一个高度为20行的区域就需要分两次操作。增大缓冲区尤其是高度可以提升性能但会消耗更多内存。这是一个权衡。原因3SPI频率太低。尝试在硬件允许范围内提高SPI时钟频率。原因4开启了LVGL的复杂特效如阴影、渐变、混合模式等会显著增加渲染负担。在性能紧张的场合尽量使用扁平化设计。5.6 SPIFFS文件上传失败提示“SPIFFS Upload Failed”首先确认你安装了正确的SPIFFS上传工具。然后关闭所有可能占用串口的终端软件如串口监视器。如果还不行尝试在ESP32上电启动的瞬间点击上传按钮或者先按住BOOT键再按一下RST键进入下载模式然后再上传SPIFFS。最后分享一个我个人的调试习惯分步验证法。不要试图一次性集成所有功能。先写一个最简单的TFT_eSPI测试程序比如画一个矩形确保屏幕硬件和基础驱动是好的。然后再单独测试SPIFFS读取文件。最后再集成LVGL。每一步都通过串口打印关键信息如“SPIFFS init OK”、“File opened”。这样当问题出现时你能迅速定位到是哪一个环节出了错。嵌入式开发就是这样耐心和细致的排查比写出华丽的代码更重要。当你看到第一张图片稳定地显示在那块小小的1.44寸屏幕上时那种成就感会让你觉得所有的调试都是值得的。本文还有配套的精品资源点击获取