ARTICLE DETAIL

资讯详情

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

基于ESP32和墨水屏的DIY电子阅读器制作全攻略

基于ESP32和墨水屏的DIY电子阅读器制作全攻略 去年冬天翻抽屉,找出一台屏幕漏液的MP4,里面躺着几本年轻时没看完的电子书。我盯着那块坏屏看了半天,突然冒出个念头:现在主流阅读器动辄千元,功能堆得很多,但我真正需要的其实只是一块墨水屏、能传文件、能看书。于是就有了这个项目:用ESP32做主控、2.13寸墨水屏做显示,自己搭了一个支持局域网传书的电子阅读器。手机连上它开的Wi-Fi热点,浏览器里把TXT文件扔进去,墨水屏上就能一页页显示出来;整套硬件成本控制在60元内,代码也开源了。这篇文章会把选型逻辑、硬件接线、软件实现和调试过程完整写一遍,尤其适合想复刻这个项目、或者打算从零学ESP32驱动墨水屏的朋友。1. 为什么选ESP32加墨水屏:这个阅读器的定位与选型逻辑1.1 传书阅读器需要什么样的主控很多朋友看到电子阅读器第一反应是STM32,毕竟低功耗、稳定,做产品很合适。但这个项目要求能传书,传书意味着要么有USB Mass Storage,要么有网络,STM32自身没有Wi-Fi,想联网就必须外挂ESP8266或者W5500等以太网芯片,硬件复杂度和成本立刻上去了。ESP32的情况完全不同,它自带2.4GHz Wi-Fi和蓝牙,双核240MHz,跑一个Web服务器绰绰有余。价格还便宜,开发板十几二十块钱一片。我拿它和几类方案对比过:方案成本联网能力开发难度适合场景STM32F103C8T6约8元需外挂模块较高追求极致功耗的场景ESP8266约10元仅Wi-Fi中等简单控制、单一任务ESP32约15元Wi-Fi蓝牙中等本项目这种多功能读取设备树莓派Zero W约150元Wi-Fi低但功耗高需要跑完整系统的场景选ESP32的另一个理由是内存和Flash。做阅读器需要解析文本、渲染墨水屏,ESP32有520KB SRAM和4MB Flash,虽然和手机没法比,但处理纯文本绰绰有余。相比之下,ESP8266只有160KB SRAM和1MB Flash,跑Web服务器还要同时处理中文字库,会非常局促。1.2 2.13寸墨水屏的优缺点屏幕是整个项目里最叛逆的选择——现在随便一台手机屏幕都是6.7寸、120Hz高刷,2.13寸墨水屏只有约250×122像素(不同厂家的模块略有差异),看惯了手机的人第一次见到这个话题都会问:这么小能看书吗?它能。2.13寸墨水屏的定位不是给你看复杂排版,而是显示纯粹的文字。实测一屏大概能放10行中文、每行15个字左右,一本150页的书大约翻350到400页。对零散时间阅读来说完全够用,而且墨水屏有手机屏幕给不了的三个优势:不发光,靠反射环境光显示,长时间看眼睛不累;静态显示几乎不耗电,只有翻页那一刻才消耗电流;断电后画面不消失,这就是所谓的双稳态特性。它的缺点也必须说清楚。刷新速度慢,2到3秒刷新一次,习惯了手机120Hz高刷的人,用这东西翻页要有点耐心。另外2.13寸确实是入门尺寸,如果有条件,我建议复刻时直接换2.9寸或者4.2寸,体验会更好,代码逻辑完全不用改,只是驱动类和分辨率参数变一下。1.3 和Kindle这类现成设备的真实差异有朋友问:为什么不直接买台Kindle?价格、屏幕、生态都比DIY强太多,何必折腾。这问题我在做之前就想过。我的回答是:DIY阅读器追求的不是更好用,而是可控和可玩。Kindle的传书方式多少要依赖云服务,而自己做的机器,传书协议、显示逻辑、外壳结构全部在自己的掌控里,想加什么功能随时改代码。它甚至不需要互联网,只要一个Wi-Fi热点就能工作。做这个项目还有一层意义:把一块平平无奇的墨水屏模块和一粒ESP32芯片组合起来,变成一件每天愿意放在床头使用的物品。这种从零件到成品的过程,本身就是硬件DIY最大的乐趣。如果你只是为了读书,那直接买Kindle,省钱省心;如果你享受从零搭起来一个东西的过程,并且还想学到ESP32驱动墨水屏、搭建Web服务这些技能,那DIY这条路会让你收获更多。2. 系统运行原理:从手机里的TXT到墨水屏上的文字2.1 整体工作流程这个阅读器整套运行流程可以拆成四步:手机或电脑连接ESP32发出的Wi-Fi热点(AP模式),或者让ESP32连和手机同一个路由器(STA模式)。在浏览器地址栏输入192.168.4.1(AP模式)或者路由器分配的局域网IP,打开一个简单的上传页面。选择TXT文件,浏览器通过HTTP POST把文件内容上传给ESP32,固件将文件保存到LittleFS文件系统或SD卡。墨水屏读取文件内容,按页渲染显示,三个物理按键分别负责上一页、下一页和返回书库。这套流程看上去简单,但里面每一环都有值得抠的细节。比如HTTP上传文件时,ESP32的WebServer库是边收边写,不能等整个文件收完再一次性写盘,因为ESP32内存放不下大文件;墨水屏显示也不能把整个TXT读进内存再排版,必须按行读取、按页渲染。这些限制直接决定了代码怎么写。2.2 墨水屏的双稳态显示与刷新机制墨水屏的显示原理和手机屏幕完全不同。它内部有大量微胶囊,每个胶囊里装着带正电荷的白色粒子和带负电荷的黑色粒子。施加电场时,白色粒子和黑色粒子朝不同方向移动,当电场撤掉后,粒子会停留在原地,形成稳定的黑白图像,这就是双稳态。所以墨水屏断电后画面并不会消失,反而像个电子纸一样把内容一直留在屏幕上。驱动墨水屏需要经过一个流程:先把要显示的图像按像素逐位写入驱动芯片内部的RAM,再发送刷新命令,屏幕会先清屏再逐行显示新内容。SPI接口上传输的是图像点阵数据,1bit代表一个像素,黑色为0、白色为1。以2.13寸250×122分辨率为例,一屏图像的数据量是250×122÷8,约3.8KB,对ESP32来说很小。实际使用中一定要分清全局刷新和局部刷新。全局刷新效果最好,但每次都要经历黑屏→白屏→出图的过程,视觉上比较明显;局部刷新只更新变化区域,速度快、闪烁小,但长时间使用容易留下残影。我的策略是:翻页时用局部刷新,每翻10页或切换书籍时强制做一次全局刷新来洗屏。这个细节对阅读体验影响很大,后面踩坑部分还会细说。2.3 中文显示的三个关键点墨水屏显示中文是这个项目里最容易被低估的坑。英文字母和数字可以靠很小的点阵字库解决,但中文常用字有3000多个,全量字库体积不小,而且编码方式还得分UTF-8和GBK,处理不好就是满屏乱码。我实际做的时候踩了三个坎,这里先预告一下:第一,必须统一编码。我做的是UTF-8输入,但有些TXT文件是ANSI/GBK编码的,读进来就乱,所以我在电脑端写了一个小脚本,把要传的书统一转成UTF-8再上传。第二,字库要选对。如果使用u8g2库加载u8g2_font_unifont_t_chinese这类全中文字体,字库体积会占用大量Flash空间,但优点是显示效果好、支持反白。我这里用电量的考量是:把字体文件放在LittleFS里按需读取。第三,渲染过程需要逐字处理。把UTF-8编码的字符串按字符逐个解码,查点阵字库,再拼到页面的缓冲区里,按行排满后交给墨水屏驱动刷新。直接调用Arduino默认的Serial.print没问题,但渲染到墨水屏时,库默认的ASCII字体根本不含中文字形,必须换。3. 硬件准备与接线:照着这份清单和连线表操作3.1 材料清单与采购建议先给一张完整的物料表,方便你照着买:物料型号/规格参考价格备注主控板ESP32 DevKitC V415~25元建议选CP2102串口芯片版本墨水屏模块2.13寸,SSD1680驱动30~50元要买带FPC转接板的,方便接线物理按键轻触开关6×6mm1元/10个用到3个电池/供电18650锂电池TP4056充电板8~15元可选,USB供电也可以外壳3D打印/亚克力0~20元先用洞洞板搭,后面再装壳墨水屏这类硬件采购时有个容易忽略的点:不同批次的分辨率甚至驱动芯片可能不一样。微雪、合宙这类大厂的产品页面会标注具体型号,我手上的模块是250×122、SSD1680驱动芯片,买的时候最好选明确标注同一款驱动的屏,否则代码里GxEPD2库的驱动类要换。3.2 引脚接线表接线是硬件部分最容易出错的一环。墨水屏模块一般有8个引脚,VCC、GND、DIN、CLK、CS、DC、RST、BUSY,其中前4个和普通的SPI设备完全一致,后面3个是控制引脚,BUSY用来让主控确认屏幕是否忙。我的接法如下:墨水屏引脚功能连接ESP32引脚VCC电源正极3V3GND电源负极GNDDINSPI数据输入GPIO23(MOSI)CLKSPI时钟GPIO18(SCK)CS片选GPIO5DC数据/命令选择GPIO17RST复位GPIO16BUSY忙状态输出GPIO4三个功能按键分别接GPIO32、GPIO33和GPIO25,另一端接地。ESP32内部有上拉电阻,所以不需要外部上拉,但代码里要把引脚模式设置为INPUT_PULLUP。有个容易踩的细节:墨水屏模块的VCC建议用3.3V,不要直接接5V。虽然有些模块板载了电平转换电路可以容忍5V,但为了保险起见,我全程从ESP32开发板的3.3V引脚取电。另外,如果屏幕在接线时突然显示不正常,第一步先检查杜邦线是不是松了,尤其是BUSY和DC这两根,接触不良会导致非常诡异的刷新问题。3.3 供电方案与外壳供电我做了两版。第一版直接USB供电,充电宝插上就能用,优点是简单;缺点是不便携。第二版换成了18650锂电池加TP4056充电板,电池和充电板放在外壳下层,给ESP32开发板的5V引脚供电,开发板上的AMS1117稳压芯片会把电压降到3.3V给屏幕。这里要提醒一下:如果电池容量是2000mAh,理论上够用很久,但实际续航取决于你怎么用。墨水屏静态不耗电,但ESP32一直开着Wi-Fi待机电流有80毫安左右,如果屏幕常亮不关,大概能用一天多。我的解决办法是做了深度睡眠:超过10分钟没有按键操作,ESP32进入esp_deep_sleep状态,按任意键通过外部GPIO唤醒,唤醒后重新连接Wi-Fi。这样耗电骤降,一到两周充一次电没问题。外壳我直接用3D打印做了一个像相框一样的壳,屏幕嵌在正面,按键开孔,背面留了一个USB充电口。没有打印机也不碍事,用亚克力板裁两块夹起来,或者拿硬纸板叠出一个空腔,一样能用,毕竟这是DIY,外壳再丑也是自己做的。4. 软件实现:固件从零到能传书阅读器的关键代码4.1 开发环境搭建写固件我用的是Arduino IDE,虽然它的代码提示不如VS Code,但对于这种中等复杂度的项目,生态成熟、库好装是最大的优势。ESP-IDF功能更强,但开发效率低,不适合快速复刻。开发环境搭建分三步:在Arduino IDE的开发板管理器地址里加入ESP32支持包的JSON链接,然后通过开发板管理器搜索ESP32并安装。在开发板里选择ESP32 Dev Module,Flash Size选4MB。因为传书需要文件系统、翻页需要存储阅读进度,还要安装GxEPD2(墨水屏驱动)、U8g2_for_Adafruit_GFX(中文渲染)这些库,通过库管理器搜索安装即可。这些环境配置都是公开常规操作,不会遇到什么特殊障碍。安装完以后,下一步就是驱动墨水屏。4.2 墨水屏驱动的初始化与刷新墨水屏驱动我直接用GxEPD2库,它把不同厂家的屏幕都封装成了统一的类。核心代码如下:#include GxEPD2_BW.h #include GxEPD2_3C.h // 根据你的屏幕型号选择驱动类 GxEPD2_BWGxEPD2_213_GDEY0213B74, GxEPD2_213_GDEY0213B74::HEIGHT display( GxEPD2_213_GDEY0213B74(/*CS*/5, /*DC*/17, /*RST*/16, /*BUSY*/4) ); void displayText(String text) { display.setRotation(0); display.setFullWindow(); display.fillScreen(GxEPD_WHITE); // 这里用U8g2 for Adafruit GFX渲染中文文本 u8g2Fonts.setFont(u8g2_font_unifont_t_chinese); u8g2Fonts.setCursor(10, 20); u8g2Fonts.print(text); display.display(); }这段代码里最重要的一步是display.display(),它会触发一次完整的刷新流程。画图像的过程只是往缓冲区里写数据,真正的电信号刷新由display()完成,这就是为什么它后面要等2到3秒。我一开始不知道这个区别,以为调了fillScreen就立刻出效果,结果屏幕上半天没反应,还以为是接线问题,凭空浪费了半小时。4.3 局域网传书Web服务的实现传书的核心是一个运行在ESP32上的HTTP服务器。Arduino环境里的WebServer库提供了现成的HTTP处理能力,但上传文件的处理逻辑必须自己写好。代码思路是这样的:#include WebServer.h #include LittleFS.h WebServer server(80); void setup() { LittleFS.begin(); server.on(/, HTTP_GET, handleIndex); // 显示上传页面 server.on(/upload, HTTP_POST, handleUploadDone, handleUploadProcess); server.on(/books, HTTP_GET, handleBookList); // 显示书籍列表 server.begin(); } void handleUploadProcess() { HTTPUpload upload server.upload(); if (upload.status UPLOAD_FILE_START) { currentFile LittleFS.open(/books/ String(upload.filename), w); } else if (upload.status UPLOAD_FILE_WRITE) { currentFile.write(upload.buf, upload.currentSize); // 边收边写 } else if (upload.status UPLOAD_FILE_END) { currentFile.close(); } }关键点在UPLOAD_FILE_WRITE分支里:WebServer收到数据块时,必须立刻写入LittleFS,不能在RAM里攒着。我之前图省事,先把整个上传内容放在一个String里,一本书800KB,TXT读进内存再写盘,ESP32直接重启。后来改成边收边写,大文件也能稳定上传。上传页面的HTML可以写成字符串内联在固件里,一个小表单,一个文件选择框,一个提交按钮,再加一个已上传书籍的列表,不需要外置HTML文件。4.4 阅读器状态管理与翻页逻辑传书服务的功能解决之后,阅读器本身的体验就不光是能显示了,还要能连续阅读、记住进度。我的状态管理逻辑是这样:在LittleFS下建了一个/books目录存书文件,另建一个/settings.json保存当前正在阅读的书名和页码。翻页时,固件按当前页码去TXT文件里定位行号。TXT是纯文本,每行长度不固定,所以不能直接按偏移量跳到指定行,只能从文件头逐行扫描,遇到换行符就计数。这样效率不高,但这台阅读器的场景是偶尔翻一次页,数十毫秒的扫描耗时完全可接受,换来的是代码简单可靠。翻页逻辑的核心框架如下:void nextPage() { currentPage; renderPage(currentPage); saveReadingProgress(currentBook, currentPage); }renderPage函数负责把当前页的行文本渲染到墨水屏缓冲区,再调用display.display()。如果到了文件末尾,就把页码限制在最大页,不让它继续往后翻。按键防抖我用的是软件延时判断,不引入外部中断库,几十行代码就搞定了。5. 实战中的坑:调试记录与排查方法5.1 屏幕不显示,是不是CS和DC接反了这个项目第一版接线,我栽了个跟头。屏幕刚通电时完全没反应,画布上一片空白,怎么发命令都没响应。按照老套路先量电压,3.3V正常;再试SPI速度,降到1MHz也还是白屏;换了一个屏幕模块,问题依旧。排查到这一步已经可以断定问题不在外设,而在接线逻辑上。我拿起放大镜重新看模块丝印,才发现手里这块2.13寸模块的DC引脚丝印是DC,而ESP32端我接的是GPIO17,代码里定义的也是GPIO17,按说不该有问题。但仔细一量,杜邦线插在面包板上的位置偏了一格,CS和DC实际是交叉接反的。接线看似接对了,实则错位。这个问题的教训是:遇到屏幕没反应,不要急着怀疑代码或硬件损坏,先把所有引脚用万用表蜂鸣档从面板一路通到ESP32引脚,逐一确认。5.2 刷出来是花屏或残影严重有一段时间,屏幕刷新后内容能出来,但总带着上一页的残影,翻几页之后画面就变得很脏。这个问题的根源是局部刷新和全局刷新没有配合好。我一开始贪图刷新速度,全程都用局部刷新,但这种刷新模式的电压波形比全局刷新弱,时间长了黑色粒子清不干净,残影就越积越重。解决思路就是前面提到的混合刷新策略:平时翻页用局部刷新,但每翻满一定页数后强制执行一次全局刷新。全局刷新时屏幕会先整体闪一下,把旧的粒子状态彻底清掉,然后再显示新内容。这个洗屏动作虽然有点晃眼,但能保证画面长期干净。代码里加一个计数器就行,超过10页就display.display(true)强制全刷。另外提醒一句:在墨水屏刷新过程中拔电源,会造成非常严重的残影,以后哪怕正常刷新也洗不干净,遇到这种情况只能多刷几次全局刷新来缓解。5.3 中文乱码与文件太大打不开中文显示的问题前面原理章节提过,这里说说实际排查过程。有一天我往机器里传了几本书,其中一本显示正常,另外几本全是乱码。同样一个渲染代码,为什么有的正常有的乱码?我对比了文件内容,发现正常的是UTF-8编码,乱码的是ANSI编码。TXT文件从哪里来、用什么编辑器保存,直接决定了编码格式。我最终的处理方案是在电脑端用脚本把所有电子书统一转换成UTF-8,然后在固件里做一层兼容:文件读取时如果发现非法UTF-8字节序列,就自动按GBK方式解码再重新编码。这样虽然牺牲了一点性能,但胜在省心,任何来源的TXT都能正常显示。另一个大坑是文件过大导致ESP32重启。我最初把整本书一次性读进内存再排版,遇到几百KB的大部头直接内存溢出。后面改成按行读取、按页缓存,才彻底解决。这个改动看似简单,但涉及文件指针在不同页面间的定位,实现的复杂度上升了一个档次。5.4 大坑预警:供电不足导致Wi-Fi反复重启这个坑我一定要单独拿出来说。系统跑起来后,只要不开启Wi-Fi传书功能,一切正常;一旦浏览器开始上传文件,ESP32就会反复重启。一开始我怀疑是代码bug,打印出重启原因发现是Brownout detector was triggered,也就是电压跌落触发了欠压保护。原因在于USB供电线质量太差,加上墨水屏刷新瞬间电流和Wi-Fi发射电流叠加,把3.3V电压拉到了阈值以下。ESP32的欠压保护比较灵敏,电压稍微掉一点就自动复位。解决办法也很粗暴:换质量好的USB线、单独给墨水屏用一个大电容稳压。我后来直接用18650电池供电,这个问题再也没出现过。所以如果你遇到Wi-Fi一开就重启或者上传文件到一半重启,优先考虑供电问题,别急着查代码。5.5 扩展提示:如果不走Wi-Fi而走网线,LAN8720的三个坑有人会问:局域网传书用Wi-Fi会不会丢包、不稳定?如果对稳定性要求更高,可以给ESP32接一个LAN8720以太网模块走网线。我单独试过这个方案,方向上可行,但踩了三个坑,这里一并记录:第一,PHY地址配置。LAN8720的PHY地址默认由模块上的电阻决定,常见的是0或1,代码里ETH.begin()时如果没设对,网络根本起不来。第二,复位引脚。有些人图省事不连复位引脚,结果是模块偶尔能启动、偶尔完全没反应。接一个GPIO到LAN8720的NRST,并且在ETH.begin()之前先拉低再拉高,问题就消失了。第三,供电不足。LAN8720工作电流比想象中要大,和Wi-Fi一样也会把电压拉低,最好给以太网模块单独一路3.3V稳压,不要和屏幕共用一条细杜邦线。5.6 排查问题速查表现象可能原因解决方法屏幕完全无反应接线错位、SPI引脚定义错误万用表逐根排查,先跑官方例程刷新花屏、残影严重未做全局刷新、刷新中断电混合刷新策略,避免刷新中断电中文乱码编码不统一统一UTF-8,固件做编码兼容大文件打不开/重启内存溢出按行读取、按页渲染Wi-Fi一开就重启供电不足触发欠压保护换供电线、加电容、改用电池上传文件到一半断连边收边写未实现WebServer的Upload回调里即时写盘这张表其实已经覆盖了我做这个项目90%的问题,当你的机器出故障时,按表排查多半能定位到问题。6. 开源代码怎么用:编译、烧录与二次开发思路6.1 仓库结构与核心文件这块项目的代码我开源在GitHub仓库里,为了方便你直接入手,我先梳理一下仓库里的核心文件构成。你看到的代码大概分为几块:主程序入口、墨水屏驱动配置、Web服务器逻辑、阅读器状态管理和文件系统工具。用Arduino IDE打开时,只需要加载根目录下的.ino文件,其余.cpp和.h文件会被自动包含进来。值得留意的文件有三个:config.h是全局配置,里面放着Wi-Fi热点名称、密码、墨水屏引脚定义;web.h里是上传页面和书籍列表页面的HTML模板;reader.h是阅读器核心,负责文本按页分割和翻页状态保存。这几处是你二次开发时最常改动的地方。我在代码里加了比较详细的注释,关键函数前面都有一段说明。6.2 从零开始烧录的完整步骤拿到代码后,按下面这套步骤操作,可以把固件烧进ESP32:安装Arduino IDE,在开发板管理器里安装ESP32支持包。安装依赖库:GxEPD2、U8g2_for_Adafruit_GFX、ArduinoJson(用于保存阅读进度)。打开项目.ino文件,在config.h里改成你自己的屏幕型号和引脚接线。开发板选择ESP32 Dev Module,Flash Size选4MB,Partition Scheme选Huge APP(3MB No OTA/1MB SPIFFS)。按住ESP32开发板上的BOOT键,插入USB线,然后在工具菜单选择对应串口,点击上传。烧录完成后,打开串口监视器,波特率115200,看到IP地址或者AP Mode提示,说明启动成功。烧录过程唯一需要注意的是分区方案。因为我们要存书,文件系统至少要有1MB空间,如果选默认的Default 4MB with spiffs也可以,但应用区会被压缩,代码体积稍大就可能溢出。我最终用的是3MB No OTA/1MB SPIFFS,把OTA升级功能砍掉,换来了更大的APP区。6.3 还能怎么改:四个延伸方向把这套代码跑通之后,你会发现它的扩展潜力比想象中要大。这里说四个我觉得很值得动手的方向。第一个方向是换大屏。把2.13寸换成2.9寸或4.2寸,显示体验会有质的提升。代码里只需要改config.h中的屏幕驱动类和分辨率参数,但要注意大屏刷新时间更长,缓冲区也更大,4.2寸的缓冲区大约6KB,ESP32完全扛得住。第二个方向是加书签和目录功能。目前只能记住阅读进度,想快速跳转到指定章节比较麻烦。可以在settings.json里多存几个书签,翻页界面增加一个书签列表页面,代码量不大,但体验提升很明显。第三个方向是加入笔记或摘录。墨水屏虽然不适合输入,但可以通过按键把当前选中的句子写入到一个notes.txt文件,之后再上传到电脑整理。这个功能本质上是往文件系统里追加文本,顺便加一个UI入口就行。第四个方向是电源管理优化。ESP32内置ADC的线性度不太好,如果直接拿它测电池电压,结果会跳来跳去,不建议用它做精确电量计。更靠谱的做法是外接一个MAX17048电量计芯片,或者用电阻分压加软件校准的方式粗略估算。我实测过内置ADC的分压测量方案,能显示四档电量,但精度确实一般,胜在零成本。代码仓库的具体链接我放在文末评论区,如果你遇到问题,或者改出了更好玩的功能,欢迎来交流。最后分享一个小体会:这个项目一开始我以为难点在墨水屏驱动,实际做下来才发现,真正花时间的是文本排版、文件管理和各种边缘情况——比如大文件、中文编码、断电重启。这些恰恰是嵌入式开发里最考验人的部分。如果你打算复刻,建议按先点亮屏幕、再传文件、最后精修阅读体验的顺序推进,每步都验证通了再做下一步,会少走很多弯路。祝你也早日做出属于自己的那台阅读器。
返回列表