644 lines
19 KiB
Markdown
Raw Normal View History

# SSD1677 E-Ink Display Driver Guide
## GDEQ0426T82 (4.26" 800×480) on Xteink X4
> **Note:** This document was AI-generated by consolidating multiple reference documents. Please verify critical details against the official SSD1677 datasheet.
Complete reference for programming the SSD1677 e-paper display controller, including initialization, image updates, custom LUT creation, and low-level protocol details.
Based on the GxEPD2_426_GDEQ0426T82 driver implementation.
---
# Table of Contents
- [Hardware Configuration](#hardware-configuration)
- [SPI Communication](#spi-communication)
- [Initialization](#initialization)
- [RAM Operations](#ram-operations)
- [Writing Image Data](#writing-image-data)
- [Display Updates](#display-updates)
- [Custom LUT Guide](#custom-lut-guide)
- [Complete Workflows](#complete-workflows)
- [Command Reference](#command-reference)
- [Timing & Power Management](#timing--power-management)
---
# Hardware Configuration
- **Controller**: SSD1677
- **Display**: 800×480 pixels (100×60 bytes = 48,000 bytes)
- **SPI Pins**: SCLK=8, MOSI=10, CS=21, DC=4, RST=5, BUSY=6
- **SPI Settings**: 40MHz (spec: 20MHz, but 40MHz works), MSB First, SPI Mode 0
---
# SPI Communication
## Low-Level Protocol
### Command Format
```
DC=LOW, CS=LOW → Transfer(command_byte) → CS=HIGH, DC=HIGH
```
### Data Format
```
DC=HIGH, CS=LOW → Transfer(data_byte) → CS=HIGH
```
### Bulk Transfer Format
```
CS=LOW → Transfer(byte1) → Transfer(byte2) → ... → CS=HIGH
```
## Reset Sequence
```
RST=HIGH → delay(20ms) → RST=LOW → delay(2ms) → RST=HIGH → delay(20ms)
```
---
# Initialization
## Minimal Initialization Example
```c
void ssd1677_init() {
// 1. Software Reset
epd_cmd(0x12); // SWRESET
epd_wait_busy(); // Wait for busy to clear
// 2. Driver Output Control
epd_cmd(0x01);
epd_data(0xA7); // 680 rows -> 0x2A7, low byte
epd_data(0x02); // high byte
epd_data(0x00); // GD=0, SM=0, TB=0
// 3. Data Entry Mode
epd_cmd(0x11);
epd_data(0x03); // X+, Y+
// 4. RAM X Start/End
epd_cmd(0x44);
epd_data(0x00); // X start = 0
epd_data(0x3B); // X end = 959 / 8 = 0x3B
// 5. RAM Y Start/End
epd_cmd(0x45);
epd_data(0x00); // Y start (low byte)
epd_data(0x00); // Y start (high byte)
epd_data(0xA7); // Y end (low byte)
epd_data(0x02); // Y end (high byte)
// 6. Border Control
epd_cmd(0x3C);
epd_data(0xC0); // Border = Hi-Z
// 7. Temperature Sensor (internal)
epd_cmd(0x18);
epd_data(0x80);
}
```
## Detailed Initialization Sequence
After reset, then:
| Step | Command | Data | Description |
|------|---------|------|-------------|
| 1 | `0x12` | - | Software Reset (SWRESET) |
| - | Wait BUSY | - | Wait for reset to complete |
| 2 | `0x18` | `0x80` | Temperature sensor control (internal) |
| 3 | `0x0C` | `0xAE`, `0xC7`, `0xC3`, `0xC0`, `0x40` | Booster soft start configuration |
| 4 | `0x01` | `0xDF`, `0x01`, `0x02` | Driver output control (479 gates = HEIGHT-1) |
| 5 | `0x3C` | `0x01` | Border waveform control |
| 6 | Set RAM area | See below | Configure full screen area |
| 7 | `0x46` | `0xF7` | Auto write BW RAM (clear to white) |
| 8 | Wait BUSY | - | Wait for auto-write to complete |
| 9 | `0x47` | `0xF7` | Auto write RED RAM (clear to white) |
| 10 | Wait BUSY | - | Wait for auto-write to complete |
### Command Explanations
**Software Reset (0x12)**
Resets the internal registers (except deep sleep). Mandatory after power-up.
**Driver Output Control (0x01)**
Sets display height and scan direction.
- Byte 1: `(HEIGHT - 1) % 256` = `0xDF` (479 & 0xFF)
- Byte 2: `(HEIGHT - 1) / 256` = `0x01` (479 >> 8)
- Byte 3: `0x02` (interlaced/SM mode)
**Data Entry Mode (0x11)**
Controls RAM addressing: `0x03 = X increment, Y increment`.
**Set RAM Window (0x44 & 0x45)**
Defines the region written during RAM writes.
For full 960×680 screen, X=0..0x3B, Y=0..0x2A7.
**Border Waveform (0x3C)**
Controls VBD (border pixel behavior). `0xC0 = Hi-Z`, common default.
**Temperature Sensor (0x18)**
`0x80 = use internal sensor`.
---
# RAM Operations
## RAM Area Configuration
Sets the window for subsequent RAM writes. Y-coordinates are reversed due to hardware gates orientation.
**Important:** X addresses are specified in **pixels**, not bytes. The controller handles the byte conversion internally.
**For coordinates (x, y, w, h):**
1. Calculate reversed Y: `y_rev = HEIGHT - y - h`
2. **Set RAM Entry Mode** (Command `0x11`):
- Data: `0x01` (X increment, Y decrement - Y reversed)
3. **Set RAM X Address** (Command `0x44`) - **In pixels**:
- Data[0]: `x % 256` (X start LSB)
- Data[1]: `x / 256` (X start MSB)
- Data[2]: `(x + w - 1) % 256` (X end LSB)
- Data[3]: `(x + w - 1) / 256` (X end MSB)
4. **Set RAM Y Address** (Command `0x45`):
- Data[0]: `(y_rev + h - 1) % 256` (Y start LSB)
- Data[1]: `(y_rev + h - 1) / 256` (Y start MSB)
- Data[2]: `y_rev % 256` (Y end LSB)
- Data[3]: `y_rev / 256` (Y end MSB)
5. **Set RAM X Counter** (Command `0x4E`):
- Data[0]: `x % 256` (Initial X LSB)
- Data[1]: `x / 256` (Initial X MSB)
6. **Set RAM Y Counter** (Command `0x4F`):
- Data[0]: `(y_rev + h - 1) % 256` (Initial Y LSB)
- Data[1]: `(y_rev + h - 1) / 256` (Initial Y MSB)
---
# Writing Image Data
## Write to Current Buffer (Command 0x24)
```c
void ssd1677_write_bw(uint8_t *buffer, uint32_t size) {
// Set RAM Address Counters
epd_cmd(0x4E); // X counter
epd_data(0x00);
epd_cmd(0x4F); // Y counter
epd_data(0x00);
epd_data(0x00);
// Write BW RAM
epd_cmd(0x24);
for (uint32_t i = 0; i < size; i++)
epd_data(buffer[i]);
}
```
**Process:**
1. Configure RAM area with `_setPartialRamArea(x, y, w, h)`
2. Send command `0x24`
3. Start bulk transfer (CS=LOW)
4. Transfer image data bytes (one bit per pixel, MSB first)
- Total bytes = `(w * h) / 8`
- `0xFF` = white, `0x00` = black
5. End transfer (CS=HIGH)
**Explanation:**
- **0x4E / 0x4F** set starting address for RAM.
- **0x24** selects the BW image buffer.
## Write to Previous Buffer (Command 0x26)
Same as above but use command `0x26` instead of `0x24`. Used for differential updates.
## Full Screen Clear
1. Write to previous buffer: `_setPartialRamArea(0, 0, 800, 480)` → Command `0x26` → 48000 bytes of `0xFF`
2. Write to current buffer: `_setPartialRamArea(0, 0, 800, 480)` → Command `0x24` → 48000 bytes of `0xFF`
3. Perform full refresh
## Full Frame Example
```c
void ssd1677_display_frame(uint8_t *bw, uint8_t *red) {
ssd1677_write_bw(bw, BW_BUFFER_SIZE);
epd_cmd(0x26); // Write RED RAM
for (int i = 0; i < RED_BUFFER_SIZE; i++)
epd_data(red[i]);
ssd1677_update();
}
```
---
# Display Updates
## Power On
| Command | Data | Description |
|---------|------|-------------|
| `0x22` | `0xE0` | Display update control sequence |
| `0x20` | - | Master activation (trigger update) |
| Wait | ~100ms | Wait while BUSY pin is HIGH |
## Full Refresh
```c
void ssd1677_update() {
epd_cmd(0x22);
epd_data(0xC7); // Display mode: load LUT + refresh + power off
epd_cmd(0x20); // Master activation
epd_wait_busy(); // Wait for driving waves to complete
}
```
**Detailed Sequence:**
| Step | Command | Data | Description |
|------|---------|------|-------------|
| 1 | `0x21` | `0x40`, `0x00` | Display update control (bypass RED as 0, single chip) |
| 2a | `0x1A` | `0x5A` | Temperature register (fast mode only) |
| 2b | `0x22` | `0xD7` | Update sequence (fast mode) |
| **OR** | | | |
| 2b | `0x22` | `0xF7` | Update sequence (normal mode, extended temp) |
| 3 | `0x20` | - | Master activation |
| Wait | ~1600ms | Wait while BUSY pin is HIGH |
**Explanation:**
- **0x22 / 0xC7** tells SSD1677 which tasks to run (enable analog, load LUT, drive display).
- **0x20** starts the entire update cycle.
- **epd_wait_busy()** waits until the driver finishes waveform driving.
**Fast vs Normal Mode**: `useFastFullUpdate=true` uses faster refresh but limited temperature range.
### Display Update Control 2 (0x22) Bit Documentation
Based on driver implementation analysis:
| Bit | Hex | Name | Effect |
|-----|-----|------|--------|
| 7 | 0x80 | CLOCK_ON | Start internal oscillator |
| 6 | 0x40 | ANALOG_ON | Enable analog power rails (VGH/VGL drivers) |
| 5 | 0x20 | TEMP_LOAD | Load temperature (internal or external) |
| 4 | 0x10 | LUT_LOAD | Load waveform LUT |
| 3 | 0x08 | MODE_SELECT | Mode 1/2 selection |
| 2 | 0x04 | DISPLAY_START | Run display update |
| 1 | 0x02 | ANALOG_OFF | Analog shutdown phase |
| 0 | 0x01 | CLOCK_OFF | Disable internal oscillator |
**Common Patterns:**
- Full refresh (first power on): `0xC0 | 0x34 = 0xF4` (CLOCK+ANALOG+TEMP+LUT+DISPLAY)
- Full refresh (already on): `0x34` (TEMP+LUT+DISPLAY)
- Half refresh with high temp: `0xD4` (CLOCK+ANALOG+LUT+DISPLAY)
- Fast refresh with custom LUT: `0x0C` (MODE+DISPLAY)
- Fast refresh without custom LUT: `0x1C` (LUT+MODE+DISPLAY)
## Partial Refresh
| Command | Data | Description |
|---------|------|-------------|
| `0x21` | `0x00`, `0x00` | Display update control (RED normal, single chip) |
| `0x22` | `0xFC` | Partial update sequence |
| `0x20` | - | Master activation |
| Wait | ~600ms | Wait while BUSY pin is HIGH |
---
# Custom LUT Guide
## What is a LUT?
The SSD1677 uses a **Look-Up Table (LUT)** to control **pixel waveform driving** during updates.
Each pixel (BW/RED) needs a sequence of voltage phases to switch states correctly.
A LUT controls:
- Voltage level per phase (VSH1, VSH2, VSL, HiZ)
- VCOM toggling pattern
- Duration of each phase (TP0TP7)
- Phase repetitions
- Additional red-pixel handling
## LUT Structure (111 bytes)
Used in the driver implementation for grayscale support:
| Byte Range | Size | Purpose |
|------------|------|---------||
| 049 | 50 | VS waveforms (5 groups × 10 bytes) |
| 5099 | 50 | TP/RP timing groups (10 groups × 5 bytes) |
| 100104 | 5 | Frame rate control |
| 105 | 1 | VGH (Gate voltage) - sent via 0x03 |
| 106 | 1 | VSH1 (Source voltage 1) - sent via 0x04 |
| 107 | 1 | VSH2 (Source voltage 2) - sent via 0x04 |
| 108 | 1 | VSL (Source voltage low) - sent via 0x04 |
| 109 | 1 | VCOM voltage - sent via 0x2C |
| 110 | 1 | Reserved |
**Note:** Bytes 105-109 are sent using separate voltage control commands after loading the main LUT.
## How to Build a Custom LUT
### Step 1 — Define Source Voltage Waveform (WS0WS7)
You choose for each phase:
- VSH1 (medium positive)
- VSH2 (strong positive — drives white)
- VSL (strong negative — drives black)
- HiZ (float)
These define **pixel movement direction** and strength.
### Step 2 — Define VCOM Waveform (WS8WS14)
VCOM biases the entire display.
These bytes define:
- On/off toggling per phase
- Matching with source driver phases
- Ghost reduction
### Step 3 — Phase Timing TP0TP7 (WS15WS23)
Each TPx sets duration of a phase.
Longer = cleaner image, slower refresh.
Shorter = faster, but potential ghosting.
### Step 4 — Repeat Counts & Finalization (WS24WS33)
These adjust:
- How many times each phase repeats
- Red pigment extra driving
- Cleanup phases
## How to Load a Custom LUT
A custom LUT is written using **Command 0x32**:
```
CMD 0x32
DATA WS0
DATA WS1
...
DATA WS33
```
The first **105 bytes** are written to the LUT register (0x32), followed by separate voltage control commands.
```c
// Load LUT (111-byte format with voltage controls)
void ssd1677_load_lut_extended(const uint8_t* lut) {
// Load main LUT (105 bytes: VS + TP/RP + frame rate)
epd_cmd(0x32);
for (int i = 0; i < 105; i++)
epd_data(lut[i]);
// Set voltage values from bytes 105-109
epd_cmd(0x03); // Gate voltage (VGH)
epd_data(lut[105]);
epd_cmd(0x04); // Source voltages (VSH1, VSH2, VSL)
epd_data(lut[106]); // VSH1
epd_data(lut[107]); // VSH2
epd_data(lut[108]); // VSL
epd_cmd(0x2C); // VCOM voltage
epd_data(lut[109]);
}
```
## How to Apply (Use) the Custom LUT
After loading the LUT, tell the display to **use it**.
### 1. Configure Display Update Mode (0x22)
Typical value enabling LUT usage:
```
CMD 0x22
DATA 0xF7
```
### 2. Start Master Activation (0x20)
```
CMD 0x20
WAIT BUSY = LOW
```
While BUSY is high, the LUT waveform is driving the display.
```c
// Apply LUT
void ssd1677_apply_lut() {
epd_cmd(0x22);
epd_data(0xF7); // Use LUT
epd_cmd(0x20); // Master Activation
epd_wait_busy();
}
```
## LUT Summary
**Build a custom LUT**
- Create 111 bytes: 105 for LUT register + 5 voltage values + 1 reserved
**Use a custom LUT**
1. Write with **0x32**
2. Enable with **0x22**
3. Trigger with **0x20**
**Optional**
- Burn to OTP with **0x36**
## Grayscale Rendering with Custom LUTs
The driver implements 4-level grayscale using a multi-pass technique with custom LUTs.
### Grayscale Principle
1. **First pass (Black/White):** Write BW framebuffer to both RAM buffers, perform standard refresh
2. **Second pass (Grayscale):** Write LSB and MSB grayscale buffers, apply custom grayscale LUT, perform fast refresh
3. The custom LUT creates intermediate gray levels by controlling pixel voltage phases
### Grayscale LUT Structure
The driver includes two grayscale LUTs (111 bytes each):
- `lut_grayscale`: Forward grayscale rendering
- `lut_grayscale_revert`: Cleans up grayscale artifacts back to pure BW
Key characteristics:
- Uses different voltage sequences for 4 gray levels (00, 01, 10, 11)
- Frame timing optimized for fast refresh (~500ms)
- VS waveforms: 50 bytes (5 groups × 10 bytes)
- TP/RP timing: 50 bytes (10 groups × 5 bytes)
- Voltages: VGH=0x17, VSH1=0x41, VSH2=0xA8, VSL=0x32, VCOM=0x30
---
# Complete Workflows
## Full Screen Update (Initial or Complete Refresh)
```
1. _InitDisplay() [if not initialized]
2. _setPartialRamArea(0, 0, 800, 480)
3. Write to previous buffer: Command 0x26 + 48000 bytes
4. Write to current buffer: Command 0x24 + 48000 bytes
5. _Update_Full()
```
## Partial Update (Fast Refresh)
```
1. _InitDisplay() [if not initialized]
2. [First time only] Clear screen buffers with full refresh
3. _setPartialRamArea(x, y, w, h)
4. Write image: Command 0x24 + image bytes
5. _Update_Part()
6. Write image again: Command 0x24 + image bytes
7. Write to previous: Command 0x26 + same image bytes
```
**Why write twice?** Partial updates compare current vs previous buffer. Writing to both buffers after refresh prevents ghosting on next update.
## Minimal Usage Example
```c
ssd1677_init();
ssd1677_display_frame(bw_image, red_image);
```
## Complete Example: Fast Refresh with Double Buffering
The driver implements double buffering to enable fast partial updates:
```cpp
// Initialize display
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)
display.displayBuffer(FAST_REFRESH);
// Next frame
// ... modify fb ...
display.displayBuffer(FAST_REFRESH);
```
**How it works:**
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
## Auto-Write Commands for Fast Clear
Commands `0x46` and `0x47` allow rapid buffer clearing:
```c
// Clear BW RAM to white pattern
sendCommand(0x46); // Auto write BW RAM
sendData(0xF7); // Fill pattern
waitWhileBusy();
// Clear RED RAM to white pattern
sendCommand(0x47); // Auto write RED RAM
sendData(0xF7); // Fill pattern
waitWhileBusy();
```
This is much faster than writing 48,000 bytes manually during initialization.
---
# Command Reference
| Command | Name | Purpose |
|---------|------|---------|
| `0x01` | Driver Output Control | Set gate scanning (HEIGHT) |
| `0x0C` | Booster Soft Start | Configure boost converter |
| `0x10` | Deep Sleep Mode | Enter low power mode |
| `0x11` | Data Entry Mode | Set X/Y increment direction |
| `0x12` | Software Reset | Reset controller |
| `0x18` | Temperature Sensor | Control temp sensor |
| `0x1A` | Temperature Register | Set temp value (fast mode) |
| `0x20` | Master Activation | Trigger display update |
| `0x21` | Display Update Control | Configure update mode |
| `0x22` | Display Update Sequence | Set update waveform |
| `0x24` | Write RAM (BW) | Write to current buffer |
| `0x26` | Write RAM (RED/OLD) | Write to previous buffer |
| `0x03` | Gate Voltage | Set VGH voltage level |
| `0x04` | Source Voltage | Set VSH1, VSH2, VSL voltages |
| `0x2C` | Write VCOM | Set VCOM voltage |
| `0x32` | Write LUT Register | Load custom 105-byte LUT (part of 111-byte structure) |
| `0x36` | Write OTP | Burn LUT to one-time-programmable memory |
| `0x3C` | Border Waveform | Configure border behavior |
| `0x44` | Set RAM X Address | Define X window (in pixels) |
| `0x45` | Set RAM Y Address | Define Y window (in pixels) |
| `0x46` | Auto Write BW RAM | Fast fill BW RAM with pattern |
| `0x47` | Auto Write RED RAM | Fast fill RED RAM with pattern |
| `0x4E` | Set RAM X Counter | Set initial X position (in pixels) |
| `0x4F` | Set RAM Y Counter | Set initial Y position (in pixels) |
---
# Timing & Power Management
## Timing Specifications
| Operation | Duration | Notes |
|-----------|----------|-------|
| Reset pulse | 10ms | Low duration |
| Power on | ~100ms | BUSY signal duration |
| Power off | ~200ms | BUSY signal duration |
| Full refresh | ~1600ms | Normal mode, wait for BUSY |
| Partial refresh | ~600ms | Wait for BUSY |
| Software reset delay | 10ms | After command 0x12 |
## BUSY Signal Monitoring
- **Pin**: GPIO6 (INPUT)
- **Active level**: HIGH
- **Polling**: Read pin until LOW, with timeout protection
- **Timeout**: 10000ms (10 seconds)
- **Usage**: Wait after commands `0x20` (master activation)
## Power Off
| Command | Data | Description |
|---------|------|-------------|
| `0x22` | `0x83` | Power off sequence |
| `0x20` | - | Master activation |
| Wait | ~200ms | Wait while BUSY pin is HIGH |
## Hibernate (Deep Sleep)
1. Execute Power Off sequence
2. Send command `0x10` (Deep Sleep Mode)
3. Send data `0x01` (Enter deep sleep)
**Wake from Hibernate**: Requires hardware reset via RST pin.
---
# Important Notes
- BUSY pin *must* be polled after reset and update
- All RAM writes auto-increment based on data entry mode
- SSD1677 can display BW-only or RED-only if desired
- 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
- First write after init should be full refresh to clear ghost images