显示屏与桌面作品 / 显示与总线实践发布 内容更新

ESP32 墨水屏第一次刷新:选对控制器和驱动类

从GDEY0213B74面板号锁到SSD1680与GxEPD2具体类,完成整屏图案、BUSY异常和断电保持三类可执行检查;明确void超时与光学成功的区别。

读完能做什么
得到不自动刷新的完整墨水屏例程,按面板号复建,并用原日志和照片分开记录BUSY状态、光学刷新与断电保持。
开始之前
已完成前置教程,能核对GPIO、串口、模块供电并复建固定框架示例。
所需器材
官方 ESP32-S3-DevKitC-1 v1.1 / ESP32-S3-WROOM-1-N8R8,8MB Flash、Octal PSRAM;USB-to-UART 桥接串口115200。、Good Display GDEY0213B74黑白面板(SSD1680Z,122×250可见)、DESPI-C02转接板,P3标2.2Ω/0.47Ω并选2.2Ω、10kΩ片选上拉、BUSY侧10kΩ上拉、可选SPST与短线
版本组合
参考板:官方 ESP32-S3-DevKitC-1 v1.1 / ESP32-S3-WROOM-1-N8R8,8MB Flash、Octal PSRAM;USB-to-UART 桥接串口115200。工具/框架:Arduino CLI1.5.1 / Arduino-ESP32 3.3.12 / GxEPD2 1.6.9驱动/图形:GxEPD2 1.6.9 / Adafruit GFX1.12.4 / BusIO1.17.4面板/转接:GDEY0213B74 Rev1.0 2021-03-17资料 / DESPI-C02 2.2Ω选项板
适用范围

官方 ESP32-S3-DevKitC-1 v1.1 / ESP32-S3-WROOM-1-N8R8,8MB Flash、Octal PSRAM;USB-to-UART 桥接串口115200。GxEPD2_213_GDEY0213B74,GFX1.12.4/BusIO1.17.4;SPI4MHz;未实板。

这篇验证到了哪一步

资料核对已通过
docs/esp32-evidence/displays-expanded.md#d04版本:Arduino CLI1.5.1 / Arduino-ESP32 3.3.12 / GxEPD2 1.6.9验证日期:
构建 / 配置已通过
docs/esp32-evidence/d04-build.log对象:esp32s3 / N8R8版本:Arduino CLI1.5.1 / Arduino-ESP32 3.3.12 / GxEPD2 1.6.9验证日期:
仿真已通过
docs/esp32-evidence/displays-host-checks.json#d04对象:同源纯逻辑host fixture;不模拟电气/控制器验证日期:
本站实板待验证
未取得实板;观测记录保持空白,未验证光学、总线或供电。

构建通过只说明指定环境中的代码或配置通过检查;实物功能、功耗和稳定性以对应的实板记录为准。

01 / 显示与总线实践

先读FPC型号,尺寸相同也不能套驱动

本篇锁Good Display GDEY0213B74黑白面板,控制器SSD1680Z,可见122×250;不带前光或触摸后缀。只知道“2.13寸”“V4”不能证明它就是这个面板,GDEH0213B73或三色屏也不能用本篇类试刷。

裸面板不能接几根GPIO就驱动,需要DESPI-C02上的升压外围。选择P3明确标2.2Ω/0.47Ω的当前型号并断电选2.2Ω,与面板手册p29参考电路匹配;旧DESPI-C02 Rev1.1文档列3Ω选项,不能把旧板丝印假定成当前2.2Ω。本任务不教带电更换FPC。

做到这里,你应该看到
把面板标签、转接板正反面与P3照片保存;身份不符就先停在核对步骤。

参考:[1] Good Display GDEY0213B74:面板/SSD1680Z/122×250[2] 厂家面板Rev1.0 2021-03-17 PDF(Seeed托管)[3] DESPI-C02当前2.2Ω/0.47Ω板、电压与接口[4] DESPI-C02旧Rev1.1文档索引:3Ω旧修订须区分

02 / 显示与总线实践

类名锁到面板,区分可见宽与RAM宽

使用GxEPD2 1.6.9的GxEPD2_213_GDEY0213B74;header定义WIDTH=128、WIDTH_VISIBLE=122、HEIGHT=250。完整1bpp缓冲是128×250/8=4000字节,而GxEPD2_BW的可见画布采用122宽,rotation1得到250×122。

setFullWindow/firstPage/nextPage走全窗口刷新;该固定类启用useFastFullUpdate的全窗口波形,本篇没有关闭这个fast全更设置,也没有把它称作局部刷新。首次写入可能先清屏、出现多次闪动,函数一次调用不等于肉眼只闪一次。

参考:[5] GxEPD2 1.6.9面板类:宽度、BUSY高与10s等待[6] GxEPD2 1.6.9面板具体刷新、hibernate实现[8] GxEPD2 1.6.9:BW画布与分页

03 / 显示与总线实践

只用3.3V,BUSY始终是输入

SCK12/J1-18、SDI11/J1-17、CS5/J1-5、D/C4/J1-4、RES6/J1-6,3V3/J1-1接转接板3.3V、GND/J1-22共地。CS加10kΩ至3V3;无MISO。SPI mode0、MSB先行、4MHz,DESPI-C02用10ms低有效复位。

模块BUSY经预装SPST接GPIO10/J1-16,在MCU侧另加10kΩ到3V3:正常闭合,高表示忙,断开后MCU端被拉高。GPIO10只输入,不能用输出硬拉模块BUSY。所有FPC/供电接线与P3切换均断电完成;没有开关可直连BUSY并跳过注入任务。

参考:[2] 厂家面板Rev1.0 2021-03-17 PDF(Seeed托管)[3] DESPI-C02当前2.2Ω/0.47Ω板、电压与接口[7] GxEPD2 1.6.9:_waitWhileBusy/回调/selectSPI[10] DevKitC-1 v1.1排针

04 / 显示与总线实践

完整依赖锁与固定构建命令

Arduino CLI1.5.1/core3.3.12、GxEPD2 1.6.9、Adafruit GFX1.12.4、BusIO1.17.4。下载的sketch.yaml仅定义profile,没有default_profile;正常复用已装全局工具,按完整FQBN编译。首次安装先按README固定每个库版本,GxEPD2用--no-deps避免自动漂移依赖。

IDE对应8MB Flash、OPI PSRAM、QIO、8M with spiffs、USB CDC On Boot Disabled、Hardware CDC and JTAG;连接USB-to-UART桥接口,115200串口。程序先epdSPI.begin显式指定引脚,再selectSPI;固定core里已初始化的SPI begin直接返回,不会被库的无参begin换到默认引脚。

作者实际编译Exit0,程序346380字节、全局变量28248字节;这些是构建工具报告,不是整机功耗或实板运行结果。

代码 / 配置
arduino-cli compile --fqbn 'esp32:esp32:esp32s3:FlashSize=8M,PSRAM=opi,FlashMode=qio,PartitionScheme=default_8MB,CDCOnBoot=default,USBMode=hwcdc' --build-path /tmp/d04-build .
arduino-cli upload --fqbn 'esp32:esp32:esp32s3:FlashSize=8M,PSRAM=opi,FlashMode=qio,PartitionScheme=default_8MB,CDCOnBoot=default,USBMode=hwcdc' -p /dev/cu.YOUR_UART_PORT --input-dir /tmp/d04-build

参考:[7] GxEPD2 1.6.9:_waitWhileBusy/回调/selectSPI[9] Arduino-ESP32 3.3.12 SPI begin实现

05 / 显示与总线实践

完整程序:手动刷新、观察预算与停止标志

启动只打印ready,不自动刷新。发送r才尝试,先检查BUSY低;同次上电两次尝试至少180秒,这是本例保守教学限制,不是本面板寿命或最小刷新时间实测。原样helper有单独host fixture,覆盖时间回绕和锁存。

代码 / 配置
// ---- BusyBudget.h ----
// SPDX-License-Identifier: MIT
#pragma once
#include <stdint.h>
struct BusyBudget {
 static constexpr uint32_t limitMs=8000;
 uint32_t started=0;bool active=false;bool fault=false;
 void begin(uint32_t now){started=now;active=true;fault=false;}
 void observe(uint32_t now,bool busy){if(active&&busy&&uint32_t(now-started)>=limitMs)fault=true;}
 bool finish(uint32_t now,bool busy){observe(now,busy);active=false;if(busy)fault=true;return !fault;}
};
inline bool refresh_due(uint32_t now,uint32_t last,bool attempted){return !attempted||uint32_t(now-last)>=180000;}
// ---- esp32-epaper-first-refresh.ino ----
// SPDX-License-Identifier: MIT
#include <Arduino.h>
#include <SPI.h>
#include <GxEPD2_BW.h>
#include <gdey/GxEPD2_213_GDEY0213B74.h>
#include "BusyBudget.h"
// Good Display GDEY0213B74 + DESPI-C02 (2.2/0.47 ohm variant, P3=2.2 ohm).
constexpr int PIN_SCK=12,PIN_MOSI=11,PIN_CS=5,PIN_DC=4,PIN_RST=6,PIN_BUSY=10;
SPIClass epdSPI(FSPI);
GxEPD2_BW<GxEPD2_213_GDEY0213B74,GxEPD2_213_GDEY0213B74::HEIGHT>
 display(GxEPD2_213_GDEY0213B74(PIN_CS,PIN_DC,PIN_RST,PIN_BUSY));
BusyBudget budget;
bool attempted=false,halted=false;
uint32_t lastAttempt=0,sequence=0;
void busyCallback(const void*){
 budget.observe(millis(),digitalRead(PIN_BUSY)==HIGH);
 delay(1); // callback only observes; GxEPD2 owns its wait loop and timeout.
}
bool preflight(){
 uint32_t start=millis();
 while(digitalRead(PIN_BUSY)==HIGH&&uint32_t(millis()-start)<2000)delay(1);
 return digitalRead(PIN_BUSY)==LOW;
}
void fullRefresh(){
 if(halted){Serial.println("HALTED: power off, repair, then restart");return;}
 if(!refresh_due(millis(),lastAttempt,attempted)){Serial.println("WAIT: 180 s between attempts");return;}
 if(!preflight()){halted=true;Serial.println("STOP: BUSY high before call; no display transaction started");return;}
 attempted=true;lastAttempt=millis();++sequence;
 budget.begin(millis());
 // Reset pulse 10 ms for DESPI-C02. Re-init after hibernate; initial=true.
 display.init(115200,true,10,false);
 display.setRotation(1); // visible canvas 250 x 122; controller RAM 128 x 250.
 display.setFullWindow();
 Serial.printf("BEGIN full-window sequence=%lu canvas=%dx%d\n",(unsigned long)sequence,display.width(),display.height());
 display.firstPage();
 do{
  display.fillScreen(GxEPD_WHITE);
  display.drawRect(0,0,display.width(),display.height(),GxEPD_BLACK);
  display.fillRect(8,8,30,30,GxEPD_BLACK);
  display.drawRect(display.width()-38,8,30,30,GxEPD_BLACK);
  display.setTextColor(GxEPD_BLACK);display.setTextSize(1);
  display.setCursor(48,14);display.print("GDEY0213B74");
  display.setCursor(48,29);display.print("SSD1680 / FULL WINDOW");
  display.setCursor(8,53);display.print("BLACK  WHITE  BORDER");
  display.setCursor(8,70);display.print("SEQ ");display.print(sequence);
  display.setCursor(8,88);display.print("CALL RETURN != OPTICAL PASS");
  display.setCursor(8,105);display.print("250 x 122 / ROTATION 1");
 }while(display.nextPage());
 if(!budget.finish(millis(),digitalRead(PIN_BUSY)==HIGH)){
  halted=true;
  Serial.println("STOP: busy budget/level fault; driver call has returned; no hibernate or further display calls");
  return;
 }
 Serial.println("CALL_RETURNED_BUSY_LOW: inspect screen and record; no optical verification");
 budget.begin(millis());display.hibernate();
 if(!budget.finish(millis(),digitalRead(PIN_BUSY)==HIGH)){
  halted=true;Serial.println("STOP: hibernate call returned with fault; power off and repair");return;
 }
 Serial.println("HIBERNATE_CALL_RETURNED: MCU and board still powered; no current measurement");
}
void setup(){
 Serial.begin(115200);delay(300);
 pinMode(PIN_BUSY,INPUT); // external 10k pull-up on MCU side of optional BUSY SPST.
 digitalWrite(PIN_CS,HIGH);pinMode(PIN_CS,OUTPUT);
 if(!epdSPI.begin(PIN_SCK,-1,PIN_MOSI,PIN_CS)){halted=true;Serial.println("STOP: SPI begin failed");return;}
 display.epd2.selectSPI(epdSPI,SPISettings(4000000,MSBFIRST,SPI_MODE0));
 display.epd2.setBusyCallback(busyCallback);
 Serial.println("D04 ready; verify panel ID, then send r for full-window refresh. No auto refresh.");
}
void loop(){
 if(Serial.available()){char c=Serial.read();if(c=='r'||c=='R')fullRefresh();}
 delay(1);
}
06 / 显示与总线实践

先看完整图案,再做断电保持任务

正常调用返回且BUSY低打印CALL_RETURNED_BUSY_LOW,程序立即调用hibernate,不等待人工确认。HIBERNATE_CALL_RETURNED之后用肉眼另验黑块、白底空心块、文字与四边并拍照。这两条日志都只是API与BUSY状态,ESP32和开发板仍通电,没有光学、深睡或电流测量。

完成正常图案和休眠后,才断开所有USB/外供并消除其他信号回灌,观察60秒和5分钟后的同一图像。记录是否保持和照片,不外推无限保存时间、刷新寿命或整机零功耗。

  1. 断电确认标签/P3、接线与BUSY开关闭合,打开115200串口,等待D04 ready。
  2. 发r,保存BEGIN至返回日志与实际调用耗时,核对250×122画布和图案;有闪动不自动判失败。
  3. 正常hibernate返回后断开全部供电,60s与5min拍照;observations.csv填真实观察。
  4. 恢复供电,程序仍只ready;需再次手动r,当前上电实例的180s门限重新开始。
做到这里,你应该看到
硬件维度仍pending:作者未进行刷新、BUSY测量、断电保持或电流实验。

参考:[1] Good Display GDEY0213B74:面板/SSD1680Z/122×250[2] 厂家面板Rev1.0 2021-03-17 PDF(Seeed托管)[6] GxEPD2 1.6.9面板具体刷新、hibernate实现[8] GxEPD2 1.6.9:BW画布与分页

07 / 显示与总线实践

8秒观察不是取消,10秒也是每个等待的时限

固定库的_waitWhileBusy在BUSY高时循环;超过10,000,000微秒打印Busy Timeout!后break,函数返回void,随后内部代码仍可能继续发命令。一个高层调用可包含多次等待,所以不能承诺“总调用最多10秒”或“没有返回错误就是成功”。

本例回调每轮delay(1),只观察当前调用开始后是否出现高BUSY且已达8秒,达到即锁存fault。它不能中断库,也不能阻止库超时后继续内部命令;应用仅在高层调用返回后检查fault或BUSY仍高,异常时停后续显示调用、不hibernate。

这不是严格8s/10s取消机制;真实控制器一直忙时库仍继续命令的风险并未由此修复。原样host fixture只验观察器状态和门限,未模拟GxEPD2、SSD1680或波形。

参考:[2] 厂家面板Rev1.0 2021-03-17 PDF(Seeed托管)[5] GxEPD2 1.6.9面板类:宽度、BUSY高与10s等待[6] GxEPD2 1.6.9面板具体刷新、hibernate实现[7] GxEPD2 1.6.9:_waitWhileBusy/回调/selectSPI

08 / 显示与总线实践

两种BUSY异常分别执行,别把预检查当库超时

以下均为待实板的操作方案,空记录表没有预填预期结果。开关必须预先断电安装,运行时仅断开/闭合BUSY信号;不注入电源短路,不用GPIO输出拉高。

调用前高:保持开关断开再上电,发r,约2秒后应STOP且不启动显示事务。这个用例只证明预检查路径。

调用中失联:开关闭合正常启动,发r,在BEGIN之后、面板正在刷新时断开BUSY信号。预期库日志出现Busy Timeout!并在高层调用终于返回后STOP;人工可能错过窗口,须记录断开的时刻与完整日志,不能只看最终BUSY高就判用例通过。

保存日志,等当前调用返回后断电,闭合开关,重查FPC/P3/3.3V再上电。异常r始终HALTED,不会自动重试;若调用未返回,可断电恢复,不能以关闭看门狗或欠压检测作为修复。

做到这里,你应该看到
“调用前拒绝”“驱动内部超时”“正常光学刷新”“断电保持”四类证据互不替代。

参考:[2] 厂家面板Rev1.0 2021-03-17 PDF(Seeed托管)[7] GxEPD2 1.6.9:_waitWhileBusy/回调/selectSPI

09 / 显示与总线实践

保留原日志和照片,恢复后再谈下一种屏幕

若没有图案,按顺序查面板号/类、P3、3.3V、FPC方向与卡扣、RES/CS/DC/SCK/SDI、BUSY是否被错误悬空。不能从不刷新的旧画面推断程序仍在正常更新,也不能靠反复刷新未知控制器试运气。

复刷先关monitor,经F06的BOOT按住→RESET→松BOOT进入下载模式;改外设接线仍断电。下载原创源码与文档MIT,第三方说明在THIRD_PARTY.md;GxEPD2是GPL-3.0,组合固件的分发须遵守其许可,下载未附第三方库和二进制。

前置F07提供资料/器件清单,F06提供串口与恢复流程。下一步可以回到D03对照持续发光TFT与双稳态墨水屏的验收方式;后续刷新耗时/残影/电量需要单独实测,不能从本例推断。

参考:[1] Good Display GDEY0213B74:面板/SSD1680Z/122×250[2] 厂家面板Rev1.0 2021-03-17 PDF(Seeed托管)[3] DESPI-C02当前2.2Ω/0.47Ω板、电压与接口[11] GxEPD2 1.6.9 GPL-3.0许可

BACK TO THE SOURCE

资料来源与版本说明

固定版本源与具体模块资料逐项核对;构建与主机逻辑检查通过,所有实板观察待测。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。

  1. Good Display GDEY0213B74:面板/SSD1680Z/122×250
  2. 厂家面板Rev1.0 2021-03-17 PDF(Seeed托管)
  3. DESPI-C02当前2.2Ω/0.47Ω板、电压与接口
  4. DESPI-C02旧Rev1.1文档索引:3Ω旧修订须区分
  5. GxEPD2 1.6.9面板类:宽度、BUSY高与10s等待
  6. GxEPD2 1.6.9面板具体刷新、hibernate实现
  7. GxEPD2 1.6.9:_waitWhileBusy/回调/selectSPI
  8. GxEPD2 1.6.9:BW画布与分页
  9. Arduino-ESP32 3.3.12 SPI begin实现
  10. DevKitC-1 v1.1排针
  11. GxEPD2 1.6.9 GPL-3.0许可