ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ESP32驱动ST7735彩屏翻页时钟:从选型到动画实现

ESP32驱动ST7735彩屏翻页时钟:从选型到动画实现 1. 为什么我最终选了小尺寸彩屏做翻页时钟1.1 从一块吃灰的1.8寸TFT说起去年整理元件盒翻出来一块1.8寸的TFT彩屏ST7735驱动128x160分辨率SPI接口当时买的时候想做个天气站后来一直没动。正好那阵子想给书桌添个时钟手机看时间太容易分心一拿起来就刷半小时。市面上的翻页时钟要么是黑白的要么尺寸太大要么就是成品价格不便宜。我就想干脆用手头这块彩屏做一个翻页动画加上彩色数字视觉上比普通数码管舒服得多。这个项目的核心其实就三件事ESP32驱动TFT彩屏、获取准确时间、实现翻页动画效果。听起来简单但真做起来光屏幕刷新率和动画流畅度就调了好几天。我用的主控是ESP32不是Arduino UNO原因后面会详细说。屏幕是1.8寸ST7735S128x160像素SPI接口价格便宜驱动库成熟。整个项目做下来硬件成本不到四十块但学到的东西比买十个成品都值。这篇文章适合谁看如果你有Arduino基础想进阶到ESP32或者想做一个带彩屏的桌面小物件那这篇内容应该能帮你少走不少弯路。我会把选型逻辑、接线、代码结构、动画实现、踩过的坑全部摊开讲你照着做基本能复现出来。1.2 翻页时钟的核心需求拆解翻页时钟和普通数字时钟最大的区别在于动画。普通时钟只需要每秒更新数字翻页时钟需要模拟机械翻页的效果数字从中间折叠、翻转、落下。这个动画如果做得不好要么卡顿要么闪烁要么看起来像PPT。所以核心需求可以拆成四个层次时间获取需要网络对时保证时间准确。ESP32自带WiFi连NTP服务器是最省事的方案。屏幕驱动TFT彩屏通过SPI通信需要合适的库来驱动。TFT_eSPI是ESP32上性能最好的选择之一。动画渲染翻页效果需要逐帧绘制对刷新率有要求。ESP32的双核和较高主频在这里优势明显。外壳与供电小尺寸屏幕适合做桌面摆件供电用USB线或者锂电池都行。这四个层次里最容易被低估的是动画渲染。很多人以为翻页就是换个数字实际上要做出“翻”的感觉需要把数字分成上下两半分别做压缩和拉伸中间还要有阴影过渡。这部分我后面会详细讲。1.3 为什么不用Arduino UNO而选ESP32Arduino UNO是一块好板子但它做这个项目有几个硬伤。UNO的ATmega328P主频只有16MHzSRAM只有2KB驱动128x160的彩屏已经比较吃力更别说做逐帧动画。我实测过UNO刷新一屏纯色大概要几十毫秒翻页动画根本跑不起来。ESP32的优势就很明显了对比项Arduino UNOESP32主频16MHz240MHzSRAM2KB520KBFlash32KB4MB起WiFi需外接模块内置SPI速率较低可达80MHz价格约20元约15-25元ESP32的SPI时钟可以跑到40MHz甚至80MHzTFT_eSPI库配合DMA刷屏速度比UNO快一个数量级。而且内置WiFi可以直接连NTP对时不需要额外的模块。价格上ESP32开发板现在比UNO还便宜没有理由不用。注意ESP32的GPIO电压是3.3VTFT彩屏模块如果标称5V逻辑实际大部分ST7735模块都支持3.3V直接连就行。但如果你用的是5V的屏幕模块需要加电平转换否则可能烧屏或者通信不稳定。2. 硬件选型与接线细节2.1 屏幕选型ST7735还是ILI9341小尺寸彩屏常见的有两种驱动ST7735和ILI9341。ST7735常见于1.8寸和1.44寸屏幕分辨率128x160或128x128ILI9341常见于2.4寸和2.8寸分辨率240x320。我选ST7735的原因很简单尺寸小。桌面时钟不需要太大1.8寸刚好再大就占地方了。而且ST7735的价格更便宜SPI接口占用的引脚也少。ILI9341虽然分辨率高但240x320的屏幕做翻页动画ESP32的刷新压力会大不少除非用DMA加速。如果你手头只有ILI9341也能做但建议用2.4寸以上的屏幕否则数字太小看不清。ST7735的128x160分辨率显示四个数字HH:MM刚好每个数字大概30x50像素留出间距和翻页动画的空间。屏幕的接口是SPI需要连接的引脚包括SCL/SCKSPI时钟SDA/MOSISPI数据RES/Reset复位DC/RS数据/命令切换CS片选VCC3.3VGND地BLK/LED背光控制可以接3.3V常亮也可以接GPIO做PWM调光有些模块还有SDO/MISO引脚用于读取屏幕状态但一般用不到可以不接。2.2 ESP32引脚分配与接线表ESP32的GPIO可以灵活映射但SPI有默认的IO_MUX引脚用默认引脚速度最快。我用的ESP32 DevKit V1SPI默认引脚是SCK GPIO 18MOSI GPIO 23MISO GPIO 19不用CS GPIO 5其他控制引脚可以随便选我用了DC GPIO 2RES GPIO 4BLK GPIO 15PWM调光完整接线表如下屏幕引脚ESP32引脚说明VCC3.3V电源GNDGND地SCLGPIO 18SPI时钟SDAGPIO 23SPI数据RESGPIO 4复位DCGPIO 2数据/命令CSGPIO 5片选BLKGPIO 15背光提示不同厂家的ST7735模块引脚顺序可能不同接线前一定要看模块背面的丝印。我买过一块模块VCC和GND是反的差点烧了。2.3 供电方案与外壳思路供电最简单的方式是USB线直接插5V充电头ESP32开发板上有AMS1117稳压到3.3V。整个系统功耗大概在100-150mAUSB口完全够用。如果想做无线版本可以用18650锂电池加TP4056充电模块再加一个升压模块到5V。但锂电池供电有个问题ESP32的WiFi在连接时会瞬间拉高电流如果电池保护板限流太低可能会重启。我试过用一块旧手机电池WiFi连接时偶尔会掉电重启后来换了块放电倍率高一点的电池就好了。外壳方面3D打印是最方便的。我画了一个简单的方形外壳屏幕嵌在前面ESP32藏在后面底部留USB开口。如果你没有3D打印机用亚克力板切割也行或者直接买个透明的塑料盒挖个屏幕孔。外壳不影响功能但做好了颜值提升很大。3. 开发环境搭建与库配置3.1 Arduino IDE还是PlatformIO这个问题我被问过很多次。Arduino IDE上手快但管理库和依赖比较麻烦尤其是TFT_eSPI这种需要修改配置文件的库。PlatformIO基于VSCode库管理更规范编译速度也快但需要一点学习成本。我的建议是如果你只是做这一个项目用Arduino IDE就够了。TFT_eSPI的配置虽然要改文件但步骤固定改一次就行。如果你打算长期玩ESP32建议直接上PlatformIO后面做复杂项目会省很多事。Arduino IDE的版本建议用2.x1.8.x虽然稳定但代码补全和界面体验差不少。ESP32的开发板支持需要额外安装在“首选项”里添加开发板管理器网址然后在开发板管理器里搜索“esp32”安装。安装包比较大下载慢的话可以找离线包。3.2 TFT_eSPI库的安装与配置TFT_eSPI是ESP32上驱动TFT屏幕的首选库性能好支持DMA社区活跃。安装方式在Arduino IDE的库管理器里搜索“TFT_eSPI”安装最新版。安装完之后不要直接编译需要先修改配置文件。找到Arduino库目录下的TFT_eSPI/User_Setup.h或者用User_Setup_Select.h来包含自定义配置。我建议新建一个User_Setup.h放在项目目录里但TFT_eSPI默认不支持项目级配置所以还是改库文件最直接。需要修改的关键配置#define ST7735_DRIVER // 选择ST7735驱动 #define TFT_WIDTH 128 // 屏幕宽度 #define TFT_HEIGHT 160 // 屏幕高度 #define TFT_CS 5 // 片选引脚 #define TFT_DC 2 // 数据/命令引脚 #define TFT_RST 4 // 复位引脚 #define TFT_SCLK 18 // SPI时钟 #define TFT_MOSI 23 // SPI数据 #define TFT_MISO 19 // 不用也要定义 #define LOAD_GLCD // 加载默认字体 #define LOAD_FONT2 // 加载字体2 #define LOAD_FONT4 // 加载字体4 #define LOAD_FONT6 // 加载字体6 #define LOAD_FONT7 // 加载字体7 #define LOAD_FONT8 // 加载字体8 #define SMOOTH_FONT // 平滑字体还有一个关键配置是SPI频率#define SPI_FREQUENCY 40000000 // 40MHzST7735的SPI最高支持到40MHz左右再高可能会花屏。我实测40MHz很稳刷屏速度足够做动画。如果你用的杜邦线比较长可能需要降到27MHz甚至20MHz否则会有干扰。注意修改完User_Setup.h后如果编译报错说找不到文件检查一下是不是改错了库目录。Arduino IDE的库目录一般在“文档/Arduino/libraries”下面。3.3 NTP对时与WiFi连接ESP32连WiFi很简单用WiFi.h库就行。NTP对时用time.h和configTime函数。代码大概长这样#include WiFi.h #include time.h const char* ssid 你的WiFi名称; const char* password 你的WiFi密码; void setupWiFi() { WiFi.begin(ssid, password); while (WiFi.status() ! WL_CONNECTED) { delay(500); Serial.print(.); } Serial.println(WiFi connected); configTime(8 * 3600, 0, ntp.aliyun.com, pool.ntp.org); }configTime的第一个参数是时区偏移单位是秒。中国是UTC8所以是8*360028800。第二个参数是夏令时偏移中国不用夏令时填0。对时完成后用localtime或者getLocalTime获取时间struct tm timeinfo; if (getLocalTime(timeinfo)) { int hour timeinfo.tm_hour; int minute timeinfo.tm_min; int second timeinfo.tm_sec; }这里有个坑NTP对时不是瞬间完成的需要几秒钟。如果你在setup里对时后立刻读时间可能读到的是1970年。我的做法是在loop里检查时间是否有效无效就显示“--:--”有效后再正常显示。4. 翻页动画的实现原理与代码拆解4.1 翻页效果的视觉拆解翻页时钟的动画本质上是把数字分成上下两半上半部分向下翻转下半部分向上翻转中间有一个折叠的过程。真实的机械翻页钟数字印在翻页板上板子从中间折叠上半部分翻下来盖住下半部分然后新的数字从后面翻上来。在屏幕上模拟这个效果可以简化成几个阶段静止状态显示当前数字上半部分和下半部分完整。翻页开始上半部分开始向下压缩高度逐渐减小同时下半部分保持不动。中间状态上半部分压缩到几乎为零此时显示的是新数字的上半部分。翻页结束新数字的上半部分从中间向下展开覆盖下半部分最终显示完整的新数字。这个过程如果逐帧绘制大概需要10-15帧每帧间隔20-30毫秒整体动画时长200-300毫秒。太快了看不清太慢了显得拖沓。4.2 用TFT_eSPI的Sprite做双缓冲直接在屏幕上绘制动画会有严重的闪烁问题。因为SPI刷屏需要时间你擦除旧内容再绘制新内容中间会有一瞬间的空白看起来就是闪。解决办法是用**Sprite精灵**做双缓冲。TFT_eSPI支持创建Sprite在内存里绘制好一整帧然后一次性推送到屏幕。这样屏幕上的内容始终是完整的不会闪。创建Sprite的代码TFT_eSPI tft TFT_eSPI(); TFT_eSprite spr TFT_eSprite(tft); void setup() { tft.init(); tft.setRotation(1); // 横屏 spr.createSprite(128, 160); // 创建和屏幕同尺寸的Sprite spr.setColorDepth(16); // 16位色 }createSprite会在内存里分配一块缓冲区128x160x2字节 40KB。ESP32有520KB SRAM完全够用。如果你用更大的屏幕比如240x320需要150KB也还能接受。绘制时先往Sprite里画画完调用pushSprite推送到屏幕spr.fillSprite(TFT_BLACK); // 清空 spr.setTextColor(TFT_WHITE); spr.drawString(12:34, 10, 50); spr.pushSprite(0, 0); // 推送到屏幕这样屏幕刷新是原子的不会闪。4.3 数字上半部分与下半部分的分离绘制翻页动画的关键是把数字拆成上下两半。TFT_eSPI没有直接提供“只绘制上半部分”的函数但可以用裁剪区域来实现。TFT_eSPI的Sprite支持setClipRect可以设置一个矩形区域之后的绘制只在这个区域内生效。比如要绘制数字的上半部分spr.setClipRect(0, 0, 128, 80); // 只在上半部分绘制 spr.drawString(12, 10, 50); spr.clearClipRect();下半部分同理spr.setClipRect(0, 80, 128, 80); // 只在下半部分绘制 spr.drawString(12, 10, 50); spr.clearClipRect();但翻页动画需要更精细的控制上半部分要逐渐压缩下半部分要保持不动。压缩可以通过改变裁剪区域的高度来实现// 上半部分从80像素压缩到0 for (int h 80; h 0; h - 8) { spr.fillSprite(TFT_BLACK); spr.setClipRect(0, 0, 128, h); spr.drawString(oldHour, 10, 50); spr.clearClipRect(); spr.setClipRect(0, 80, 128, 80); spr.drawString(oldHour, 10, 50); spr.clearClipRect(); spr.pushSprite(0, 0); delay(20); }这段代码会让上半部分逐渐消失看起来像是向下折叠。但真实的翻页效果还需要一个“新数字上半部分从中间展开”的过程以及阴影过渡。完整的实现代码比较长我放在下一节详细讲。4.4 动画帧率与刷新率的平衡动画流畅度取决于帧率和刷新率。帧率是每秒绘制多少帧刷新率是屏幕每秒更新多少次。TFT_eSPI的pushSprite速度取决于SPI频率和屏幕尺寸。128x160的屏幕40MHz SPI一帧大概需要5-8毫秒理论上可以跑到100帧以上。但实际做动画时不需要那么高的帧率。翻页动画15帧左右就够了每帧间隔20毫秒总时长300毫秒。这样CPU占用低也不会因为刷新太快导致画面撕裂。我试过把帧间隔降到10毫秒动画确实更流畅但看起来有点“滑”不像机械翻页那种有节奏的感觉。20-25毫秒的间隔配合适当的缓动函数看起来最自然。缓动函数可以用简单的二次函数float easeOutQuad(float t) { return t * (2 - t); }这样动画开始快、结束慢模拟机械翻页的惯性。5. 完整代码结构与关键函数解析5.1 整体代码框架整个项目的代码可以分成几个模块WiFi与NTP模块连接网络获取时间。屏幕初始化模块初始化TFT和Sprite。时间显示模块把时间格式化成字符串绘制到Sprite。翻页动画模块实现数字变化的动画效果。主循环检测时间变化触发动画。代码结构如下#include WiFi.h #include time.h #include TFT_eSPI.h TFT_eSPI tft TFT_eSPI(); TFT_eSprite spr TFT_eSprite(tft); int lastHour -1; int lastMinute -1; void setup() { Serial.begin(115200); setupWiFi(); setupScreen(); } void loop() { struct tm timeinfo; if (!getLocalTime(timeinfo)) { delay(100); return; } int hour timeinfo.tm_hour; int minute timeinfo.tm_min; if (hour ! lastHour || minute ! lastMinute) { animateFlip(lastHour, lastMinute, hour, minute); lastHour hour; lastMinute minute; } delay(100); }这个框架很简单但有几个细节要注意lastHour和lastMinute初始化为-1这样第一次循环一定会触发动画。getLocalTime如果失败直接返回不更新显示。5.2 时间格式化与显示布局时间显示格式是HH:MM四个数字加一个冒号。128x160的屏幕横屏后是160x128。我用的布局是数字区域宽度120像素高度80像素居中。每个数字宽度25像素高度50像素。冒号宽度10像素高度50像素。字体用TFT_eSPI的Font7这是一个7段数码管风格的字体适合做时钟。字号设置为2或者3根据实际效果调整。spr.setTextDatum(MC_DATUM); // 居中对齐 spr.setTextColor(TFT_WHITE, TFT_BLACK); spr.setTextSize(3); spr.drawString(12:34, 80, 64);setTextDatum(MC_DATUM)设置对齐方式为“中间居中”这样坐标就是文字的中心点。setTextColor的第二个参数是背景色这样文字绘制时会自动填充背景避免重叠。5.3 翻页动画函数逐行解析animateFlip函数是核心接收旧时间和新时间执行翻页动画。我把它拆成几个阶段void animateFlip(int oldH, int oldM, int newH, int newM) { String oldStr formatTime(oldH, oldM); String newStr formatTime(newH, newM); // 阶段1上半部分向下折叠 for (int h 80; h 0; h - 8) { spr.fillSprite(TFT_BLACK); // 绘制旧数字的上半部分逐渐压缩 spr.setClipRect(0, 0, 160, h); drawTime(spr, oldStr); spr.clearClipRect(); // 绘制旧数字的下半部分保持不动 spr.setClipRect(0, 80, 160, 48); drawTime(spr, oldStr); spr.clearClipRect(); spr.pushSprite(0, 0); delay(20); } // 阶段2新数字上半部分从中间展开 for (int h 0; h 80; h 8) { spr.fillSprite(TFT_BLACK); // 绘制新数字的上半部分逐渐展开 spr.setClipRect(0, 0, 160, h); drawTime(spr, newStr); spr.clearClipRect(); // 绘制新数字的下半部分 spr.setClipRect(0, 80, 160, 48); drawTime(spr, newStr); spr.clearClipRect(); spr.pushSprite(0, 0); delay(20); } }formatTime把小时和分钟格式化成“HH:MM”字符串drawTime负责把字符串绘制到Sprite上。setClipRect的参数是(x, y, width, height)注意这里的坐标是相对于Sprite的。这个实现比较粗糙没有阴影和渐变但基本效果已经有了。如果要更逼真可以在折叠过程中加一个半透明的黑色遮罩模拟阴影。5.4 阴影与渐变效果的实现阴影效果可以通过绘制半透明矩形来实现。TFT_eSPI的Sprite支持fillRect但不支持透明度。要实现半透明可以用颜色混合把背景色和黑色按比例混合得到不同灰度的颜色。uint16_t blendColor(uint16_t fg, uint16_t bg, float alpha) { uint8_t r1 (fg 11) 0x1F; uint8_t g1 (fg 5) 0x3F; uint8_t b1 fg 0x1F; uint8_t r2 (bg 11) 0x1F; uint8_t g2 (bg 5) 0x3F; uint8_t b2 bg 0x1F; uint8_t r r1 * alpha r2 * (1 - alpha); uint8_t g g1 * alpha g2 * (1 - alpha); uint8_t b b1 * alpha b2 * (1 - alpha); return (r 11) | (g 5) | b; }然后在折叠区域绘制一个渐变色的矩形从透明到黑色模拟阴影。这个效果做出来之后翻页的立体感会强很多。提示颜色混合计算比较耗时如果每帧都算可能会影响帧率。可以预先计算好一组渐变颜色存在数组里用的时候直接查表。6. 常见问题与避坑指南6.1 屏幕花屏、白屏、颜色不对这是最常见的问题原因通常有几个接线问题SPI线太长或者接触不良。杜邦线超过10厘米就容易出问题建议用短一点的线或者直接焊在板子上。我一开始用20厘米的杜邦线花屏严重换成10厘米就好了。SPI频率太高40MHz不是所有模块都能稳跑。如果花屏降到27MHz或者20MHz试试。在User_Setup.h里改SPI_FREQUENCY。颜色顺序不对ST7735有RGB和BGR两种颜色顺序如果红色显示成蓝色就是顺序反了。在User_Setup.h里加#define TFT_RGB_ORDER TFT_BGR或者TFT_RGB试一下哪个对。初始化参数不对不同厂家的ST7735模块初始化序列可能略有不同。TFT_eSPI支持多种初始化选项比如ST7735_INITB、ST7735_GREENTAB等。如果显示异常可以试试不同的选项。6.2 WiFi连接不稳定或NTP对时失败ESP32的WiFi连接有时候会卡住尤其是路由器信号弱的时候。我的做法是加一个超时机制unsigned long startTime millis(); while (WiFi.status() ! WL_CONNECTED) { if (millis() - startTime 10000) { Serial.println(WiFi connect timeout); ESP.restart(); } delay(500); }10秒连不上就重启避免卡死。NTP对时失败通常是服务器的问题。configTime可以传多个服务器地址按顺序尝试。我一般用ntp.aliyun.com和pool.ntp.org国内访问阿里云NTP比较快。如果对时一直失败检查一下时区设置。configTime的第一个参数是时区偏移秒数中国是28800。如果填0时间会差8小时。6.3 动画卡顿、闪烁、撕裂动画卡顿通常是帧率太低或者SPI速度不够。检查一下pushSprite的耗时可以在代码里加计时unsigned long t1 micros(); spr.pushSprite(0, 0); unsigned long t2 micros(); Serial.println(t2 - t1);128x160的屏幕40MHz SPI一帧应该在5-8毫秒。如果超过20毫秒说明SPI速度不够或者接线有问题。闪烁通常是没用Sprite直接在屏幕上绘制。用Sprite双缓冲可以解决。撕裂是刷新过程中屏幕内容不一致通常是pushSprite和绘制没有同步。TFT_eSPI的pushSprite是阻塞的一般不会撕裂。如果用了DMA可能需要加同步机制。6.4 内存不足与Sprite创建失败ESP32的SRAM有520KB但分成几块最大连续内存可能只有160KB左右。128x160x2 40KB的Sprite没问题但如果你用240x320的屏幕需要150KB可能会创建失败。检查内存使用Serial.println(ESP.getFreeHeap());如果内存不足可以降低颜色深度到8位256色内存减半。或者用更小的Sprite只绘制变化区域。注意创建Sprite后如果不再使用记得调用deleteSprite释放内存。频繁创建和删除Sprite会导致内存碎片。6.5 常见问题速查表问题可能原因解决方法花屏SPI线太长/频率太高缩短线/降频白屏接线错误/初始化失败检查接线/换初始化选项颜色反RGB/BGR顺序改TFT_RGB_ORDERWiFi连不上信号弱/密码错检查密码/加超时重启NTP失败服务器不可达换服务器/检查时区动画卡SPI慢/帧率低提高SPI频率/减少帧数闪烁没用Sprite用Sprite双缓冲内存不足Sprite太大降色深/缩小Sprite7. 外壳制作与最终组装7.1 3D打印外壳的设计要点外壳设计要考虑几个因素屏幕的固定、ESP32的放置、USB接口的开口、散热。我画的外壳是前后两片式前面板开屏幕窗口后面板开USB和散热孔。屏幕窗口的尺寸要比屏幕可视区域大1-2毫米避免遮挡。屏幕用热熔胶或者双面胶固定在前面板内侧。ESP32用M3铜柱固定在后面板或者直接用双面胶粘。USB开口的位置要对准ESP32的USB接口开口尺寸比接口大一点方便插拔。散热孔开在顶部和底部形成对流。如果你没有3D打印机可以用亚克力板激光切割或者买一个现成的塑料盒改造。关键是屏幕固定要稳不能晃动。7.2 组装步骤与注意事项组装顺序很重要先接线再固定否则线接好了装不进去。焊接排针如果屏幕模块没有焊排针先焊上。ESP32开发板一般自带排针。连接杜邦线按照接线表连接屏幕和ESP32。线长控制在10厘米以内。测试功能先不装外壳通电测试屏幕显示和WiFi连接。固定屏幕用热熔胶把屏幕固定在前面板。固定ESP32用铜柱或双面胶固定在后板。合壳把前后板合上拧螺丝。最终测试通电检查显示和动画。提示热熔胶固定屏幕时不要涂太多否则溢出来影响美观。可以用少量胶点在屏幕四角。7.3 背光调光与夜间模式背光太亮晚上刺眼可以加一个光敏电阻或者用PWM调光。TFT_eSPI支持背光PWM但需要把BLK引脚接到支持PWM的GPIO上。ESP32的PWM用ledcSetup和ledcAttachPinledcSetup(0, 5000, 8); // 通道05kHz8位分辨率 ledcAttachPin(15, 0); // GPIO15接通道0 ledcWrite(0, 128); // 50%亮度夜间模式可以设置一个时间范围比如晚上10点到早上6点自动降低亮度到20%。这个逻辑放在loop里根据当前时间调整PWM值。8. 功能扩展与进阶玩法8.1 加一个温度湿度传感器手头有DHT11或者DHT22的话可以加一个温湿度显示。DHT11便宜但精度一般DHT22贵一点但更准。接线很简单数据引脚接一个GPIO加上拉电阻。代码用DHT库#include DHT.h DHT dht(14, DHT22); void setup() { dht.begin(); } void loop() { float temp dht.readTemperature(); float humi dht.readHumidity(); // 显示到屏幕 }显示布局可以改成上面时间、下面温湿度或者温湿度轮流显示。8.2 用MPU6050做翻转检测MPU6050是六轴传感器可以检测加速度和角速度。用它做翻转检测屏幕翻转时自动旋转显示方向。接线用I2CSDA和SCL接ESP32的默认I2C引脚。代码用Adafruit_MPU6050库读取加速度值判断重力方向然后调用tft.setRotation切换方向。这个功能挺实用尤其是你把时钟放在不同角度的时候。8.3 接入Home Assistant做智能家居显示ESP32可以跑MQTT接入Home Assistant显示家里的温度、湿度、空气质量等数据。或者反过来把时钟的时间同步到Home Assistant。MQTT用PubSubClient库连接MQTT broker订阅主题收到消息后更新屏幕显示。这个扩展需要一点智能家居的基础但做出来很酷。8.4 用ESP32-S3做更流畅的动画ESP32-S3是ESP32的升级版双核240MHz支持更多的GPIO和更大的内存。如果觉得ESP32的动画还不够流畅可以换S3。S3的SPI速度更快DMA支持更好刷屏可以跑到80MHz。不过S3的引脚定义和ESP32不同接线要重新查。TFT_eSPI也支持S3配置稍微改一下就行。9. 我踩过的坑与实操心得9.1 杜邦线是最大的敌人这个项目我调试时间最长的问题就是花屏。一开始以为是代码问题查了半天最后发现是杜邦线太长。20厘米的杜邦线SPI信号质量很差40MHz根本跑不了。换成10厘米的线立刻就好了。所以如果你遇到花屏先检查线再检查代码。线的问题比代码的问题更常见。9.2 NTP对时不要放在setup里死等我一开始在setup里连WiFi、对时然后才进loop。结果如果WiFi连不上整个程序就卡在setup里屏幕一直黑屏。后来改成在loop里检查WiFi状态连不上就显示“Connecting...”连上了再对时。这样用户体验好很多。9.3 Sprite的内存要省着用128x160的Sprite占40KBESP32的SRAM虽然大但WiFi协议栈也要占不少。如果你还加了其他功能比如MQTT、传感器内存可能会紧张。我的做法是只创建一个Sprite反复使用不频繁创建删除。9.4 动画的节奏感比流畅度更重要我一开始追求高帧率把帧间隔降到10毫秒动画确实很流畅但看起来不像翻页像渐变。后来把帧间隔调到25毫秒加了缓动函数反而更像机械翻页的感觉。所以做动画不要只看帧率节奏感更重要。真实的机械翻页钟翻页速度不是匀速的开始快、中间慢、最后快有一个明显的“咔哒”感。在代码里用缓动函数模拟这个节奏效果会好很多。9.5 外壳的散热孔不能省ESP32和屏幕工作时会发热尤其是WiFi连接时。我第一个外壳没开散热孔夏天用的时候屏幕后面很烫后来加了散热孔就好了。散热孔不用太大几个小孔就行关键是形成对流。9.6 背光调光要留余量PWM调光时占空比不要设到0否则屏幕完全黑看起来像坏了。最低设到10-20保证还能看到内容。最高也不要设到100留一点余量延长背光寿命。9.7 代码里加一个看门狗ESP32长时间运行可能会死机尤其是WiFi不稳定的时候。加一个看门狗定时器定期喂狗如果程序卡死自动重启。#include esp_task_wdt.h void setup() { esp_task_wdt_init(10, true); // 10秒超时 esp_task_wdt_add(NULL); } void loop() { esp_task_wdt_reset(); // 喂狗 // 其他代码 }这样即使程序跑飞了10秒后也会自动重启不会一直卡死。9.8 屏幕的视角问题ST7735的视角不是很好从侧面看颜色会偏。如果你把时钟放在桌面上从正面看没问题但从侧面看就发白。这是屏幕本身的特性没办法完全解决。如果在意视角可以选IPS屏视角好很多但价格贵一点。9.9 时间同步的频率NTP对时不需要太频繁一天一次就够了。ESP32的RTC精度还可以一天误差大概几秒。我设置的是每6小时对时一次兼顾精度和网络负载。9.10 最后分享一个调试技巧调试屏幕显示时不要一开始就画复杂的界面。先画纯色确认屏幕能正常显示再画文字确认字体和颜色最后做动画。一步一步来出问题了容易定位。我一开始直接上完整代码结果花屏不知道是接线问题、初始化问题还是绘制问题。后来从纯色开始一步步加功能很快就找到了问题所在。这个项目做下来硬件成本不高但学到的东西很实在。ESP32的SPI驱动、Sprite双缓冲、NTP对时、动画缓动这些技术点在其他项目里也能用。如果你也想做一个桌面时钟建议从最简单的版本开始先让屏幕亮起来再慢慢加功能。不要一上来就追求完美做出来比做好更重要。
返回列表