
1. 项目概述ESP32驱动ST7789显示屏的挑战与价值在嵌入式开发尤其是物联网设备或智能硬件的原型设计阶段一块清晰、小巧的显示屏往往是实现人机交互的关键。ESP32作为一款功能强大的Wi-Fi/蓝牙双模微控制器因其丰富的GPIO资源和成熟的生态成为了许多开发者的首选。而ST7789驱动的IPS LCD屏以其出色的色彩表现和视角在小尺寸显示领域备受青睐。然而将这两者结合起来对于许多初次尝试的开发者来说却可能是一个不小的挑战——引脚定义不明确、库函数配置复杂、初始化失败等问题屡见不鲜。我自己在为一个环境监测仪项目选型时就遇到了这个“甜蜜的烦恼”。我需要一块尺寸足够小、显示效果足够好的屏幕来实时展示温湿度和空气质量数据。1.14英寸的ST7789 IPS屏几乎完美契合需求但在实际接线和驱动时我花了远比预期多的时间去查阅各种零碎的论坛帖子和库文档。很多教程要么过于简略要么使用的开发板或库版本不同导致代码无法直接运行。正是这段“踩坑”经历让我决定把整个从硬件连接到软件调试的过程系统地整理出来。本文将以最常用的ESP32 DevKit V1开发板基于ESP-WROOM-32模组和通用的1.14英寸ST7789 IPS屏为例手把手带你完成从零到一的显示驱动并深入讲解每个步骤背后的原理和可能遇到的“坑”。无论你是刚接触硬件的软件工程师还是希望为项目添加显示功能的创客这篇内容都将提供一份可直接“抄作业”的详细指南。2. 核心硬件解析与接线方案设计2.1 硬件选型与引脚功能深度解读工欲善其事必先利其器。在动手接线之前我们必须对两件核心硬件的接口特性有清晰的认识这是避免后续一切混乱的基础。ESP32开发板引脚考量我们使用的ESP32 DevKit V1开发板其GPIO引脚并非全部“生而平等”。有些引脚在启动时有特殊功能盲目使用会导致设备无法启动或运行不稳定。例如GPIO0、GPIO2、GPIO15等引脚在上电时的电平状态会影响ESP32的启动模式如进入下载模式。因此在选择连接显示屏的引脚时我们需要避开这些“敏感”引脚。此外为了获得更好的SPI通信性能我们应优先选择硬件SPI引脚HSPI或VSPI。ESP32通常有两组硬件SPIVSPI默认引脚GPIO23-MOSI, GPIO19-MISO, GPIO18-SCLK, GPIO5-CS和HSPI默认引脚GPIO13-MOSI, GPIO12-MISO, GPIO14-SCLK, GPIO15-CS。使用硬件SPI比软件模拟SPIBit-Banging速度更快、更稳定且不占用CPU资源。ST7789显示屏引脚定义市面上常见的1.14英寸IPS屏模块其背板通常会引出8个引脚有些是7个缺少BLK。我们需要明确每一个的作用VCC电源正极接3.3V。绝对禁止接5V否则会永久损坏屏幕。GND电源地与ESP32共地。SCL串行时钟线接ESP32的SPI时钟引脚。SDA串行数据线接ESP32的SPI主设备输出从设备输入MOSI引脚。这里需要注意在SPI通信中从设备屏幕的“SDA”通常对应主设备的“MOSI”。RES复位引脚低电平有效。用于在初始化前或出现异常时对屏幕控制器进行硬件复位。DC数据/命令选择引脚。这是驱动ST7789的关键。当DC引脚为低电平时ESP32发送的是命令字节如设置屏幕方向、开显示等当为高电平时发送的是要显示的像素数据。没有这个引脚屏幕就无法区分指令和图像数据。CS片选引脚低电平有效。当多个SPI设备共享同一组SPI总线SCLK, MOSI时通过拉低对应设备的CS引脚来选中它进行通信。如果总线上只有一个SPI设备如本例理论上可以将CS引脚直接接地使其一直选中但为了代码规范性和未来扩展性建议仍然连接到可控的GPIO上。BLK背光控制引脚。接高电平3.3V背光亮接低电平GND背光灭。注意有些屏幕的BLK是低电平点亮需要查阅具体屏幕的数据手册。如果该引脚悬空背光可能默认全亮或不亮。2.2 具体接线方案与原理图分析基于以上分析并结合了引脚可用性和硬件SPI性能我推荐以下接线方案。这套方案经过了实际项目验证稳定可靠。ESP32 DevKit V1 与 1.14 ST7789 IPS 接线表ESP32 引脚连接至 ST7789 引脚线色参考可选功能说明与注意事项3.3VVCC红色电源正极。务必确认是3.3V而非5V引脚。GNDGND黑色电源地。确保共地这是通信的基础。GPIO 18SCL绿色SPI时钟线。这是ESP32 VSPI的默认SCLK引脚。GPIO 23SDA蓝色SPI数据线。这是ESP32 VSPI的默认MOSI引脚。GPIO 2DC紫色数据/命令选择。选择了一个通用且启动时无冲突的GPIO。GPIO 4RES黄色复位引脚。同样是一个安全的GPIO。GPIO 15CS白色片选引脚。注意GPIO15在启动时会输出低电平但这通常不影响屏幕因为初始化流程中会重新控制它。如果你非常谨慎可以换用GPIO5VSPI默认CS。3.3VBLK橙色背光控制。直接接3.3V使其常亮。若需控制可接GPIO如GPIO32并通过代码控制。实操心得接线时最忌讳的就是“凭感觉”。强烈建议使用不同颜色的杜邦线并严格按照表格对应连接。一次错误的接线如电源接反就可能导致硬件损坏。在通电前花一分钟时间从头到尾再检查一遍这个习惯能帮你省下无数排查时间。为什么选择这些引脚性能优先GPIO18和GPIO23组成了ESP32的VSPI硬件接口确保了显示刷新的最高速度和稳定性。规避冲突避开了GPIO0、GPIO2需注意上电时状态、GPIO12等可能在启动时具有特殊功能的引脚保证了系统启动的稳定性。预留控制将CS、DC、RES连接到普通GPIO便于通过软件精确控制时序。特别是DC引脚必须在正确的时刻切换电平。3. 软件环境搭建与库的选型3.1 Arduino IDE配置与ESP32开发板支持硬件连接妥当后我们需要在软件层面做好准备。首先确保你的Arduino IDE已经安装并配置好了ESP32的开发板支持。安装ESP32开发板定义打开Arduino IDE点击文件-首选项。在“附加开发板管理器网址”中填入以下网址如果已有其他用逗号分隔https://espressif.github.io/arduino-esp32/package_esp32_index.json点击工具-开发板-开发板管理器。在搜索框中输入“esp32”找到由“Espressif Systems”提供的“ESP32”开发板包点击安装。这个过程可能需要下载一些时间请保持网络通畅。选择正确的开发板与端口安装完成后在工具-开发板中选择你的ESP32型号例如“ESP32 Dev Module”。在工具-端口中选择你的ESP32所连接的COM口Windows或串口设备MacOS/Linux。如果端口列表是灰色的请检查USB线是否接好或是否需要安装CH340/CP210x等USB转串口芯片的驱动。3.2 TFT显示库的选型与安装驱动ST7789我们需要一个强大的图形库。这里我强烈推荐TFT_eSPI库。它是由Bodmer开发的一款专为ESP32、ESP8266等平台优化的TFT驱动库支持包括ST7789在内的数十种控制器性能优异且直接支持硬件SPI。为什么不直接用Adafruit_ST7789库Adafruit的库同样优秀且通用但TFT_eSPI针对ESP系列进行了深度优化特别是在使用硬件SPI时其帧缓冲和绘图API的效率更高并且内置了中文字库支持等实用功能对于后续开发更复杂的UI更为友好。安装与配置TFT_eSPI库安装库在Arduino IDE中点击项目-加载库-管理库...。在库管理器中搜索“TFT_eSPI”找到Bodmer的版本点击安装。关键一步配置用户设置文件这是驱动成功与否的核心。TFT_eSPI库通过一个用户配置文件来适配不同的屏幕和引脚连接。找到Arduino的库安装目录。通常在我的文档\Arduino\libraries或类似路径下。进入TFT_eSPI文件夹找到User_Setup.h这个文件。不要直接修改它在同级目录下你会看到一个User_Setup_Select.h文件。用文本编辑器如VS Code、Notepad打开它。这个文件里有很多行被注释掉的#include语句每一行对应一种屏幕或开发板的预配置。我们需要指定使用我们自己的配置。找到这样一行//#include User_Setup.h。去掉这行开头的注释符号//使其生效#include User_Setup.h // 使用自定义的用户设置保存并关闭User_Setup_Select.h文件。配置User_Setup.h文件现在打开真正的User_Setup.h文件进行配置。你需要修改或确认以下几个关键部分定义驱动器和屏幕尺寸找到并确保以下行被正确定义#define ST7789_DRIVER // 定义使用的驱动器为ST7789 #define TFT_WIDTH 135 // 屏幕的物理宽度像素 #define TFT_HEIGHT 240 // 屏幕的物理高度像素定义引脚连接根据我们的接线表找到并修改以下引脚定义部分// 对于ESP32 DevKit V1 (VSPI) #define TFT_CS 15 // 片选引脚 Chip select control pin #define TFT_DC 2 // 数据/命令选择引脚 Data Command control pin #define TFT_RST 4 // 复位引脚 Reset pin (could connect to ESP32 RST pin if you don‘t use it) //#define TFT_RST -1 // 如果屏幕的RESET接在了ESP32的EN/RST引脚上可以设为-1并注释上一行 #define TFT_MOSI 23 // 主设备输出接屏幕SDA #define TFT_SCLK 18 // 时钟接屏幕SCL // 对于ST7789我们不需要MISO引脚因为屏幕不会回传数据给MCU启用硬件SPI确保硬件SPI被启用#define USE_HSPI_PORT // 注释掉这行则使用VSPI。对于我们的接线GPIO18,23使用的是VSPI所以应该注释掉USE_HSPI_PORT或者不定义它。注意ESP32的VSPI是默认SPI。我们的引脚18,23属于VSPI因此不需要定义USE_HSPI_PORT。HSPI的默认引脚是(13,12,14,15)。如果你发现编译后屏幕不工作可以尝试交换注释USE_HSPI_PORT这行。设置SPI频率可以适当提高SPI速度以获得更快的刷新率但需确保屏幕能稳定工作。ST7789通常可以跑到40MHz甚至更高。#define SPI_FREQUENCY 40000000 // 40 MHz SPI时钟。如果屏幕出现雪花或花屏可以降低这个值如20000000。保存文件完成以上修改后保存User_Setup.h文件。避坑指南User_Setup.h的配置错误是导致屏幕白屏、花屏或不显示的最常见原因。请务必逐字核对引脚编号、驱动器宏定义和屏幕尺寸。一个常见的错误是TFT_WIDTH和TFT_HEIGHT填反了导致显示方向异常。4. Arduino代码实现与深度解析环境配置完毕现在我们来编写和解读驱动代码。我将提供一个从简单到复杂的示例并解释每一部分的作用。4.1 基础显示测试代码创建一个新的Arduino项目并粘贴以下代码。这段代码将完成屏幕初始化、清屏、显示文字和绘制基本图形的功能。#include TFT_eSPI.h // 包含TFT_eSPI图形库 TFT_eSPI tft TFT_eSPI(); // 创建显示屏对象 void setup() { Serial.begin(115200); // 初始化串口用于调试输出 Serial.println(ST7789 TFT Test Start...); // 初始化TFT显示屏 tft.init(); tft.setRotation(1); // 设置屏幕方向 (0, 1, 2, 3 分别对应0°, 90°, 180°, 270°旋转) // 根据你的屏幕安装方向调整这个参数。如果文字是横着的就尝试改成0或2。 tft.fillScreen(TFT_BLACK); // 用黑色清空屏幕 // 设置文本颜色和大小然后在指定位置打印“Hello World” tft.setTextColor(TFT_WHITE, TFT_BLACK); // 前景色白色背景色黑色 tft.setTextSize(2); // 设置文本大小1为最小 tft.setCursor(10, 30); // 设置文本起始坐标 (x, y) tft.println(Hello World!); // 绘制一个矩形 tft.drawRect(10, 60, 50, 30, TFT_GREEN); // 在(10,60)位置画一个宽50、高30的绿色空心矩形 // 绘制一个填充的圆形 tft.fillCircle(100, 75, 15, TFT_RED); // 在(100,75)位置画一个半径为15的红色实心圆 // 绘制一条线 tft.drawLine(10, 100, 120, 100, TFT_BLUE); // 从(10,100)到(120,100)画一条蓝色线段 Serial.println(TFT Test Done.); } void loop() { // 主循环这里暂时什么都不做 // delay(100); }代码逐行解析与关键点#include TFT_eSPI.h与TFT_eSPI tft引入库并实例化一个全局的显示对象tft后续所有操作都通过这个对象进行。tft.init()这是最关键的一步。它根据User_Setup.h中的配置初始化SPI总线、复位屏幕、发送一系列初始化命令序列来配置ST7789控制器。如果此步骤后屏幕背光亮但无显示白屏多半是初始化命令或引脚配置不对。tft.setRotation(1)设置屏幕的逻辑显示方向。由于屏幕的物理安装方向可能不同这个函数可以让你以最舒适的方式观看内容。参数0-3对应0°、90°、180°、270°顺时针旋转。如果上电后文字是竖着的尝试修改这个值。tft.fillScreen(TFT_BLACK)用指定颜色填充整个屏幕。这是一个快速清屏的方法。TFT_BLACK,TFT_WHITE,TFT_RED,TFT_GREEN,TFT_BLUE等都是库预定义的颜色常量。文本显示setTextColor设置颜色setTextSize设置大小倍数setCursor定位println输出。注意坐标原点(0,0)在当前旋转方向的左上角。图形绘制库提供了丰富的绘图函数如drawRect空心矩形、fillRect实心矩形、drawCircle、fillCircle、drawLine、drawPixel等。这些函数的参数通常是起始坐标、尺寸和颜色。编译与上传确保开发板和端口选择正确。点击“上传”按钮。首次上传可能需要长按ESP32板上的“BOOT”按钮使其进入下载模式有些开发板会自动完成。上传成功后ESP32会自动重启。你应该能在屏幕上看到“Hello World!”文字以及绿色矩形、红色圆和蓝色横线。4.2 高级功能与性能优化基础显示成功后我们可以探索更高级的功能这对实际项目至关重要。1. 使用帧缓冲Frame Buffer实现平滑动画直接操作屏幕tft.drawXxx在绘制复杂图形或动画时可能会闪烁。TFT_eSPI库支持在ESP32的PSRAM如果可用或内部RAM中创建帧缓冲先在内存中完成所有绘制然后一次性刷新到屏幕。#include TFT_eSPI.h TFT_eSPI tft TFT_eSPI(); void setup() { tft.init(); tft.setRotation(1); tft.fillScreen(TFT_BLACK); // 尝试初始化帧缓冲。如果ESP32有PSRAM将使用它否则使用内部RAM。 // 参数为颜色深度16代表16位色RGB565。 if (tft.initDMA(true)) { // 启用DMA传输效率更高 Serial.println(DMA and Frame Buffer initialized.); } else { Serial.println(Frame Buffer initialization failed!); } tft.startWrite(); // 开始写入帧缓冲在某些模式下需要 // 进行一系列绘制操作这些操作不会立即显示 for (int i 0; i 10; i) { tft.drawFastHLine(10, 20 i*5, 100, TFT_CYAN); } tft.endWrite(); // 结束写入库可能会在此刻或后续自动推送缓冲 // 或者使用更高级的“Sprite”精灵对象它是独立的帧缓冲 TFT_eSprite sprite TFT_eSprite(tft); // 创建一个精灵关联到tft对象 sprite.createSprite(80, 60); // 在内存中创建一个80x60像素的绘图区域 sprite.fillSprite(TFT_BLACK); // 用黑色填充精灵 sprite.setTextColor(TFT_YELLOW); sprite.drawString(Sprite!, 10, 10); sprite.fillCircle(40, 40, 20, TFT_ORANGE); sprite.pushSprite(50, 80); // 将精灵的内容一次性绘制到屏幕的(50,80)位置 sprite.deleteSprite(); // 释放精灵占用的内存 } void loop() { // 使用帧缓冲或精灵制作动画 static int x 0; TFT_eSprite ball TFT_eSprite(tft); ball.createSprite(20, 20); ball.fillSprite(TFT_BLACK); // 用透明色这里用黑色模拟实际可用透明色填充 ball.fillCircle(10, 10, 9, TFT_MAGENTA); // 简单的动画循环 for (x 0; x 115; x) { ball.pushSprite(x, 120, TFT_BLACK); // 在新位置绘制同时指定旧位置被覆盖区域的颜色用于擦除 delay(20); } ball.deleteSprite(); }2. 显示图片BMP格式TFT_eSPI库内置了绘制BMP位图的功能。你需要将图片转换为16位色RGB565的BMP格式并存储在ESP32的文件系统如SPIFFS中。准备图片使用工具如Image2Lcd、LCD Image Converter将图片转换为16-bit色彩深度、Windows Bitmap格式并注意宽度和高度需匹配或小于屏幕尺寸。上传文件到SPIFFS在Arduino IDE中安装“ESP32 Sketch Data Upload”插件将转换好的.bmp文件放入项目目录下的data文件夹然后通过工具-ESP32 Sketch Data Upload上传。代码示例#include TFT_eSPI.h #include SPIFFS.h // 包含SPIFFS文件系统库 TFT_eSPI tft TFT_eSPI(); void setup() { Serial.begin(115200); tft.init(); tft.setRotation(1); tft.fillScreen(TFT_BLACK); if (!SPIFFS.begin(true)) { // 挂载SPIFFS文件系统 Serial.println(SPIFFS Mount Failed); return; } // 绘制BMP图片。参数文件名起始x坐标起始y坐标 bool success tft.drawBmpFile(SPIFFS, /image.bmp, 0, 0); if (!success) { Serial.println(Failed to draw BMP); } } void loop() {}3. 优化SPI速度与功耗在User_Setup.h中我们已经设置了SPI_FREQUENCY。你可以尝试提高这个值如80000000来获得更快的刷新率但必须以屏幕稳定工作为前提。如果出现花屏、数据错误请降低频率。此外如果不需要显示时可以通过控制BLK引脚如果连接到GPIO来关闭背光这是降低功耗最有效的方式。#define BLK_PIN 32 // 假设背光控制接在GPIO32 void setup() { pinMode(BLK_PIN, OUTPUT); digitalWrite(BLK_PIN, HIGH); // 背光开启 // ... 其他初始化 } void loop() { // 显示一些内容 delay(5000); digitalWrite(BLK_PIN, LOW); // 进入低功耗模式关闭背光 delay(5000); digitalWrite(BLK_PIN, HIGH); // 唤醒开启背光 }5. 深度调试与常见问题排查实录即使按照教程一步步操作也难免会遇到问题。下面是我在多次项目中总结出的问题排查清单涵盖了从硬件到软件的各个环节。5.1 现象屏幕完全无反应背光不亮排查步骤1检查电源测量电压用万用表测量屏幕VCC和GND引脚之间的电压确认是否为稳定的3.3V。ESP32的3.3V引脚输出能力有限约500mA如果连接了其他大电流设备可能导致电压被拉低。检查接线确认VCC和GND没有接反且接触牢固。杜邦线接触不良是新手最常见的问题。排查步骤2检查背光如果BLK引脚是独立控制的检查它是否被正确拉高接3.3V或代码设置为HIGH。有些屏幕背光默认低电平点亮需要尝试拉低BLK。直接用导线将屏幕的BLK引脚连接到3.3V看背光是否亮起。5.2 现象背光亮但屏幕为白屏、花屏或乱码排查步骤1检查SPI通信引脚确认SCL和SDA确保SCL时钟和SDA数据没有接反。SCL必须有规律的方波信号。使用逻辑分析仪或示波器这是最直接的排查工具。观察CS、DC、RESET引脚在初始化过程中的电平变化以及SCL和SDA上是否有数据波形。如果没有任何波形说明SPI根本没有启动重点检查代码初始化部分和库配置。排查步骤2检查库配置文件User_Setup.h驱动器型号确认#define ST7789_DRIVER已启用且没有其他驱动器宏被重复定义。屏幕尺寸确认TFT_WIDTH和TFT_HEIGHT是否正确135和240。一个典型错误是这两个值填反了。引脚定义逐字核对TFT_CS,TFT_DC,TFT_RST,TFT_MOSI,TFT_SCLK的引脚号是否与你的物理接线完全一致。SPI端口如果你的接线使用的是VSPIGPIO18,23确保USE_HSPI_PORT被注释掉。反之如果使用HSPIGPIO14,13则需要启用它。排查步骤3降低SPI频率在User_Setup.h中将SPI_FREQUENCY从4000000040MHz改为2000000020MHz或1000000010MHz。过高的频率可能导致信号质量差屏幕控制器无法正确解析。排查步骤4检查复位时序在setup()中tft.init()之前手动添加一个复位序列试试pinMode(TFT_RST, OUTPUT); digitalWrite(TFT_RST, HIGH); delay(50); digitalWrite(TFT_RST, LOW); delay(50); digitalWrite(TFT_RST, HIGH); delay(150); // 等待复位完成 tft.init();如果屏幕RESET引脚接在了ESP32的EN引脚上需要在User_Setup.h中设置#define TFT_RST -1并确保代码中没有再操作这个引脚。5.3 现象显示内容错位、旋转不正确或只有部分区域显示排查步骤1调整屏幕旋转setRotation()在代码中尝试tft.setRotation(0),(1),(2),(3)找到符合你物理安装方向的值。排查步骤2检查偏移设置Offset有些ST7789屏幕模块在初始化时图像起始坐标可能有偏移。你可以在User_Setup.h中查找并调整以下定义如果存在#define TFT_INVOFF 0x20 // 显示反转关闭 #define TFT_INVON 0x21 // 显示反转开启 // 或者尝试修改初始化命令更常见的是在代码初始化后使用tft.setViewport(x, y, width, height)来设置一个逻辑视口但这通常用于高级UI布局。排查步骤3确认绘图坐标记住tft.setCursor()和所有绘图函数的坐标都是基于当前旋转方向的左上角为原点(0,0)。如果你的内容画在了屏幕外可能是坐标计算错误。5.4 现象编译或上传错误fatal error: TFT_eSPI.h: No such file or directory库未正确安装。通过Arduino IDE的库管理器重新安装TFT_eSPI并确认项目文件夹下没有同名的旧版库文件冲突。上传时提示“Timed out waiting for packet header”或“Failed to connect to ESP32”ESP32未进入下载模式。尝试在点击上传按钮后迅速按下并释放ESP32板上的“BOOT”按钮有些板子标记为“IO0”。检查USB线是否完好尝试更换USB口或USB线。检查开发板型号和端口是否选择正确。5.5 进阶调试技巧串口打印调试信息在代码关键位置如setup()开始、初始化前后加入Serial.println()语句通过串口监视器观察程序执行到了哪一步。简化测试当遇到复杂问题时创建一个最简化的测试程序。只包含库引用、对象创建、init()、fillScreen()和drawPixel()。排除其他代码的干扰。查阅库的示例TFT_eSPI库自带大量示例文件-示例-TFT_eSPI。从最基本的Hello_World示例开始运行如果官方示例能工作那问题一定出在你的配置或接线上。驱动一块显示屏看似简单但涉及硬件连接、软件配置、通信协议多个层面。耐心按照“电源 - 背光 - 通信 - 配置 - 代码”的顺序进行排查大部分问题都能迎刃而解。当你看到第一句“Hello World!”稳定地显示在屏幕上时那种成就感就是嵌入式开发最纯粹的乐趣之一。希望这份详尽的指南能帮你扫清障碍更快地将创意变为现实。