ESP-IDF project for an ESP32 driving an SSD1619A 4.2-inch 400x300 black/white/red e-paper panel through hardware SPI.
The app displays a sectioned driver test page covering colors, geometry, pixels, bitmaps, UTF-8 Chinese text, and scaled image rendering.
Default wiring:
| ESP32 | SSD1619A panel |
|---|---|
| GPIO18 | SCL / SCK / CLK |
| GPIO23 | SDA / DIN / MOSI |
| GPIO16 | RES# / RST |
| GPIO17 | D/C# |
| GPIO5 | CS# |
| GPIO19 | BUSY |
| 3V3 | VCC / VCI / VDDIO |
| GND | GND |
Required strap pins:
| Panel pin | Level | Meaning |
|---|---|---|
| BS1 | GND | 4-wire SPI |
| M/S# | 3.3V | Single-chip master mode |
.
├── CMakeLists.txt
├── main
│ ├── CMakeLists.txt
│ ├── main.c
│ ├── epd_image.h
│ └── drivers
│ └── epd
│ ├── epd_ssd1619a.c
│ ├── epd_ssd1619a.h
│ ├── sarasa_subset.c
│ └── sarasa_subset.h
main/assets/fonts/SarasaMonoSC.c is intentionally ignored by Git because the full LVGL-generated font is large and makes the firmware exceed the default 1 MB app partition. The firmware uses sarasa_subset.c, a compact subset generated from that font.
Use an ESP-IDF shell:
idf.py set-target esp32
idf.py build
idf.py -p COMx flash monitorReplace COMx with your serial port.
Include the driver:
#include "epd_ssd1619a.h"Minimal full-screen flow:
ESP_ERROR_CHECK(epd_init());
epd_clear(EPD_COLOR_WHITE);
epd_draw_text_utf8(20, 20, "中文测试", EPD_COLOR_BLACK);
epd_draw_rect(10, 10, EPD_WIDTH - 20, EPD_HEIGHT - 20, EPD_COLOR_RED);
ESP_ERROR_CHECK(epd_display_framebuffer());The framebuffer color model uses two 1-bit planes:
| Color | BW RAM | RED RAM |
|---|---|---|
| White | 1 | 0 |
| Black | 0 | 0 |
| Red | 0 | 1 |
Framebuffer and primitive drawing:
void epd_clear(epd_color_t color);
void epd_set_pixel(int x, int y, epd_color_t color);
void epd_draw_hline(int x, int y, int w, epd_color_t color);
void epd_draw_vline(int x, int y, int h, epd_color_t color);
void epd_draw_line(int x0, int y0, int x1, int y1, epd_color_t color);
void epd_draw_rect(int x, int y, int w, int h, epd_color_t color);
void epd_fill_rect(int x, int y, int w, int h, epd_color_t color);
void epd_draw_circle(int x0, int y0, int r, epd_color_t color);
void epd_fill_circle(int x0, int y0, int r, epd_color_t color);Text:
void epd_draw_string_5x7(int x, int y, const char *s, epd_color_t color, int scale);
void epd_draw_text_utf8(int x, int y, const char *s, epd_color_t color);epd_draw_text_utf8() currently supports the compact Sarasa subset used by the demo: 中 文 测 试 驱 动 字 图 片, plus ASCII fallback via the built-in 5x7 font.
Bitmaps:
void epd_draw_bitmap_1bpp(...);
void epd_draw_bitmap_2plane(...);
void epd_draw_bitmap_2plane_fit(...);epd_draw_bitmap_2plane_fit() keeps aspect ratio and uses nearest-neighbor scaling, useful for showing a large 400x300 image in a smaller box:
epd_draw_bitmap_2plane_fit(
290, 155,
76, 44,
EPD_IMAGE_WIDTH,
EPD_IMAGE_HEIGHT,
epd_image_bw,
epd_image_red
);esp_err_t epd_display_framebuffer(void);
esp_err_t epd_display_area(int x, int y, int w, int h);
void epd_set_update_control(uint8_t control2);
esp_err_t epd_set_frame_rate(uint16_t hz);
esp_err_t epd_write_lut(const uint8_t *lut, uint32_t len);
esp_err_t epd_deep_sleep(void);
esp_err_t epd_deep_sleep_mode(epd_deep_sleep_mode_t mode);Notes:
- SPI is configured for 20 MHz hardware SPI, matching the SSD1619A write-speed limit.
epd_display_area()writes only a clipped rectangular RAM window. The X range expands to 8-pixel boundaries because SSD1619A RAM X addressing is byte based.- Custom LUTs are panel-specific. Use
epd_write_lut()only with known-good waveform data for your panel. - Exiting deep sleep requires a hardware reset.
- Draw into the ESP32-side framebuffer.
- Send the black/white plane with command
0x24. - Send the red plane with command
0x26. - Select the update sequence with command
0x22. - Start the refresh with command
0x20. - Wait for
BUSYto become inactive.
The MCU only prepares and sends image RAM. The SSD1619A uses its waveform/LUT and high-voltage driver blocks to physically move the e-paper pigment.