644 lines
19 KiB
Markdown
644 lines
19 KiB
Markdown
|
|
# 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
|