玩ESP32的人迟早会碰到一块屏幕。不管你是做温湿度计、桌面时钟、离线的仪表盘还是想给蓝牙控制的小项目加一个可视化界面LCD屏几乎是绕不开的组件。而网上关于ESP32驱动LCD的教程要么只讲了某个屏的零散接线要么一上来就扔给你一份库文件让你自己琢磨真正能把SPI、8080并口、引脚选择、代码配置这些事讲清楚的反而很少。这篇文章想做的就是这样一件事从最常用的SPI屏开始再到速度更快的8080并口屏把选型、接线、驱动配置、排错这些环节完整走一遍让手里同时拿着ESP32和TFT屏幕的你不再对着资料发愁。我写这篇内容的时候按“从简单到复杂”的顺序来组织。只要你之前点亮过LED或者跑通过一个Blink例程就能跟上节奏如果你已经能玩转SPI设备也能在并口屏的性能优化、DMA配置这些章节里找到可用的细节。好直接开始。1. 看清需求LCD屏驱动到底在解决什么问题1.1 从“点灯”到“点亮屏幕”单色屏、TFT屏与分辨率很多新手拿到一块LCD屏第一反应是“屏幕是不是就该像手机一样插上电就显示画面”。实际上LCD屏跟单片机之间并没有一个通用的“视频线”。你看到的是MCU通过一组通信协议把像素数据一列一列、一行一行地写到屏幕控制芯片内部的显存里再由控制芯片驱动液晶分子翻转最终显示出画面。按照显示屏的类型常见的有两种。一种是段码屏或者字符屏比如电子表上那种数字显示驱动方式比较原始一般用HT1621这类芯片去扫描另一种就是大家常说的TFT彩色屏像1.8寸ST7735、2.4寸ILI9341这类屏内部有完整的像素矩阵每一个像素点由RGB三色子像素组成。这篇文章主要说的是后者。分辨率决定了你有多少个像素点要处理。128x160的1.8寸屏总共20480个像素320x240的2.4寸屏总共76800个像素。如果你用RGB565格式也就是一个像素占2字节一帧数据量分别是40KB和150KB。这个数字看起来不起眼但放到8位或者部分32位MCU上传输方式和速度就成了关键问题。这也是为什么驱动屏之前我们要先把接口方案想清楚。1.2 SPI、8080并口、I2C三种常见接口到底差在哪屏和MCU之间的“道路”决定了你能以多快的速度把像素数据搬运到显存里。目前LCD接口大致有三类。I2C接口在低分辨率屏上比较常见比如0.96寸OLED以及部分小尺寸TFT。I2C只需要两根信号线SDA和SCL接线最省但速度通常在400kHz到1MHz之间。对于需要频繁刷新动态数据、切换页面、做动画的彩色TFT屏来说I2C完全是瓶颈流畅度会很差。换句话说I2C适合“静态显示”或者“每隔几秒变一下”的场景。SPI接口是大多数TFT屏的选择。它至少需要SCLK、MOSI、DC、CS、RST这五根线。SPI可以跑20MHz到80MHz一帧320x240的RGB565数据在40MHz下大概需要30ms左右实际刷新率能到20到30fps对绝大部分界面交互是足够的。8080并口则是老牌LCD接口从单片机驱动彩屏时代就存在。它用DB0到DB15这样的多根数据线并行传输数据配合WR、RD、CS、DC、RST这些控制线。优势是每个时钟周期可以传8位或者16位数据所以传输速率远高于SPI特别适合像ESP32这类引脚资源充足的主控在需要流畅动画或者高分辨率画面时首选。缺点是占用引脚多接线复杂而且代码里的引脚映射一旦错一位画面就乱得没法看。1.3 选型前必看的3个关键参数决定一块屏幕是否适合你的项目我建议先看这三个参数。第一个是控制器型号。同样的物理尺寸和分辨率屏背后的驱动IC可能完全不同。1.8寸屏常见ST7735S1.3寸或1.54寸常见ST77892.4寸到3.5寸常见ILI93414寸以上还有ILI9488、ST7796等。驱动库基本都是按IC型号来适配的买屏之前一定要确认型号否则代码容易不兼容。第二个是接口类型。有些屏只支持SPI有些屏通过电阻选择可以切换SPI和8080并口还有一些只支持并口。接口类型直接影响你能用的引脚数量和传输效率。买屏时看商品详情页的“接口方式”或者直接问卖家要规格书规格书里通常会写清楚芯片型号、工作电压、接口定义和背光参数。第三个是背光与供电电压。多数TFT屏的逻辑电压是3.3V背光LED灯串通常需要单独限流控制。如果你用的是5V供电的Arduino板子还要注意电平转换如果你用ESP323.3V IO可以直接驱动大多数屏幕控制IC但背光电流仍要单独处理。别小看第三点很多人屏幕能亮但颜色发暗、或者模块发烫都是供电和背光环节出了问题。2. 硬件选型与接线实操SPI屏和8080并口屏各需要哪些线2.1 常见LCD型号盘点与选购建议先聊选购。以我自己买过的几块屏为例1.8寸ST7735S128x160SPI接口价格便宜适合做小体积仪表但分辨率低、可视角度一般。1.54寸ST7789240x240SPI接口方屏显示效果好交互界面可以做得比较精致是小项目里很平衡的尺寸。2.4寸ILI9341320x240SPI或8080接口老牌经典资料最全新手出现问题容易搜到解决方案。3.5寸ILI9488480x320通常为8080接口SPI模式对通信速率要求很高一般建议用并口或者RGB接口。选购时尽量选“模块化”的屏也就是厂家已经帮忙把电平转换、背光限流电阻、甚至触摸芯片都集成好的成品模块。这样你只需要把模块的引脚接到主控上即可。如果买的是裸屏还得自己配转接板比较麻烦。另外像“1.8寸TFT LCD分辨率128x160”这种词搜索时很有用你可以直接拿它去找对应的型号和库示例。2.2 ESP32/ESP32-S2引脚分配与接线表重点来了。ESP32和ESP32-S2的引脚资源不同接线前最好先看一眼你手里的开发板原理图确认引脚是否被板载Flash、LED、USB等占用了。下面是基于Arduino生态的一套稳妥接法大家可以直接照着接也可以按自己的板子调整。SPI接线以2.4寸ILI9341为例LCD引脚ESP32开发板ESP32-S2开发板VCC3.3V3.3VGNDGNDGNDCSGPIO5GPIO5RSTGPIO4GPIO4DCGPIO2GPIO2MOSI/SDAGPIO23GPIO35SCLK/SCLGPIO18GPIO36LED背光3.3V串电阻或GPIO控制3.3V串电阻或GPIO控制这里有几个细节要注意。ESP32-S2的VSPI/FSPI映射跟ESP32不完全一样默认SPI引脚在不同库中可能不同很多库允许自己在配置里定义所以最终以代码为准。另外ESP32-S2没有传统意义上的GPIO23/18作为SPI默认引脚所以我建议了GPIO35/36这些是ESP32-S2开发板上比较常用的可用引脚。8080并口接线以2.4寸ILI9341并口模块为例16位数据线LCD引脚ESP32开发板说明CSGPIO5片选RSTGPIO4复位DCRSGPIO2数据/命令选择WRGPIO15写信号RDGPIO16读信号不读屏可接高电平或GPIO控制DB0-DB7GPIO12, 13, 14, 25, 26, 27, 17, 16低8位数据线DB8-DB15GPIO22, 21, 19, 18, 23, 33, 32, 35高8位数据线LED3.3V串电阻或GPIO背光并口接线容易出错的地方是高8位和低8位一定要跟库里的定义一致。如果某一位接反颜色会变得像花屏一样离谱这时候优先检查每一根数据线是否按代码里的宏定义一一对应。另外如果你的屏是8位并口而不是16位可以省略高8位数据线但驱动库初始化时要改成8位模式。2.3 供电与背光最容易忽略的两个坑第一坑是背光。LCD屏模块上的LED脚标着背光如果直接用3.3V和GND去驱动大尺寸屏的背光可能亮度不足或电流过大。很多模块上已经有背光限流电阻直接接3.3V没问题但有些模块没有长时间满电流运行会发烫、减寿。稳妥做法是接一个IO口通过NPN三极管或者MOS管控制背光配合PWM调节亮度这样还能实现自动息屏。最简单的PWM亮度控制代码pinMode(LED_PIN, OUTPUT); analogWrite(LED_PIN, 128); // 0-255128为半亮度如果模块直接接3.3V亮度太高、或想软件调光就用一颗NPN三极管搭一个低边开关集电极接背光LED负极发射极接地基极串1k电阻接PWM引脚。这种接法能让你用PWM实现平滑的亮度渐变比单纯开关背光实用得多。第二坑是总线电平。有些屏幕控制器同时支持3.3V和5V逻辑但很多TFT裸屏的IO电平上限是3.3V。ESP32本身是3.3V IO所以直连没问题。但如果你的板子带5V的电平比如老款Arduino最好加电平转换否则长期使用可能损伤屏幕时序电路。别小看这个坑很多人一开始用5V板子点亮了结果用几天屏幕就出问题。3. 驱动代码实现从初始化到显示一张图片3.1 驱动框架选型TFT_eSPI、Arduino_GFX还是LovyanGFX硬件接好接下来就是软件。Arduino生态里LCD驱动库里最有名的三家是TFT_eSPI、Arduino_GFX和LovyanGFX。TFT_eSPI是老牌库由Bodmer维护对ST7735、ST7789、ILI9341等大量IC支持很好。它的特点是通过修改User_Setup.h这个配置文件来定义引脚和屏幕型号不用改代码适合新手。缺点是配置时容易忘记改文件或者搞错IC型号。Arduino_GFX是moononournation维护的库API跟Adafruit GFX接近支持设备很多也支持并口和RGB屏配置方式比TFT_eSPI更灵活一些。LovyanGFX主打性能和更多高级功能比如DMA传送、双缓冲、多屏操作在ESP32上跑分很高。缺点是学习曲线稍陡API风格跟传统GFX不太一样。我的建议如果是新手第一次点亮屏幕优先用TFT_eSPI因为它网上资料最多教程也最完整。如果是做稍微复杂一点的界面、或者需要特别流畅的动画可以试试LovyanGFX。如果你的项目以后要接LVGL那LovyanGFX和TFT_eSPI都有对应的LVGL移植示例不用担心兼容性。3.2 TFT_eSPI配置与SPI接线验证以ESP32 2.4寸ILI9341 SPI为例先安装TFT_eSPI库然后找到Arduino的libraries文件夹下的TFT_eSPI/User_Setup.h确认这些宏的值#define ILI9341_DRIVER #define TFT_CS 5 #define TFT_DC 2 #define TFT_RST 4 #define TFT_MISO 19 // 通常不用读屏的话也可不接 #define TFT_MOSI 23 #define TFT_SCLK 18如果你的屏只写不读绝大多数应用都不需要读MISO可以不接但很多屏模块上仍有MISO引脚悬空即可。如果模块上MOSI和SDA两个标号都有通常SDA就是MOSI。配置好之后可以先用官方示例中的graphicstest来验证。刷进去如果看到连续的色块、线条滚动说明接线和初始化没问题如果白屏、花屏就进入后面的排错章节。有一个小提示TFT_eSPI的User_Setup.h里还有多个驱动宏比如ST7789_DRIVER、ST7735_DRIVER如果屏是ST7789但又没改驱动宏会显示不出正确颜色或者花屏反之把ILI9341_DRIVER改成ST7789_DRIVER同时确认显示屏分辨率设置比如ST7789有240x240和240x320两种变体。这类“同尺寸不同IC”的情况是最容易踩的坑买屏时多问卖家一句“主控是什么”能帮你省下大量排查时间。3.3 8080并口模式下的代码差异如果你的屏是8080并口模块那么TFT_eSPI也支持但配置方式和SPI不同。比如在User_Setup.h里要这样#define ILI9341_DRIVER #define TFT_CS 5 #define TFT_DC 2 #define TFT_RST 4 #define TFT_WR 15 #define TFT_RD 16 #define TFT_D0 12 #define TFT_D1 13 #define TFT_D2 14 #define TFT_D3 25 #define TFT_D4 26 #define TFT_D5 27 #define TFT_D6 17 #define TFT_D7 16 #define TFT_D8 22 #define TFT_D9 21 #define TFT_D10 19 #define TFT_D11 18 #define TFT_D12 23 #define TFT_D13 33 #define TFT_D14 32 #define TFT_D15 35然后TFT_eSPI会自动启用8位或16位并行模式前提是这些宏都定义在User_Setup.h里。需要注意的是并口模式不经过SPI外设而是用GPIO直接模拟总线时序所以代码中对引脚的定义非常关键。部分板子如果同时使用并口和WiFi要注意高位引脚是否和PSRAM、Flash等冲突ESP32的GPIO16/17等通常没问题但不同开发板差异很大。还有一点8080并口模式下有些库会默认启用“读回”功能也就是通过RD引脚读取屏幕显存内容。如果你的接线没接RD或者没有接读取方向的那根线有可能导致初始化卡住。解决办法是把RD引脚接高电平或者在配置里关闭相关读回。3.4 显示中文、图片与动态数据的完整流程先说说显示中文。TFT_eSPI默认不支持中文文字需要通过字库文件把字模转换成像素数据。最简单的方案是用LVGL的字体工具或者“img2lcd”之类的工具生成字模再用drawBitmap把字模画到屏幕上。如果你不想折腾字模也可以用Adafruit GFX的字体机制但中文字库文件很大不适合直接放在小Flash的板子上。这里给一个比较实用的方案用TFT_eSPI的pushImage函数显示图片配合在线图片转换工具把JPG/PNG转成RGB565数组。比如一张64x64的图标转换后会生成一个uint16_t数组显示时tft.pushImage(x, y, 64, 64, image_data);动态数据部分比如显示温湿度或者时间要在循环里反复调用drawString或者setCursor。注意刷屏区域的频率控制tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.setCursor(10, 20); tft.print(temperature);如果数值变化太快或者太慢建议用“局部刷新”只重画数值区域而不是全屏clear。比如可以在数值显示区域先画一个底色矩形再用设置背景色的方式覆盖旧值。这样做的好处是减少屏幕刷新次数也能顺手降低对主循环的阻塞。4. 实际操作中的常见问题与排错4.1 花屏、白屏、颜色不对的一线排查顺序我见过太多人花屏就怀疑屏坏了其实大部分时候问题出在复位时序或驱动宏配置上。遇到白屏/花屏按这个顺序排查。第一步确认背光亮不亮。不亮就看背光接线和供电亮再继续。第二步看复位引脚。很多屏在初始化时需要复位TFT_eSPI库会自动控制RST引脚如果你RST接错引脚初始化时序不对屏幕就可能白屏。第三步看DC引脚。如果是SPI屏DC引脚接错会导致数据被当成命令或者命令被当成数据结果是“有显示但完全错乱”是很典型的花屏。第四步确认驱动宏和分辨率。ILI9341用了ST7735的驱动宏或者分辨率设置错误都会出现颜色偏移、显示区域不对的问题。在这一步用官方的graphicstest测试例程是最好的验证手段。第五步确认时钟频率。有些屏或杜邦线质量不好SPI跑到40MHz以上就会出现噪声干扰导致颜色随机跳动。降低频率到10MHz或者20MHz试试往往能直接解决。4.2 SPI通信不稳定时钟频率、杜邦线与片选SPI不稳定主要三个原因。第一个是频率过高。TFT_eSPI里可以设置SPI_FREQUENCY参数默认可能是27MHz或40MHz如果你用的杜邦线较长、又没有接地线信号质量会变差。解决办法是把频率降到10MHz或20MHz除非你确定线材足够短。第二个是接线接触不良。很多模块的排针焊盘和杜邦线之间容易虚接尤其是没有焊接排针、直接插在面包板上的时候。我建议屏幕模块用排线焊死或者用质量好一点的母对母杜邦线尽量让线短一些。这里多说一句“SPI通信不生效”这个现象很多时候就是接触不良造成的拔插一次就好但过一会儿又复发这时候别怀疑代码先换线材。第三个是片选问题。如果是软件片选库会在每次通信时手动拉低CS如果另一个外设也在占用同一个SPI总线或者CS接在了别的引脚上就可能出现数据串扰。SPI总线的硬件片选和软件片选在库中都有定义TFT_eSPI默认用软件片选但片选引脚不能随便复用。还有一个冷门问题如果屏幕和SD卡共用SPI总线SD卡的数据线干扰可能导致屏幕偶尔花屏。这时候可以给SD卡那条线留出单独的处理或者降低SPI频率。如果你想用“spi硬件片选与软件片选”做一次完整的取舍分析结论一般是硬件片选能省代码但引脚固定软件片选灵活但要在高频下注意GPIO翻转速度。4.3 性能优化DMA、双缓冲与帧率当你发现屏幕显示一整个320x240的图片要几百毫秒时自然会想优化。优化的关键有两个方向。第一个是使用DMA。TFT_eSPI在ESP32上可以开启DMA来搬运数据而不用CPU逐字节去写。用spi.beginTransaction等接口或者调用库的pushImageDMA方法都能减少CPU占用。代码如下tft.startWrite(); tft.pushImageDMA(0, 0, 240, 320, buffer); tft.endWrite();第二个是双缓冲。也就是在内存中准备一个和屏幕大小相同的缓冲区先在缓冲区里画好内容再一次推到屏幕上。这样能避免用户看到“边画边闪”的情况。缺点是RGB565的320x240缓冲区需要150KB内存ESP32-S2没有大PSRAM时会不够。可以考虑缩小缓冲区只做局部双缓冲或者用LovyanGFX的分区渲染。如果这些优化还不够就要考虑换8080并口屏或者使用ESP32-S3加PSRAM的方案因为并口的并行数据吞吐量比SPI高很多。在做动画、游戏、或者高刷新率仪表盘时这个差距会非常明显。5. 进阶扩展触摸、多屏与小项目实践5.1 触摸屏接线与手势处理带触摸的屏一般分为电阻触摸和电容触摸。常用的是XPT2046电阻触摸芯片接线方式类似SPI需要CLK、CS、DIN、DOUT和IRQ引脚。在TFT_eSPI里可以用TFT_eSPI::getTouch方法读取坐标uint16_t x, y; bool pressed tft.getTouch(x, y);电阻屏需要校准一般读取触摸坐标后和屏幕分辨率做线性映射。电容触摸则通常通过I2C接口连接一个触摸控制器比如FT6236、CST816S使用对应的触摸库读取坐标。手势处理上可以检测按下和抬起之间的坐标差判断是点击、滑动还是长按。实际做界面时我建议把“触摸按下”和“触摸释放”分开处理避免按键误触发。5.2 多屏、分屏与低功耗显示ESP32作为主控可以同时接多块屏幕。如果是两块SPI屏最简单的方式是把CS分成两个引脚共用SCLK和MOSI如果两块屏是同一个控制器型号驱动起来比较简单只要切换CS就可以分别操作。如果混合使用SPI和并口屏要注意引脚的占用和速度匹配一般不太建议但也不是不行。分屏显示的思路是把屏幕划分为多个区域每个区域维护独立的刷新脏标记只有变化的部分才重绘。这样能极大降低刷屏负担也降低了功耗。低功耗方面可以做“息屏”关闭背光同时进入ESP32的modem sleep模式按需求定时唤醒刷新。对于一些需要电池供电的小项目这个处理能让续航提升不少。5.3 实战做一个带显示的小项目这里分享一个我自己做过的小东西一个桌面迷你气象站。用ESP32连WiFi获取天气接口数据显示在1.54寸ST7789屏上每30分钟刷新一次。硬件清单ESP32开发板、1.54寸ST7789屏、一个DHT11温湿度传感器可选USB线供电。接线按照前面2.2的SPI方案。代码逻辑大致是初始化屏幕连接WiFi请求天气API解析JSON然后把天气图标、温度和湿度以局部刷新的方式更新到屏幕上。代码量不大但把SPI驱动、HTTP请求、JSON解析、局部刷新都串起来了。如果你想做更流畅的界面可以考虑加LVGL。LVGL和TFT_eSPI配合可以做按钮、进度条、列表这些组件配合触摸屏就能做一个完整的嵌入式UI。LVGL的移植核心是提供显示缓冲区刷新函数Arduino_GFX和LVGL官方都有对应示例照着改一下User_Setup和lv_conf.h里的颜色深度、缓冲区大小即可。最后再说两句在实际操作中我最深的体会是LCD驱动本身并不难难的是你第一次面对一个花花绿绿、时序复杂的模块时很容易被各种“可能原因”淹没。所以我的建议一直很简单先把接线确认三遍再确定驱动IC型号最后用官方示例点亮不要一开始就想着自己写底层时序。很多人卡住都是因为忽略了一个引脚定义或者驱动宏。还有一个你们一定会碰到的小技巧如果显示区域只用了屏幕的一半剩下的半屏是黑边或者乱码优先检查分辨率宏和初始化时传入的偏移参数。比如ST7789常见的是240x240屏但它内部还有240x320的版本库里的偏移量和尺寸要跟实际模块一致。这个调整参数的过程其实比想象中频繁尤其当你买过不同批次、不同封装的小屏之后你会回来感谢这句话。希望这篇指南能帮你把屏幕点亮。等你的第一块屏幕跑起来你就知道ESP32这波操作其实比想象中简单不少。
