# 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) - [Buffer Modes](#buffer-modes) - [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 --- # 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 ``` 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(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 ### 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 Buffering The driver supports two buffering modes for fast partial updates: ### Dual Buffer Mode (Default) **Memory usage:** 96KB (two 48KB buffers) ```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 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(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 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 - 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