1. 从"钥匙扣"到"随身信息终端":eChain 到底想解决什么问题
第一次看到 eChain 这个名字,很多人会以为它就是个挂在包上的电子挂件,顶多显示个时间或者二维码。但真正上手做过一轮之后你会发现,这个项目的核心价值根本不在于"好看",而在于它把一块ESP32-S3开发板、一块ePaper 墨水屏和一套Wi-Fi 配网逻辑塞进了一个可以随身携带的小体积里,让它变成一个低功耗、可长期显示、随时能改内容的"数字身份牌"。
我最初做这个项目的动机很朴素:我经常参加一些线下活动,每次都要掏出手机翻二维码,屏幕一亮一灭,电量掉得飞快,遇到信号差的时候加载半天。后来我想,能不能做一个东西,平时几乎不耗电,需要展示信息的时候按一下就能看到,而且内容还能通过无线方式随时更新?eChain 就是在这个需求下长出来的。
它适合谁?如果你手上有ESP32-S3-N16R8 Mini 开发板(16MB Flash、带 Wi-Fi),又刚好对Arduino IDE有点基础,那这个项目几乎是为你量身定做的。它不需要你懂复杂的电路设计,也不需要焊接一堆贴片元件,核心就是"开发板 + 屏幕 + 电池 + 外壳"四件套。但如果你想把它做得真正能天天带出门,里面有不少细节值得提前想清楚。
这篇文章我会从选型逻辑、环境搭建、屏幕驱动、配网更新、功耗控制到外壳装配,完整走一遍。中间会穿插我自己踩过的坑,尤其是Arduino IDE 在 Mac 上启动一直等待、ESP32 离线包安装这类高频问题,都会给出可复现的解决路径。读完你应该能独立做出一块属于自己的 eChain,并且知道每个参数为什么这么设。
2. 硬件选型:为什么是 ESP32-S3-N16R8 加墨水屏
2.1 ESP32-S3-N16R8 Mini 的算力与存储余量
选主控的时候我对比过好几块板子。ESP32-C3 便宜,但只有单核,跑 Wi-Fi 加屏幕刷新的时候会有点吃力;ESP32 经典款生态最成熟,但 Flash 普遍偏小,做 OTA 升级和存放多套显示内容时捉襟见肘。最后落在ESP32-S3-N16R8上,原因很直接:16MB Flash + 8MB PSRAM这个组合,在这个体积和价位上几乎没有对手。
16MB Flash 意味着什么?你可以把多套字体、多张位图、甚至几套完整的 UI 资源都烧进去,不用为了省空间把界面做得寒酸。8MB PSRAM 更关键,墨水屏做全屏刷新时需要一块帧缓冲,如果屏幕分辨率稍大,比如 2.9 寸的 296×128,单色帧缓冲大概 4.7KB,看起来不大,但如果你要做灰度或者多级缓存,PSRAM 就是救命的那部分。S3 的双核 240MHz 也让我在配网的时候可以一个核处理网络,另一个核安心刷屏,不会互相卡。
注意:市面上有些标着"ESP32-S3 Mini"的板子实际 Flash 只有 4MB,买之前一定看清楚型号里的"N16R8",N16 代表 16MB Flash,R8 代表 8MB PSRAM,缺一个都会影响后续体验。
2.2 墨水屏的刷新特性与型号取舍
墨水屏最大的优点是断电后画面不消失,这对 eChain 这种"看一眼就收起来"的场景简直是天作之合。但它也有个绕不开的缺点:刷新慢,全屏刷新通常要 2 到 3 秒,而且刷新瞬间会有明显的黑白闪烁。
我试过 1.54 寸、2.13 寸和 2.9 寸三种。1.54 寸太小,二维码放上去扫不出来;2.9 寸显示面积舒服,但整机厚度上去了,挂在钥匙串上有点累赘。最后我选了2.13 寸、250×122 分辨率的版本,尺寸和可读性平衡得最好。驱动芯片常见的是SSD1680或IL0373,买的时候要确认卖家给的例程库对应哪个芯片,不然初始化会一直白屏。
墨水屏还有个"局部刷新"的能力,很多新手不知道。局部刷新只更新画面变化的部分,速度快很多,也不怎么闪。但它有个限制:局部刷新次数多了之后,残影会累积,所以一般建议每做十几次局部刷新就插一次全屏刷新来"洗"一下屏幕。这个策略我在 eChain 里是写进代码的,后面会讲。
2.3 供电方案:锂电池与充电管理的搭配
便携设备绕不开电池。我用的是3.7V 锂聚合物电池,容量 500mAh 左右,配合一块TP4056 充电管理模块。这里有个细节:ESP32-S3 的工作电压是 3.3V,而锂电池满电是 4.2V,所以中间必须有个稳压环节。我一开始图省事直接用开发板上的 LDO,结果发现电池从 4.2V 掉到 3.4V 的过程中,LDO 发热明显,效率也一般。
后来我换成了低压差稳压(LDO)加电源路径管理的方案,充电和供电可以同时进行,不会出现"边充边用导致电池反复充放"的问题。如果你想让 eChain 更省电,可以在固件里加一个深度睡眠逻辑:不操作的时候让 ESP32-S3 进入 deep sleep,电流能压到几十微安,500mAh 的电池理论上能撑好几个月。当然实际会有自放电和屏幕待机功耗,但撑几周是没问题的。
| 部件 | 型号/规格 | 选它的理由 |
|---|---|---|
| 主控 | ESP32-S3-N16R8 Mini | 16MB Flash + 8MB PSRAM,双核,Wi-Fi |
| 屏幕 | 2.13 寸 ePaper 250×122 | 尺寸适中,局部刷新可用,功耗极低 |
| 电池 | 3.7V 500mAh 锂聚合物 | 体积小,容量够日常使用 |
| 充电 | TP4056 模块 | 成熟便宜,带过充保护 |
| 稳压 | 低压差 LDO | 减少发热,提升电池利用率 |
3. Arduino IDE 环境搭建:Mac 用户最容易卡住的几个点
3.1 安装 ESP32 开发板支持包的两种路径
Arduino IDE 装 ESP32 支持包,官方推荐的是在"首选项"里填开发板管理器网址,然后在开发板管理器里搜索安装。但国内网络环境下,这个方式经常卡在下载阶段,进度条一动不动。我试过两种替代方案,都比较稳。
第一种是离线包安装。你去下载对应版本的 ESP32 离线包(通常是一个压缩包),解压后放到 Arduino 的硬件目录下。Mac 上的路径一般是~/Library/Arduino15/packages/,把解压出来的esp32文件夹放进去,重启 IDE 就能在开发板列表里看到 ESP32-S3 了。这个方式的好处是完全不依赖网络,装完就能用。
第二种是用开发板管理器的镜像源。有些社区维护了国内的镜像地址,把它替换掉默认的下载地址,速度会快很多。不过镜像源偶尔会同步滞后,版本不是最新的,如果你要用某些新特性,还是建议用离线包。
提示:离线包的版本要和你的 Arduino IDE 版本大致匹配。IDE 太老、离线包太新,会出现"编译时报找不到某个工具链"的错误。我一般会把 IDE 升到 2.x 的较新版本再装离线包。
3.2 "启动时一直等待"的根因与处理
Mac 上有个特别烦人的现象:Arduino IDE 双击之后,图标在 Dock 上跳啊跳,就是不出窗口,鼠标变成转圈,等半天没反应。这个问题我遇到过好几次,排查下来主要有两个原因。
一个是IDE 在扫描网络端口或者等待某个后台服务。Arduino IDE 启动时会去检测串口设备,如果系统里有虚拟串口或者蓝牙串口在反复握手,它就可能卡住。解决办法是先把不必要的串口设备拔掉,或者在系统设置里禁用掉那些虚拟端口,再启动 IDE。
另一个原因是配置文件损坏。Arduino IDE 的配置存在~/Library/Arduino15/下面,如果preferences.txt或者某个缓存文件写坏了,启动就会卡。这时候可以先把Arduino15文件夹整体改名备份,让 IDE 重新生成一份默认配置,通常就能正常启动了。启动成功后再把之前备份的packages文件夹挪回去,开发板支持包就不用重装。
我还遇到过一次是Java 运行环境的问题。Arduino IDE 2.x 是基于 Electron 的,但底层还是依赖一些 Java 组件。如果系统里的 Java 版本太乱,也会导致启动异常。这种情况重装 IDE 往往比修 Java 更快。
3.3 串口驱动与开发板识别
ESP32-S3 通过 USB 连接电脑后,Mac 上一般能自动识别出串口,但有些板子用的是 CH340 或者 CP2102 这类 USB 转串口芯片,需要额外装驱动。如果你在"工具 > 端口"里看不到任何设备,先检查两件事:一是 USB 线是不是只能供电不能传数据的那种(这种线特别坑,我中招过),二是驱动有没有装。
装好驱动后,端口名通常长这样:/dev/cu.usbserial-xxxx或者/dev/cu.wchusbserial-xxxx。选对端口之后,点上传,如果一直卡在Connecting...,可以试着按住板子上的 BOOT 键再点上传,等出现Writing...再松开。这是 ESP32-S3 进入下载模式的常规操作,不是板子坏了。
4. 墨水屏驱动与显示逻辑:让画面"稳"下来的关键
4.1 初始化时序与白屏问题的排查
墨水屏驱动最容易出的问题就是白屏或者花屏。我第一块屏折腾了整整一个晚上,最后发现是初始化时序里少了一个复位延时。墨水屏上电之后需要一段复位时间,如果代码里delay给得太短,驱动芯片还没准备好,后续发的指令就全乱了。
常见的驱动库比如GxEPD2或者Waveshare 的 e-Paper 库,内部已经封装好了初始化流程,直接用它们的init()一般不会出问题。但如果你是自己写底层 SPI 通信,那就要严格对照数据手册里的时序图,尤其是复位引脚的电平变化和每个指令之间的等待时间。
还有一个隐蔽的坑:SPI 速率。墨水屏的 SPI 时钟不能太高,我一开始设到 10MHz,结果画面偶尔会缺一块。降到 4MHz 之后就非常稳定了。这个速率对刷新时间的影响其实不大,因为墨水屏本身刷新就慢,瓶颈不在 SPI 上。
4.2 全屏刷新与局部刷新的策略组合
前面提到过,局部刷新快但会累积残影。我在 eChain 里的策略是这样的:日常内容切换用局部刷新,每累计 15 次局部刷新后强制做一次全屏刷新。这个数字不是拍脑袋定的,是我实测下来残影开始明显的一个临界点。你可以根据自己的屏幕型号微调,有的屏 10 次就明显了,有的能撑到 20 次。
代码层面,我会用一个全局变量记录局部刷新次数,每次局部刷新后自增,达到阈值就调用全屏刷新函数并把计数清零。全屏刷新虽然闪一下,但能把屏幕"洗干净",后续局部刷新又会很清爽。
int partialCount = 0; const int partialLimit = 15; void refreshScreen(bool full) { if (full || partialCount >= partialLimit) { display.setFullWindow(); display.display(false); // 全屏刷新 partialCount = 0; } else { display.setPartialWindow(0, 0, display.width(), display.height()); display.display(true); // 局部刷新 partialCount++; } }4.3 显示内容的排版与字体选择
eChain 上要显示的东西无非几类:文字信息、二维码、简单图标。墨水屏是单色的,所以排版要尽量简洁,别想着堆太多元素。我一般把屏幕分成上下两块:上面一行显示标题或者名字,下面主体区域放二维码或者一段说明文字。
字体方面,中文字体很占空间。如果你的 eChain 只需要显示英文和数字,用内置的FreeSans系列就够了。要显示中文的话,得用U8g2 字体或者自己取模。我建议只取你实际用到的那几个汉字的字模,不要整段字库往里塞,16MB Flash 虽然大,但也没必要浪费。
二维码的生成我用的是一个轻量的二维码库,生成之后转成位图数组,再交给墨水屏库去画。这里要注意二维码的静区(四周留白),留得太窄会导致扫码识别率下降。我一般四周各留 4 个像素以上的白边。
5. Wi-Fi 配网与内容更新:让 eChain 真正"活"起来
5.1 首次配网的交互设计
一个便携设备如果每次改内容都要插线烧录,那体验就太差了。所以 eChain 必须支持Wi-Fi 配网。我的做法是:设备上电后如果没连过网,就进入配网模式,屏幕显示一个提示,告诉用户去连它的热点。
具体流程是 ESP32-S3 先开一个AP 热点,用户手机连上这个热点后,会自动弹出一个配置页面( captive portal ),在页面里填入家里的 Wi-Fi 名称和密码。提交之后,ESP32-S3 切换到 STA 模式去连接,连上了就把凭证存到NVS(非易失存储)里,下次开机直接连,不用再配。
这个交互对普通用户很友好,不需要装 App,也不需要懂技术。但有个细节要注意:配网页面最好做得极简,就两个输入框加一个按钮,别搞一堆花哨的东西,手机小屏幕上操作越简单越好。
5.2 内容拉取与本地缓存
连上网之后,eChain 要能从某个地址拉取要显示的内容。我一般让它去请求一个简单的接口,返回 JSON 格式的数据,里面包含标题、正文和二维码链接。拿到数据后,先在内存里解析,再刷新到屏幕上。
这里有个提升体验的技巧:本地缓存。如果这次拉取失败(比如网络不好),不要直接显示错误,而是把上一次成功拉取的内容从 Flash 里读出来显示。这样即使断网,eChain 看起来也是"正常"的,只是内容没更新而已。缓存我存在 SPIFFS 或者 LittleFS 里,写一个简单的文件就行。
// 伪代码示意:拉取失败时回退到缓存 String fetchContent() { String data = httpGet(CONTENT_URL); if (data.length() > 0) { saveToCache(data); return data; } return loadFromCache(); }5.3 低功耗模式下的唤醒策略
eChain 大部分时间是待机的,所以功耗控制直接决定电池能撑多久。我的策略是:平时深度睡眠,按按键唤醒。ESP32-S3 的 deep sleep 模式下,只有 RTC 域还在工作,电流可以压到几十微安。按键接在一个支持 RTC 唤醒的 GPIO 上,按下就把芯片唤醒,刷新屏幕,然后再次进入睡眠。
如果你希望 eChain 能定时自动更新内容,也可以用定时唤醒。比如每 6 小时醒一次,连 Wi-Fi 拉一次数据,刷新完继续睡。这样既保证了内容不会太旧,又不会因为一直联网把电耗光。实测下来,500mAh 电池配合 6 小时唤醒一次的策略,用两三周没什么问题。
注意:深度睡眠唤醒后,程序是从
setup()重新开始跑的,不是从睡的地方继续。所以你的逻辑要设计成"每次醒来都是一次完整的启动流程",把状态存在 RTC 内存或者 NVS 里,别指望全局变量能保留。
6. 外壳、装配与日常使用中的真实体验
6.1 外壳设计与屏幕开窗
电路调通之后,接下来就是把它装进一个能带出门的壳子里。我用的是3D 打印,材料选 PLA 就够,没必要上更贵的。设计外壳时最关键的是屏幕开窗的尺寸,要刚好比屏幕的可视区域大一点点,留出 0.5mm 左右的余量,不然装配的时候容易压到屏幕边缘。
按键的位置也要提前想好。我第一版把按键放在侧面,结果装在包里经常被误触,后来改到正面偏下的位置,需要刻意按才触发,误触少了很多。充电口我留在了底部,用 Type-C,开孔稍微大一点,方便插线。
6.2 装配顺序与排线处理
装配顺序我建议是:先把屏幕固定在外壳前盖,再把主控和电池用双面胶或者热熔胶固定在后盖,最后把排线接好再合盖。排线一定要留一点余量,不要绷得太紧,否则合盖的时候容易把焊点扯断。我第一块屏就是因为排线太紧,用了几天之后接触不良,画面开始闪。
电池的固定也要注意,不要让它直接压在主控的发热区域上。虽然 ESP32-S3 功耗不高,但充电的时候 TP4056 会发热,电池长期贴着热源对寿命不好。我在电池和主板之间垫了一小片绝缘胶带,既隔热又防短路。
6.3 日常使用中的几个小技巧
用了一段时间之后,我总结了几个让 eChain 更好用的小习惯。第一,屏幕内容不要频繁切换,墨水屏的刷新寿命是有限的,虽然官方标称几百万次,但能省则省,没必要每分钟刷一次。第二,充电的时候尽量别同时用,虽然电源路径管理能支持边充边用,但发热会叠加,长期这样对电池不好。第三,定期做一次全屏刷新,哪怕内容没变,也能把残影清掉,保持画面干净。
还有一点是关于二维码的。如果你用 eChain 展示二维码给别人扫,记得把屏幕亮度(墨水屏没有背光,这里指的是对比度)和角度考虑进去。墨水屏在强光下反而更清楚,但在昏暗环境里需要找个好角度。我一般会把二维码放在屏幕中央偏上的位置,方便对方举着手机对准。
7. 我在这套方案里踩过的坑和最后的取舍
回过头看,eChain 这个项目最大的挑战不是某一项技术,而是在体积、功耗、成本、可维护性之间找平衡。我一开始想加很多功能,比如蓝牙、蜂鸣器、多个按键,后来发现每加一样东西,外壳就要重新设计,功耗也要重新算,最后果断砍掉,只保留最核心的"显示 + 配网 + 低功耗"三件事。
Arduino IDE 的环境问题也让我吃了不少苦头。Mac 上那个"启动一直等待"的毛病,我前后折腾了三四次才彻底搞清楚是配置文件的问题。现在我的习惯是,每次装完新版本 IDE,先把Arduino15文件夹备份一份,出问题直接还原,比一点点排查快得多。
如果你也打算做 eChain,我的建议是先把最小系统跑通:一块板子、一块屏、一段能显示"Hello"的代码。确认这条链路没问题之后,再往上加 Wi-Fi、加电池、加外壳。很多人一上来就想做完整版,结果卡在某个环节就放弃了。分阶段推进,每完成一步都能看到成果,这个过程本身也挺有意思的。
最后分享一个我在调试时常用的小方法:把屏幕的刷新日志通过串口打出来,每次刷新都记录是全屏还是局部、耗时多少、当前计数是多少。这样一旦出现残影或者白屏,你能很快定位到是哪一次刷新出的问题,而不是盲目地改代码。这个习惯帮我省下了大量试错时间。