Add a few details in the readme and update the SSD1677_GUIDE
This commit is contained in:
parent
56796aa6d3
commit
c831938fca
78
libs/display/EInkDisplay/README.md
Normal file
78
libs/display/EInkDisplay/README.md
Normal file
@ -0,0 +1,78 @@
|
||||
# EInkDisplay library
|
||||
|
||||
This is a focused low-level interaction library for the X4 display.
|
||||
|
||||
It's best paried with a higher-level GFX library to help with rendering shapes, text, and images.
|
||||
Along with dealing with display orientation.
|
||||
|
||||
See the [SSD1677 guide](./doc/SSD1677_GUIDE.md) for details on the implemention.
|
||||
|
||||
## Usage
|
||||
|
||||
### Setup
|
||||
|
||||
```cpp
|
||||
// Display SPI pins (custom pins for XteinkX4, not hardware SPI defaults)
|
||||
#define EPD_SCLK 8 // SPI Clock
|
||||
#define EPD_MOSI 10 // SPI MOSI (Master Out Slave In)
|
||||
#define EPD_CS 21 // Chip Select
|
||||
#define EPD_DC 4 // Data/Command
|
||||
#define EPD_RST 5 // Reset
|
||||
#define EPD_BUSY 6 // Busy
|
||||
|
||||
EInkDisplay display(EPD_SCLK, EPD_MOSI, EPD_CS, EPD_DC, EPD_RST, EPD_BUSY);
|
||||
display.begin();
|
||||
```
|
||||
|
||||
### Rendering black and white frames
|
||||
|
||||
```cpp
|
||||
// First frame
|
||||
display.clearScreen();
|
||||
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
||||
// ... your drawing code here, writing to frameBuffer, a 0 bit is black, a 1 bit is white ...
|
||||
display.displayBuffer(FAST_REFRESH);
|
||||
|
||||
// Next frame
|
||||
display.clearScreen();
|
||||
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
||||
// ... your drawing code here, writing to frameBuffer, a 0 bit is black, a 1 bit is white ...
|
||||
// Using fash refresh for fast updates
|
||||
display.displayBuffer(FAST_REFRESH);
|
||||
```
|
||||
|
||||
### Rendering greyscale frames
|
||||
|
||||
```cpp
|
||||
display.clearScreen();
|
||||
uint8_t* frameBuffer = einkDisplay.getFrameBuffer();
|
||||
// ... regular drawing code from before, all gray pixels should also be marked as black ...
|
||||
display.displayBuffer(FAST_REFRESH);
|
||||
|
||||
|
||||
// Grayscale rendering
|
||||
// Refetch the frame buffer to ensure it's up to date
|
||||
frameBuffer = einkDisplay.getFrameBuffer();
|
||||
|
||||
// Dark gray rendering
|
||||
// Clear the screen with 0, all 0 bits are considered out of bounds for grayscale rendering
|
||||
// Only mark the bits you want to be gray as 1
|
||||
display.clearScreen(0x00);
|
||||
// ... exact same screen content as before, but only mark the **dark** grays pixels with `1`, rest leave as `0`
|
||||
display.copyGrayscaleLsbBuffers(frameBuffer);
|
||||
|
||||
display.clearScreen(0x00);
|
||||
// ... exact same screen content as before, but mark the **light and dark** grays pixels with `1`, rest leave as `0`
|
||||
display.copyGrayscaleMsbBuffers(frameBuffer);
|
||||
display.displayGrayBuffer();
|
||||
|
||||
// All done :)
|
||||
```
|
||||
|
||||
### Power off
|
||||
|
||||
To ensure the display locks the image in, it's important to power off the display before exiting the program.
|
||||
|
||||
```cpp
|
||||
display.deepSleep();
|
||||
```
|
||||
@ -11,6 +11,7 @@ Based on the GxEPD2_426_GDEQ0426T82 driver implementation.
|
||||
|
||||
# Table of Contents
|
||||
- [Hardware Configuration](#hardware-configuration)
|
||||
- [Buffer Modes](#buffer-modes)
|
||||
- [SPI Communication](#spi-communication)
|
||||
- [Initialization](#initialization)
|
||||
- [RAM Operations](#ram-operations)
|
||||
@ -31,6 +32,111 @@ Based on the GxEPD2_426_GDEQ0426T82 driver implementation.
|
||||
|
||||
---
|
||||
|
||||
# Buffer Modes
|
||||
|
||||
The EInkDisplay driver supports two buffering modes, selectable at compile time:
|
||||
|
||||
## Dual Buffer Mode (Default)
|
||||
|
||||
**Memory usage:** 96KB (two 48KB framebuffers)
|
||||
|
||||
- Two framebuffers in ESP32 RAM: `frameBuffer0` and `frameBuffer1`
|
||||
- Buffers alternate roles as "current" and "previous" using `swapBuffers()`
|
||||
- On each `displayBuffer()`:
|
||||
- Current buffer → BW RAM (0x24)
|
||||
- Previous buffer → RED RAM (0x26)
|
||||
- Display controller compares them for differential fast refresh
|
||||
- Buffers swap roles
|
||||
- **Advantage:** Fast buffer switching with no overhead
|
||||
- **Disadvantage:** Uses 96KB of precious RAM
|
||||
|
||||
## Single Buffer Mode (Memory Optimized)
|
||||
|
||||
**Memory usage:** 48KB (one 48KB framebuffer)
|
||||
|
||||
Enable by defining `EINK_DISPLAY_SINGLE_BUFFER_MODE` before including EInkDisplay.h:
|
||||
|
||||
```cpp
|
||||
#define EINK_DISPLAY_SINGLE_BUFFER_MODE
|
||||
#include <EInkDisplay.h>
|
||||
```
|
||||
|
||||
Or in your build system (e.g., platformio.ini):
|
||||
|
||||
```ini
|
||||
build_flags =
|
||||
-D EINK_DISPLAY_SINGLE_BUFFER_MODE
|
||||
```
|
||||
|
||||
- Single framebuffer in ESP32 RAM: `frameBuffer0`
|
||||
- Display's internal RED RAM acts as "previous frame" storage
|
||||
- On each `displayBuffer()`:
|
||||
- **FAST_REFRESH:**
|
||||
- New frame → BW RAM (0x24)
|
||||
- Display refresh (compares BW vs existing RED RAM)
|
||||
- New frame → RED RAM (0x26) - syncs for next refresh
|
||||
- **HALF/FULL_REFRESH:**
|
||||
- New frame → both BW RAM and RED RAM (0x24 and 0x26)
|
||||
- Display refresh
|
||||
- Extra RED RAM sync (already contains correct frame)
|
||||
- **Advantages:**
|
||||
- Saves 48KB RAM (critical for ESP32-C3 with ~380KB usable)
|
||||
- Fast refresh still works via differential updates
|
||||
- **Disadvantages:**
|
||||
- Extra RED RAM write after each refresh (~100ms overhead)
|
||||
- Grayscale rendering requires temporary buffer allocation
|
||||
- `swapBuffers()` not available (not needed)
|
||||
|
||||
## Choosing a Mode
|
||||
|
||||
**Use Dual Buffer Mode if:**
|
||||
- RAM is plentiful
|
||||
- You want absolute minimum latency
|
||||
- You don't need every KB of RAM
|
||||
|
||||
**Use Single Buffer Mode if:**
|
||||
- Running on memory-constrained hardware (like ESP32-C3)
|
||||
- 48KB RAM savings is critical for your application
|
||||
- ~100ms extra per refresh is acceptable
|
||||
|
||||
## API Differences Between Modes
|
||||
|
||||
Most of the API is identical between modes, but there are a few differences:
|
||||
|
||||
| Feature | Dual Buffer | Single Buffer |
|
||||
|---------|-------------|---------------|
|
||||
| `getFrameBuffer()` | ✓ Available | ✓ Available |
|
||||
| `clearScreen()` | ✓ Available | ✓ Available |
|
||||
| `displayBuffer()` | ✓ Available | ✓ Available |
|
||||
| `swapBuffers()` | ✓ Available | ✗ Not available |
|
||||
| `cleanupGrayscaleBuffers()` | ✗ Not needed | ✓ Required after grayscale |
|
||||
| Memory overhead | 96KB always | 48KB + temp 48KB during grayscale |
|
||||
|
||||
**Code that works in both modes:**
|
||||
```cpp
|
||||
display.begin();
|
||||
display.clearScreen();
|
||||
display.displayBuffer(FAST_REFRESH);
|
||||
```
|
||||
|
||||
**Code specific to single buffer mode:**
|
||||
|
||||
(This is not required in dual buffer mode)
|
||||
|
||||
```cpp
|
||||
// Before grayscale
|
||||
const auto bwBuffer = static_cast<uint8_t *>(malloc(EInkDisplay::BUFFER_SIZE));
|
||||
memcpy(bwBuffer, display.getFrameBuffer(), EInkDisplay::BUFFER_SIZE);
|
||||
|
||||
// ... grayscale rendering ...
|
||||
|
||||
// After grayscale
|
||||
display.cleanupGrayscaleBuffers(bwBuffer);
|
||||
free(bwBuffer);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# SPI Communication
|
||||
|
||||
## Low-Level Protocol
|
||||
@ -512,9 +618,13 @@ ssd1677_init();
|
||||
ssd1677_display_frame(bw_image, red_image);
|
||||
```
|
||||
|
||||
## Complete Example: Fast Refresh with Double Buffering
|
||||
## Complete Example: Fast Refresh with Buffering
|
||||
|
||||
The driver implements double buffering to enable fast partial updates:
|
||||
The driver supports two buffering modes for fast partial updates:
|
||||
|
||||
### Dual Buffer Mode (Default)
|
||||
|
||||
**Memory usage:** 96KB (two 48KB buffers)
|
||||
|
||||
```cpp
|
||||
// Initialize display
|
||||
@ -538,7 +648,71 @@ display.displayBuffer(FAST_REFRESH);
|
||||
1. Two internal buffers (`frameBuffer0` and `frameBuffer1`) alternate as current/previous
|
||||
2. On `displayBuffer()`, current buffer written to BW RAM (0x24), previous to RED RAM (0x26)
|
||||
3. Controller compares buffers and only updates changed pixels
|
||||
4. Buffers swap roles after each display
|
||||
4. Buffers swap roles after each display using `swapBuffers()`
|
||||
|
||||
### Single Buffer Mode (Memory Optimized)
|
||||
|
||||
**Memory usage:** 48KB (one 48KB buffer) - saves 48KB RAM
|
||||
|
||||
Enable by defining `EINK_DISPLAY_SINGLE_BUFFER_MODE` before including EInkDisplay.h.
|
||||
|
||||
```cpp
|
||||
// Initialize display (same as dual buffer)
|
||||
display.begin();
|
||||
display.clearScreen(0xFF);
|
||||
display.displayBuffer(FULL_REFRESH);
|
||||
|
||||
// Draw content to framebuffer
|
||||
uint8_t* fb = display.getFrameBuffer();
|
||||
// ... draw into fb ...
|
||||
|
||||
// Fast refresh (compares with previous frame in display's RED RAM)
|
||||
display.displayBuffer(FAST_REFRESH);
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
1. Only one internal buffer (`frameBuffer0`)
|
||||
2. On `displayBuffer()`:
|
||||
- **FAST_REFRESH:** Write new frame to BW RAM (0x24), RED RAM already contains previous frame from last refresh
|
||||
- **HALF/FULL_REFRESH:** Write new frame to both BW and RED RAM (0x24 and 0x26)
|
||||
- After refresh, always sync RED RAM with current frame for next differential update
|
||||
3. Controller compares BW RAM (new) vs RED RAM (old) for differential updates
|
||||
4. RED RAM acts as the "previous frame buffer" for fast refresh
|
||||
|
||||
**Trade-offs:**
|
||||
- **Pro:** Saves 48KB of RAM (critical for ESP32-C3 with only ~380KB usable)
|
||||
- **Con:** Extra RED RAM write after each refresh (~100ms overhead)
|
||||
- **Con:** Cannot preserve screen content during grayscale rendering without external buffer
|
||||
|
||||
### Grayscale Rendering in Single Buffer Mode
|
||||
|
||||
Single buffer mode requires special handling for grayscale rendering since the BW framebuffer is overwritten:
|
||||
|
||||
```cpp
|
||||
// Store BW buffer after the BW render but before grayscale render
|
||||
const auto bwBuffer = static_cast<uint8_t *>(malloc(EInkDisplay::BUFFER_SIZE));
|
||||
const auto frameBuffer = display.getFrameBuffer();
|
||||
memcpy(bwBuffer, frameBuffer, EInkDisplay::BUFFER_SIZE);
|
||||
|
||||
// Perform grayscale rendering (overwrites BW and RED RAM)
|
||||
display.clearScreen(0x00);
|
||||
// ... render grayscale LSB to frameBuffer ...
|
||||
display.copyGrayscaleMsbBuffers(frameBuffer);
|
||||
|
||||
display.clearScreen(0x00);
|
||||
// ... render grayscale MSB to frameBuffer ...
|
||||
display.copyGrayscaleLsbBuffers(frameBuffer);
|
||||
|
||||
// Display grays
|
||||
display.displayGrayBuffer();
|
||||
|
||||
// After grayscale render
|
||||
display.cleanupGrayscaleBuffers(bwBuffer);
|
||||
free(bwBuffer);
|
||||
```
|
||||
|
||||
The `cleanupGrayscaleBuffers()` method restores the BW buffer to both the framebuffer and RED RAM, ensuring proper state
|
||||
for subsequent fast refreshes
|
||||
|
||||
## Auto-Write Commands for Fast Clear
|
||||
|
||||
@ -638,6 +812,11 @@ This is much faster than writing 48,000 bytes manually during initialization.
|
||||
- All X coordinates and widths must be multiples of 8 (byte boundaries)
|
||||
- Y coordinates are reversed in hardware (gates bottom-to-top)
|
||||
- RAM auto-increments after each byte transfer
|
||||
- Total RAM size: 48,000 bytes (800×480 ÷ 8)
|
||||
- Dual-buffer system enables differential partial updates
|
||||
- Display controller internal RAM: 96,000 bytes (800×480 ÷ 8 * 2 buffers: BW + RED)
|
||||
- ESP32 buffer memory usage:
|
||||
- **Dual buffer mode:** 96KB (two 48KB buffers for fast buffer swapping)
|
||||
- **Single buffer mode:** 48KB (one 48KB buffer, uses display's RED RAM for differential)
|
||||
- Differential partial updates require RED RAM to contain the previous frame
|
||||
- First write after init should be full refresh to clear ghost images
|
||||
- Single buffer mode adds ~100ms overhead per refresh (extra RED RAM sync)
|
||||
- Grayscale rendering in single buffer mode requires temporary 48KB allocation
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user