# 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, 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. ```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