Files
typewriter/firmware/README.md
Julien Calixte 55ba0db0f6 chore(firmware): scaffold Spike 1 Blink crate
Generated from esp-rs/esp-idf-template for the ESP32-S3 std target.
src/main.rs toggles GPIO 2 every 500 ms and logs `blink N` over USB-
serial — the minimum bring-up surface called out in
docs/v0.1-mvp-technical.md (Spike 1: confirm toolchain, flash, and basic
GPIO). edition=2024 with rust-version=1.85.

No editor/render/git/usb/fs modules yet; those land per the spike
methodology when later spikes need them.
2026-05-23 14:36:51 +02:00

3.6 KiB

Typoena firmware

Rust crate targeting xtensa-esp32s3-espidf. See the project root README.md and docs/v0.1-mvp-technical.md for the wider context.

Current state

Spike 1 — Blink. Toggles GPIO 2 every 500 ms and logs blink N to the USB-serial console. This proves three things only:

  1. The Espressif Rust toolchain (Xtensa) is installed and on PATH.
  2. The crate links against esp-idf-svc and compiles for xtensa-esp32s3-espidf.
  3. Basic GPIO output works on real silicon (verified post-flash, once the board is on the bench).

Everything past that — EPD, SD, USB host, partial refresh, Wi-Fi/TLS, gitoxide push — is its own follow-up spike per docs/v0.1-mvp-technical.md.

Build

Once per shell session, source the espup env (sets LIBCLANG_PATH and adds the Xtensa GCC to PATH):

. ~/export-esp.sh

Then from this directory:

cargo build --release

The first build is slow (the esp-idf C sources are checked out and built under .embuild/). Subsequent builds are incremental.

Flash (when hardware is on the bench)

cargo run --release triggers espflash flash --monitor via the runner configured in .cargo/config.toml. With the ESP32-S3-DevKitC-1 connected over USB you should see:

[…] blink 0
[…] blink 1
[…] blink 2
…

at 1 Hz on the serial monitor, and — if an LED is wired from GPIO 2 → 330 Ω → GND — the LED blinks in lockstep.

Pin choice

GPIO 2 is a safe general-purpose pin on the ESP32-S3-DevKitC-1: it's not tied to a strapping function at boot and not muxed to the USB or PSRAM peripherals. If you want to drive the on-board addressable LED instead, that's WS2812 on GPIO 48 and needs a different driver — out of scope for Spike 1.

Editor / rust-analyzer

The repo-level .zed/settings.json configures rust-analyzer for this crate:

  • cargo.target is pinned to xtensa-esp32s3-espidf with allTargets = false, so RA doesn't try to also check the crate for the host target (which can't build esp-idf-sys).
  • binary.path is pinned to the rustup-managed rust-analyzer (stable toolchain), not Zed's bundled one. Reason: recent Zed builds ship a rust-analyzer that calls cargo metadata --lockfile-path, which is still gated behind -Z unstable-options in cargo 1.95 and fails on both the stable and esp toolchains. The rustup-managed RA is version-locked to the cargo it ships with and avoids the flag.

If a contributor on a different machine has issues, regenerate the path:

rustup component add rust-analyzer --toolchain stable
rustup which rust-analyzer --toolchain stable
# put the printed path into .zed/settings.json under lsp.rust-analyzer.binary.path

Two things rust-analyzer still needs from the environment Zed was launched in:

  • LIBCLANG_PATH — required by bindgen inside esp-idf-sys.
  • The Xtensa GCC on PATH — required by embuild during cargo check.

Both are set by ~/export-esp.sh. The pragmatic workflow:

. ~/export-esp.sh
zed /Users/julien/jclab/typewriter   # or: open from this shell

If Zed is launched from Finder/Dock instead, rust-analyzer will report bindgen errors on the first esp-idf-sys check. Close Zed, source the env in a terminal, and relaunch from there.

Toolchain pins

rust-toolchain.toml pins the channel to esp (installed by espup install). Cargo.toml currently includes git [patch.crates-io] overrides for esp-idf-sys / esp-idf-hal / esp-idf-svc (template default). These follow master and may need pinning to released versions if a master commit breaks the build.