Pure-Rust, no_std, Embassy-async firmware for the LilyGo T-Encoder-Pro
(ESP32-S3-R8). v1 = clean HAL + driver foundation bringing up every peripheral
with a minimal embedded-graphics demo. Mirrors the conventions of the proven
tdecknav project (/home/ray/projects/mapper/tdecknav).
- 16 MB flash, 8 MB OCTAL/OPI PSRAM →
ESP_HAL_CONFIG_PSRAM_MODE=octal(never quad). - Display: CO5300 AMOLED, 390×390, RGB565, QSPI. SDIO0=11 SDIO1=13 SDIO2=7 SDIO3=14 SCLK=12 CS=10 RST=4 EN(power)=3. Framebuffer ≈ 304 KB → PSRAM.
- Touch: CST816 I2C
0x15. Shared I2C SDA=5 SCL=6. INT=9 RST=8. - Encoder: A=1, B=2, button=0 (GPIO0 is a boot strap pin).
- Buzzer: GPIO17, LEDC PWM (2000 Hz, 8-bit in C reference).
- Pin source of truth:
/home/ray/projects/encoder/T-Encoder-Pro/libraries/Mylibrary/pin_config.h.
- Display flush is a CHUNKED QSPI burst, not a single PSRAM DMA write. First chunk
cmd=0x32/addr=0x003C00; continuation chunks useCommand::None/Address::None; stage each chunk through a small DRAM DMA buffer (PSRAM is the source via copy). - CS is MANUAL. Build the display SPI without
Spi::with_cs; drive GPIO10 as a plainOutput, low across the whole burst, high only at the end. Hardware CS pulses between transfers and breaks the RAMWR continuation. - Reg writes:
cmd=0x02,addr=(reg<<8). Init table ported fromArduino_CO5300.h. - Encoder: PCNT needs explicit high/low limits + interrupt accumulation (limited-width counter). Document GPIO0 strap behavior (download mode if held at reset).
- Workspace monorepo. Library crates dual-target (
no_stddevice +stdhost tests):enc-config,enc-co5300,enc-touch,enc-input,enc-ui,enc-bus. Device binaryenc-app(xtensa-only). Plusxtaskand optionalenc-hostsimulator. - Single async
init_hardware() -> BootHandles;mainspawns per-module Embassy tasks (render, encoder, button, touch, buzzer, ui). Comms viaembassy-syncChannel/SignaloverStaticCell.spawn_or_halt!(park, never panic). - Display driver generic over a
Co5300Bustrait (begin_pixels/push_chunk/end_pixels) so the DMA bus can swap to a blocking fallback without touching driver/UI.Co5300implementsembedded-graphicsDrawTarget. - Large statics (framebuffer) in PSRAM via a custom linker section (
.psram_uninit_ext, copytdecknav/nav-app/dram2_ext.x).
- All cargo commands go through
./cargo-esp(loads ESP env +RUSTUP_TOOLCHAIN=esp). - Device is on
/dev/ttyACM0(USB303a:1001, native USB-Serial/JTAG). - Flash/run:
./cargo-esp run -p enc-app(runnerprobe-rs run --chip esp32s3;espflash flash --monitoris the fallback).espflash,probe-rs,espupinstalled. - Custom partition table (
enc-app/partitions.csv, adds a dedicatedsettingspartition for NVS): the enc-app espflash runner passes--partition-table partitions.csv. Manualespflash flashneeds that flag too, else settings persistence is silently disabled (graceful — falls back to defaults). esp-hal = "=1.1.1"; derive the whole esp/embassy version matrix from the upstream esp-hal 1.1.1 esp32s3 embassy template and freezeCargo.lock. Do not copy tdecknav's older pins verbatim.
- Files < 500 lines; modules atomic; tests in separate files/modules.
- clippy pedantic, clean — zero warnings. No
#[allow(...)]anywhere; fix or delete. - No
unwrap()/expect()/panic!()in production paths (tests OK). Propagate via?and typed error enums. - No
#[ignore]d tests. No excluded tests. - No
Box/dynand no needless clones/allocations without explicit user sign-off. - Minimize dependencies (major-version-only where possible; esp ecosystem needs exact
pins). Run
cargo deny checkon dependency changes. - Idiomatic, statement-oriented Rust: pattern matching, typed enums, errors over codes.
- Comments and rustdoc short and to the point.
./cargo-esp clippy --fix --all-features --allow-dirty --all-targets --workspace
./cargo-esp fix --all-features --allow-dirty --all-targets --workspace
./cargo-esp fmt --all
# only after the above are clean:
./cargo-esp test --verbose --all-features --workspace # host-target cratesRoute noisy command output through context-mode (ctx_execute); print only summaries.
Apply #[spec(...)] to non-trivial / pub / boundary-crossing functions with
non-trivial input domains or output guarantees (e.g. framebuffer index/window bounds,
encoder detent sign/remainder, widget-within-area). Specs compile away by default;
validate with RUSTFLAGS="--cfg anodized_panic" ./cargo-esp test. Never relax a spec
to make a test pass.
./cargo-esp tarpaulin --all-features --workspace --out lcov then cargo crap — no
coverage drop, newly added functions never excluded. (Pure host-target crates; drivers
are verified on-device per the plan's phase acceptance gates.)
- Run all shell commands via context-mode (
ctx_execute/ctx_batch_execute), not raw Bash, except whitelist mutations (git writes, mkdir/mv/rm, package installs). - Verify code with Codex (ask-codex skill) before EVERY commit. Adversarial review; fix findings before committing. Codex is read-only — we implement.
- Update
config.example.tomlwhen configuration changes. - Scope discipline: fix warnings in modules you touch; no unrequested side-quests.
0 scaffold + octal-PSRAM smoke test → 1a QSPI transport spike (manual CS, chunked
burst) → 1b solid color → 2 embedded-graphics demo → 3 encoder → 4 touch → 5 integrated
UI. Full plan: ~/.claude/plans/we-re-now-planning-the-pure-simon.md.
T-Encoder-Pro/libraries/Arduino_GFX-1.5.0/src/databus/Arduino_ESP32QSPI.cpp— QSPI protocol.T-Encoder-Pro/libraries/Arduino_GFX-1.5.0/src/display/Arduino_CO5300.h— init table.tdecknav/nav-app/src/boot.rs,.cargo/config.toml,dram2_ext.x,Cargo.toml— patterns to mirror.