Skip to content

Devin-Cooper/RLCD

Repository files navigation

RLCD

Playground for building and testing different setups for the Waveshare ESP32-S3-RLCD-4.2 development board.

Board Overview

The ESP32-S3-RLCD-4.2 is a development board featuring a 4.2" reflective LCD that requires no backlight.

Component Specification
Display 4.2" RLCD, 400×300 pixels, 1-bit monochrome, SPI (ST7305 driver)
SoC ESP32-S3-WROOM-1-N16R8 (dual-core Xtensa LX7 @ 240MHz)
Memory 16MB Flash, 8MB PSRAM, 512KB SRAM
Connectivity WiFi 2.4GHz, Bluetooth 5 LE
Audio ES8311 codec, ES7210 ADC with dual-mic array
Sensors SHTC3 (temperature/humidity)
RTC PCF85063
Power 18650 battery holder, USB-C
Storage TF card slot (FAT32)

Projects

esp32_terminal (SSH Terminal & Dashboard)

ESP-IDF firmware that turns the board into a self-contained SSH terminal and server dashboard. Connect a Bluetooth keyboard, point it at a Linux server, and get a full interactive shell on the 400x300 reflective display.

Features

  • Terminal mode -- full VT100/xterm emulator rendering htop, vim, and other TUI apps on the 1-bit display
  • Dashboard mode -- curated server stats (CPU, memory, disk, network, GPU, Docker, screen sessions) refreshed on a configurable interval
  • SSH client -- libssh 0.11.4 (vendored in-tree as components/libssh/) with hardware-accelerated AES-128-CTR, Ed25519 / ECDSA P-256 / RSA-SHA2 auth, TOFU host key verification
  • BLE keyboard -- NimBLE HID host with auto-reconnect to bonded devices, full keycode-to-terminal translation (arrows, function keys, Ctrl combos)
  • WiFi -- auto-connect to known networks by signal strength; new networks are added via SD card JSON config (on-screen password entry is not implemented).
  • 3 font sizes -- 80x37 (5x7), 66x30 (6x9), 50x23 (8x12) -- cycle with button or F-key
  • Menu system -- overlay navigable via keyboard or physical buttons
  • Power management -- WiFi modem-sleep during active SSH (~20mA). Light sleep is disabled because ST7305 needs continuous SPI power.
  • Recovery firmware -- 512KB factory-partition recovery image with auto-fallback: if main fails to mark_app_valid within 30s, bootloader boots recovery which keeps USB-JTAG CDC stable and displays a "RECOVERY MODE" banner on the LCD. Recovery exposes a minimal REPL (ping/info/reboot-ota/erase-nvs/coredump-dump)

Hardware-Informed Optimizations

The firmware is tuned for the ESP32-S3's specific hardware profile:

Decision Why
AES-128-CTR over AES-GCM Hardware AES-CTR primitive: 7.5 MB/s (ESP32-S3 crypto peripheral). AES-GCM GHASH in software: 1.35 MB/s (5.5x slower). Note: end-to-end SSH throughput measures ~1 MB/s, bounded by the application send path in ssh_client (16-byte queue items, 5 ms poll), not the crypto primitive.
Ed25519 over RSA-2048 Ed25519 end-to-end auth ~813 ms (post-swap measured: TCP connect + KEX + pubkey). libssh vendors the Ed25519 reference impl so mbedTLS doesn't need to support it — that's why libssh2+mbedTLS cannot deliver Ed25519 at all and the swap to libssh happened.
Framebuffer in DMA SRAM SPI DMA requires internal SRAM. PSRAM would need cache coherence workaround
Scrollback in PSRAM Large, sequential access pattern benefits from 64-byte cache line prefetch
64KB data cache Trades 32KB SRAM for substantially better PSRAM throughput
NimBLE over Bluedroid Saves ~170KB flash and ~30KB RAM. ESP32-S3 is BLE-only anyway
SSH on Core 1 Isolates crypto from WiFi/BLE protocol stacks on Core 0

Architecture

Core 0 (Protocol)           Core 1 (Application)
WiFi Driver (P:23)          Display/SPI DMA (P:12)
BLE Controller (P:23)       SSH/libssh (P:10)
lwIP TCP/IP (P:18)          ANSI Parser (P:8)
BLE HID Input (P:9)         Dashboard Poll (P:5)

Flash Partition Layout (16MB)

Offset Size Name Purpose
0x9000 24 KB nvs WiFi/SSH credentials (plaintext; encryption deferred)
0x10000 8 KB otadata rollback state (main → factory fallback)
0x20000 512 KB factory recovery firmware — USB-JTAG REPL + "RECOVERY MODE" banner
0xA0000 10 MB ota_0 main application (single OTA slot)
0xAA0000 4 MB littlefs SSH keys, known_hosts, dashboard config
0xEA0000 1 MB coredump ELF coredump after panic

One-time migration from the old dual-OTA layout: see MIGRATION-factory-ota.md. Subsequent dev iterations use idf.py -C esp32_rendering flash to rewrite only the main slot.

Building

Requires ESP-IDF v5.5+ and the 1bit-display library:

cd esp32_rendering

# Set the path to the 1bit-display library (default: ../../1bit-display relative to esp32_rendering/)
export ONEBIT_LIB_DIR=/path/to/1bit-display

idf.py set-target esp32s3
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor

First-Time Setup

  1. Flash firmware and power on
  2. Pair a BLE keyboard -- long-press Button A to enter pairing mode (30s timeout)
  3. Connect WiFi -- the device auto-connects to networks saved in NVS or in /sdcard/wifi.json. Adding a new network from the UI is not implemented.
  4. Configure SSH -- drop a JSON file into /sdcard/servers/ with {name, host, port, username, dashboard[]}, then pick it from the Servers menu. (In-app Settings editor is not yet implemented.)
  5. Authenticate -- password on first connect; for key auth, set key_path in the server JSON pointing to a LittleFS-resident Ed25519 PEM private key

Dashboard Configuration

Create /littlefs/dashboard.cfg with one command per line (label|command format):

CPU|cat /proc/loadavg
Memory|free -m
Disk|df -h
GPU|cat /sys/class/drm/card0/device/gpu_busy_percent
Screens|screen -ls

Falls back to built-in defaults if no config file exists.

Simulator

A Python/Pygame simulator for prototyping 1-bit display designs before deploying to hardware. Includes a rendering toolkit inspired by Lucas Pope's Mars After Midnight visual techniques.

Features

  • Portable rendering core - Pure Python modules that map cleanly to C++ for ESP32 porting
  • Optimized 1-bit framebuffer - Byte-aligned operations for efficient rendering
  • Drawing primitives - Bresenham lines, scanline polygon fill, midpoint circles
  • Bayer dithering - 5 pattern levels for visual texture (0%, 25%, 50%, 75%, 100%)
  • Bezier curves - Cubic beziers with auto-smooth tangents and texture-ball strokes
  • Vector typography - Full A-Z alphabet, numerals 0-9, punctuation with scalable stroke width
  • Animation system - Breathing, wiggle, and transition effects for organic movement
  • Layout helpers - Centered, right-aligned, and multi-line text rendering

Running the Simulator

cd simulator
python -m venv venv
source venv/bin/activate  # or `venv\Scripts\activate` on Windows
pip install -r requirements.txt
python main.py --scale 2

Controls:

  • SPACE - Cycle through demo modes
  • 1-5 - Jump to specific mode
  • A - Toggle animation effects
  • S - Save screenshot
  • Q / ESC - Quit

Demo Modes:

  1. Patterns - All 5 dither patterns in hexagonal shapes (breathing animation)
  2. Bezier - Organic curves with texture-ball strokes (wiggle animation)
  3. Numerals - Full digit set at multiple sizes with live clock
  4. Clock Sketch - Combined composition preview (wiggle + breathing)
  5. Typography - Full A-Z alphabet, sample phrases, and mixed text

hello_vu

ESP-IDF firmware implementing a dual-channel VU meter using the onboard microphone array.

Features

  • Real-time audio level visualization
  • Dual VU meters (left/right channels) with 16 segments each
  • Automatic gain control with adaptive noise floor
  • 20 FPS display refresh

Building

cd hello_vu
idf.py set-target esp32s3
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor

Project Structure

RLCD/
├── esp32_rendering/            # SSH Terminal & Dashboard (main project)
│   ├── main/                   # Application entry point and boot sequence
│   ├── components/
│   │   ├── st7305/             # ST7305 reflective LCD driver (SPI + DMA)
│   │   ├── rendering/          # Graphics primitives (clock face, shapes)
│   │   ├── wifi_manager/       # WiFi lifecycle, NVS credentials, auto-connect
│   │   ├── ssh_client/         # libssh SSH with optimized cipher suites
│   │   ├── ble_hid/            # NimBLE BLE keyboard HID host
│   │   ├── input_queue/        # Unified FreeRTOS event queue
│   │   ├── app/                # Menu, dashboard, terminal mode, settings
│   │   ├── buttons/            # Debounced button handler
│   │   ├── sensors/            # RTC, temperature/humidity, battery
│   │   └── i2c_bsp/            # I2C bus abstraction
│   ├── sdkconfig.defaults      # ESP32-S3 optimized config
│   └── partitions.csv          # 16MB flash layout with OTA + LittleFS
├── simulator/                  # Python/Pygame display simulator
├── hello_vu/                   # ESP-IDF VU meter project
├── REFERENCES/                 # Component datasheets
└── README.md

Development Environment

This repository uses:

  • Python 3.9+ with Pygame for the simulator
  • ESP-IDF v5.x for firmware development

Prerequisites

Partition Layout & Recovery

The 16 MB flash carries a 512 KB factory recovery firmware alongside a 10 MB main application slot. If the main app boots badly (panic before esp_ota_mark_app_valid_cancel_rollback(), 30 s stability check fails, etc.) the bootloader automatically boots the recovery firmware — which keeps USB-JTAG CDC visible so the host can always reflash without pressing BOOT.

First-time / layout-change flash: ./flash.sh builds both projects and writes all four regions (bootloader + partition table + factory + main) per-offset, preserving NVS so WiFi credentials survive. See MIGRATION-factory-ota.md.

Dev inner loop: ./dev-flash.sh [port] [test] rewrites only the main slot at 0xA0000. Recovery, partition table, and NVS are untouched. Do NOT use idf.py -C esp32_rendering flash — ESP-IDF defaults its app offset to the first app partition (factory, 0x20000) and would overwrite the recovery firmware.

Force recovery boot: hold button A (GPIO 18) at reset for ≥5 s. The bootloader erases otadata and boots factory on the next cycle — useful when ota_0 is marked valid but broken anyway.

Test Infrastructure

Two complementary test surfaces:

Host unit tests (Catch2)

Pure-logic unit tests under host_test/app/ run without hardware.

cd host_test
cmake -S . -B build
cmake --build build -j
ctest --test-dir build --output-on-failure

On-device scenarios

The pytest + pyserial harness at host_test/scenarios/ drives real firmware over the ESP32-S3's built-in USB-JTAG CDC via the test_console REPL component — ~40 commands covering input injection, NVS, filesystem, introspection, and runtime control. 14 scenarios cover boot, menu cycling, font settings, pairing, WiFi, servers, settings persistence, and the legacy-NVS migration path. See host_test/scenarios/README.md for fixture docs, the full command set, and concurrency caveats.

Build the test-overlay firmware and run the suite:

cd esp32_rendering
idf.py -DSDKCONFIG_DEFAULTS="sdkconfig.defaults;sdkconfig.test" \
       -p /dev/cu.usbmodem4101 flash
cd ../host_test/scenarios
pip install -r requirements.txt
export TEST_WIFI_SSID='your-ssid' TEST_WIFI_PASS='your-pass'
python -m pytest -v

The test overlay routes the primary console to USB-JTAG CDC (default production builds use UART0) and enables FreeRTOS trace facilities for the tasklist REPL command. Production firmware has zero test-console code — the component is gated by CONFIG_TEST_CONSOLE_ENABLED and produces empty sources when disabled.

Reference Documentation

Waveshare Resources

Design Inspiration

Espressif Resources

License

See LICENSE file.

About

playground for the waveshare rLCD dev board

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors