A distraction-free Markdown text editor for ESP32-S3-based development boards with reflective displays.
| Board | Display |
|---|---|
| Waveshare ESP32-S3-RLCD-4.2 | 4.2" reflective LCD, 400x300 |
| M5Stack PaperS3 | 4.7" e-paper (ED047TC1), 540x960 |
| Waveshare ESP32-S3-Touch-LCD-3.49 | 3.49" color IPS (AXS15231B), 640x172 |
| Guition JC3248W535 | 3.5" color IPS (AXS15231B), 480x320 |
| LilyGO T-Display-S3 | 1.9" color IPS (ST7789), 320x170 |
A few demo videos are available on my YouTube channel.
The Waveshare ESP32-S3-RLCD-4.2 provides the smoothest and most responsive user interaction. But the screen is very fragile, and the device needs a proper enclosure, preferably with a protective glass. Also, the contrast is very low, so iit needs a good lighting for comfortable work. The screen broke during the tests.
The M5Stack PaperS3 is so far the most usable option: it is compact, packed in a good enclosure with magnets on the back, and the contrast is much higher than that of the RLCD display. The reaction is significantly slower than with RLCD, but still acceptable.
I also tried UC8179-based e-paper displays (such as those used by the Seeed Studio reTerminal E1001 and the Waveshare E-Paper Driver HAT) and they proved to be too slow for an interactive Markdown editor: even with fast partial updates, the panel cannot keep up with typing and quickly accumulates ghosting artefacts. Support for UC8179 has therefore been removed from the codebase.
Before building, select the target board with idf.py menuconfig.
Navigate to DRAFTLING Configuration > Hardware Model and choose
the board you are building for:
- Waveshare ESP32-S3-RLCD-4.2 -- 4.2" reflective LCD (400x300)
- M5Stack PaperS3 -- 4.7" e-paper (ED047TC1, 540x960). Driver is
a 1-bpp B/W shim over the official
m5stack/M5GFXmanaged component; partial refresh and grayscale are not implemented yet. - Generic ESP32 + color LCD: Waveshare ESP32-S3-Touch-LCD-3.49 -- 3.49" IPS color LCD (AXS15231B, 640x172, QSPI). Touch input is not used.
- Generic ESP32 + color LCD: Guition JC3248W535 -- 3.5" IPS color LCD (AXS15231B, 480x320, QSPI). Touch input is not used.
- LilyGO T-Display-S3 -- 1.9" IPS color LCD (ST7789, 320x170,
8-bit i80 parallel). On-board battery monitor and BOOT button for
deep-sleep wake; no on-board MicroSD slot, so an external SD must
be wired to a free SPI bus (default pins documented in
main/app_config.h).
On color LCD boards, the editor offers a runtime-selectable color theme (F1 -> Settings -> Color theme): dark green on black (default), amber/orange on black, or white on black.
The display resolution and driver are configured automatically based on the selected model. You can also adjust the Display rotation angle in the same menu.
The user connects a Bluetooth keyboard and edits Markdown files stored on the SD card. The reflective LCD needs no backlight and works well in daylight. On request the device connects to WiFi and synchronizes files with a remote Git repository via the GitHub REST API.
| Feature | Waveshare RLCD-4.2 | M5Stack PaperS3 |
|---|---|---|
| MCU | ESP32-S3 (16 MB flash, 8 MB OPI PSRAM) | ESP32-S3 (16 MB flash, 16 MB PSRAM) |
| Display | 4.2" reflective LCD, 400x300, SPI | 4.7" e-paper ED047TC1, 540x960, parallel I80 |
| Storage | MicroSD (SDMMC 1-bit) | Onboard MicroSD (SPI3) |
| Input | BLE HID keyboard | BLE HID keyboard |
| Connectivity | WiFi 802.11 b/g/n | WiFi 802.11 b/g/n |
| Battery monitor | GPIO4 ADC (3:1 divider) | GPIO3 ADC (2:1 divider) |
| Wake from sleep | GPIO18 button | BOOT button (GPIO0) |
- WYSIWYG Markdown editing on reflective display
- Bluetooth keyboard input with auto-discovery and pairing
- File browser to open and manage
.mdfiles on the SD card (entries sorted alphabetically, directories first) - Markdown rendering: headings (H1-H4), bullet and numbered lists, blockquotes, code fences, horizontal rules, inline bold/italic/code
- Gap buffer text engine for efficient editing (256 KB document limit)
- WiFi station mode with credentials from NVS or
/sdcard/wifi.cfg - Git sync via GitHub REST API (pull and push
.mdfiles) - Per-file metadata sidecars: when a
.mdfile is closed (or before the device enters deep sleep), the editor records the current cursor position and scroll line in a hidden sidecar file named.<basename>.metanext to the document (for examplenotes.md->.notes.md.meta). The next time the file is opened, the cursor is restored to its previous position and the view scrolls so the cursor is visible. The.metafiles are hidden from the file browser (they start with a dot) and are ignored by Git sync (which only handles*.md).
| Shortcut | Action |
|---|---|
| F1 | Open settings menu (BLE, WiFi, Git, Layout) |
| Arrow keys | Move cursor |
| Home / End | Start / end of line |
| PgUp / PgDn | Scroll by page |
| Ctrl+S | Save file |
| Ctrl+O | Open file browser |
| Ctrl+N | New file |
| Ctrl+L | Cycle keyboard layout |
| Ctrl+G | Git sync (pull + push) |
| Ctrl+W | Toggle WiFi (connect / disconnect) |
| Ctrl+F | Find |
| Ctrl+H | Find + Replace (Tab switches field, Enter = next match, Ctrl+Enter = replace + next) |
| Ctrl+R | Force full e-paper refresh (clears ghosting; e-paper boards only) |
| Ctrl+Home/End | Start / end of document |
| Ctrl+Left/Right | Word movement |
| Escape | Switch to file browser |
The editor supports four keyboard layouts that can be switched with Ctrl+L or through the F1 menu:
| Code | Layout |
|---|---|
| US | US-English (QWERTY) |
| UA | Ukrainian (Cyrillic) |
| DE | German (QWERTZ with umlauts) |
| FR | French (AZERTY with accents) |
The current layout is shown in the title bar.
The set of compiled-in keyboard layouts is configurable via
idf.py menuconfig under DRAFTLING Keyboard Layouts. Each layout
can be independently enabled or disabled. By default US-English and
Ukrainian are enabled. Disabling unused layouts saves flash space.
Requires ESP-IDF
v5.3 or later. ESP-IDF 6.0 and newer are not supported yet because
the m5stack/M5GFX managed component (used by the M5Stack PaperS3
display backend) is not compatible with ESP-IDF 6.x. Use any ESP-IDF
v5.x release (v5.3 - v5.5 confirmed working).
idf.py set-target esp32s3
idf.py build
idf.py -p /dev/ttyACM0 flash monitorIf you update sdkconfig.defaults or pull new changes, delete the generated
sdkconfig so the defaults are re-applied:
rm -f sdkconfig
idf.py set-target esp32s3
idf.py buildRun idf.py menuconfig to open the interactive configuration UI.
Draftling adds two custom menus described below. All other options
(Bluetooth, LVGL fonts, etc.) use the ESP-IDF defaults from
sdkconfig.defaults and normally do not need to be changed.
Found at the top-level DRAFTLING Configuration menu.
| Option | Type | Default | Description |
|---|---|---|---|
| Hardware Model | choice | Waveshare ESP32-S3-RLCD-4.2 | Select the target board. Display resolution and driver are set automatically. |
| -- Waveshare ESP32-S3-RLCD-4.2 | 4.2" reflective LCD, 400x300 | ||
| -- M5Stack PaperS3 | 4.7" e-paper, ED047TC1, 540x960 (driver via m5stack/M5GFX) | ||
| Display rotation angle | choice | 0 degrees | Rotate the display by 0, 90, 180, or 270 degrees. |
| E-paper full-refresh interval | int | 30 | M5Stack PaperS3 only: number of partial refreshes between full refreshes. |
| LCD backlight brightness (%) | int | 75 | Color-LCD boards only (AXS15231B, ST7789): backlight PWM duty in percent. The backend drives the BL GPIO with an LEDC PWM signal (~5 kHz, 10-bit). 0 = off, 100 = full brightness. |
Note about the M5Stack PaperS3: the M5GFX-based driver uses the single-pulse
epd_fastwaveform for partial refreshes (one visible flash, ~80-150 ms per update). A full refresh (3-5 s) is performed automatically everyDRAFTLING_EPD_FULL_REFRESH_INTERVALpartials (default 30) to clear residual ghosting; tune the interval inidf.py menuconfig.
Found at the top-level DRAFTLING Keyboard Layouts menu. Each layout can be independently enabled or disabled. Disabling unused layouts saves flash space.
| Option | Default | Description |
|---|---|---|
| US-English (QWERTY) | y | Standard US keyboard layout |
| Ukrainian (Cyrillic) | y | Ukrainian Cyrillic layout |
| German (QWERTZ) | n | German layout with umlauts |
| French (AZERTY) | n | French layout with accents |
Place these on the SD card root:
MySSID
MyPassword
repo_url=https://github.com/user/repo
branch=main
token=ghp_xxxxxxxxxxxx
path=docs/
The token is a GitHub Personal Access Token with repo scope.
Keep this file private.
main/ Application entry point, pin definitions, Kconfig
components/
display/ Display driver (RLCD SPI) and LVGL v9 port
sd_card/ SD card (SDMMC) file operations
ble_keyboard/ BLE HID keyboard host (Bluedroid)
kb_layout/ Keyboard layout translation (US/UA/DE/FR)
fonts/ Custom LVGL fonts (Latin, Latin-1 Supplement, Cyrillic)
editor/ Gap-buffer editor, Markdown parser, LVGL UI, menu
wifi_manager/ WiFi STA connection manager
git_sync/ GitHub REST API file synchronization
standby/ Deep-sleep / standby timer manager
This project is licensed under the MIT License. See LICENSE for details.