ESP32-S3打造SoulCage互动吊坠:从TFT屏幕驱动到电源管理全解析
2026/9/24 13:13:29 网站建设 项目流程

1. 项目缘起与整体设计思路

1.1 这个吊坠到底是个什么东西

SoulCage 直译过来叫“灵魂囚笼”,听起来挺中二,但它的本质其实是一个带屏幕的互动式电子吊坠。核心玩法是:一块小尺寸 TFT 屏幕里关着一个“数字灵魂”——一个会呼吸、会闪烁、会随着你晃动而做出反应的光点或小生物。你把它挂在脖子上,它就像随身携带的一个小生命,安静的时候微微脉动,你走动时它会兴奋地跳动,你把它翻过来它甚至会“生气”。

这个项目的硬件核心是ESP32-S3,一块在创客圈里非常能打的微控制器。选它不是因为别的,就是因为它同时满足了三个硬性条件:第一,体积够小,有 QFN 封装的模组可以塞进吊坠尺寸的 PCB;第二,自带 WiFi 和蓝牙,后续想扩展手机互动不用换主控;第三,算力足够驱动 SPI 接口的 TFT 屏幕并且跑得动实时动画。屏幕用的是TFT_eSPI这个库来驱动,配合一块 1.14 寸或 1.3 寸的 IPS 屏,分辨率 135x240 或 240x240,刚好够画一个简单的像素风灵魂。

适合谁来参考这篇内容?如果你已经玩过 Arduino、点过 LED、接过几根杜邦线,想做一个有外壳、能戴出门、别人看了会问“这啥”的小玩意,那这个项目就是为你准备的。如果你完全零基础,也没关系,我会把每一步拆到你能跟着做,但你需要有耐心,因为这个项目涉及焊接、外壳组装和调试,不是插上面包线就能收工的级别。

1.2 为什么选 ESP32-S3 而不是 Arduino Nano 或 RP2040

这个问题我在立项的时候纠结了很久,最后选 ESP32-S3 是基于以下几个实际考量。

算力与内存。驱动一块 240x240 的 IPS 屏做 30fps 的动画,每帧要刷新的像素是 57600 个,16 位色深就是 115200 字节的数据量。Arduino Nano 的 ATmega328P 只有 2KB RAM,连一帧的缓冲都放不下,直接出局。RP2040 有 264KB RAM,跑这个没问题,但它的优势在于 PIO 编程,对于 SPI 屏幕驱动来说并没有比 ESP32-S3 更简单。ESP32-S3 有 512KB 的 SRAM,双核 240MHz,跑 TFT_eSPI 加动画逻辑绰绰有余,甚至还能剩出资源做蓝牙连接。

封装尺寸。ESP32-S3 有官方模组 ESP32-S3-WROOM-1,尺寸是 18mm x 25.5mm,虽然不算特别小,但对于一个吊坠来说,如果你把 PCB 设计成 30mm x 50mm 左右的椭圆形,是完全可以塞进去的。RP2040 的芯片本身更小(7mm x 7mm),但它需要外部 Flash 和晶振,整体面积算下来差不多。

开发便利性。ESP32-S3 在 Arduino IDE 里的支持已经非常成熟,板子管理器里直接选“ESP32S3 Dev Module”就能用,不需要额外的配置。TFT_eSPI 库对 ESP32 系列的支持也是最好的,有专门的优化。

功耗。这一点对吊坠来说很关键。ESP32-S3 支持多种低功耗模式,在只驱动屏幕显示简单动画的情况下,配合一块 300mAh 的锂聚合物电池,可以做到 4-6 小时的续航。如果你把亮度调低、动画帧率降到 15fps,撑 8 小时也不是不可能。

注意:ESP32-S3 的 GPIO 数量虽然多,但并不是所有引脚都能随便用。GPIO 0、3、45、46 是 strapping 引脚,上电时的电平状态会影响启动模式,接屏幕的时候要避开或者加下拉电阻。GPIO 19 和 20 是 USB D-/D+,如果你要用 USB 下载程序,这两个脚不能接其他外设。

1.3 屏幕选型:为什么是 ST7789 而不是 ILI9341

TFT_eSPI 支持的屏幕驱动芯片很多,最常见的是 ILI9341(2.4 寸以上)和 ST7789(1.3 寸以下)。对于吊坠这种尺寸,ST7789 是唯一合理的选择,原因很简单:ILI9341 的屏幕最小也是 2.4 寸,对角线 6 厘米,挂在脖子上像块砖。

ST7789 驱动的 IPS 屏有几个规格要注意区分:

  • 分辨率:常见的有 240x240、135x240、240x320。吊坠用 240x240 最合适,正方形,视觉上最平衡。
  • 接口:必须是 SPI 接口,因为 ESP32-S3 的并行接口引脚太多,吊坠 PCB 走线会很痛苦。
  • 引脚数:一般有 7 针(VCC、GND、SCL、SDA、RES、DC、CS)和 8 针(多一个 BLK 背光控制)两种。建议选 8 针的,背光可以 PWM 调光,省电。

我实测下来,中景园的 1.3 寸 240x240 IPS 屏配合 TFT_eSPI 库,在 ESP32-S3 上刷满屏纯色大约需要 12ms,也就是理论最高 80fps 左右。实际跑动画的时候,因为要计算像素,稳定在 30-40fps 没问题。

1.4 在 Tinkercad 里先做电路仿真验证

Tinkercad Circuits 是一个免费的在线电路仿真平台,虽然它不支持 ESP32-S3(只支持 Arduino Uno 等少数几款),但你可以用它来验证外围电路的逻辑。比如:

  • SPDT 开关的接线方式。SoulCage 上有一个 SPDT 滑动开关,用来切换“充电模式”和“运行模式”。在 Tinkercad 里你可以用 SPDT 开关加 LED 来模拟这个逻辑,确认开关的公共端、常开端、常闭端接对了没有。
  • 分压电阻的计算。如果你要用 ESP32-S3 的 ADC 来检测电池电压,需要两个电阻分压。Tinkercad 里可以直接搭出来,用万用表测中间点的电压,验证分压比是否正确。
  • LED 指示灯的逻辑。充电时亮红灯、充满亮绿灯,这个逻辑在 Tinkercad 里用几个 LED 和电阻就能验证。

提示:Tinkercad 的仿真不能替代真实焊接,但它能帮你省下至少两轮打板的钱。我第一版 PCB 就是因为 SPDT 开关的引脚顺序搞反了,导致开关拨到“运行”时实际是断开电池、接通充电,白白浪费了两周等板子。

2. 核心细节解析与实操要点

2.1 ESP32-S3 的引脚分配与 TFT_eSPI 配置

TFT_eSPI 库的配置是整个项目里最容易卡住新手的环节。它不像其他库那样在代码里传引脚参数,而是通过一个叫User_Setup.h的头文件来配置。这个文件在 Arduino 的 libraries 文件夹里,路径通常是Documents/Arduino/libraries/TFT_eSPI/User_Setup.h

你需要修改的关键行如下:

#define ST7789_DRIVER // 告诉库用的是 ST7789 驱动 #define TFT_WIDTH 240 #define TFT_HEIGHT 240 #define TFT_CS 10 // 片选 #define TFT_DC 9 // 数据/命令切换 #define TFT_RST 8 // 复位 #define TFT_SCLK 12 // SPI 时钟 #define TFT_MOSI 11 // SPI 数据 #define TFT_BL 7 // 背光控制(可选) #define SPI_FREQUENCY 40000000 // SPI 时钟频率,40MHz

这里有几个坑我要提前说清楚。

第一,SPI 频率不是越高越好。ST7789 的 datasheet 标称最高 62.5MHz,但实际走线长度、PCB 质量、杜邦线质量都会影响稳定性。我试过 80MHz,屏幕直接花屏;降到 40MHz 就非常稳定。如果你用杜邦线连接,建议先降到 27MHz 测试,确认能显示后再往上加。

第二,TFT_BL 引脚如果不用 PWM 调光,可以直接接 3.3V。但吊坠的电池容量有限,背光是大头,一定要用 PWM 控制。ESP32-S3 的 LEDC 外设可以输出 PWM,代码里用ledcSetup()ledcAttachPin()配置即可。

第三,GPIO 编号要避开 strapping 引脚。我上面给的 7、8、9、10、11、12 都是安全的。如果你换成 GPIO 0 做 CS,上电时如果屏幕没有正确初始化,GPIO 0 被拉低,ESP32-S3 会进入下载模式,程序就跑不起来。

2.2 灵魂动画的渲染逻辑

“数字灵魂”的视觉效果,本质上是一个基于噪声函数的粒子系统。我试过几种方案,最后选了一个计算量小、效果又好的方法:用 Perlin 噪声驱动一个圆形光点的半径和透明度变化。

具体来说,屏幕中心有一个基础圆,半径 20 像素。每一帧,我用三个不同频率的 Perlin 噪声值分别控制:

  • 半径偏移:r = 20 + noise(t * 0.5) * 8
  • 透明度:alpha = 128 + noise(t * 0.3 + 100) * 127
  • 颜色偏移:hue = noise(t * 0.2 + 200) * 360

Perlin 噪声的实现我用的是FastNoiseLite库,它非常轻量,在 ESP32-S3 上跑一次 1D 噪声大约只要 2 微秒。每帧计算 3 次噪声,加上绘制圆形的 SPI 传输时间,整体帧率能稳定在 35fps 左右。

绘制圆形不能用tft.fillCircle(),因为那个函数不支持透明度。我的做法是:先用tft.fillScreen(TFT_BLACK)清屏,然后用tft.drawPixel()逐像素画一个带 alpha 混合的圆。听起来很慢,但实际上 240x240 的屏幕,圆的直径最大也就 56 像素,需要计算的像素点大约 2500 个,ESP32-S3 完全扛得住。

实操心得:如果你觉得逐像素画太慢,可以预计算一个圆形模板数组,存每个像素相对于圆心的距离。运行时只需要查表加噪声偏移,速度能再快一倍。我用的是uint8_t dist_map[56][56],占用 3136 字节,对 ESP32-S3 来说九牛一毛。

2.3 SPDT 开关的硬件逻辑设计

SPDT 是“单刀双掷”开关,三个引脚:公共端(COM)、常开端(NO)、常闭端(NC)。在 SoulCage 里,我用它来实现充电与运行的切换

电路逻辑是这样的:

  • 开关拨到“充电”位置时,COM 连到 NC,电池的正极接到 TP4056 充电模块的 B+ 输入端,同时断开 ESP32-S3 的供电。
  • 开关拨到“运行”位置时,COM 连到 NO,电池正极接到 ESP32-S3 的 3.3V 稳压器输入端。

这样设计的好处是,充电时主控完全断电,不会出现“边充边用”导致的电池管理混乱。TP4056 的 BAT 输出直接接电池,OUT 输出接负载,但我不建议用 OUT 给 ESP32-S3 供电,因为 TP4056 的放电保护电流只有 1A 左右,ESP32-S3 峰值电流可能超过这个值。更稳妥的方案是电池直接接一个独立的 DW01 保护板,再接到开关。

在 Tinkercad 里验证这个逻辑很简单:用一个 SPDT 开关、两个 LED、两个 330 欧姆电阻和一块 9V 电池。开关拨到一边,红灯亮;拨到另一边,绿灯亮。确认接线无误后再画 PCB。

2.4 电池与电源管理

吊坠的电池我选的是301230 规格的锂聚合物电池,容量 110mAh,尺寸 3mm x 12mm x 30mm。这个尺寸刚好能塞进我设计的吊坠外壳里。如果你想要更长续航,可以用 401230(150mAh)或 501230(200mAh),但外壳要相应加厚。

充电芯片用的是TP4056,这是最经典的单节锂电充电 IC,外围只需要一个电阻设定充电电流、两个 LED 指示状态。充电电流我设定在 100mA,对应的电阻值是 10kΩ(公式:I = 1200 / R,R 单位 kΩ,I 单位 mA)。100mA 充 110mAh 电池,大约 1.5 小时充满,对吊坠来说可以接受。

稳压部分,ESP32-S3 需要 3.3V 供电,我用的是ME6211C33,这是一颗超低压差 LDO,静态电流只有 40uA,非常适合电池供电的场景。它的最大输出电流是 500mA,ESP32-S3 在 WiFi 关闭、屏幕中等亮度下大约消耗 80mA,余量充足。

注意:锂聚合物电池非常娇贵,过充、过放、短路都会导致鼓包甚至起火。TP4056 模块一定要买带 DW01 保护芯片的版本,或者自己在电池和电路之间加一块保护板。我见过有人直接把电池焊到 TP4056 的 B+ B- 上,结果电池鼓得像个小气球,幸好发现得早。

3. 实操过程与核心环节实现

3.1 在 Tinkercad 里搭建验证电路

虽然 Tinkercad 不支持 ESP32-S3,但你可以用 Arduino Uno 来验证电源切换逻辑SPDT 开关接线。具体步骤如下:

  1. 打开 Tinkercad Circuits,新建一个电路。
  2. 从元件库拖入:Arduino Uno、SPDT 开关、红色 LED、绿色 LED、两个 330Ω 电阻、9V 电池。
  3. 接线:电池正极接 SPDT 的 COM 端。SPDT 的 NC 端接红色 LED 正极,NO 端接绿色 LED 正极。两个 LED 的负极各接一个电阻,电阻另一端接电池负极。
  4. 点击“开始仿真”,拨动开关,观察 LED 切换。

这个仿真验证的是:开关在切换时,两个回路是互斥的。实际项目中,红色 LED 代表“充电中”,绿色 LED 代表“运行中”,逻辑完全一致。

如果你还想验证分压电路,可以再加两个电阻(比如 100kΩ 和 100kΩ)串联在电池正负极之间,用 Tinkercad 的万用表测中间点电压。9V 电池经过 1:1 分压,中间点应该是 4.5V。实际项目中,电池是 3.7V-4.2V,分压后是 1.85V-2.1V,ESP32-S3 的 ADC 量程是 0-3.3V,刚好能测。

3.2 PCB 设计与打板

PCB 我用的是KiCad,免费开源,功能足够。板子尺寸设计成 28mm x 48mm 的椭圆形,厚度 1.6mm。布局上,ESP32-S3 模组放在中间偏上,屏幕排线接口放在顶部,电池放在背面,充电模块和开关放在底部。

几个设计要点:

  • 屏幕排线接口:ST7789 的 FPC 排线是 0.5mm 间距、8 针,需要用一个 FPC 连接器。我用的型号是Molex 5034800800,立贴式,高度 1.2mm。
  • 天线净空区:ESP32-S3 模组的 PCB 天线下方不能铺铜,也不能走线。我在板子顶部留了 15mm x 8mm 的净空区,确保 WiFi 和蓝牙信号不受影响。
  • 电池焊盘:用两个大焊盘,间距 2mm,方便焊接电池引线。焊盘旁边丝印标注“+”“-”,防止焊反。
  • 开关开孔:SPDT 开关我用的是SS12D00微型滑动开关,柄长 3mm,需要在外壳上开一个 3mm x 1.5mm 的槽。

打板我用的嘉立创,5 片 28mm x 48mm 的板子,沉金工艺,总价不到 30 块钱。从下单到收货大约 5 天。

3.3 焊接与组装

焊接顺序很重要,先焊矮的元件,再焊高的元件。我的顺序是:

  1. ESP32-S3 模组:用热风枪 280°C 吹焊,或者用烙铁加焊锡膏。模组底部有散热焊盘,必须焊牢,否则大电流时会不稳定。
  2. LDO 和充电 IC:ME6211C33 和 TP4056 都是 SOT-23-5 封装,引脚间距 0.95mm,用尖头烙铁加 0.3mm 焊锡丝,配合放大镜操作。
  3. 电阻电容:0603 封装,用镊子夹住,先焊一端,再焊另一端。
  4. FPC 连接器:这是最难的,引脚间距 0.5mm,我用了助焊剂加拖焊法,焊完后用放大镜检查有没有连锡。
  5. SPDT 开关:直插元件,最后焊。
  6. 电池引线:用 30AWG 硅胶线,长度 3cm,焊到电池焊盘上。注意正负极不要接反。

实操心得:焊接 FPC 连接器的时候,我建议先用高温胶带把连接器固定在 PCB 上,防止移位。拖焊的时候,烙铁温度设 320°C,刀头烙铁比尖头更好用,因为刀头能同时接触多个引脚,焊锡会自然分开。焊完后用万用表蜂鸣档测相邻引脚,确认没有短路。

3.4 代码烧录与调试

代码烧录用 Arduino IDE,板子选“ESP32S3 Dev Module”,端口选对应的 COM 口。第一次烧录时,需要按住 BOOT 键再按 RESET 键,让 ESP32-S3 进入下载模式。烧录完成后按 RESET 键运行。

代码结构分为三部分:

  • 初始化:配置 TFT_eSPI、LEDC PWM、ADC。
  • 主循环:读取加速度计(我用的是 LIS3DH,I2C 接口),根据加速度值调整灵魂动画的参数。
  • 动画渲染:每帧计算噪声、绘制圆形、刷新屏幕。

加速度计的数据用来控制灵魂的“情绪”:

  • 静止时:灵魂缓慢呼吸,半径变化幅度小。
  • 晃动时:灵魂快速跳动,半径变化幅度大。
  • 翻转时(Z 轴为负):灵魂变成红色,表示“生气”。

调试的时候,我建议先用Serial.println()输出加速度值,确认数据正常后再接入动画逻辑。TFT_eSPI 的tft.setCursor()tft.print()也可以用来在屏幕上显示调试信息,非常方便。

4. 常见问题与排查技巧实录

4.1 屏幕不亮或花屏

这是最常见的问题,排查顺序如下:

现象可能原因解决方法
完全黑屏背光没接或没开检查 TFT_BL 是否接 3.3V 或 PWM 输出
白屏初始化失败检查 RST 引脚是否接对,SPI 频率是否过高
花屏SPI 频率过高或走线太长降低 SPI_FREQUENCY 到 27MHz
颜色不对颜色顺序配置错误在 User_Setup.h 里调整 TFT_RGB_ORDER
显示偏移分辨率设置错误确认 TFT_WIDTH 和 TFT_HEIGHT 与实际屏幕一致

我遇到过一次花屏,折腾了一下午,最后发现是杜邦线太长(20cm),SPI 信号质量太差。换成 10cm 的线就正常了。所以如果你在面包板上测试,尽量用短线。

4.2 ESP32-S3 无法进入下载模式

这个问题通常和 strapping 引脚有关。ESP32-S3 的 GPIO 0 在上电时如果被拉低,会进入下载模式。如果你的电路里 GPIO 0 接了其他外设,比如屏幕的 CS,上电时屏幕还没初始化,GPIO 0 可能被拉低,导致芯片一直停在下载模式。

解决方法:把屏幕的 CS 换到其他 GPIO,比如 GPIO 10。或者在上电时确保 GPIO 0 为高电平,可以加一个 10kΩ 的上拉电阻。

4.3 电池续航太短

如果你发现充满电只能用一两个小时,检查以下几点:

  • 背光亮度:把 PWM 占空比从 100% 降到 30%,续航能翻倍。
  • 屏幕刷新率:从 60fps 降到 30fps,功耗降低约 20%。
  • WiFi 和蓝牙:如果不用,在代码里调用WiFi.mode(WIFI_OFF)btStop()关闭。
  • LDO 静态电流:ME6211C33 的静态电流是 40uA,如果换成普通 LDO(比如 AMS1117),静态电流 5mA,电池几天就耗光了。

我实测下来,110mAh 电池、背光 30%、30fps、WiFi 关闭的情况下,续航大约 5.5 小时。如果你想要更长,可以换 200mAh 电池,续航能到 10 小时。

4.4 灵魂动画卡顿

如果动画看起来一顿一顿的,先检查帧率。在代码里加一个计数器,每秒打印一次帧数。如果低于 20fps,可能是以下原因:

  • 噪声计算太慢:FastNoiseLite 虽然快,但如果你每帧调用几十次,也会拖慢。减少到 3-5 次。
  • 逐像素绘制太慢:改用预计算模板,或者用tft.pushImage()直接推一张预渲染的图片。
  • SPI 传输瓶颈:240x240 全屏刷新在 40MHz SPI 下需要约 12ms,如果你每帧都全屏刷新,帧率上限就是 80fps。但如果你只刷新变化区域,速度会快很多。

避坑技巧:TFT_eSPI 有一个tft.setSwapBytes(true)的设置,如果你用 pushImage 显示图片,颜色不对的时候试试这个。另外,tft.init()之后一定要调用tft.setRotation(),否则屏幕方向可能不对。

4.5 充电时指示灯不亮

TP4056 的充电指示灯逻辑是:充电中红灯亮,充满绿灯亮。如果两个都不亮,检查:

  • 电池是否接反了。TP4056 的 B+ 接电池正极,B- 接负极,接反了芯片不工作。
  • 充电电流设定电阻是否焊好。10kΩ 电阻如果虚焊,充电电流为 0。
  • USB 供电是否正常。用万用表测 TP4056 的 VCC 引脚,应该是 5V。

我遇到过一批 TP4056 模块,充电指示灯是共阳极的,和常见规格相反,导致我以为没充电,其实是灯亮的方式不对。所以买模块的时候要看清楚规格书。

5. 外壳设计与佩戴体验

5.1 3D 打印外壳的设计要点

外壳我用 Fusion 360 设计,分前后两片,用 M2 螺丝固定。尺寸是 32mm x 52mm x 12mm,比 PCB 稍大一圈,留出 2mm 的壁厚。

设计要点:

  • 屏幕开窗:屏幕可视区域是 24mm x 24mm,开窗做成 23mm x 23mm,稍微小一点,遮住屏幕边缘的排线。
  • 开关开槽:SPDT 开关的柄是 3mm x 1.5mm,开槽做成 4mm x 2mm,留出活动余量。
  • 充电口:TP4056 的 USB 口是 Micro USB 或 Type-C,开槽要对准。我用的 Type-C,开槽 9mm x 3.5mm。
  • 挂绳孔:顶部留一个 4mm 直径的孔,穿挂绳用。
  • 散热孔:ESP32-S3 和 LDO 会发热,外壳底部开几个 1mm 的小孔,帮助散热。

打印材料我用的是PETG,比 PLA 更耐摔,而且有一定的韧性。打印参数:层高 0.2mm,填充 20%,壁厚 1.2mm。

5.2 佩戴舒适度调整

吊坠挂在脖子上,重量和尺寸很关键。我第一版外壳用了实心填充,重量 28 克,戴久了脖子酸。第二版改成 20% 填充,重量降到 18 克,舒服多了。

另外,挂绳的选择也有讲究。我用的是2mm 直径的伞绳,长度 60cm,刚好到胸口位置。如果你想要更轻,可以用 1.5mm 的蜡线,但承重差一些。

实操心得:外壳的内侧可以贴一层薄海绵或绒布,防止 PCB 和外壳摩擦导致短路。我用的是 1mm 厚的 EVA 泡棉,剪成和外壳内腔一样的形状,用双面胶固定。

5.3 后续扩展方向

这个项目做完之后,我还在想几个扩展方向。一个是加一个电容触摸按键,按一下灵魂会做出不同反应,比如变颜色、加速跳动。ESP32-S3 自带触摸引脚,不需要额外芯片。另一个是加一个微型振动马达,灵魂“生气”的时候震动一下,反馈更直接。

如果你有蓝牙音箱或者智能家居中枢,还可以让 SoulCage 通过蓝牙发送状态,比如“灵魂开心”时让灯变暖色,“灵魂生气”时让灯变红色。ESP32-S3 的蓝牙 5.0 支持 BLE,功耗很低,对续航影响不大。

我个人在实际操作中的体会是,这个项目最难的不是代码,也不是焊接,而是把所有的东西塞进一个足够小的外壳里。我前后改了四版 PCB,才把尺寸压到 28mm x 48mm。如果你也想做类似的项目,建议先用纸板剪一个和 PCB 一样大的模型,把屏幕、电池、开关都摆上去,确认空间够用再画板。这个笨办法帮我省了至少三次打板的钱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询