Copied verbatium from https://github.com/CidVonHighwind/microreader/blob/main/doc/SSD1677_GUIDE.md
19 KiB
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
- SPI Communication
- Initialization
- RAM Operations
- Writing Image Data
- Display Updates
- Custom LUT Guide
- Complete Workflows
- Command Reference
- 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
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):
-
Calculate reversed Y:
y_rev = HEIGHT - y - h -
Set RAM Entry Mode (Command
0x11):- Data:
0x01(X increment, Y decrement - Y reversed)
- Data:
-
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)
- Data[0]:
-
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)
- Data[0]:
-
Set RAM X Counter (Command
0x4E):- Data[0]:
x % 256(Initial X LSB) - Data[1]:
x / 256(Initial X MSB)
- Data[0]:
-
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)
- Data[0]:
Writing Image Data
Write to Current Buffer (Command 0x24)
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:
- Configure RAM area with
_setPartialRamArea(x, y, w, h) - Send command
0x24 - Start bulk transfer (CS=LOW)
- Transfer image data bytes (one bit per pixel, MSB first)
- Total bytes =
(w * h) / 8 0xFF= white,0x00= black
- Total bytes =
- 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
- Write to previous buffer:
_setPartialRamArea(0, 0, 800, 480)→ Command0x26→ 48000 bytes of0xFF - Write to current buffer:
_setPartialRamArea(0, 0, 800, 480)→ Command0x24→ 48000 bytes of0xFF - Perform full refresh
Full Frame Example
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
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, Hi‑Z)
- VCOM toggling pattern
- Duration of each phase (TP0–TP7)
- Phase repetitions
- Additional red-pixel handling
LUT Structure (111 bytes)
Used in the driver implementation for grayscale support:
| Byte Range | Size | Purpose | |------------|------|---------|| | 0–49 | 50 | VS waveforms (5 groups × 10 bytes) | | 50–99 | 50 | TP/RP timing groups (10 groups × 5 bytes) | | 100–104 | 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 (WS0–WS7)
You choose for each phase:
- VSH1 (medium positive)
- VSH2 (strong positive — drives white)
- VSL (strong negative — drives black)
- Hi‑Z (float)
These define pixel movement direction and strength.
Step 2 — Define VCOM Waveform (WS8–WS14)
VCOM biases the entire display.
These bytes define:
- On/off toggling per phase
- Matching with source driver phases
- Ghost reduction
Step 3 — Phase Timing TP0–TP7 (WS15–WS23)
Each TPx sets duration of a phase.
Longer = cleaner image, slower refresh.
Shorter = faster, but potential ghosting.
Step 4 — Repeat Counts & Finalization (WS24–WS33)
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.
// 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.
// 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
- Write with 0x32
- Enable with 0x22
- 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
- First pass (Black/White): Write BW framebuffer to both RAM buffers, perform standard refresh
- Second pass (Grayscale): Write LSB and MSB grayscale buffers, apply custom grayscale LUT, perform fast refresh
- 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 renderinglut_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
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:
// 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:
- Two internal buffers (
frameBuffer0andframeBuffer1) alternate as current/previous - On
displayBuffer(), current buffer written to BW RAM (0x24), previous to RED RAM (0x26) - Controller compares buffers and only updates changed pixels
- Buffers swap roles after each display
Auto-Write Commands for Fast Clear
Commands 0x46 and 0x47 allow rapid buffer clearing:
// 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)
- Execute Power Off sequence
- Send command
0x10(Deep Sleep Mode) - 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