ESP32+墨水屏驱动避坑记录

简介

这里没有教科书式枯燥的“背景”或“定义”,如果需要那些,搜索引擎会给你更好的答案。我只是一个对硬件感兴趣的门外汉,在这里记录一下我的“爬坑”经历,以及那些我觉得值得被留下的瞬间。

故事的起因是一个说大不大、说小不小的"安全隐患"。我当时把一部退役的旧手机插着电,强行充当桌面时钟。结果电池很快鼓包了,像个随时会炸的地雷;再加上 LCD 屏幕在深夜里的漏光实在刺眼,体验极差。

恰巧我瞥见了桌角的 Kindle——那种几周不用充电、低功耗且护眼的质感,让我一下子就想明白了:墨水屏,才是桌面时钟的最佳选择。不仅如此,它还可以显示更多自定义的内容。在 B 站转了一圈后,发现这早已是神仙打架的成熟领域,各种开源项目、现成方案应有尽有。

以我的性格,能自己动手折腾的,绝不买现成的。于是我筹划着买回原材料自己拼装。一来是追求绝对的自定义自由,二来也想以此为切口,正式敲开单片机世界的大门。

至于为什么强调是“去年年初”开始的……这个后文再说(如果我没忘的话😝)。这些不重要的细节,也不必纠结。

声明:本文记录的是我个人使用 4.2 寸墨水屏的经验,其他尺寸或型号的屏幕我没测试过,仅供参考。

正文

以下内容是我对单片机的一些基础知识的总结,所查学习的资料均来源于互联网,并基于我自己粗浅的理解。如果有错误的地方,欢迎指正。 目前我的项目,还处于早期测试阶段,还没有连接电池,所以电池电压采样引脚暂时没有提及。之后会在项目完善后,再添加相关内容。

技术栈速查

这一节是给自己和后来人留的"备忘录",列出了这个项目涉及的核心概念。如果你也是新手,可以当成一份快速索引。

单片机的基础逻辑

  • GPIO(开关):硬件只有 0 和 1,高电平和低电平。
  • SPI (墨水屏的方言)
    • CS (片选): 告诉某个屏幕“我要对你说话了”。
    • BUSY (忙状态): 屏幕在刷新时会变“忙”,代码必须等它刷完。
  • ADC (电力感知): 通过 GPIO 34 检测电压。这不只是为了显示电量,更是为了在电压过低时强制休眠,保护电池。

图形处理

  • 像素与坐标:从左上角(0,0)开始,向右是 X 轴递增,向下是 Y 轴递增。
    • 4.2寸屏,分辨率是400*300。每个像素都有自己的坐标,例如(100,200)表示距离左边缘100个单位,距离上边缘200个单位的位置。
  • 点阵字体:这里的核心在于精简。ESP32 空间有限,通常只提取常用汉字生成 .u8g2 字库,而不是塞一整套完整字体。

网络&数据

  • Wi-Fi 连接管理:让 ESP32 自动重连家里路由器的代码模板。
  • API 调用与 JSON 解析
    • HTTP 请求:去抓取天气、B站、或者个人网站/Blog的后台抓取数据。
    • 数据清洗:从一大串乱码中精准抠出“温度:20°C”或“粉丝:5000”。

低功耗设计

  • 低功耗睡眠 (Deep Sleep):让单片机刷完屏后进入“休眠”,这样几节电池就能让它跑几个月。

开发环境

  • Arduino IDE 是目前对新手最友好的选择,自带丰富的库和示例代码,支持 ESP32 和 ESP8266 开发。但是没有办法使用AI辅助开发,这也导致了新手在使用Arduino IDE时,会遇到很多问题。

Esp-32/8266

刚开始我也分不清这俩,干脆在闲鱼上两个都买了回来。简单来说,ESP32 性能更强、功能更多(双核、蓝牙、更多 GPIO),ESP8266 更便宜、更省电。 对于墨水屏时钟这种应用,用ESP32更好,而且后续扩展空间更大。

  • 在 Arduino IDE 中
    1. 打开 工具开发板开发板管理器
    2. 搜索 esp32,安装官方库
    3. 选择对应的开发板型号即可

转接板

转接板

市面上有很多不同版本的转接板,但是功能几乎相同。需要将墨水屏的引脚(24Pin是常见的屏幕引脚)连接到转接板的24Pin上。再将转接板的引脚连接到Esp-32的引脚。每个转接板的引脚标注可能都不太一样,但只是名称不同,其功能是相同的,具体引脚的链接,可以参考下方引脚的表格

墨水屏

单IC vs 双IC:一个让我放弃一年的坑

最开始,我没有注意到屏幕 IC 的问题——就是墨水屏背面那条黑色的小芯片。

24年初买回来的第一块屏幕,兴冲冲地接好线、上传代码,结果屏幕死活不亮。翻遍了 GitHub 、试了无数个示例代码,甚至怀疑过是转接板坏了、ESP32 坏了、排线接触不良……折腾了几个星期,最后彻底放弃,把整套东西扔进了抽屉。

这一放,就是一年

直到最近打算做一些新的硬件项目,重新翻出这堆东西,才在某个 B 站视频的弹幕里看到有人提了一嘴"双 IC 要用专门的库"。我把屏幕翻出来一看——果然是双 IC。换了某位大神魔改的 <GxEPD2_2IC_BW.h> 库——秒亮。

那一刻的心情,大概就是"我 TM 白白浪费了一年"。

单IC & 双IC

所以,如果你用单 IC 的库去驱动双 IC 屏幕,大概率不工作,甚至完全白屏。反之亦然。

重要提醒:单 IC 双色和单 IC 三色虽然都是单 IC,但用的库也不一样!

血泪教训 双 IC 屏幕如果需要飞线,一定要问清楚商家或找准位置,接错会直接烧板子(我已经烧了两块了😭)。

库 GxEPD2:目前最靠谱的库

GxEPD2 是目前对墨水屏支持最完善的 Arduino 库,工程化程度高,文档也齐全。

根据屏幕类型选择对应的头文件:

#include <GxEPD2_BW.h> // 单IC 双色屏幕(黑白)
#include <GxEPD2_3C.h> // 单IC 三色屏幕(黑白红/黑白黄)
#include <GxEPD2_2IC.h> // 双IC 双色屏幕(黑白)

GitHub 仓库:ZinggJM/GxEPD2

引脚与电阻选型

4.2 寸屏幕面积大,驱动电流需求也大。如果用 3Ω 电阻,内阻太大会导致屏幕可能刷不出来或者显示极淡。

推荐配置

  • 3.7 寸及以下:3Ω
  • 4.2 寸及以上:0.47Ω

常用接线方案

转接板标识 ESP32 引脚 功能说明 备注
VCC 3.3V 电源 可接 3.3V 或 5V
GND GND 接地 -
DIN (MOSI/SDI) GPIO 11 SPI 数据引脚 主机输出,从机输入
CLK (SCK/SCL) GPIO 12 SPI 时钟引脚 同步时钟信号
CS GPIO 10 片选引脚 低电平有效
DC GPIO 9 数据/命令控制 高=数据,低=命令
RST (RES) GPIO 14 复位引脚 低电平复位
BUSY GPIO 13 忙状态输出 低电平表示屏幕正在刷新
ADC GPIO 34 电池电压检测 用于低电量保护

引脚尽量避开 26-32 号引脚,因为它们通常用于其他功能(如 SPI 或 I2C)。

如果板子上有RST引脚,千万不要直接插,它和墨水屏代码里的 RST 是两码事。屏幕代码里的 RST:它是屏幕驱动芯片(控制墨水颗粒那个小芯片)的“清空键”。

需要避开的引脚

  • GPIO 0:启动模式切换引脚,上电时状态会影响启动
  • GPIO 2:通常接板载 LED,启动时不稳定
  • GPIO 12/15:某些模组用这两个引脚决定启动电压,慎用

测试代码

下面是我在 Arduino IDE 中使用的代码模板,用于驱动 4.2 寸双色墨水屏(GDEY042T81)。它不只是静态显示,而是加入了一些“随机性”——就像生活一样,虽然规则确定,但每一刻都独一无二。

注意❗️ Arduino 默认会把上传速率设为921600。如果你的程序上传中报错,尝试把上传速率设为115200(在 工具 -> upload speed 中选择)。

#include <GxEPD2_BW.h>
#include <U8g2_for_Adafruit_GFX.h>

// 引脚定义:CS(10), DC(9), RST(14), BUSY(13)
GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> display(
    GxEPD2_420_GDEY042T81(10, 9, 14, 13));

U8G2_FOR_ADAFRUIT_GFX u8g2Fonts;

// 删掉物理连线,直接给个满电数值
float batteryVoltage = 4.2;


void setup() {
  Serial.begin(115200);
  // 降低波特率至 115200 烧录更稳
  display.init(115200, true, 50, false);
  u8g2Fonts.begin(display);

  randomSeed(analogRead(0) + micros());

  generateTreasureArt();
}
void generateTreasureArt() {
  display.setRotation(0);
  display.setFullWindow(); // 满屏刷新,防止残影
  display.firstPage();

  do {
    display.fillScreen(GxEPD_WHITE);
    for(int i=0; i<100; i++) {
      int x = random(0, 400);
      int y = random(0, 300);
      display.drawPixel(x, y, GxEPD_BLACK); // 随机噪点
      if(i % 5 == 0) {
          display.drawLine(x, y, x + random(-20, 20), y + random(-20, 20), GxEPD_BLACK);
      }
    }
    for(int i=0; i<5; i++) {
      int x = random(0, 300);
      int y = random(0, 200);
      int r = random(20, 60);
      display.drawCircle(x, y, r, GxEPD_BLACK);
      display.drawCircle(x, y, r-2, GxEPD_BLACK);
      if(random(0, 2) > 0) display.fillCircle(x+10, y+10, r/3, GxEPD_BLACK);
    }

    display.fillRoundRect(80, 100, 220, 100, 12, GxEPD_BLACK);
    display.drawRoundRect(80, 100, 220, 100, 12, GxEPD_WHITE);

    u8g2Fonts.setForegroundColor(GxEPD_WHITE);
    u8g2Fonts.setBackgroundColor(GxEPD_BLACK);
    u8g2Fonts.setFont(u8g2_font_wqy16_t_gb2312);
    u8g2Fonts.drawUTF8(135, 145, "仝 遥 的 宝 藏");

    u8g2Fonts.setFont(u8g2_font_helvB12_tf);
    u8g2Fonts.drawUTF8(148, 170, "Troves.me");

    u8g2Fonts.setForegroundColor(GxEPD_BLACK);
        u8g2Fonts.setBackgroundColor(GxEPD_WHITE);
    u8g2Fonts.setFont(u8g2_font_6x10_tf);
    String meta = "RANDOM_GEN: " + String(millis()) + " | ADDR: 0x" + String(random(0xFFFF), HEX);
    u8g2Fonts.drawUTF8(80, 290, meta.c_str());

  } while (display.nextPage());
  display.hibernate(); // 必须休眠!否则屏幕寿命会骤减
}

void loop() {}

加载库

#include <GxEPD2_BW.h> // 单IC 双色屏幕
#include <GxEPD2_3C.h> // 单IC 三色屏幕

定义屏幕对象

GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> display(GxEPD2_420_GDEY042T81(/*CS=*/ 10, /*DC=*/ 9, /*RST=*/ 14, /*BUSY=*/ 13));

初始化屏幕

display.init(115200);

完整代码

#include <GxEPD2_BW.h>
#include <GxEPD2_3C.h>

GxEPD2_BW<GxEPD2_420_GDEY042T81, GxEPD2_420_GDEY042T81::HEIGHT> display(GxEPD2_420_GDEY042T81(/*CS=*/ 10, /*DC=*/ 9, /*RST=*/ 14, /*BUSY=*/ 13));

void setup() {
  display.init(115200);
}

void loop() {
  display.fillScreen(GxEPD_WHITE);
  display.setCursor(0, 0);
  display.setTextColor(GxEPD_BLACK);
  display.setTextSize(2);
  display.println("Hello, World!");
  display.update();
  delay(2000);
}

墨水屏使用禁忌

这些都是容易踩的坑,有些甚至会直接损坏屏幕:

  • 局刷不能滥用 支持局刷的屏幕,不能一直用局刷。建议每做 5-10 次局刷后,进行一次全刷清屏,否则会出现残影和鬼影。

  • 不刷新时必须休眠 屏幕不刷新时,必须调用 display.hibernate() 进入睡眠,或者直接断电。长时间保持高电压会损坏电泳膜片,且无法修复。

  • 刷新频率限制

    • 建议刷新间隔 ≥ 180 秒
    • 至少每 24 小时刷新一次
    • 长期不用时,刷白后存放(参考数据手册的储存条件)
  • 睡眠后需重新初始化 屏幕进入睡眠模式后会忽略所有数据指令,必须重新调用 display.init() 才能刷新。

  • FPC 排线很脆弱 只能沿屏幕水平方向弯曲,绝对不能垂直弯曲,否则会断线。

  • 屏幕本体怕摔怕压 墨水屏比普通 LCD 脆弱得多,避免跌落、碰撞、按压。

我走过的弯路(也许你能避开)

回顾这一年多的折腾,有些坑真的让人哭笑不得:

  • 最大的坑:单IC vs 双IC 去年买的第一块屏幕,因为不知道双 IC 需要专门的库,整整一年没点亮。今年重新捡起来,换了个库,秒亮。

  • 烧了两块板子 双 IC 屏幕需要飞线时,我没问清楚位置,直接按照单 IC 的接法来,结果两块转接板直接报废。这个学费交得有点贵。

  • 上传失败的循环 Arduino IDE 默认波特率是 921600,我的 USB 线不太稳定,一开始以为是代码有问题,后来才发现降到 115200 就好了。

  • 屏幕越刷越淡 刚开始不知道 4.2 寸屏要用 0.47Ω 电阻,用的 3Ω,屏幕能刷新但特别淡,还以为是屏幕质量问题。

这些坑,希望你能绕过去。

特别鸣谢/参考来源