Skip to content

Latest commit

 

History

History
647 lines (469 loc) · 19.6 KB

File metadata and controls

647 lines (469 loc) · 19.6 KB

SSD1677 E-Ink Display Driver Guide

GDEQ0426T82 (4.26" 800x480) on Xteink X4

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

  • Controller: SSD1677
  • Display: 800x480 pixels (100x60 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:

  1. 0x12 — Software Reset (SWRESET), then wait BUSY
  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)
  7. 0x46 0xF7 — Auto write BW RAM (clear to white), then wait BUSY
  8. 0x47 0xF7 — Auto write RED RAM (clear to white), then wait BUSY

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 960x680 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)

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

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

  • 0x22 0xE0 — Display update control sequence
  • 0x20 — Master activation (trigger update)
  • Wait ~100ms 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:

  1. 0x21 0x40, 0x00 — Display update control (bypass RED as 0, single chip)
  2. For fast mode: 0x1A 0x5A (temperature register), then 0x22 0xD7
  3. For normal mode: 0x22 0xF7 (extended temp)
  4. 0x20 — Master activation
  5. Wait ~1600ms 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 7 (0x80) CLOCK_ON — Start internal oscillator
  • Bit 6 (0x40) ANALOG_ON — Enable analog power rails (VGH/VGL drivers)
  • Bit 5 (0x20) TEMP_LOAD — Load temperature (internal or external)
  • Bit 4 (0x10) LUT_LOAD — Load waveform LUT
  • Bit 3 (0x08) MODE_SELECT — Mode 1/2 selection
  • Bit 2 (0x04) DISPLAY_START — Run display update
  • Bit 1 (0x02) ANALOG_OFF — Analog shutdown phase
  • Bit 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

  • 0x21 0x00, 0x00 — Display update control (RED normal, single chip)
  • 0x22 0xFC — Partial update sequence
  • 0x20 — Master activation
  • Wait ~600ms 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:

  • Bytes 0-49 (50 bytes) — VS waveforms (5 groups x 10 bytes)
  • Bytes 50-99 (50 bytes) — TP/RP timing groups (10 groups x 5 bytes)
  • Bytes 100-104 (5 bytes) — Frame rate control
  • Byte 105 — VGH (Gate voltage) - sent via 0x03
  • Byte 106 — VSH1 (Source voltage 1) - sent via 0x04
  • Byte 107 — VSH2 (Source voltage 2) - sent via 0x04
  • Byte 108 — VSL (Source voltage low) - sent via 0x04
  • Byte 109 — VCOM voltage - sent via 0x2C
  • Byte 110 — 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:

  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 x 10 bytes)
  • TP/RP timing: 50 bytes (10 groups x 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:

  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:

// 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

  • 0x01 Driver Output Control — Set gate scanning (HEIGHT)
  • 0x03 Gate Voltage — Set VGH voltage level
  • 0x04 Source Voltage — Set VSH1, VSH2, VSL voltages
  • 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
  • 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

  • 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

  • 0x22 0x83 — Power off sequence
  • 0x20 — Master activation
  • Wait ~200ms 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.


Sunlight Fading Issue

Problem

The XTEINK X4's SSD1677 display driver IC is packaged as "Gold Bump Die" without resin protection. This makes the IC susceptible to UV radiation. In bright sunlight, this causes the screen to fade to white.

White X4 devices are more affected than black ones due to lower UV absorption by the case.

Solution

Power down the display's VBUS after rendering each page. This is done by setting the analog shutdown bits in the Display Update Control 2 command (0x22):

// After refresh, power down display to prevent UV fading
sendCommand(0x22);
sendData(displayMode | 0x03);  // Set ANALOG_OFF_PHASE (bit 1) and CLOCK_OFF (bit 0)
sendCommand(0x20);
waitWhileBusy();

The firmware implements this as the "Sunlight Fading Fix" setting in Device Settings. When enabled:

  • Sets bits 0 and 1 in the 0x22 command after each refresh
  • Adds ~100-200ms overhead per page turn (power-on cycle)
  • Screen will power back on automatically for the next refresh

This fix was developed by the crosspoint-reader community.

Physical Alternative

For permanent protection, apply UV-blocking tape over the driver IC area on the display PCB.


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 (800x480 / 8)
  • Dual-buffer system enables differential partial updates
  • First write after init should be full refresh to clear ghost images
  • In sunlight: enable "Sunlight Fading Fix" setting to prevent UV-induced screen fading