Skip to content

Commit 286bc85

Browse files
HarukiToredaCopilot
authored andcommitted
T-mini Eink S3 Support for both InkHUD and BaseUI (#9856)
* Tmini Eink fix * tuning * better refresh * Fix to lora pins to be like the original. * Update pins_arduino.h * removed dead flags from previous tests * Update src/graphics/niche/Drivers/EInk/GDEW0102T4.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent e282491 commit 286bc85

File tree

18 files changed

+771
-67
lines changed

18 files changed

+771
-67
lines changed

src/graphics/EInkDisplay2.cpp

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ bool EInkDisplay::connect()
143143
#ifdef ELECROW_ThinkNode_M1
144144
// ThinkNode M1 has a hardware dimmable backlight. Start enabled
145145
digitalWrite(PIN_EINK_EN, HIGH);
146+
#elif defined(MINI_EPAPER_S3)
147+
// T-Mini Epaper S3 requires panel power rail enabled before SPI transfer.
148+
digitalWrite(PIN_EINK_EN, HIGH);
149+
delay(10);
146150
#else
147151
digitalWrite(PIN_EINK_EN, LOW);
148152
#endif
@@ -202,7 +206,8 @@ bool EInkDisplay::connect()
202206
}
203207

204208
#elif defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || \
205-
defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER)
209+
defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) || \
210+
defined(MINI_EPAPER_S3)
206211
{
207212
// Start HSPI
208213
hspi = new SPIClass(HSPI);
@@ -216,9 +221,13 @@ bool EInkDisplay::connect()
216221

217222
// Init GxEPD2
218223
adafruitDisplay->init();
224+
#if defined(MINI_EPAPER_S3)
225+
adafruitDisplay->setRotation(3);
226+
#else
219227
adafruitDisplay->setRotation(3);
220228
#if defined(CROWPANEL_ESP32S3_5_EPAPER) || defined(CROWPANEL_ESP32S3_4_EPAPER)
221229
adafruitDisplay->setRotation(0);
230+
#endif
222231
#endif
223232
}
224233
#elif defined(PCA10059) || defined(ME25LS01)
@@ -259,17 +268,6 @@ bool EInkDisplay::connect()
259268
adafruitDisplay->setRotation(3);
260269
adafruitDisplay->setPartialWindow(0, 0, EINK_WIDTH, EINK_HEIGHT);
261270
}
262-
#elif defined(MINI_EPAPER_S3)
263-
spi1 = new SPIClass(HSPI);
264-
spi1->begin(PIN_SPI1_SCK, PIN_SPI1_MISO, PIN_SPI1_MOSI, PIN_EINK_CS);
265-
266-
// Create GxEPD2 objects
267-
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *spi1);
268-
adafruitDisplay = new GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT>(*lowLevel);
269-
270-
// Init GxEPD2
271-
adafruitDisplay->init();
272-
adafruitDisplay->setRotation(1);
273271
#elif defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_VISION_MASTER_E213)
274272

275273
// Detect display model, before starting SPI

src/graphics/EInkDisplay2.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,12 @@ class EInkDisplay : public OLEDDisplay
8989
// If display uses HSPI
9090
#if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \
9191
defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \
92-
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) || defined(ELECROW_ThinkNode_M5)
92+
defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) || defined(ELECROW_ThinkNode_M5) || \
93+
defined(MINI_EPAPER_S3)
9394
SPIClass *hspi = NULL;
9495
#endif
9596

96-
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK) || \
97-
defined(MINI_EPAPER_S3)
97+
#if defined(HELTEC_MESH_POCKET) || defined(SEEED_WIO_TRACKER_L1_EINK) || defined(HELTEC_MESH_SOLAR_EINK)
9898
SPIClass *spi1 = NULL;
9999
#endif
100100

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
2+
3+
#include "./GDEW0102T4.h"
4+
5+
#include <cstring>
6+
7+
using namespace NicheGraphics::Drivers;
8+
9+
// LUTs from GxEPD2_102.cpp (GDEW0102T4 / UC8175).
10+
static const uint8_t LUT_W_FULL[] = {
11+
0x60, 0x5A, 0x5A, 0x00, 0x00, 0x01, //
12+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
13+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
14+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
15+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
16+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
17+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
18+
};
19+
20+
static const uint8_t LUT_B_FULL[] = {
21+
0x90, 0x5A, 0x5A, 0x00, 0x00, 0x01, //
22+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
23+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
24+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
25+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
26+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
27+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
28+
};
29+
30+
static const uint8_t LUT_W_FAST[] = {
31+
0x60, 0x01, 0x01, 0x00, 0x00, 0x01, //
32+
0x80, 0x12, 0x00, 0x00, 0x00, 0x01, //
33+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
34+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
35+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
36+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
37+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
38+
};
39+
40+
static const uint8_t LUT_B_FAST[] = {
41+
0x90, 0x01, 0x01, 0x00, 0x00, 0x01, //
42+
0x40, 0x14, 0x00, 0x00, 0x00, 0x01, //
43+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
44+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
45+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
46+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
47+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
48+
};
49+
50+
GDEW0102T4::GDEW0102T4() : UC8175(width, height, supported) {}
51+
52+
void GDEW0102T4::setFastConfig(FastConfig cfg)
53+
{
54+
// Clamp out only clearly invalid PLL settings.
55+
if (cfg.reg30 < 0x05)
56+
cfg.reg30 = 0x05;
57+
fastConfig = cfg;
58+
}
59+
60+
GDEW0102T4::FastConfig GDEW0102T4::getFastConfig() const
61+
{
62+
return fastConfig;
63+
}
64+
65+
void GDEW0102T4::configCommon()
66+
{
67+
// Init path aligned with GxEPD2_GDEW0102T4 (UC8175 family).
68+
sendCommand(0xD2);
69+
sendData(0x3F);
70+
71+
sendCommand(0x00);
72+
sendData(0x6F);
73+
74+
sendCommand(0x01);
75+
sendData(0x03);
76+
sendData(0x00);
77+
sendData(0x2B);
78+
sendData(0x2B);
79+
80+
sendCommand(0x06);
81+
sendData(0x3F);
82+
83+
sendCommand(0x2A);
84+
sendData(0x00);
85+
sendData(0x00);
86+
87+
sendCommand(0x30); // PLL / drive clock
88+
sendData(0x13);
89+
90+
sendCommand(0x50); // Last border/data interval; subtle but can affect artifacts
91+
sendData(0x57);
92+
93+
sendCommand(0x60);
94+
sendData(0x22);
95+
96+
sendCommand(0x61);
97+
sendData(width);
98+
sendData(height);
99+
100+
sendCommand(0x82); // VCOM DC setting
101+
sendData(0x12);
102+
103+
sendCommand(0xE3);
104+
sendData(0x33);
105+
}
106+
107+
void GDEW0102T4::configFull()
108+
{
109+
sendCommand(0x23);
110+
sendData(LUT_W_FULL, sizeof(LUT_W_FULL));
111+
sendCommand(0x24);
112+
sendData(LUT_B_FULL, sizeof(LUT_B_FULL));
113+
114+
powerOn();
115+
}
116+
117+
void GDEW0102T4::configFast()
118+
{
119+
uint8_t lutW[sizeof(LUT_W_FAST)];
120+
uint8_t lutB[sizeof(LUT_B_FAST)];
121+
memcpy(lutW, LUT_W_FAST, sizeof(LUT_W_FAST));
122+
memcpy(lutB, LUT_B_FAST, sizeof(LUT_B_FAST));
123+
124+
// Second stage duration bytes are the main "darkness vs ghosting" control for this panel.
125+
lutW[7] = fastConfig.lutW2;
126+
lutB[7] = fastConfig.lutB2;
127+
128+
sendCommand(0x30);
129+
sendData(fastConfig.reg30);
130+
131+
sendCommand(0x50);
132+
sendData(fastConfig.reg50);
133+
134+
sendCommand(0x82);
135+
sendData(fastConfig.reg82);
136+
137+
sendCommand(0x23);
138+
sendData(lutW, sizeof(lutW));
139+
sendCommand(0x24);
140+
sendData(lutB, sizeof(lutB));
141+
142+
powerOn();
143+
}
144+
145+
void GDEW0102T4::writeOldImage()
146+
{
147+
// On this panel, FULL refresh is most reliable when "old image" is all white.
148+
if (updateType == FULL) {
149+
sendCommand(0x10);
150+
// Use buffered writes of 0xFF to avoid per-byte SPI transactions.
151+
const uint16_t chunkSize = 64;
152+
uint8_t ffBuf[chunkSize];
153+
memset(ffBuf, 0xFF, sizeof(ffBuf));
154+
155+
uint32_t remaining = bufferSize;
156+
while (remaining > 0) {
157+
uint16_t toSend = remaining > chunkSize ? chunkSize : static_cast<uint16_t>(remaining);
158+
sendData(ffBuf, toSend);
159+
remaining -= toSend;
160+
}
161+
return;
162+
}
163+
164+
// FAST refresh uses differential data (previous frame as old image).
165+
if (previousBuffer) {
166+
writeImage(0x10, previousBuffer);
167+
} else {
168+
writeImage(0x10, buffer);
169+
}
170+
}
171+
172+
void GDEW0102T4::finalizeUpdate()
173+
{
174+
// Keep panel out of deep-sleep between updates for better reliability of repeated FAST refresh.
175+
powerOff();
176+
}
177+
178+
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
3+
E-Ink display driver
4+
- GDEW0102T4
5+
- Controller: UC8175
6+
- Size: 1.02 inch
7+
- Resolution: 80px x 128px
8+
9+
*/
10+
11+
#pragma once
12+
13+
#ifdef MESHTASTIC_INCLUDE_NICHE_GRAPHICS
14+
15+
#include "configuration.h"
16+
17+
#include "./UC8175.h"
18+
19+
namespace NicheGraphics::Drivers
20+
{
21+
22+
class GDEW0102T4 : public UC8175
23+
{
24+
private:
25+
static constexpr uint16_t width = 80;
26+
static constexpr uint16_t height = 128;
27+
static constexpr UpdateTypes supported = (UpdateTypes)(FULL | FAST);
28+
29+
public:
30+
struct FastConfig {
31+
uint8_t reg30;
32+
uint8_t reg50;
33+
uint8_t reg82;
34+
uint8_t lutW2;
35+
uint8_t lutB2;
36+
};
37+
38+
GDEW0102T4();
39+
void setFastConfig(FastConfig cfg);
40+
FastConfig getFastConfig() const;
41+
42+
protected:
43+
void configCommon() override;
44+
void configFull() override;
45+
void configFast() override;
46+
void writeOldImage() override;
47+
void finalizeUpdate() override;
48+
49+
private:
50+
FastConfig fastConfig = {0x13, 0xF2, 0x12, 0x0E, 0x14};
51+
};
52+
53+
} // namespace NicheGraphics::Drivers
54+
55+
#endif // MESHTASTIC_INCLUDE_NICHE_GRAPHICS

0 commit comments

Comments
 (0)