Dave Allie 98a5aa1f89
[EInkDisplay] Add basic implementation for displayWindow (#9)
Adds a basic implementation of `displayWindow` which displays a subset
of the current frame buffer to the screen. It is significantly less
performant than a full screen reload, so it shouldn't be used unless the
underlying content is hard to get/rerender.

I've also spotted a little bit of ghosting, but it's not fully clear as
to why that is. My only thought it the LUT and grayscale mode isn't
fully disengaged or the RED RAM buffer isn't correctly setup (but afaict
it should be populated correctly).

Going to merge this to continue testing this feature out in the wild,
but consider it experimental.
2025-12-17 00:01:47 +11:00

102 lines
3.1 KiB
C++

#pragma once
#include <Arduino.h>
#include <SPI.h>
class EInkDisplay {
public:
// Constructor with pin configuration
EInkDisplay(int8_t sclk, int8_t mosi, int8_t cs, int8_t dc, int8_t rst, int8_t busy);
// Destructor
~EInkDisplay() = default;
// Refresh modes (guarded to avoid redefinition in test builds)
enum RefreshMode {
FULL_REFRESH, // Full refresh with complete waveform
HALF_REFRESH, // Half refresh (1720ms) - balanced quality and speed
FAST_REFRESH // Fast refresh using custom LUT
};
// Initialize the display hardware and driver
void begin();
// Display dimensions
static constexpr uint16_t DISPLAY_WIDTH = 800;
static constexpr uint16_t DISPLAY_HEIGHT = 480;
static constexpr uint16_t DISPLAY_WIDTH_BYTES = DISPLAY_WIDTH / 8;
static constexpr uint32_t BUFFER_SIZE = DISPLAY_WIDTH_BYTES * DISPLAY_HEIGHT;
// Frame buffer operations
void clearScreen(uint8_t color = 0xFF) const;
void drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h, bool fromProgmem = false) const;
#ifndef EINK_DISPLAY_SINGLE_BUFFER_MODE
void swapBuffers();
#endif
void setFramebuffer(const uint8_t* bwBuffer) const;
void copyGrayscaleBuffers(const uint8_t* lsbBuffer, const uint8_t* msbBuffer);
void copyGrayscaleLsbBuffers(const uint8_t* lsbBuffer);
void copyGrayscaleMsbBuffers(const uint8_t* msbBuffer);
#ifdef EINK_DISPLAY_SINGLE_BUFFER_MODE
void cleanupGrayscaleBuffers(const uint8_t* bwBuffer);
#endif
void displayBuffer(RefreshMode mode = FAST_REFRESH);
// EXPERIMENTAL: Windowed update - display only a rectangular region
void displayWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void displayGrayBuffer(bool turnOffScreen = false);
void refreshDisplay(RefreshMode mode = FAST_REFRESH, bool turnOffScreen = false);
// debug function
void grayscaleRevert();
// LUT control
void setCustomLUT(bool enabled, const unsigned char* lutData = nullptr);
// Power management
void deepSleep();
// Access to frame buffer
uint8_t* getFrameBuffer() const {
return frameBuffer;
}
// Save the current framebuffer to a PBM file (desktop/test builds only)
void saveFrameBufferAsPBM(const char* filename);
private:
// Pin configuration
int8_t _sclk, _mosi, _cs, _dc, _rst, _busy;
// Frame buffer (statically allocated)
uint8_t frameBuffer0[BUFFER_SIZE];
uint8_t* frameBuffer;
#ifndef EINK_DISPLAY_SINGLE_BUFFER_MODE
uint8_t frameBuffer1[BUFFER_SIZE];
uint8_t* frameBufferActive;
#endif
// SPI settings
SPISettings spiSettings;
// State
bool isScreenOn;
bool customLutActive;
bool inGrayscaleMode;
bool drawGrayscale;
// Low-level display control
void resetDisplay();
void sendCommand(uint8_t command);
void sendData(uint8_t data);
void sendData(const uint8_t* data, uint16_t length);
void waitWhileBusy(const char* comment = nullptr);
void initDisplayController();
// Low-level display operations
void setRamArea(uint16_t x, uint16_t y, uint16_t w, uint16_t h);
void writeRamBuffer(uint8_t ramBuffer, const uint8_t* data, uint32_t size);
};