Compare commits

..

2 Commits

Author SHA1 Message Date
Julien Calixte
5540f901de docs(sd): mark SD-on-SPI3 verified on hardware
Re-ran the spike on the dedicated SPI3 wiring (CLK 14 / MOSI 15) with
the same clean mount + atomic round-trip result. Flip the docs from
"pending a re-run" to verified.
2026-07-11 11:43:34 +02:00
Julien Calixte
1b9987c4ee fix(sd): correct stale shared-bus log strings to SPI3
The boot banner, success line, and round-trip payload still said
"shared SPI2" / "shared bus" after the move to SPI3. Cosmetic only —
serial output now matches the dedicated-bus wiring.
2026-07-11 11:43:34 +02:00
4 changed files with 13 additions and 15 deletions

View File

@@ -30,5 +30,5 @@ live in [`spikes.md`](spikes.md) and
[`v0.1-mvp-technical.md`](v0.1-mvp-technical.md#hardware-bring-up-order),
with failure write-ups in [`postmortems/`](postmortems/README.md). Notable: the
keyboard runs bus-powered on the S3's native USB port, and the SD/FAT stack is
verified on a 32 GB card (2026-07-11), now moving to its own SPI3 host per
ADR-012 ([postmortem](postmortems/2026-07-05-spike3-sd-cmd59.md)).
verified on a 32 GB card on its own SPI3 host (2026-07-11, ADR-012)
([postmortem](postmortems/2026-07-05-spike3-sd-cmd59.md)).

View File

@@ -191,8 +191,8 @@ it is explicitly **not recommended** and not applied.
- [x] Enable PSRAM (done, `CONFIG_SPIRAM`) and build Spike 7 (git push) — both
complete; see [Spike 7 postmortem](2026-07-05-spike7-gix-https-push.md).
- [x] Settle the **shared-bus arbitration** decision → **SD on its own SPI3**
(ADR-012, 2026-07-11); spike rewired to SPI3 (SCK 14 / MOSI 15), pending a
bench re-run on the new wiring.
(ADR-012, 2026-07-11); spike rewired to SPI3 (SCK 14 / MOSI 15) and
**re-verified on hardware 2026-07-11** — same mount + round-trip result.
- [ ] **Still open:** implement the FatFS atomic-save (unlink-then-rename +
`*.tmp` boot-recovery) in the real `persistence` module.

View File

@@ -71,9 +71,8 @@ binary — [`src/bin/sd_fat.rs`](src/bin/sd_fat.rs), flashed with `just flash-sd
brings up the SD card, mounts FAT at `/sd`, and exercises the persistence
module's atomic save (write `*.tmp` → fsync → rename → read-back). Per ADR-012
the SD runs on its **own SPI3 host****SCK 14 · MOSI 15 · MISO 13 · SD CS 10**
— leaving the EPD alone on SPI2. (The 2026-07-11 bench proof below ran on the
earlier shared-SPI2 wiring; the code is now rewired to SPI3, pending a re-run
after moving the SCK/MOSI jumpers to 14/15.)
— leaving the EPD alone on SPI2. Verified on the dedicated SPI3 bus 2026-07-11
(same mount + round-trip result as the initial shared-SPI2 bring-up).
Bench result (genuine 32 GB SDHC card): mounts at 10 MHz, `29806 MiB total`,
atomic round-trip byte-identical. Two findings baked into the code:
@@ -91,9 +90,8 @@ atomic round-trip byte-identical. Two findings baked into the code:
for its whole lifetime, and persistence runs on its own thread, so a shared bus
would need an EPD rewrite plus a cross-thread mutex on the save path. Instead the
SD gets its own SPI3 — the EPD stays untouched, no arbitration. Remaining before
persistence lands in `main.rs`: re-run the spike on the SPI3 wiring, then wire
the atomic save (unlink-then-rename + `*.tmp` boot-recovery) into a `persistence`
module.
persistence lands in `main.rs`: wire the atomic save (unlink-then-rename +
`*.tmp` boot-recovery) into a `persistence` module.
**Spike 5 — partial refresh + typing: verified 2026-07-04.** `main.rs` wires
the keyboard to the panel: [`src/usb_kbd.rs`](src/usb_kbd.rs) feeds decoded

View File

@@ -77,11 +77,11 @@ fn main() -> Result<()> {
esp_idf_svc::sys::link_patches();
esp_idf_svc::log::EspLogger::initialize_default();
log::info!("Typoena — Spike 3 (SD/FAT on shared SPI2), {BUILD_TAG}");
log::info!("Typoena — Spike 3 (SD/FAT on dedicated SPI3), {BUILD_TAG}");
match run() {
Ok(()) => {
log::info!("✅ Spike 3 complete — mount + atomic write/fsync/rename/read-back on shared bus")
log::info!("✅ Spike 3 complete — mount + atomic write/fsync/rename/read-back on dedicated SPI3")
}
Err(e) => log::error!("❌ Spike 3 failed: {e:?}"),
}
@@ -93,7 +93,7 @@ fn main() -> Result<()> {
}
fn run() -> Result<()> {
let card = mount_sd().context("mounting SD over SPI2")?;
let card = mount_sd().context("mounting SD over SPI3")?;
// SAFETY: `card` is a live handle returned by a successful mount.
let (max_khz, real_khz) = unsafe { ((*card).max_freq_khz, (*card).real_freq_khz) };
@@ -111,7 +111,7 @@ fn run() -> Result<()> {
Ok(())
}
/// Init the shared SPI2 bus and mount the card. Returns the card handle (kept
/// Init the dedicated SPI3 bus and mount the card. Returns the card handle (kept
/// alive for the program's lifetime; the spike never unmounts).
fn mount_sd() -> Result<*mut sys::sdmmc_card_t> {
// 1) Initialize SPI3 with the SD's four lines. Dedicated bus (ADR-012) — no
@@ -224,7 +224,7 @@ fn file_roundtrip() -> Result<()> {
let path = format!("{MOUNT_STR}/spike3.md");
let tmp = format!("{path}.tmp"); // two dots → exercises long-filename support
let payload =
format!("typoena spike 3\n{BUILD_TAG}\nshared SPI2: SCK12 MOSI11 MISO13, SD CS10\n");
format!("typoena spike 3\n{BUILD_TAG}\ndedicated SPI3: SCK14 MOSI15 MISO13, SD CS10\n");
{
let mut f = fs::File::create(&tmp).context("create tmp")?;