2026-10-09 08:47:35 -10:00
|
|
|
#pragma once
|
|
|
|
|
// Faster image writes for large GxEPD2 panels.
|
|
|
|
|
//
|
|
|
|
|
// GxEPD2 sends the framebuffer to the panel one byte at a time: every byte is
|
|
|
|
|
// its own SPI.transfer(uint8_t) call, and on nRF52 each of those is a complete
|
|
|
|
|
// EasyDMA transaction with several microseconds of fixed setup overhead --
|
|
|
|
|
// independent of the SPI clock. A partial refresh here writes the full screen
|
|
|
|
|
// five times (GxEPDDisplay::endFrame's inverse-redrive pass, then
|
|
|
|
|
// GxEPD2_BW::display()), so on an 800x480 panel (48 KB a screen) that overhead
|
|
|
|
|
// alone blocks the main loop for well over a second per refresh.
|
|
|
|
|
//
|
|
|
|
|
// These subclasses override only the image writes, sending each row as one
|
|
|
|
|
// bulk SPI.transfer(buf, nullptr, n) -- the same bytes, in the same order, to
|
|
|
|
|
// the same RAM window -- so the cost becomes just the wire time. Everything
|
|
|
|
|
// else (init, refresh, power) is the stock driver. The first write (panel init
|
|
|
|
|
// and the initial screen clear) and the rarely used mirror/PGM variants go
|
|
|
|
|
// through the stock path unchanged.
|
|
|
|
|
//
|
|
|
|
|
// Use by naming the subclass as the panel: -D EINK_DISPLAY_MODEL=GxEPD2_426_GDEQ0426T82_Fast
|
|
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
#ifdef _GxEPD2_426_GDEQ0426T82_H_
|
|
|
|
|
class GxEPD2_426_GDEQ0426T82_Fast : public GxEPD2_426_GDEQ0426T82 {
|
|
|
|
|
typedef GxEPD2_426_GDEQ0426T82 Base;
|
|
|
|
|
uint8_t _row[WIDTH / 8]; // one panel row, in RAM (EasyDMA can't read flash)
|
|
|
|
|
|
|
|
|
|
bool canFast(bool mirror_y, bool pgm) const {
|
|
|
|
|
return _init_display_done && !_initial_write && !mirror_y && !pgm;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Same RAM window as the stock driver's (private) _setPartialRamArea():
|
|
|
|
|
// the gates are reversed on this panel, so y is mirrored and entered decreasing.
|
2026-10-10 16:14:18 -10:00
|
|
|
// Copied from GxEPD2 1.6.2 (src/gdeq/GxEPD2_426_GDEQ0426T82.cpp) -- when
|
|
|
|
|
// bumping GxEPD2, check the library's version still matches this one.
|
2026-10-09 08:47:35 -10:00
|
|
|
void setRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
|
|
|
|
y = HEIGHT - y - h;
|
|
|
|
|
_writeCommand(0x11);
|
|
|
|
|
_writeData(0x01);
|
|
|
|
|
_writeCommand(0x44);
|
|
|
|
|
_writeData(x % 256);
|
|
|
|
|
_writeData(x / 256);
|
|
|
|
|
_writeData((x + w - 1) % 256);
|
|
|
|
|
_writeData((x + w - 1) / 256);
|
|
|
|
|
_writeCommand(0x45);
|
|
|
|
|
_writeData((y + h - 1) % 256);
|
|
|
|
|
_writeData((y + h - 1) / 256);
|
|
|
|
|
_writeData(y % 256);
|
|
|
|
|
_writeData(y / 256);
|
|
|
|
|
_writeCommand(0x4e);
|
|
|
|
|
_writeData(x % 256);
|
|
|
|
|
_writeData(x / 256);
|
|
|
|
|
_writeCommand(0x4f);
|
|
|
|
|
_writeData((y + h - 1) % 256);
|
|
|
|
|
_writeData((y + h - 1) / 256);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The stock _writeImage(), with the per-byte loop replaced by one bulk
|
2026-10-10 16:14:18 -10:00
|
|
|
// transfer per row (clipping and indexing unchanged). Also from GxEPD2 1.6.2.
|
2026-10-09 08:47:35 -10:00
|
|
|
void fastWrite(uint8_t command, const uint8_t bitmap[], int16_t x, int16_t y, int16_t w, int16_t h, bool invert) {
|
|
|
|
|
delay(1); // as the stock driver: yield
|
|
|
|
|
int16_t wb = (w + 7) / 8;
|
|
|
|
|
x -= x % 8;
|
|
|
|
|
w = wb * 8;
|
|
|
|
|
int16_t x1 = x < 0 ? 0 : x;
|
|
|
|
|
int16_t y1 = y < 0 ? 0 : y;
|
|
|
|
|
int16_t w1 = x + w < int16_t(WIDTH) ? w : int16_t(WIDTH) - x;
|
|
|
|
|
int16_t h1 = y + h < int16_t(HEIGHT) ? h : int16_t(HEIGHT) - y;
|
|
|
|
|
int16_t dx = x1 - x;
|
|
|
|
|
int16_t dy = y1 - y;
|
|
|
|
|
w1 -= dx;
|
|
|
|
|
h1 -= dy;
|
|
|
|
|
if ((w1 <= 0) || (h1 <= 0)) return;
|
|
|
|
|
setRamArea(x1, y1, w1, h1);
|
|
|
|
|
_writeCommand(command);
|
|
|
|
|
_startTransfer();
|
|
|
|
|
const int16_t nb = w1 / 8;
|
|
|
|
|
for (int16_t i = 0; i < h1; i++) {
|
|
|
|
|
const uint8_t* src = &bitmap[dx / 8 + int32_t(i + dy) * wb];
|
|
|
|
|
if (invert) { for (int16_t j = 0; j < nb; j++) _row[j] = ~src[j]; }
|
|
|
|
|
else { memcpy(_row, src, nb); }
|
|
|
|
|
_pSPIx->transfer(_row, nullptr, nb);
|
|
|
|
|
}
|
|
|
|
|
_endTransfer();
|
|
|
|
|
delay(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
GxEPD2_426_GDEQ0426T82_Fast(int16_t cs, int16_t dc, int16_t rst, int16_t busy) : Base(cs, dc, rst, busy) {}
|
|
|
|
|
using Base::writeImage; // keep the other overloads visible
|
|
|
|
|
|
|
|
|
|
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) override {
|
|
|
|
|
if (!canFast(mirror_y, pgm)) { Base::writeImage(bitmap, x, y, w, h, invert, mirror_y, pgm); return; }
|
|
|
|
|
fastWrite(0x24, bitmap, x, y, w, h, invert);
|
|
|
|
|
}
|
|
|
|
|
void writeImageForFullRefresh(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) override {
|
|
|
|
|
if (!canFast(mirror_y, pgm)) { Base::writeImageForFullRefresh(bitmap, x, y, w, h, invert, mirror_y, pgm); return; }
|
|
|
|
|
fastWrite(0x26, bitmap, x, y, w, h, invert); // set previous
|
|
|
|
|
fastWrite(0x24, bitmap, x, y, w, h, invert); // set current
|
|
|
|
|
}
|
|
|
|
|
void writeImageAgain(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) override {
|
|
|
|
|
if (!canFast(mirror_y, pgm)) { Base::writeImageAgain(bitmap, x, y, w, h, invert, mirror_y, pgm); return; }
|
|
|
|
|
fastWrite(0x26, bitmap, x, y, w, h, invert); // set previous
|
|
|
|
|
fastWrite(0x24, bitmap, x, y, w, h, invert); // set current
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
#endif
|