Paperd.Ink Library 0.0.5
Library for interacting with Paperd.Ink devices.
Loading...
Searching...
No Matches
GxEPD2_420_M01.cpp
Go to the documentation of this file.
1// Display Library for SPI e-paper panels from Dalian Good Display and boards from Waveshare.
2// Requires HW SPI and Adafruit_GFX. Caution: these e-papers require 3.3V supply AND data lines!
3//
4// based on Demo Example from Good Display: http://www.e-paper-display.com/download_list/downloadcategoryid=34&isMode=false.html
5// Controller: UC8176 (IL0398) : https://www.good-display.com/public/html/pdfjs/viewer/viewernew.html?file=https://v4.cecdn.yun300.cn/100001_1909185148/UC8176-1.pdf
6// Controller: IL0398 (UC8176) : http://www.e-paper-display.com/download_detail/downloadsId=537.html
7//
8// Author: Jean-Marc Zingg
9//
10// Version: see library.properties
11//
12// Library: https://github.com/ZinggJM/GxEPD2
13
14#include "GxEPD2_420_M01.h"
15
16GxEPD2_420_M01::GxEPD2_420_M01(int16_t cs, int16_t dc, int16_t rst, int16_t busy) :
17 GxEPD2_EPD(cs, dc, rst, busy, LOW, 10000000, WIDTH, HEIGHT, panel, hasColor, hasPartialUpdate, hasFastPartialUpdate)
18{
19}
20
21void GxEPD2_420_M01::clearScreen(uint8_t value)
22{
23 writeScreenBuffer(value);
24 refresh(true);
25 writeScreenBuffer(value);
26}
27
29{
30 _initial_write = false; // initial full screen buffer clean done
31 if (!_using_partial_mode) _Init_Part();
33 {
34 _writeCommand(0x10); // init old data
36 for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++)
37 {
38 _transfer(value);
39 }
41 }
42 _writeCommand(0x13);
44 for (uint32_t i = 0; i < uint32_t(WIDTH) * uint32_t(HEIGHT) / 8; i++)
45 {
46 _transfer(value);
47 }
49}
50
51void GxEPD2_420_M01::writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
52{
53 if (_initial_write) writeScreenBuffer(); // initial full screen buffer clean
54 delay(1); // yield() to avoid WDT on ESP8266 and ESP32
55 int16_t wb = (w + 7) / 8; // width bytes, bitmaps are padded
56 x -= x % 8; // byte boundary
57 w = wb * 8; // byte boundary
58 int16_t x1 = x < 0 ? 0 : x; // limit
59 int16_t y1 = y < 0 ? 0 : y; // limit
60 int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit
61 int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit
62 int16_t dx = x1 - x;
63 int16_t dy = y1 - y;
64 w1 -= dx;
65 h1 -= dy;
66 if ((w1 <= 0) || (h1 <= 0)) return;
67 if (!_using_partial_mode) _Init_Part();
68 //uint32_t start = micros();
69 _writeCommand(0x91); // partial in
70 _setPartialRamArea(x1, y1, w1, h1);
71 _writeCommand(0x13);
73 for (int16_t i = 0; i < h1; i++)
74 {
75 for (int16_t j = 0; j < w1 / 8; j++)
76 {
77 uint8_t data;
78 // use wb, h of bitmap for index!
79 int16_t idx = mirror_y ? j + dx / 8 + ((h - 1 - (i + dy))) * wb : j + dx / 8 + (i + dy) * wb;
80 if (pgm)
81 {
82#if defined(__AVR) || defined(ESP8266) || defined(ESP32)
83 data = pgm_read_byte(&bitmap[idx]);
84#else
85 data = bitmap[idx];
86#endif
87 }
88 else
89 {
90 data = bitmap[idx];
91 }
92 if (invert) data = ~data;
93 _transfer(data);
94 }
95 }
97 _writeCommand(0x92); // partial out
98 //Serial.print("GxEPD2_420_M01::writeImage took "); Serial.println(micros() - start);
99 delay(1); // yield() to avoid WDT on ESP8266 and ESP32
100}
101
102void GxEPD2_420_M01::writeImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
103 int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
104{
105 if (_initial_write) writeScreenBuffer(); // initial full screen buffer clean
106 delay(1); // yield() to avoid WDT on ESP8266 and ESP32
107 if ((w_bitmap < 0) || (h_bitmap < 0) || (w < 0) || (h < 0)) return;
108 if ((x_part < 0) || (x_part >= w_bitmap)) return;
109 if ((y_part < 0) || (y_part >= h_bitmap)) return;
110 int16_t wb_bitmap = (w_bitmap + 7) / 8; // width bytes, bitmaps are padded
111 x_part -= x_part % 8; // byte boundary
112 w = w_bitmap - x_part < w ? w_bitmap - x_part : w; // limit
113 h = h_bitmap - y_part < h ? h_bitmap - y_part : h; // limit
114 x -= x % 8; // byte boundary
115 w = 8 * ((w + 7) / 8); // byte boundary, bitmaps are padded
116 int16_t x1 = x < 0 ? 0 : x; // limit
117 int16_t y1 = y < 0 ? 0 : y; // limit
118 int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x; // limit
119 int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y; // limit
120 int16_t dx = x1 - x;
121 int16_t dy = y1 - y;
122 w1 -= dx;
123 h1 -= dy;
124 if ((w1 <= 0) || (h1 <= 0)) return;
125 if (!_using_partial_mode) _Init_Part();
126 _writeCommand(0x91); // partial in
127 _setPartialRamArea(x1, y1, w1, h1);
128 _writeCommand(0x13);
130 for (int16_t i = 0; i < h1; i++)
131 {
132 for (int16_t j = 0; j < w1 / 8; j++)
133 {
134 uint8_t data;
135 // use wb_bitmap, h_bitmap of bitmap for index!
136 int16_t idx = mirror_y ? x_part / 8 + j + dx / 8 + ((h_bitmap - 1 - (y_part + i + dy))) * wb_bitmap : x_part / 8 + j + dx / 8 + (y_part + i + dy) * wb_bitmap;
137 if (pgm)
138 {
139#if defined(__AVR) || defined(ESP8266) || defined(ESP32)
140 data = pgm_read_byte(&bitmap[idx]);
141#else
142 data = bitmap[idx];
143#endif
144 }
145 else
146 {
147 data = bitmap[idx];
148 }
149 if (invert) data = ~data;
150 _transfer(data);
151 }
152 }
153 _endTransfer();
154 _writeCommand(0x92); // partial out
155 delay(1); // yield() to avoid WDT on ESP8266 and ESP32
156}
157
158void GxEPD2_420_M01::writeImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
159{
160 if (black)
161 {
162 writeImage(black, x, y, w, h, invert, mirror_y, pgm);
163 }
164}
165
166void GxEPD2_420_M01::writeImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
167 int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
168{
169 if (black)
170 {
171 writeImagePart(black, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm);
172 }
173}
174
175void GxEPD2_420_M01::writeNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
176{
177 if (data1)
178 {
179 writeImage(data1, x, y, w, h, invert, mirror_y, pgm);
180 }
181}
182
183void GxEPD2_420_M01::drawImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
184{
185 writeImage(bitmap, x, y, w, h, invert, mirror_y, pgm);
186 refresh(x, y, w, h);
187 writeImage(bitmap, x, y, w, h, invert, mirror_y, pgm);
188}
189
190void GxEPD2_420_M01::drawImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
191 int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
192{
193 writeImagePart(bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm);
194 refresh(x, y, w, h);
195 writeImagePart(bitmap, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm);
196}
197
198void GxEPD2_420_M01::drawImage(const uint8_t* black, const uint8_t* color, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
199{
200 writeImage(black, color, x, y, w, h, invert, mirror_y, pgm);
201 refresh(x, y, w, h);
202 writeImage(black, color, x, y, w, h, invert, mirror_y, pgm);
203}
204
205void GxEPD2_420_M01::drawImagePart(const uint8_t* black, const uint8_t* color, int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap,
206 int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
207{
208 writeImagePart(black, color, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm);
209 refresh(x, y, w, h);
210 writeImagePart(black, color, x_part, y_part, w_bitmap, h_bitmap, x, y, w, h, invert, mirror_y, pgm);
211}
212
213void GxEPD2_420_M01::drawNative(const uint8_t* data1, const uint8_t* data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert, bool mirror_y, bool pgm)
214{
215 writeNative(data1, data2, x, y, w, h, invert, mirror_y, pgm);
216 refresh(x, y, w, h);
217 writeNative(data1, data2, x, y, w, h, invert, mirror_y, pgm);
218}
219
220void GxEPD2_420_M01::refresh(bool partial_update_mode)
221{
222 if (partial_update_mode) refresh(0, 0, WIDTH, HEIGHT);
223 else
224 {
225 if (_using_partial_mode) _Init_Full();
226 _Update_Full();
227 _initial_refresh = false; // initial full update done
228 }
229}
230
231void GxEPD2_420_M01::refresh(int16_t x, int16_t y, int16_t w, int16_t h)
232{
233 if (_initial_refresh) return refresh(false); // initial update needs be full update
234 // intersection with screen
235 int16_t w1 = x < 0 ? w + x : w; // reduce
236 int16_t h1 = y < 0 ? h + y : h; // reduce
237 int16_t x1 = x < 0 ? 0 : x; // limit
238 int16_t y1 = y < 0 ? 0 : y; // limit
239 w1 = x1 + w1 < int16_t(WIDTH) ? w1 : int16_t(WIDTH) - x1; // limit
240 h1 = y1 + h1 < int16_t(HEIGHT) ? h1 : int16_t(HEIGHT) - y1; // limit
241 if ((w1 <= 0) || (h1 <= 0)) return;
242 // make x1, w1 multiple of 8
243 w1 += x1 % 8;
244 if (w1 % 8 > 0) w1 += 8 - w1 % 8;
245 x1 -= x1 % 8;
246 if (!_using_partial_mode) _Init_Part();
247 if (usePartialUpdateWindow) _writeCommand(0x91); // partial in
248 _setPartialRamArea(x1, y1, w1, h1);
249 _Update_Part();
250 if (usePartialUpdateWindow) _writeCommand(0x92); // partial out
251}
252
254{
255 _PowerOff();
256}
257
259{
260 _PowerOff();
261 if (_rst >= 0)
262 {
263 _writeCommand(0x07); // deep sleep
264 _writeData(0xA5); // check code
265 _hibernating = true;
266 }
267}
268
269void GxEPD2_420_M01::_setPartialRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
270{
271 uint16_t xe = (x + w - 1) | 0x0007; // byte boundary inclusive (last byte)
272 uint16_t ye = y + h - 1;
273 x &= 0xFFF8; // byte boundary
274 _writeCommand(0x90); // partial window
275 _writeData(x / 256);
276 _writeData(x % 256);
277 _writeData(xe / 256);
278 _writeData(xe % 256);
279 _writeData(y / 256);
280 _writeData(y % 256);
281 _writeData(ye / 256);
282 _writeData(ye % 256);
283 _writeData(0x01); // don't see any difference
284 //_writeData(0x00); // don't see any difference
285}
286
287void GxEPD2_420_M01::_PowerOn()
288{
289 if (!_power_is_on)
290 {
291 _writeCommand(0x04);
292 _waitWhileBusy("_PowerOn", power_on_time);
293 }
294 _power_is_on = true;
295}
296
297void GxEPD2_420_M01::_PowerOff()
298{
299 _writeCommand(0x02); // power off
300 _waitWhileBusy("_PowerOff", power_off_time);
301 _power_is_on = false;
302 _using_partial_mode = false;
303}
304
305void GxEPD2_420_M01::_InitDisplay()
306{
307 if (_hibernating) _reset();
308 _writeCommand(0x00); //panel setting
309 _writeData(0x1f); //LUT from OTP
310 _writeData(0x0d); //waiting for the electronic paper IC to release the idle signal
311 _writeCommand(0x50); //VCOM AND DATA INTERVAL SETTING
312 _writeData(0x97);
313}
314
315// experimental partial screen update LUTs with balanced charge
316// LUTs are filled with zeroes
317
318#define T1 20 // charge balance pre-phase
319#define T2 20 // optional extension
320#define T3 40 // color change phase (b/w)
321#define T4 40 // optional extension for one color
322#define T5 3 // white sustain phase
323#define T6 3 // black sustain phase
324
325const unsigned char GxEPD2_420_M01::lut_20_vcom0_partial[] PROGMEM =
326{
327 0x00, T1, T2, T3, T4, 1, // 00 00 00 00
328};
329
330const unsigned char GxEPD2_420_M01::lut_21_ww_partial[] PROGMEM =
331{ // 10 w
332 0x02, T1, T2, T3, T5, 1, // 00 00 00 10
333};
334
335const unsigned char GxEPD2_420_M01::lut_22_bw_partial[] PROGMEM =
336{ // 10 w
337 //0x48, T1, T2, T3, T4, 1, // 01 00 10 00
338 0x5A, T1, T2, T3, T4, 1, // 01 01 10 10 more white
339};
340
341const unsigned char GxEPD2_420_M01::lut_23_wb_partial[] PROGMEM =
342{ // 01 b
343 0x84, T1, T2, T3, T4, 1, // 10 00 01 00
344 //0xA5, T1, T2, T3, T4, 1, // 10 10 01 01 more black
345};
346
347const unsigned char GxEPD2_420_M01::lut_24_bb_partial[] PROGMEM =
348{ // 01 b
349 0x01, T1, T2, T3, T6, 1, // 00 00 00 01
350};
351
352void GxEPD2_420_M01::_Init_Full()
353{
354 _InitDisplay();
355 _PowerOn();
356 _using_partial_mode = false;
357}
358
359void GxEPD2_420_M01::_Init_Part()
360{
361 _InitDisplay();
363 {
364 _writeCommand(0x01); // POWER SETTING
365 _writeData (0x03); // VDS_EN, VDG_EN internal
366 _writeData (0x00); // VCOM_HV, VGHL_LV=16V
367 _writeData (0x2b); // VDH=11V
368 _writeData (0x2b); // VDL=11V
369 _writeCommand(0x06); // boost soft start
370 _writeData (0x17); // A
371 _writeData (0x17); // B
372 _writeData (0x17); // C
373 _writeCommand(0x00); // panel setting
374 _writeData(0x3f); // 300x400 B/W mode, LUT set by register
375 _writeCommand(0x30); // PLL setting
376 _writeData (0x3a); // 3a 100HZ 29 150Hz 39 200HZ 31 171HZ
377 _writeCommand(0x61); // resolution setting
378 _writeData (WIDTH / 256);
379 _writeData (WIDTH % 256);
380 _writeData (HEIGHT / 256);
381 _writeData (HEIGHT % 256);
382 _writeCommand(0x82); // vcom_DC setting
383 _writeData (0x1A); // -0.1 + 26 * -0.05 = -1.4V from OTP
384 _writeCommand(0x50); // VCOM AND DATA INTERVAL SETTING
385 //_writeData(0x97); // WBmode:VBDF 17|D7 VBDW 97 VBDB 57 WBRmode:VBDF F7 VBDW 77 VBDB 37 VBDR B7
386 _writeData(0xd7); // border floating to avoid flashing
387 _writeCommand(0x20);
388 _writeDataPGM(lut_20_vcom0_partial, sizeof(lut_20_vcom0_partial), 44 - sizeof(lut_20_vcom0_partial));
389 _writeCommand(0x21);
390 _writeDataPGM(lut_21_ww_partial, sizeof(lut_21_ww_partial), 42 - sizeof(lut_21_ww_partial));
391 _writeCommand(0x22);
392 _writeDataPGM(lut_22_bw_partial, sizeof(lut_22_bw_partial), 42 - sizeof(lut_22_bw_partial));
393 _writeCommand(0x23);
394 _writeDataPGM(lut_23_wb_partial, sizeof(lut_23_wb_partial), 42 - sizeof(lut_23_wb_partial));
395 _writeCommand(0x24);
396 _writeDataPGM(lut_24_bb_partial, sizeof(lut_24_bb_partial), 42 - sizeof(lut_24_bb_partial));
397 }
398 _PowerOn();
399 _using_partial_mode = true;
400}
401
402void GxEPD2_420_M01::_Update_Full()
403{
404 _writeCommand(0x12); //display refresh
405 _waitWhileBusy("_Update_Full", full_refresh_time);
406}
407
408void GxEPD2_420_M01::_Update_Part()
409{
410 _writeCommand(0x12); //display refresh
411 _waitWhileBusy("_Update_Part", partial_refresh_time);
412}
#define T2
#define T6
#define T3
#define T5
const unsigned char GxEPD2_420_M01::lut_20_vcom0_partial[] PROGMEM
#define T4
#define T1
void writeImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, int16_t x, int16_t y, int16_t w, int16_t h, bool invert=false, bool mirror_y=false, bool pgm=false)
static const uint16_t power_off_time
GxEPD2_420_M01(int16_t cs, int16_t dc, int16_t rst, int16_t busy)
static const uint16_t HEIGHT
void clearScreen(uint8_t value=0xFF)
void drawImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert=false, bool mirror_y=false, bool pgm=false)
void drawImagePart(const uint8_t bitmap[], int16_t x_part, int16_t y_part, int16_t w_bitmap, int16_t h_bitmap, int16_t x, int16_t y, int16_t w, int16_t h, bool invert=false, bool mirror_y=false, bool pgm=false)
static const uint16_t power_on_time
void writeImage(const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert=false, bool mirror_y=false, bool pgm=false)
static const bool hasPartialUpdate
void writeScreenBuffer(uint8_t value=0xFF)
void refresh(bool partial_update_mode=false)
static const uint16_t full_refresh_time
void drawNative(const uint8_t *data1, const uint8_t *data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert=false, bool mirror_y=false, bool pgm=false)
void writeNative(const uint8_t *data1, const uint8_t *data2, int16_t x, int16_t y, int16_t w, int16_t h, bool invert=false, bool mirror_y=false, bool pgm=false)
static const uint16_t partial_refresh_time
static const uint16_t WIDTH
static const bool usePartialUpdateWindow
void _endTransfer()
void _writeCommand(uint8_t c)
void _writeData(uint8_t d)
bool _using_partial_mode
Definition GxEPD2_EPD.h:118
void _waitWhileBusy(const char *comment=0, uint16_t busy_time=5000)
bool _power_is_on
Definition GxEPD2_EPD.h:118
bool _initial_refresh
Definition GxEPD2_EPD.h:117
void _startTransfer()
void _transfer(uint8_t value)
void _reset()
bool _initial_write
Definition GxEPD2_EPD.h:117
int16_t _rst
Definition GxEPD2_EPD.h:112
bool _hibernating
Definition GxEPD2_EPD.h:118
void _writeDataPGM(const uint8_t *data, uint16_t n, int16_t fill_with_zeroes=0)