ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ESP32驱动墨水屏实战:GxEPD2库接线、初始化与显示全攻略

2026/9/25 1:30:56 拓冰建站 浏览量
ESP32驱动墨水屏实战:GxEPD2库接线、初始化与显示全攻略 写ESP32驱动墨水屏这件事我前后折腾了差不多一个周末。最开始是看中墨水屏低功耗、阳光下可读的特性想给一个物联网小终端做显示模块结果一上手就被GxEPD2库的类名、初始化参数、引脚映射绕得头晕。等真正跑通之后回头看其实套路很固定只是缺少一篇把接线、库选型、初始化、显示流程全部串起来的文章。这篇就按我实际踩坑的顺序来写从引脚配置到图像显示每一步都给出可以直接抄作业的方案。这个GxEPD2库是目前Arduino生态里驱动墨水屏最主流的方案作者是ZinggJM支持大量常见墨水屏型号包括单色屏和三色屏。它把不同厂商、不同分辨率的屏幕差异封装在统一的接口后面理论上你只需要改一行类名和引脚定义就能切换屏幕。听起来很美好但恰恰是这个“统一接口”在初次使用时会带来不少困惑比如为什么有的屏幕初始化要传两个参数有的要传三个为什么有的能局部刷新有的只能全屏刷新。这些细节属于“不看源码根本不知道”的范畴后面我会逐个拆开讲。我用的是ESP32开发板搭配一块4.2寸三色墨水屏主控是ESP32-WROOM-32模组。选择ESP32的主要原因很简单它支持Wi-Fi和蓝牙方便后续做远程更新和推送而且Arduino环境下的配置非常成熟。如果你手头是ESP8266或者树莓派Pico思路完全一样只是引脚编号和库的底层配置略有差异。先说结论整个项目最难的部分不是写显示代码而是第一次把线接对、把库装好、把屏幕唤醒。这三个环节任何一个出问题表现出来都是白屏或者花屏而且很不好排查。下面我会把每一部分都展开讲包括我试过的错误接线方式以及最终确定的稳定方案。1. 硬件准备与接线ESP32和墨水屏之间到底该怎么连1.1 需要准备的材料清单ESP32开发板一块建议选带USB转串口芯片的版本调试方便。我用的是经典的DEVKIT V138引脚那种某宝上十几块钱的就行。墨水屏模块一块注意区分裸屏和带驱动板的模块。带驱动板的模块通常已经集成了电平转换和稳压电路接线更简单也不容易烧屏。杜邦线若干建议母对母因为ESP32和墨水屏模块的排针通常都是公头。如果墨水屏排线间距和ESP32不匹配可能需要自己焊排针或者用转接板这个看具体模块设计。1.2 SPI通信引脚映射为什么不能随意接GxEPD2库底层走的是SPI通信协议控制墨水屏需要四根信号线加两根控制线。SPI协议本身只要SCK时钟、MOSI主出从入两根线就能单向传输数据但因为墨水屏的数据量比较大还需要一根DC线来区分当前发送的是命令还是数据一根CS线来片选另外还要一根RST线复位屏幕一根BUSY线让屏幕告诉主控“我忙不忙”。ESP32的SPI引脚有硬件映射同一组SPI总线的SCK和MOSI引脚不是随便选的。Arduino环境下如果你用SPI.begin()默认初始化SCK是GPIO18MOSI是GPIO23。这两个引脚在大多数ESP32开发板上都不能改因为硬件SPI外设直接连到了这些引脚。其他控制引脚理论上可以自由分配但有几个注意点不要占用GPIO0、GPIO2、GPIO12等特殊引脚GPIO0是Boot模式选择脚GPIO12是MTDI引脚上电状态会影响板子启动。尽量避开GPIO1和GPIO3这两个是板载串口通信脚调试日志全靠它们。BUSY引脚务必选支持输入模式的引脚所有GPIO都支持但注意不要和板载LED、按键冲突。我最终确定并稳定运行的接线方案如下墨水屏模块引脚ESP32引脚说明VCC3.3V3V3墨水屏模块的电源必须3.3VGNDGND共地DIN / MOSIGPIO23SPI数据线硬件固定CLK / SCKGPIO18SPI时钟线硬件固定CSGPIO5片选软件配置DCGPIO17数据/命令选择软件配置RSTGPIO16复位软件配置BUSYGPIO4忙状态检测软件配置1.3 电源问题为什么墨水屏会闪烁或者显示异常墨水屏刷新时需要短暂的高电流尤其是三色屏在刷红色部分时功耗会突然上升。如果你的供电是直接从ESP32的3V3引脚取的而USB线质量又差很可能会在刷新瞬间出现电压跌落表现出来就是屏幕颜色不均匀或者刷到一半花屏。我一开始用的是一根细长的USB供电线刷屏过程中多次出现红色区域颜色深浅不一的情况。换了一根粗线又给墨水屏加了一个单独的100uF电容之后问题就消失了。如果你手头有万用表可以在刷屏瞬间量一下3V3引脚电压如果低于3.0V基本就是供电问题。注意墨水屏模块的VCC千万不要接5V绝大多数墨水屏驱动板的逻辑电平是3.3V接5V大概率会烧模块。即使你的模块上标注支持5V也仔细看一下丝印说明再决定我见过标注“5V compatible”但实际板载稳压器会把电压压到3.3V的模块这种情况下接5V虽然不会烧但没必要冒险。2. GxEPD2库的安装与选型搞清楚类名才能少走弯路2.1 库的安装Arduino IDE里安装GxEPD2库非常简单打开库管理器搜索“GxEPD2”直接安装最新版即可。仓库地址是https://github.com/ZinggJM/GxEPD2如果库管理器速度慢也可以从GitHub下载ZIP包然后通过“添加.ZIP库”的方式安装。安装完成后建议顺手安装一个依赖库Adafruit GFX它提供了字体和基本绘图函数GxEPD2库的绘图接口风格和Adafruit GFX保持一致很多函数名都一样。2.2 看一眼源码结构从Example开始GxEPD2库自带的示例代码非常多你打开examples目录会看到一大堆文件。最推荐看的是GxEPD2_Example这个示例它涵盖了画点、画线、画圆、显示图片、显示汉字需要自己带字库等绝大多数操作。把示例代码里的类名替换成你的屏幕型号引脚定义改一下基本就能跑通。示例代码里每个屏幕型号都对应一行注释比如GxEPD2_290_BS对应2.9寸单色屏B品不这里的BS是代指型号体系GxEPD2_420_M01对应4.2寸单色屏GxEPD2_420c_Z08对应4.2寸三色屏黑、白、红这里面每个类名末尾的字母组合代表不同的驱动IC型号同样尺寸的屏幕可能因为用了不同的驱动IC而需要不同的类名。这就导致一个常见问题你买的屏幕和示例代码里的型号对不上。解决方法是问卖家要屏幕的驱动IC型号或者看模块背面丝印然后在GxEPD2库的README里搜这个型号。2.3 单色屏与三色屏在库中的使用区别单色屏只有黑白两色刷新速度快支持局部刷新部分型号。三色屏多了一个红色或者黄色刷新速度慢很多通常不支持局部刷新或者说局部刷新效果很差。在GxEPD2库里单色屏对应的类模板参数是GxEPD2_BW三色屏是GxEPD2_3C。初始化时也有区别三色屏需要额外指定一个颜色缓冲区的颜色数参数。比如// 单色屏示例 GxEPD2_290_BS display(GxEPD2_290_BS::EPD_SIZE_290x122, /*CS*/5, /*DC*/17, /*RST*/16, /*BUSY*/4); // 三色屏示例 GxEPD2_420c_Z08 display(GxEPD2_420c_Z08::EPD_SIZE_420x300, /*CS*/5, /*DC*/17, /*RST*/16, /*BUSY*/4);注意实例化的时候第一个参数是屏幕尺寸但它通常不是整数宽高而是一个特定的枚举值。不同的类对应的枚举值名称还不一样有的叫EPD_SIZE_290x122有的叫WIDTH_290这也是用这个库最让人头疼的地方。我的解决方法是直接copy示例代码里同型号的写法只改引脚定义能不动就不动。2.4 为什么ESP32用这个库要比其他平台顺手ESP32的Arduino核心已经内置了SPI库GxEPD2库直接调用SPI对象不需要额外配置。同时ESP32的GPIO数量多3.3V逻辑和墨水屏模块完美匹配不像树莓派Pico那样需要关心3.3V和5V电平转换的问题。另外ESP32的大内存也适合一次性存储整帧图像数据一张4.2寸三色屏的全屏图像差不多要100多KB内存300x420像素每个像素需要2bit来区分黑白红三色Arduino Uno那点内存根本跑不动。3. 代码实现从点亮屏幕到显示自定义图片3.1 初始化流程别急着画图先让屏幕唤醒墨水屏上电后处于睡眠状态需要先激活。GxEPD2库的初始化流程和普通LCD屏幕不太一样它有个init()方法而且这个方法需要传入两个参数display.init(/*直流*/0, /*spi速度*/4000000); // 或者使用带颜色深度的版本 display.init(115200, /*默认为-1*/);第一个参数是串口调试波特率传0表示不输出调试信息传115200会在串口监视器里打印初始化过程。第二个参数是SPI时钟频率默认是4MHz如果你信号线比较长或者杜邦线质量一般可以降到2MHz稳定性会好很多。初始化之后还需要设置显示窗口模式。墨水屏有两种显示模式全窗口和局部窗口。全窗口模式会刷新一整屏局部窗口模式只刷新指定区域。单色屏比如2.9寸的可以局部刷新三色屏建议直接全刷因为三色屏的局部刷新效果确实不行边缘会有明显的残影。// 全帧模式即全窗口模式 display.setFullWindow(); display.setRotation(0); // 0、90、180、270对应四个旋转方向有时候你会看到有人写display.setPartialWindow(0, 0, 100, 100)这样的代码这就是局部刷新模式。第一次用的时候建议先用全帧模式把基础跑通再说。3.2 画图APIGxEPD2和Adafruit GFX的共同语言GxEPD2库继承了Adafruit GFX的绘图接口这意味着你画点、画线、画矩形、画圆、输出文字的代码和在TFT屏幕上写代码几乎一样。下面这段代码能画出基本图形#include GxEPD2_BW.h #include GxEPD2_3C.h #include Fonts/FreeMonoBold9pt7b.h // ... 这里根据你的屏幕选择类名以4.2寸三色屏为例 void setup() { // 初始化串口用于调试 Serial.begin(115200); // 初始化墨水屏DC17, CS5, RST16, BUSY4 display.init(115200, true); // true表示启用fast mode能提升一点速度 display.setFullWindow(); display.setRotation(3); // 清空画面 display.firstPage(); do { display.fillScreen(GxEPD_WHITE); display.drawLine(0, 0, 200, 200, GxEPD_BLACK); display.drawCircle(100, 100, 50, GxEPD_BLACK); display.setFont(FreeMonoBold9pt7b); display.setCursor(20, 30); display.print(Hello E-Paper!); } while (display.nextPage()); // 执行实际的刷新 display.display(); // 进入睡眠模式可选 display.hibernate(); } void loop() { // 墨水屏程序通常在显示完成后就待机不需要反复刷新 }这里面比较关键的是firstPage()和nextPage()的组合。由于墨水屏的显存不像普通RGB屏幕那样直接映射到内存的某个固定区域GxEPD2库采用了一种“页面缓冲”机制在代码块里多次调用绘制函数库底层会把绘制结果分批送到屏幕驱动器中。firstPage()开启一个页面循环do...while(display.nextPage())表示循环执行到所有页面都写入完毕。然后再调用display.display()触发实际的电子墨水翻转。不要漏掉display.display()这一步我一开始漏掉了结果屏幕上一个像素都没变还以为屏幕坏了。3.3 显示图片给位图数据、解码和取模方式墨水屏只能显示位图不能直接显示JPG或PNG文件除非用SD卡模块加解码库。在Arduino这种内存有限的平台上最常用的方式是把图片转换成C语言数组然后直接烧录到Flash里。把图片转换成C数组需要三个步骤第一步准备图片选一张和屏幕分辨率一致的图片比如4.2寸三色屏是300x400像素。如果不是这个尺寸用图像处理软件PS、GIMP甚至Windows画图都行调整大小。图片建议转成灰度模式因为墨水屏没有彩色三色屏也只有黑、白、红。如果你要做三色屏需要把图片分成两层一层是黑白图一层是红色区域图。这个操作听起来麻烦但有一些图像处理工具可以自动做。第二步取模工具Windows上最常用的取模工具是Image2Lcd兼容性很好。设置的几个关键参数输出格式C数组扫描方式水平扫描库内部默认按行存取最大宽度和高度设成和屏幕一致灰度模式单色屏选1位三色屏黑白层选2位或1位红色层单独生成第三步贴代码把生成的数组复制到代码里然后通过drawBitmap()或者drawImage()函数显示。GxEPD2库提供了几种重载最常用的是显示单色位图extern const unsigned char my_image[]; // 在setup中使用 display.setFullWindow(); display.firstPage(); do { display.drawPartOfBitmap(my_image, 0, 0, 300, 400); } while (display.nextPage()); display.display();如果你用的是三色屏需要分别显示两层。先显示黑白层再显示红色层或者利用库自带的GxEPD2_3C接口做两层叠加。实际上GxEPD2_3C的drawImage方法支持传入2bit的颜色索引数组每个字节代表两个像素但手动构造这个数组比较麻烦我建议直接用Image2Lcd生成黑白层加红色层的两个数组分两次显示。3.4 局部刷新和快速刷新什么时候能用什么时候不能用墨水屏的物理特性决定了它不能像手机屏幕那样60帧刷新。电子墨水颗粒的翻转需要时间翻转太快会留下残影或者显示不均匀。GxEPD2库初始化时有个init(115200, true)的第二个参数传true会启用Fast mode。实测下来4.2寸三色屏在全刷模式下从display.display()到画面稳定大约需要8到10秒Fast mode能把时间缩短到6到7秒但残影会更明显。对大多数展示型应用来说这个速度完全可以接受毕竟墨水屏的核心卖点不是快而是静态显示零功耗。部分单色屏支持局部刷新GxEPD2库的示例代码里有一个专门的例子GxEPD2_492_WiFi_Weather展示了局部刷新的用法。局部刷新的核心逻辑是先初始化局部窗口然后只更新窗口内的内容。用这种方式可以实现电子标签上的数字、时间刷新刷新速度大约能到1秒以内。但我要提醒一下频繁局部刷新会加速墨水屏的老化而且局部刷新后会在窗口边缘留下淡淡的分界线。我的建议是能全刷就全刷真需要局部刷新至少每十次局部刷新之后做一次全刷来“洗屏”。4. 进阶玩法怎么把这张屏真正用起来4.1 接入Wi-Fi显示天气信息墨水屏最经典的应用场景就是天气日历。ESP32连接Wi-Fi获取天气API的数据然后解析出温度、天气状况绘制到墨水屏上。你需要用到的库包括WiFi库、HTTPClient库、ArduinoJson库用于解析JSON数据。核心逻辑非常简单#include WiFi.h #include HTTPClient.h #include ArduinoJson.h void fetchWeather() { HTTPClient http; http.begin(http://api.openweathermap.org/data/2.5/weather?qBeijingappidYOUR_API_KEY); int httpCode http.GET(); if (httpCode 200) { String payload http.getString(); DynamicJsonDocument doc(1024); deserializeJson(doc, payload); float temp doc[main][temp]; int weatherCode doc[weather][0][id]; // ... 把temp和weatherCode绘制到墨水屏 } http.end(); }项目跑起来之后你只需要定时唤醒ESP32获取数据刷新屏幕然后再次进入深睡眠模式。整套系统的待机功耗可以控制在微安级别一节18650电池用半年不是问题。4.2 加入掉电保持墨水屏断电后依旧显示这也是墨水屏最吸引人的特性断电后屏幕内容不会消失。这意味着你可以在设备断电的情况下把屏幕当做一个静态标签使用做桌面摆件、门牌、露营信息牌什么的都很合适。代码里有个细节值得注意刷新完成后最好调用display.hibernate()让屏幕进入睡眠状态这样可以降低功耗。如果你不需要再次刷新直接断电也没关系画面会保留在屏幕上。4.3 让ESP32定期自动刷新如果你要做一个长时间运行的信息屏可以结合ESP32的定时唤醒功能。ESP32的深睡眠模式可以用定时器唤醒醒过来后刷新屏幕然后继续睡。Arduino代码可以使用esp_sleep_enable_timer_wakeup()来实现// 设置定时唤醒单位是微秒1小时 3600 * 1000000 esp_sleep_enable_timer_wakeup(3600 * 1000000); esp_deep_sleep_start();这样整机的运行逻辑就是一个“刷新完就睡”的循环。墨水屏平时耗电几乎为零只有刷新的那几秒钟功耗才会上升很适合做电池供电的长期显示设备。5. 常见问题速查表白屏、花屏、刷新异常都怎么处理我把自己在调试中遇到的问题和最终解决办法整理成了一张表方便你对照排查。现象可能原因解决办法屏幕完全白屏无任何反应CS/DC/RST/BUSY引脚映射错误检查引脚定义确保和接线一致对照本文的接线表一一核对屏幕有闪烁但无内容SPI时钟频率太高信号线过长把init里的SPI速度降到2MHz或1MHz显示花屏有乱码类名选错屏幕驱动IC不匹配核对屏幕丝印查找对应的GxEPD2类名颜色不对红色区域变成黑色三色屏的第二层刷新顺序错误确认先刷黑白层再刷红色层或者检查drawImage的数据格式刷新速度过慢没用Fast mode或者屏幕本身响应慢init(115200, true)启用快速模式字体乱码、文字为方框字体数据未正确嵌入或者编码问题确保调用setFont()并检查文本编码是否为UTF-8刷新过程中重启供电不足换粗USB线给墨水屏加电容避免用劣质充电宝供电局部刷新后残影严重墨水屏本身限制定期全屏刷新洗屏或改用全屏刷新模式5.1 一个容易忽略的坑SPI通讯速度ESP32的SPI最快能跑到几十MHz但墨水屏模块的Flash和驱动IC不支持那么快。GxEPD2库默认的4MHz对大部分屏是安全的但如果你用的是长杜邦线超过15cm或者线材质量差建议降到2MHz。降速的操作非常简单把初始化代码里的display.init(115200, 4000000)的第二个参数改成2000000。这个代价是刷新时间会稍微变长但对稳定性是质的提升。5.2 一个容易被误导的坑三色屏不等于三个颜色通道三色屏的黑白红三色是物理上的三种粒子不是像电脑屏幕那样通过像素点混色。所以三色屏显示一张彩色照片是做不到的它只能把图片近似映射成三种颜色。你在取模时必须明确这一点否则做出来的图片颜色会很奇怪。5.3 复位引脚电阻的注意事项如果你使用的模块比较特殊RST引脚直接连到了主控GPIO而模块内部没有上拉或者下拉电阻可能出现上电瞬间屏幕随机刷新一下的情况。这是正常的不是代码问题。如果介意可以在RST引脚和GND之间接一个10kOhm下拉电阻保证上电时RST保持低电平。5.4 上传项目出错常见环境问题梳理Arduino IDE在使用ESP32开发板时偶尔会遇到上传出错的问题常见情况有三种端口不识别检查设备管理器里是否出现COM端口。未识别的话多半是USB转串口芯片驱动没装ESP32常用的CP2102和CH340驱动先装好。编译报错“Google Translate... not declared”一般是开发板库版本问题把ESP32开发板包升级或降级到稳定版本。上传时报A fatal error occurred: Failed to connect to ESP32: Timed out waiting for packet header这是因为ESP32进入不了下载模式。按住板子上的BOOT键在上传开始的那一刻松开就能解决。如果你的板子没有BOOT键手动把GPIO0接地再上电也可以进入下载模式。5.5 实测技巧怎么判断屏幕是否已经唤醒在没有示波器的情况下判断屏幕状态最简单的办法是用代码里的display.getScreenState()方法部分版本支持。或者更简单的看刷新时电流表上的读数变化。如果你有万用表串5欧姆电阻在电源线上测量电阻两端的电压刷新时电压会有一个明显的波动这可以辅助判断屏幕是否在工作。6. 我的一些个人经验GxEPD2库这套组合玩到后面你会发现墨水屏其实是个很“慢”的硬件但它的“慢”恰恰造就了它的独特价值。它不适合显示动态内容却很适合做信息密度高、更新频率低的静态展示工具。如果你准备做一个项目我的第一个建议是先接线再写代码。用最小代码量——一个Hello World文本——把屏幕点亮确认硬件链路通畅再去碰图片显示、网络请求这些复杂功能。不要一开始就直接上完整功能否则出了白屏你也分不清是硬件问题还是软件问题。第二个建议是多看示例代码少看博客。GxEPD2库的Example是我见过做得比较详尽的每个屏幕型号都配了对应的实例化代码照着改引脚就行。第三个建议是用Postman或者浏览器调试好天气API确认返回的JSON结构符合预期后再写Arduino解析代码。我一开始直接写代码调API来回编译写了三四次才成功效率很低。先用电脑把接口调通再搬进Arduino能省不少时间。最后说一个我踩过的小坑4.2寸三色屏刷新的时候如果你用手碰屏幕边缘的排线显示颜色可能会出现偏差。这是屏幕驱动电路非常敏感导致的不是硬件坏了。遇到这种情况断电等几秒重新刷新一般就恢复了。希望这篇记录能帮你少走些弯路。墨水屏项目看着神秘实际跑通了之后你会发现核心就这三件事把线接对把型号选对把刷新方式搞明白。做到这三点剩下的内容都是锦上添花。