From 99d6fd996f62728a0cbd2e77b1864f3550c92105 Mon Sep 17 00:00:00 2001 From: Strycher Date: Mon, 7 Sep 2026 22:12:54 -0400 Subject: [PATCH 01/13] docs(#282): record the 2026-09-05 UM FeatherS3 rewire sheet in HARDWARE.md The bench sheet, both header-by-header GPIO tables, the ten board-specific defines and the caveats, copied verbatim from the 2026-09-05 session record that was never written down. Adafruit 5477 tables unchanged. No firmware change. --- docs/HARDWARE.md | 170 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 169 insertions(+), 1 deletion(-) diff --git a/docs/HARDWARE.md b/docs/HARDWARE.md index 5d9f554..8811650 100644 --- a/docs/HARDWARE.md +++ b/docs/HARDWARE.md @@ -4,7 +4,8 @@ | Component | Adafruit PID | Qty | Interface | I2C Addr | Status | |-----------|--------------|-----|-----------|----------|--------| -| ESP32-S3 Feather 4MB/2MB PSRAM | [5477](https://www.adafruit.com/product/5477) | 1 | - | - | Have | +| ESP32-S3 Feather 4MB/2MB PSRAM | [5477](https://www.adafruit.com/product/5477) | 1 | - | - | Have — original MCU; pin tables below are for this board | +| UM FeatherS3D (ESP32-S3 N16R8, 16MB Flash / 8MB PSRAM) | [6399](https://www.adafruit.com/product/6399) | 1 | - | - | Have — on the bench since the 2026-09-05 rewire (see *UM FeatherS3 rewire* below); registered as `um_feather` | | GPS FeatherWing (PA1616D) | [3133](https://www.adafruit.com/product/3133) | 1 | UART | - | Have | | FeatherWing OLED 128x64 | [6313](https://www.adafruit.com/product/6313) | 1 | I2C | 0x3C | Have | | BME688 Breakout | [5046](https://www.adafruit.com/product/5046) | 1 | I2C | 0x77 | Have | @@ -116,6 +117,172 @@ The Feather form factor breaks out 21 GPIO pins on its headers. **18 are in use; **Implication:** 3 free GPIOs can support 3 additional buttons. Adding a 5-way joystick (5 more GPIOs) requires an I2C GPIO expander such as the MCP23017. +## UM FeatherS3 rewire (2026-09-05) — bench sheet and resulting pin changes + +**Status.** On 2026-09-05 the breadboard moved from the Adafruit 5477 Feather to a +**UM FeatherS3D** (Adafruit PID 6399, ESP32-S3 N16R8: 16 MB flash, 8 MB PSRAM), wired +per the bench sheet below. It is registered in the pio-flash registry as `um_feather` +(MAC `44:1b:f6:dc:ab:64`, first seen on COM22 in bootloader mode, 2026-09-07). + +**The firmware has not been changed for it.** As of `main` @ `1be96cb` (2026-09-07), +`platformio.ini` has one env, `feather_s3` (`board = adafruit_feather_esp32s3`), and every +pin above is the Adafruit value. A `feather_s3` image flashed to the UM board drives the +wrong GPIOs for TFT CS/DC/RST, FRAM_CS, SD_CS, CTP_INT, TFT_BL, the three buttons, I²C and +the GPS UART. Building for the UM board needs a second `[env:]` (16 MB flash, 8 MB PSRAM, +a 16 MB partition table) plus the ten defines in the last table changed together — that is +the board-profile work under #151. The Adafruit tables above are kept unchanged so the +project can move back to that board. + +**Source.** Everything in this section is copied from the 2026-09-05 session exchange +(05:44–06:12 EDT), recorded here under #282 after it had lived only in chat for two days. +Tables are verbatim. Where the record left a question open, it is left open here too. + +### Why the wires moved (owner's ruling, 05:48) + +UM labels its headers with **raw GPIO numbers**. There is no `A0`–`A5` or `D5`–`D13` +silkscreen on the board, and position 3 of the 16-pin header is `0` (GPIO 0) where the +Adafruit board has its second 3.3 V pin. The owner ruled that the wiring must match the +silkscreen so the board can be traced later — "silk screen doesn't match code internals" — +rather than keeping the wires in the same holes and remapping in firmware. The Adafruit +assignments are kept so the project can return to the 4MB/2MB Feather. + +An earlier table in the same exchange (05:46) mapped `A0`–`A5` to GPIO 1–6 using the +`um_feathers3` variant header's `A0`–`A12` names. Those names are **ADC channel aliases, +not header positions**, and that table was retracted at 05:59. Never derive UM header +positions from the variant's analog aliases. (The same mistake was repeated on 2026-09-07 +and is the reason this section exists.) + +### Bench sheet — unplug from, plug into (06:02) + +⚠ Adafruit has two `3V` pins. The UM board has `0` — GPIO 0, the BOOT strapping pin — in +the second one's position. Both UM 3.3 V outputs are `3V3.1` (position 2) and `3V3.2` +(position 16, the far end). + +**16-pin header** + +| Adafruit hole | → UM hole | +|---|---| +| `RST` | `RST` | +| `3V` (pos 2) | `3V3.1` | +| `3V` (pos 3) | ⛔ **not position 3** — use `3V3.2` | +| `GND` | `GND` | +| `A0` | **`17`** | +| `A1` | **`18`** | +| `A2` | **`14`** | +| `A3` | **`12`** | +| `A4` | **`6`** | +| `A5` | **`5`** | +| `SCK` | `SCK` | +| `MO` | `MO` | +| `MI` | `MI` | +| `RX` | `RX` | +| `TX` | `TX` | + +**12-pin header** + +| Adafruit hole | → UM hole | +|---|---| +| `BAT` | `BAT` | +| `EN` | `En` | +| `USB` | `5V` | +| `13` | **`11`** | +| `12` | **`10`** | +| `11` | **`7`** | +| `10` | **`3`** | +| `9` | **`1`** | +| `6` | **`38`** | +| `5` | **`33`** | +| `SCL` | `SCL` | +| `SDA` | `SDA` | + +Everything named stays named. Everything numbered or lettered changes number. +`SCK`/`MO`/`MI` are the same GPIO on both boards, so the SPI bus is a straight transfer. + +### Header position by position, with GPIOs (05:54) + +UM silkscreen as read from the FeatherS3D pinout PDF (Adafruit CDN), Adafruit silkscreen +from the ESP32-S3 Feather learn-guide pinout. Both were read from images. + +Header A (12 pins), UM order: `BAT · En · 5V · 11 · 10 · 7 · 3 · 1 · 38 · 33 · SCL · SDA` +Header B (16 pins), UM order: `RST · 3V3.1 · 0 · GND · 17 · 18 · 14 · 12 · 6 · 5 · SCK · MO · MI · RX · TX · 3V3.2` + +**16-pin header** + +| # | Adafruit 5477 | GPIO | UM FeatherS3D | GPIO | Field Compass signal | +|---|---|---|---|---|---| +| 1 | `RST` | — | `RST` | — | | +| 2 | `3V` | — | `3V3.1` | — | | +| 3 | `3V` | — | **`0`** | 0 | BOOT strapping pin — no signal here | +| 4 | `GND` | — | `GND` | — | | +| 5 | `A0` | 18 | `17` | 17 | TFT_CS | +| 6 | `A1` | 17 | `18` | 18 | TFT_DC | +| 7 | `A2` | 16 | `14` | 14 | TFT_RST (per source — see open question below) | +| 8 | `A3` | 15 | `12` | 12 | FRAM_CS | +| 9 | `A4` | 14 | `6` | 6 | CTP_INT | +| 10 | `A5` | 8 | `5` | 5 | TFT_BL | +| 11 | `SCK` | 36 | `SCK` | **36** | SPI clock | +| 12 | `MO` | 35 | `MO` | **35** | MOSI | +| 13 | `MI` | 37 | `MI` | **37** | MISO | +| 14 | `RX` | 38 | `RX` | 44 | GPS RX | +| 15 | `TX` | 39 | `TX` | 43 | GPS TX | +| 16 | `DB` (Debug TX) | — | `3V3.2` | — | | + +**12-pin header** + +| # | Adafruit 5477 | GPIO | UM FeatherS3D | GPIO | Field Compass signal | +|---|---|---|---|---|---| +| 1 | `BAT` | — | `BAT` | — | | +| 2 | `EN` | — | `En` | — | | +| 3 | `USB` | — | `5V` | — | | +| 4 | `13` | 13 | `11` | 11 | free | +| 5 | `12` | 12 | `10` | 10 | free | +| 6 | `11` | 11 | `7` | 7 | free | +| 7 | `10` | 10 | `3` | 3 | SD_CS (Adalogger) | +| 8 | `9` | 9 | `1` | 1 | BUTTON_A | +| 9 | `6` | 6 | `38` | 38 | BUTTON_B | +| 10 | `5` | 5 | `33` | 33 | BUTTON_C | +| 11 | `SCL` | 4 | `SCL` | 9 | I²C clock | +| 12 | `SDA` | 3 | `SDA` | 8 | I²C data | + +### Resulting firmware pin changes (06:12) + +Three things need **no source change** once a UM env selects the `um_feathers3` variant: + +- `Wire.begin()` takes no arguments, so I²C picks up the variant's SDA/SCL → 8/9 on UM +- `GPS_RX RX` / `GPS_TX TX` are board constants → 44/43 on UM +- `SPI_SCK/MOSI/MISO` are 36/35/37 on both boards + +Ten hardcoded values are board-specific: + +| Define | Adafruit 5477 | UM FeatherS3D | Where it lives on `main` (2026-09-07) | +|---|---|---|---| +| `TFT_CS` / `TFT_DC` / `TFT_RST` | 18 / 17 / 16 | 17 / 18 / 14 | `platformio.ini` `build_flags` (per-env by design, #184) | +| `FRAM_CS` | 15 | 12 | `src/fc_config.h` | +| `SD_CS` | 10 | 3 | `src/fc_config.h` | +| `CTP_INT` | 14 | 6 | `src/fc_config.h` | +| `TFT_BL` | 8 | 5 | `src/fc_config.h` | +| `BUTTON_A` / `BUTTON_B` / `BUTTON_C` | 9 / 6 / 5 | 1 / 38 / 33 | `src/src.ino` (`src/fc_config.h` once #280 merges) | + +Note the old GPIO 8/9 hazard disappears with this wiring: on the UM board GPIO 8/9 are +SDA/SCL, but TFT_BL lands on GPIO 5 and BUTTON_A on GPIO 1, so nothing clashes. + +### Caveats and open questions carried from the record + +- **Unverified against hardware.** The UM column was read from the pinout PDF; the owner + wired from the 06:02 bench sheet ("I think I have it rewired", 06:11). No build has run + on the UM board yet, so the mapping has not been proven by a working peripheral. +- **GPIO 2 is not on a header** on the UM board — it is the internal battery-voltage + divider (`A1` / `VBAT_SENSE` in the variant). 21 header GPIOs match UM's advertised count. +- **GPIO 33 and 38** (BUTTON_B/C positions) should be confirmed free on the quad-PSRAM + FeatherS3 before the buttons are relied on. `[hypothesis: untested]` +- **Bootloader/partitions.** Whether UM ships a UF2 bootloader that needs a reserved + partition like Adafruit's is unknown; the vendored table is 4 MB TinyUF2 and will not + fit. `[hypothesis: untested]` +- **Open question — the A2 wire.** At 06:02 the owner said the wire in Adafruit `A2` + "certainly wasn't TFT_RST in function". The source defines `A2` / GPIO 16 as `TFT_RST` + (`-D TFT_RST=16` in `platformio.ini`). What that wire actually carries on the display + was not settled in the record. Do not fill this in; check the wire. + ## I2C Device Map | Address | Device | Notes | @@ -325,3 +492,4 @@ The following input components are on hand but not yet committed to the design. | 2025-02-15 | Updated TFT to Hosyond 3.5" ST7796U IPS 480x320 | | 2025-02-15 | Added FT6336U touch, updated pin assignments, BOM status | | 2025-06-17 | Added GPIO availability audit, thermal/enclosure design notes, potential input upgrades (buttons, joystick, GPIO expander), ambient light sensor to BOM | +| 2026-09-07 | #282: recorded the 2026-09-05 UM FeatherS3D rewire — bench sheet, header-by-header GPIO tables for both boards, the ten board-specific defines, caveats; UM FeatherS3D added to BOM. Adafruit 5477 tables unchanged | From 57dfd387f15ce8be45b35e28bba5a410e451bd9b Mon Sep 17 00:00:00 2001 From: Strycher Date: Mon, 7 Sep 2026 22:23:08 -0400 Subject: [PATCH 02/13] docs(#282): record the owner's A2 ruling -- A2 is TFT_RST until proven otherwise --- docs/HARDWARE.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/HARDWARE.md b/docs/HARDWARE.md index 8811650..30514f7 100644 --- a/docs/HARDWARE.md +++ b/docs/HARDWARE.md @@ -278,10 +278,12 @@ SDA/SCL, but TFT_BL lands on GPIO 5 and BUTTON_A on GPIO 1, so nothing clashes. - **Bootloader/partitions.** Whether UM ships a UF2 bootloader that needs a reserved partition like Adafruit's is unknown; the vendored table is 4 MB TinyUF2 and will not fit. `[hypothesis: untested]` -- **Open question — the A2 wire.** At 06:02 the owner said the wire in Adafruit `A2` - "certainly wasn't TFT_RST in function". The source defines `A2` / GPIO 16 as `TFT_RST` - (`-D TFT_RST=16` in `platformio.ini`). What that wire actually carries on the display - was not settled in the record. Do not fill this in; check the wire. +- **The A2 wire — owner's ruling 2026-09-07:** *"I'm accepting that A2 is TFT_RST until + proven otherwise."* (At 06:02 on 2026-09-05 the owner had said the wire in Adafruit + `A2` "certainly wasn't TFT_RST in function"; the source defines `A2` / GPIO 16 as + `TFT_RST`, `-D TFT_RST=16` in `platformio.ini`.) So Adafruit `A2` → UM `14` carries + TFT_RST, and the UM build uses `TFT_RST=14`. If the display never comes out of reset + on the UM board, this is the first wire to check. ## I2C Device Map From 7ead2e934e0c1a4968ec50896c6af18444c62d1e Mon Sep 17 00:00:00 2001 From: Strycher Date: Mon, 7 Sep 2026 22:35:07 -0400 Subject: [PATCH 03/13] feat(#283): UM FeatherS3 build env and board pin profile (um_feathers3) A second [env:um_feathers3] for the UM FeatherS3[D] the bench was rewired to on 2026-09-05 (#282). Shared settings move to an [env] base section; each env carries board, memory, partitions, flash and its three TFT control-line flags (UM: CS 17, DC 18, RST 14). src/fc_config.h selects the other seven pins on the platform's board macro (UM: SD_CS 3, FRAM_CS 12, TFT_BL 5, CTP_INT 6, BUTTON_A/B/C 1/38/33) and errors on an unknown board. On the UM board setup() drives LDO2 (GPIO 39) HIGH before I2C, since it feeds the vertical STEMMA QT connector. partitions/default_16MB.csv is a byte-identical copy of the core's table. Both envs build (feather_s3 1,856,814 B flash / 103,248 B RAM; um_feathers3 1,797,018 B / 74,032 B). The feather_s3 image keeps the same size and defined-symbol set as the #280 build. pytest scripts/: 58 passed. --- CLAUDE.md | 22 +++++- docs/HARDWARE.md | 23 ++++-- partitions/default_16MB.csv | 7 ++ platformio.ini | 142 ++++++++++++++++++++++++++---------- src/fc_config.h | 37 +++++++++- src/src.ino | 14 +++- 6 files changed, 192 insertions(+), 53 deletions(-) create mode 100644 partitions/default_16MB.csv diff --git a/CLAUDE.md b/CLAUDE.md index e4bb083..fa0d1e0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,7 +60,7 @@ Pre-migration commit SHAs quoted in old issues refer to the archive, not here. |-----------|-------| | PROJECT_NAME | Field Compass | | PROJECT_DIR | `/c/Dev/Field_Compass` | -| BUILD_COMMAND | `pio run -e feather_s3` | +| BUILD_COMMAND | `pio run -e feather_s3` (Adafruit 5477) · `pio run -e um_feathers3` (UM FeatherS3, the bench board since 2026-09-05 — #283) | | CITADEL_PROJECT | `Field_Compass` | | GITHUB_PROJECT_ID | `PVT_kwHODGcOBc4BOJgD` | | INFRA_PROFILE | Maker | @@ -102,6 +102,14 @@ Standard PlatformIO layout. There is no `Field_Compass/` sketch directory any mo ## Build & Flash +Two envs, one per board (#283). The bench has been the UM FeatherS3 (registry name +`um_feather`) since the 2026-09-05 rewire; the Adafruit env still builds and is unchanged. +Pin mapping for both: `docs/HARDWARE.md`. + +```bash +pio run -e um_feathers3 +``` + ```bash pio run -e feather_s3 ``` @@ -116,9 +124,14 @@ identity against the registry before it writes anything, and upload` and `esptool` invocations. Preview then confirm: ```bash -python scripts/pio-flash.py preview --env feather_s3 +python scripts/pio-flash.py preview um_feather --env um_feathers3 ``` +The env must match the board the device name resolves to: `um_feather` takes +`um_feathers3`, `field_compass` (the Adafruit board) takes `feather_s3`. The wrapper +checks identity, not purpose — it will not stop a `feather_s3` image going onto the UM +board, and that image drives the wrong pins there (`docs/HARDWARE.md`). + Never hardcode a COM port — app and bootloader modes enumerate on *different* ports, so re-detect every time. The board needs a manual RESET press after a bootloader-mode flash; do not script the bootloader exit. Device state @@ -287,9 +300,10 @@ dw --project Field_Compass claim FC- # Claim one task git checkout -b fc/-short-desc # Per-issue branch # ── Compile + flash ──────────────────────────────────────── -pio run -e feather_s3 # Build (#186 layout) +pio run -e um_feathers3 # Build for the UM FeatherS3 bench board (#283) +pio run -e feather_s3 # Build for the Adafruit 5477 board python scripts/pio-flash.py list # Enumerate + match registry -python scripts/pio-flash.py preview --env feather_s3 +python scripts/pio-flash.py preview um_feather --env um_feathers3 # env must match the board python scripts/pio-flash.py confirm --token # Raw arduino-cli/esptool/pio upload are refused by block-raw-flash.py diff --git a/docs/HARDWARE.md b/docs/HARDWARE.md index 30514f7..4f7e478 100644 --- a/docs/HARDWARE.md +++ b/docs/HARDWARE.md @@ -124,14 +124,21 @@ The Feather form factor breaks out 21 GPIO pins on its headers. **18 are in use; per the bench sheet below. It is registered in the pio-flash registry as `um_feather` (MAC `44:1b:f6:dc:ab:64`, first seen on COM22 in bootloader mode, 2026-09-07). -**The firmware has not been changed for it.** As of `main` @ `1be96cb` (2026-09-07), -`platformio.ini` has one env, `feather_s3` (`board = adafruit_feather_esp32s3`), and every -pin above is the Adafruit value. A `feather_s3` image flashed to the UM board drives the -wrong GPIOs for TFT CS/DC/RST, FRAM_CS, SD_CS, CTP_INT, TFT_BL, the three buttons, I²C and -the GPS UART. Building for the UM board needs a second `[env:]` (16 MB flash, 8 MB PSRAM, -a 16 MB partition table) plus the ten defines in the last table changed together — that is -the board-profile work under #151. The Adafruit tables above are kept unchanged so the -project can move back to that board. +**Which build goes on which board.** `pio run -e um_feathers3` builds for this board +(#283): `board = um_feathers3`, 16 MB flash, quad PSRAM (`qio_qspi`), the vendored +`partitions/default_16MB.csv`, TFT CS/DC/RST 17/18/14 as per-env `build_flags`, and the +other seven pins from the `ARDUINO_FEATHERS3` block in `src/fc_config.h`. On the UM board +the firmware also drives the second LDO (GPIO 39) HIGH at boot, because it feeds the vertical +STEMMA QT connector. `pio run -e feather_s3` is unchanged and builds the Adafruit 5477 image. +**A `feather_s3` image on the UM board drives the wrong GPIOs for TFT CS/DC/RST, FRAM_CS, +SD_CS, CTP_INT, TFT_BL, the three buttons, I²C and the GPS UART** — that was the state of +`main` @ `1be96cb` (2026-09-07), before #283. The Adafruit tables above are kept unchanged so +the project can move back to that board. + +Two things a UM flash changes that are not pins: the board's shipped TinyUF2 bootloader and +CircuitPython are replaced (the 16 MB table has no `uf2` partition; restorable from UM's +releases), and there is no MAX17048 on the FeatherS3 — battery sensing is an ADC divider on +GPIO 2 — so the battery gauge reads N/A until the battery unit learns that path. **Source.** Everything in this section is copied from the 2026-09-05 session exchange (05:44–06:12 EDT), recorded here under #282 after it had lived only in chat for two days. diff --git a/partitions/default_16MB.csv b/partitions/default_16MB.csv new file mode 100644 index 0000000..67d7737 --- /dev/null +++ b/partitions/default_16MB.csv @@ -0,0 +1,7 @@ +# Name, Type, SubType, Offset, Size, Flags +nvs, data, nvs, 0x9000, 0x5000, +otadata, data, ota, 0xe000, 0x2000, +app0, app, ota_0, 0x10000, 0x640000, +app1, app, ota_1, 0x650000,0x640000, +spiffs, data, spiffs, 0xc90000,0x360000, +coredump, data, coredump,0xFF0000,0x10000, diff --git a/platformio.ini b/platformio.ini index e840130..c8944f1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,7 +24,17 @@ ; "undefined reference to setup()" — the framework builds, the sketch never ; does — which reads as a code problem rather than a config one. -[env:feather_s3] +; TWO BOARDS (#283). The bench moved from the Adafruit 5477 Feather to a UM +; FeatherS3[D] on 2026-09-05 (docs/HARDWARE.md, "UM FeatherS3 rewire"). One +; repo builds both: everything the boards share lives in the [env] base +; section below and is inherited by every [env:NAME]; each [env:NAME] carries +; only its board, memory, partition table, flash settings and the three +; TFT_eSPI control-line pins that differ per board. The other seven +; board-specific pins are selected in src/fc_config.h on the board macro the +; platform defines (ARDUINO_ADAFRUIT_FEATHER_ESP32S3 / ARDUINO_FEATHERS3). +; CI discovers [env:*] sections and skips the bare [env] (compile.yml). + +[env] ; pioarduino, NOT platformio/espressif32. Espressif stopped maintaining ; PlatformIO support and the official platform tops out at Arduino core 2.x. ; @@ -40,41 +50,10 @@ ; from building to not linking when the core moved), so the core version is ; pinned deliberately, not inherited. platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.38-1/platform-espressif32.zip -board = adafruit_feather_esp32s3 framework = arduino monitor_speed = 115200 -; --- PSRAM ------------------------------------------------------------------ -; The single most expensive setting in this file. PlatformIO's board JSON does -; not specify memory_type at all, so it must be explicit here. -; -; qio_qspi = QIO flash + QUAD-SPI PSRAM. This board (Adafruit PID 5477) has 2MB -; QUAD PSRAM, not octal - boards.txt gives build.flash_type=qio and -; build.psram_type=qspi. Setting octal (qio_opi) would leave PSRAM absent with -; NO build error: the LVGL draw buffers AND the LVGL heap (#164) both live in -; PSRAM and would fail to allocate at runtime instead. -board_build.arduino.memory_type = qio_qspi - -; --- Partition table -------------------------------------------------------- -; VENDORED, not referenced by name. PlatformIO's board JSON defaults to -; tinyuf2-partitions-4MB.csv - the WITH-OTA variant, ~1.3MB app. The firmware -; is 1,815,046 bytes and does NOT fit that. arduino-cli uses the -noota variant -; (2816K app), which is where the familiar "Maximum is 2883584" comes from. -; -; The vendored copy is byte-identical to the table the working arduino-cli -; build generates. Keeping it in-repo rather than trusting a filename to -; resolve is the same lesson as Epic A: build inputs live in git. -board_build.partitions = partitions/tinyuf2-partitions-4MB-noota.csv - -; --- Flash ------------------------------------------------------------------ -; boards.txt: build.flash_size=4MB, build.flash_freq=80m, build.flash_mode=dio. -; PlatformIO's board JSON says flash_mode=qio. dio is chosen here to match the -; arduino-cli build so B6's parity check compares like with like; qio may be a -; deliberate improvement later, but not as an accident of tooling. -board_upload.flash_size = 4MB -board_build.flash_mode = dio -board_build.f_flash = 80000000L board_build.f_cpu = 240000000L ; --- Build flags ------------------------------------------------------------- @@ -95,6 +74,9 @@ board_build.f_cpu = 240000000L ; 5313) still say TFT_eSPI runs at 80 MHz. The header is authoritative and ; docs/HARDWARE.md agrees with it at 40 MHz. Tracked as #242 — those comments ; carry the rationale for SD_SPI_FREQ (#116), so they are not a text fix. +; +; TFT_CS / TFT_DC / TFT_RST are NOT here. They differ per board and each +; [env:NAME] appends its own three after ${env.build_flags}. build_flags = -D USER_SETUP_LOADED=1 -D USER_SETUP_ID=999 @@ -106,11 +88,6 @@ build_flags = -D TFT_MOSI=35 -D TFT_MISO=37 -D TFT_SCLK=36 - ; Board-specific control lines. These three differ per board, which is why - ; build_flags rather than a shared header: a second [env:] overrides them. - -D TFT_CS=18 - -D TFT_DC=17 - -D TFT_RST=16 ; Fonts -D LOAD_GLCD=1 -D LOAD_FONT2=1 @@ -162,6 +139,97 @@ lib_deps = adafruit/Adafruit FRAM SPI@2.6.2 adafruit/RTClib@2.1.4 +; ============================================================================= +; Adafruit ESP32-S3 Feather 4MB Flash 2MB PSRAM (PID 5477) - the original board +; ============================================================================= +[env:feather_s3] +board = adafruit_feather_esp32s3 + +; --- PSRAM ------------------------------------------------------------------ +; The single most expensive setting in this file. PlatformIO's board JSON does +; not specify memory_type at all, so it must be explicit here. +; +; qio_qspi = QIO flash + QUAD-SPI PSRAM. This board (Adafruit PID 5477) has 2MB +; QUAD PSRAM, not octal - boards.txt gives build.flash_type=qio and +; build.psram_type=qspi. Setting octal (qio_opi) would leave PSRAM absent with +; NO build error: the LVGL draw buffers AND the LVGL heap (#164) both live in +; PSRAM and would fail to allocate at runtime instead. +board_build.arduino.memory_type = qio_qspi + +; --- Partition table -------------------------------------------------------- +; VENDORED, not referenced by name. PlatformIO's board JSON defaults to +; tinyuf2-partitions-4MB.csv - the WITH-OTA variant, ~1.3MB app. The firmware +; is 1,815,046 bytes and does NOT fit that. arduino-cli uses the -noota variant +; (2816K app), which is where the familiar "Maximum is 2883584" comes from. +; +; The vendored copy is byte-identical to the table the working arduino-cli +; build generates. Keeping it in-repo rather than trusting a filename to +; resolve is the same lesson as Epic A: build inputs live in git. +board_build.partitions = partitions/tinyuf2-partitions-4MB-noota.csv + +; --- Flash ------------------------------------------------------------------ +; boards.txt: build.flash_size=4MB, build.flash_freq=80m, build.flash_mode=dio. +; PlatformIO's board JSON says flash_mode=qio. dio is chosen here to match the +; arduino-cli build so B6's parity check compares like with like; qio may be a +; deliberate improvement later, but not as an accident of tooling. +board_upload.flash_size = 4MB +board_build.flash_mode = dio +board_build.f_flash = 80000000L + +; --- Board-specific TFT control lines (docs/HARDWARE.md, Pin Assignments) --- +; A0 -> TFT_CS, A1 -> TFT_DC, A2 -> TFT_RST on the Adafruit header. +build_flags = + ${env.build_flags} + -D TFT_CS=18 + -D TFT_DC=17 + -D TFT_RST=16 + +; ============================================================================= +; Unexpected Maker FeatherS3[D] (Adafruit PID 6399) - the bench board since the +; 2026-09-05 rewire (#282, #283). docs/HARDWARE.md "UM FeatherS3 rewire". +; ============================================================================= +[env:um_feathers3] +; The platform's um_feathers3 board definition: variant um_feathers3, 16 MB +; flash, memory_type qio_qspi, flash_mode qio, default_16MB.csv, and it defines +; ARDUINO_FEATHERS3 (which src/fc_config.h keys on) plus ARDUINO_USB_MODE=1 +; (hardware USB-Serial-JTAG: the app enumerates on the same 303A:1001 as the +; ROM bootloader). There is no separate FeatherS3D definition in the core; the +; [D] has the same "16MB QSPI Flash" and "8MB Extra QSPI PSRAM" as the FeatherS3 +; (Adafruit product page, 2026-09-07), so this definition is the right one. +board = um_feathers3 + +; --- PSRAM ------------------------------------------------------------------ +; Same trap as the Adafruit env: quad PSRAM, so qio_qspi. Octal (qio_opi) would +; also claim GPIO 33-37 for the PSRAM bus, and 35/36/37 ARE the SPI bus this +; project runs the TFT, SD and FRAM on. Stated explicitly even though the board +; JSON already says qio_qspi, so a board-JSON change cannot flip it silently. +board_build.arduino.memory_type = qio_qspi + +; --- Partition table -------------------------------------------------------- +; VENDORED copy of the core's default_16MB.csv (byte-identical; verified with +; cmp when it was copied). The 4 MB TinyUF2 table cannot describe this flash. +; This table has no uf2 partition: a PlatformIO flash of this env replaces the +; TinyUF2 bootloader and CircuitPython the board ships with. Restorable from +; UM's releases. The owner decides that at flash time, not this file. +board_build.partitions = partitions/default_16MB.csv + +; --- Flash ------------------------------------------------------------------ +; Board-JSON values, restated so they are visible next to the Adafruit env's. +; No arduino-cli baseline exists for this board, so there is nothing to match; +; qio is the board definition's own mode. +board_upload.flash_size = 16MB +board_build.flash_mode = qio +board_build.f_flash = 80000000L + +; --- Board-specific TFT control lines (docs/HARDWARE.md, UM rewire tables) --- +; Adafruit A0 -> UM hole 17 -> TFT_CS, A1 -> 18 -> TFT_DC, A2 -> 14 -> TFT_RST +; (the owner's 2026-09-07 ruling: A2 is TFT_RST until proven otherwise). +build_flags = + ${env.build_flags} + -D TFT_CS=17 + -D TFT_DC=18 + -D TFT_RST=14 + ; ============================================================================= ; BUILD ENVIRONMENT - two traps that cost a build each (#183) ; ============================================================================= diff --git a/src/fc_config.h b/src/fc_config.h index 363504f..d55efcc 100644 --- a/src/fc_config.h +++ b/src/fc_config.h @@ -1,10 +1,41 @@ #pragma once // fc_config.h -- extracted from src.ino by scripts/extract_unit.py (E4). -#define SD_CS 10 // Adalogger FeatherWing SD slot +// Two boards build from this tree (#283). The SPI bus is the same GPIOs on +// both; the seven pins below it differ and are selected on the board macro the +// platform's board definition supplies. I2C (SDA/SCL), the GPS UART (RX/TX) +// and TFT_CS/DC/RST are not here: the first two come from the variant, the +// TFT lines are per-env build_flags in platformio.ini because TFT_eSPI needs +// them at library compile time. Mapping: docs/HARDWARE.md, Pin Assignments +// (Adafruit) and "UM FeatherS3 rewire (2026-09-05)" (UM). #define SPI_SCK 36 // Default Feather SPI clock #define SPI_MOSI 35 // Default Feather SPI MOSI #define SPI_MISO 37 // Default Feather SPI MISO (unused for TFT) + +#if defined(ARDUINO_FEATHERS3) +// Unexpected Maker FeatherS3[D] (Adafruit 6399), env um_feathers3. UM's +// silkscreen is raw GPIO numbers; each comment names the hole the wire is in +// and the Adafruit hole it came from. +#define SD_CS 3 // UM hole 3 (was Adafruit 10) -> Adalogger FeatherWing SD slot +#define FRAM_CS 12 // UM hole 12 (was A3) -> FRAM CS +#define TFT_BL 5 // UM hole 5 (was A5) -> LED pin on display module (PWM-dimmable backlight) +#define CTP_INT 6 // UM hole 6 (was A4) -> Touch interrupt (active-low, CHANGE: fires on touch + release) + +// Button pins (directly wired, active LOW) +#define BUTTON_A 1 // UM hole 1 (was 9) +#define BUTTON_B 38 // UM hole 38 (was 6) +#define BUTTON_C 33 // UM hole 33 (was 5) + +// The FeatherS3's second LDO (IO39) powers the vertical STEMMA QT connector +// and the RGB LED. Nothing in the Arduino variant or UM's helper enables it at +// boot and its power-up default is undocumented, so setup() drives it HIGH +// before any I2C traffic. On the Adafruit board GPIO 39 is the GPS TX line, +// which is why this exists only here. +#define FC_LDO2_PIN 39 + +#elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S3) +// Adafruit ESP32-S3 Feather 4MB/2MB (PID 5477), env feather_s3. +#define SD_CS 10 // Adalogger FeatherWing SD slot #define FRAM_CS 15 // A3 -> FRAM CS #define TFT_BL 8 // A5 -> LED pin on display module (PWM-dimmable backlight) #define CTP_INT 14 // A4 -> Touch interrupt (active-low, CHANGE: fires on touch + release) @@ -13,3 +44,7 @@ #define BUTTON_A 9 #define BUTTON_B 6 #define BUTTON_C 5 + +#else +#error "Field Compass: unknown board. Add a pin block for it here and an [env:] in platformio.ini (see #283)." +#endif diff --git a/src/src.ino b/src/src.ino index f10c81b..779e08d 100644 --- a/src/src.ino +++ b/src/src.ino @@ -135,14 +135,22 @@ void setup() { snprintf(banner, sizeof(banner), "=================================\nField Compass Dual %s\n=================================\n\n", FW_VERSION); logPrintf("%s", banner); +#ifdef FC_LDO2_PIN + // UM FeatherS3 only (#283): the second LDO feeds the vertical STEMMA QT + // connector. Bring it up before anything touches I2C. See fc_config.h. + pinMode(FC_LDO2_PIN, OUTPUT); + digitalWrite(FC_LDO2_PIN, HIGH); + logPrintf("[PWR] LDO2 enabled on GPIO %d\n", FC_LDO2_PIN); +#endif + // Deselect ALL SPI slave CS pins BEFORE any SPI bus activity (#116) - // Without this, SD_CS (GPIO 10) and FRAM_CS (GPIO 15) float during TFT init, - // allowing 80MHz TFT traffic to corrupt idle SPI slaves on the shared bus. + // Without this, SD_CS and FRAM_CS float during TFT init, allowing TFT + // traffic to corrupt idle SPI slaves on the shared bus. pinMode(SD_CS, OUTPUT); digitalWrite(SD_CS, HIGH); pinMode(FRAM_CS, OUTPUT); digitalWrite(FRAM_CS, HIGH); - logPrintln("[SPI] CS pins pre-set HIGH: SD_CS=10, FRAM_CS=15"); + logPrintf("[SPI] CS pins pre-set HIGH: SD_CS=%d, FRAM_CS=%d\n", SD_CS, FRAM_CS); LOG_DEBUG("About to init TFT..."); Serial.flush(); From fba1556dbda6c84d23849dd1b5698e41ca5af549 Mon Sep 17 00:00:00 2001 From: Strycher Date: Mon, 7 Sep 2026 23:19:54 -0400 Subject: [PATCH 04/13] fix(#284): select GPSPI2 for TFT_eSPI on ESP32-S3 (USE_FSPI_PORT) TFT_eSPI 2.5.43 sets SPI_PORT to FSPI on the S3, which Arduino core 3.x defines as bus index 0; the IDF's REG_SPI_BASE(0) is 0, so every TFT register pointer was NULL+offset and tft.init() panicked (StoreProhibited at 0x10) on the first hardware run of any PlatformIO image. USE_FSPI_PORT makes SPI_PORT 2 = GPSPI2, the controller the Arduino SPI object already uses for SD and FRAM. Verified in both ELFs: writecommand now references 0x60024000/0x60024010; no NULL-based stores remain. um_feathers3 1,802,134 B flash / 74,064 B RAM; feather_s3 1,862,006 B / 103,280 B. --- platformio.ini | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/platformio.ini b/platformio.ini index c8944f1..30d1c87 100644 --- a/platformio.ini +++ b/platformio.ini @@ -80,6 +80,16 @@ board_build.f_cpu = 240000000L build_flags = -D USER_SETUP_LOADED=1 -D USER_SETUP_ID=999 + ; SPI port selection (#284). Without this, TFT_eSPI 2.5.43 on ESP32-S3 sets + ; SPI_PORT to FSPI, which Arduino core 3.x defines as bus index 0; the IDF's + ; REG_SPI_BASE(0) is 0, so every TFT register pointer is NULL+offset and the + ; first SPI write panics (StoreProhibited at 0x10) inside tft.init(). Seen on + ; the first hardware run of any PlatformIO image (2026-09-07). USE_FSPI_PORT + ; makes SPI_PORT 2 = GPSPI2, the same controller the Arduino SPI object uses + ; for SD and FRAM, which is what upstream TFT_eSPI now defaults to on the S3. + ; NOT USE_HSPI_PORT: that would put the TFT on GPSPI3 and the SD card's later + ; SPI.begin would take GPIO 36/35/37 away from it in the pin matrix. + -D USE_FSPI_PORT=1 -D ST7796_DRIVER=1 -D TFT_WIDTH=320 -D TFT_HEIGHT=480 From ef87fb37e48102de93460d9e2fbdf6c8a2cd69cd Mon Sep 17 00:00:00 2001 From: Strycher Date: Mon, 7 Sep 2026 23:49:39 -0400 Subject: [PATCH 05/13] fix(#284): one SPI driver per bus; scan both I2C buses on the FeatherS3D (#283) SD and FRAM now use TFT_eSPI's SPI instance instead of starting the Arduino SPI object on the same controller: with USE_FSPI_PORT that was two SPIClass drivers on GPSPI2, and the first SD mount on the UM board never returned. The SD re-init path no longer ends/restarts the bus (it is the display's). The FeatherS3D has two I2C buses (pinout card): I2C1 = header + first STEMMA QT = 8/9, I2C2 = second STEMMA QT = 16/15 on LDO2. Wire1 is opened on I2C2 and the boot scan reports both buses. docs/HARDWARE.md: header position 16 is LDO2 (switched 3.3 V), not a second always-on 3V3 as the bench sheet said; MAX17048 is present on I2C1; named holes verified against the card; first-boot results recorded. --- docs/HARDWARE.md | 34 ++++++++++++++++++++++++++++++---- src/fc_config.h | 19 ++++++++++++++----- src/fram.cpp | 5 ++++- src/logging.cpp | 12 +++++------- src/src.ino | 42 +++++++++++++++++++++++++++++++----------- 5 files changed, 84 insertions(+), 28 deletions(-) diff --git a/docs/HARDWARE.md b/docs/HARDWARE.md index 4f7e478..f16436f 100644 --- a/docs/HARDWARE.md +++ b/docs/HARDWARE.md @@ -137,8 +137,10 @@ the project can move back to that board. Two things a UM flash changes that are not pins: the board's shipped TinyUF2 bootloader and CircuitPython are replaced (the 16 MB table has no `uf2` partition; restorable from UM's -releases), and there is no MAX17048 on the FeatherS3 — battery sensing is an ADC divider on -GPIO 2 — so the battery gauge reads N/A until the battery unit learns that path. +releases), and the [D] **does** carry a MAX17048 fuel gauge on I2C1 (SDA 8 / SCL 9, INT on +GPIO 2) per the pinout card — an earlier draft of this paragraph said it had none, which was +the original FeatherS3's ADC divider, not the [D]. The battery unit's MAX17048 code applies +unchanged. **Source.** Everything in this section is copied from the 2026-09-05 session exchange (05:44–06:12 EDT), recorded here under #282 after it had lived only in chat for two days. @@ -171,7 +173,7 @@ the second one's position. Both UM 3.3 V outputs are `3V3.1` (position 2) and `3 |---|---| | `RST` | `RST` | | `3V` (pos 2) | `3V3.1` | -| `3V` (pos 3) | ⛔ **not position 3** — use `3V3.2` | +| `3V` (pos 3) | ⛔ **not position 3** (that is GPIO 0). ⚠ The sheet originally said "use `3V3.2`", meaning position 16 — **position 16 is `LDO2`, a switched 3.3 V output** that is off until the firmware raises GPIO 39 (pinout card, read 2026-09-07). Always-on 3.3 V is `3V3.1` (position 2) or a STEMMA QT 3V3 pin. See *Corrections from the first boot* below. | | `GND` | `GND` | | `A0` | **`17`** | | `A1` | **`18`** | @@ -232,7 +234,7 @@ Header B (16 pins), UM order: `RST · 3V3.1 · 0 · GND · 17 · 18 · 14 · 12 | 13 | `MI` | 37 | `MI` | **37** | MISO | | 14 | `RX` | 38 | `RX` | 44 | GPS RX | | 15 | `TX` | 39 | `TX` | 43 | GPS TX | -| 16 | `DB` (Debug TX) | — | `3V3.2` | — | | +| 16 | `DB` (Debug TX) | — | `LDO2` (read as `3V3.2` from the features PDF; the pinout card says `LDO2 3V3 OUT ONLY`) | — | switched 3.3 V, on when GPIO 39 is HIGH | **12-pin header** @@ -292,6 +294,30 @@ SDA/SCL, but TFT_BL lands on GPIO 5 and BUTTON_A on GPIO 1, so nothing clashes. TFT_RST, and the UM build uses `TFT_RST=14`. If the display never comes out of reset on the UM board, this is the first wire to check. +### Corrections from the first boot (2026-09-07, #283 / #284) + +Source for all of these: UM's FeatherS3D pinout card, +`github.com/UnexpectedMaker/esp32s3` → `series_d/pinout_cards/feathers3d_pinout.jpg` +(2025), read on 2026-09-07. It supersedes the features PDF the tables above were read from. + +- **Named holes verified.** `SDA` = IO8, `SCL` = IO9, `SCK` = IO36, `MO` = IO35, `MI` = IO37, + `RX` = IO44, `TX` = IO43. Numbered holes are the GPIO numbers. The `um_feathers3` variant + and the tables above agree with the card. +- **Two I2C buses.** I2C1 = header `SDA`/`SCL` + the first STEMMA QT connector = IO8/IO9, + powered by LDO1 (always on). **I2C2 = the second STEMMA QT connector = IO16 (SDA) / + IO15 (SCL), powered by LDO2** (switched, GPIO 39). Stacked FeatherWings and anything on + the first connector are on I2C1; a chain on the second connector is on I2C2 and invisible + to `Wire`. The firmware opens `Wire1` on I2C2 and scans both (#283). +- **Header position 16 is `LDO2`,** a 3.3 V *output* that is on only while GPIO 39 is HIGH. + The bench sheet above called it `3V3.2` and sent the Adafruit's second 3V wire there. That + rail is off during the bootloader and until the first line of `setup()`. Anything that must + be powered at all times belongs on `3V3.1` (position 2) or a STEMMA QT 3V3 pin. +- **MAX17048 present** on I2C1 at the usual address, INT on IO2. Ambient light sensor on + IO4, blue LED IO13, RGB LED IO40, VBUS detect IO34. IO0 and IO3 are strapping pins. +- **First run results** (image 8af7ce5): TFT init and LVGL completed and the backlight lit, + but the panel stayed blank, the I2C1 scan found zero devices (the stacked OLED wing + included), and the first SD mount never returned. Investigation continues in #283/#284. + ## I2C Device Map | Address | Device | Notes | diff --git a/src/fc_config.h b/src/fc_config.h index d55efcc..e449a5c 100644 --- a/src/fc_config.h +++ b/src/fc_config.h @@ -26,13 +26,22 @@ #define BUTTON_B 38 // UM hole 38 (was 6) #define BUTTON_C 33 // UM hole 33 (was 5) -// The FeatherS3's second LDO (IO39) powers the vertical STEMMA QT connector -// and the RGB LED. Nothing in the Arduino variant or UM's helper enables it at -// boot and its power-up default is undocumented, so setup() drives it HIGH -// before any I2C traffic. On the Adafruit board GPIO 39 is the GPS TX line, -// which is why this exists only here. +// The FeatherS3[D]'s second LDO (IO39) powers the second STEMMA QT connector +// (I2C2), the RGB LED and the header hole labelled LDO2. Nothing in the +// Arduino variant or UM's helper enables it at boot and its power-up default +// is undocumented, so setup() drives it HIGH before any I2C traffic. On the +// Adafruit board GPIO 39 is the GPS TX line, which is why this exists only +// here. #define FC_LDO2_PIN 39 +// The [D] has two I2C buses (FeatherS3D pinout card, UM github series_d): +// I2C1 = header SDA/SCL + first STEMMA QT = GPIO 8/9 = Wire; I2C2 = second +// STEMMA QT, LDO2-powered = GPIO 16/15. Wire1 is opened on I2C2 so the scan +// reports what is plugged into either connector. The Adafruit board has one +// bus, so these are absent there. +#define FC_I2C2_SDA 16 +#define FC_I2C2_SCL 15 + #elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S3) // Adafruit ESP32-S3 Feather 4MB/2MB (PID 5477), env feather_s3. #define SD_CS 10 // Adalogger FeatherWing SD slot diff --git a/src/fram.cpp b/src/fram.cpp index 248f90b..fad6d85 100644 --- a/src/fram.cpp +++ b/src/fram.cpp @@ -2,8 +2,11 @@ #include "fram.h" #include "fc_config.h" #include "logging.h" +#include // FRAM rides on TFT_eSPI's SPI instance (#284) -Adafruit_FRAM_SPI fram = Adafruit_FRAM_SPI(FRAM_CS); // SPI FRAM 256KB +// SPI FRAM 256KB, on the bus TFT_eSPI owns. The pointer is only stored here; +// Adafruit_SPIDevice dereferences it in begin(), after static init is over. +Adafruit_FRAM_SPI fram = Adafruit_FRAM_SPI(FRAM_CS, &TFT_eSPI::getSPIinstance()); bool framAvailable = false; // SPI FRAM 256KB FRAMHeader framHeader; diff --git a/src/logging.cpp b/src/logging.cpp index fd6e3c5..b20a90f 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -2,6 +2,7 @@ #include "logging.h" #include "fc_config.h" #include +#include // SD rides on TFT_eSPI's SPI instance (#284) char serialRing[SERIAL_RING_SIZE]; volatile uint16_t serialRingHead = 0; @@ -293,16 +294,13 @@ bool trySDReInit() { logPrintf("[SD] Attempting re-init #%d...\n", sdHealth.reInitCount); - // Full SPI bus reset: end SD, end SPI, re-init SPI, then re-mount SD (#116) - // This clears any stale bus state from TFT_eSPI's 80MHz DMA transfers + // End SD and re-mount it (#116). The bus itself is TFT_eSPI's instance + // (#284) and stays up: ending it here would take the display down with it. SD.end(); - SPI.end(); - delay(100); - SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI, SD_CS); - delay(100); // Allow bus + card to settle + delay(200); // Allow bus + card to settle // Try to re-initialize with conservative 4MHz clock (#116) - if (SD.begin(SD_CS, SPI, 4000000)) { + if (SD.begin(SD_CS, TFT_eSPI::getSPIinstance(), 4000000)) { sdAvailable = true; sdHealth.available = true; sdHealth.consecutiveFailures = 0; diff --git a/src/src.ino b/src/src.ino index 779e08d..e9251c1 100644 --- a/src/src.ino +++ b/src/src.ino @@ -164,8 +164,14 @@ void setup() { // Initialize LVGL (coexistence: runs alongside sprite pipeline) initLVGL(); - // Initialize I2C + // Initialize I2C (I2C1: the variant's SDA/SCL = header + first STEMMA QT) Wire.begin(); +#ifdef FC_I2C2_SDA + // UM FeatherS3[D] only: the second STEMMA QT connector is its own bus. + Wire1.begin(FC_I2C2_SDA, FC_I2C2_SCL); + logPrintf("[I2C] I2C1 on SDA=%d SCL=%d, I2C2 on SDA=%d SCL=%d\n", + SDA, SCL, FC_I2C2_SDA, FC_I2C2_SCL); +#endif // Show init screen tft.fillScreen(COLOR_BG); @@ -196,11 +202,13 @@ void setup() { initBattery(); initTouch(); // FT6336U capacitive touch (I2C 0x38) - // Initialize hardware SPI for SD card (Adalogger uses different CS pin) - // CS pins already set HIGH at top of setup() — SPI.begin() re-confirms SD_CS - SPI.begin(SPI_SCK, SPI_MISO, SPI_MOSI, SD_CS); - logPrintf("[SPI] Bus started: SCK=%d MISO=%d MOSI=%d SS=%d\n", - SPI_SCK, SPI_MISO, SPI_MOSI, SD_CS); + // SD and FRAM share the bus TFT_eSPI already started (SCK/MISO/MOSI from + // its build_flags, the same 36/37/35 on both boards). One SPIClass per + // controller: with USE_FSPI_PORT (#284) TFT_eSPI owns its own SPIClass on + // GPSPI2, and starting the Arduino SPI object on the same controller was a + // second driver on one bus. Its instance is used everywhere instead. + logPrintf("[SPI] Using TFT_eSPI's bus instance: SCK=%d MISO=%d MOSI=%d (SD_CS=%d FRAM_CS=%d)\n", + SPI_SCK, SPI_MISO, SPI_MOSI, SD_CS, FRAM_CS); initSD(); initFRAM(); // SPI FRAM 256KB (shared bus with TFT/SD) initRTC(); // Adalogger RTC - sets system time if RTC has valid time @@ -395,13 +403,24 @@ void loop() { // ============== I2C Scanner ============== +static int scanI2CBus(TwoWire &bus, const char *label); + void scanI2C() { - logPrintln("Scanning I2C bus..."); + int total = scanI2CBus(Wire, "I2C1 (Wire)"); +#ifdef FC_I2C2_SDA + total += scanI2CBus(Wire1, "I2C2 (Wire1)"); +#endif + logPrintf(" Total devices: %d\n\n", total); +} + +// One pass over 0x01-0x7E on the given bus; returns the device count. +static int scanI2CBus(TwoWire &bus, const char *label) { + logPrintf("Scanning %s...\n", label); int deviceCount = 0; for (byte address = 1; address < 127; address++) { - Wire.beginTransmission(address); - byte error = Wire.endTransmission(); + bus.beginTransmission(address); + byte error = bus.endTransmission(); if (error == 0) { const char* desc = ""; @@ -430,7 +449,8 @@ void scanI2C() { deviceCount++; } } - logPrintf(" Total devices: %d\n\n", deviceCount); + logPrintf(" %s: %d device(s)\n", label, deviceCount); + return deviceCount; } // ============== LVGL Initialization (#105) ============== @@ -583,7 +603,7 @@ void initSD() { SD.end(); delay(100 * attempt); // 200ms, 300ms, 400ms, 500ms } - if (SD.begin(SD_CS, SPI, SD_SPI_FREQ)) { + if (SD.begin(SD_CS, TFT_eSPI::getSPIinstance(), SD_SPI_FREQ)) { mounted = true; break; } From 03054f230a1ccf4704c3acbb553ca211541799c5 Mon Sep 17 00:00:00 2001 From: Strycher Date: Tue, 8 Sep 2026 00:18:26 -0400 Subject: [PATCH 06/13] fix(#285): reconfigure the task watchdog the core already started Arduino core 3.x initialises the task watchdog at 5 s before setup(), so esp_task_wdt_init(30 s) returned ESP_ERR_INVALID_STATE, the result went unchecked, and the 5 s default stayed in force; the WiFi reconnect wait then tripped it and rebooted the board every ~70 s on any board that cannot join WiFi. Reconfigure the running watchdog instead, check both results, and log the timeout actually in force. --- src/src.ino | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/src.ino b/src/src.ino index e9251c1..51f3a1c 100644 --- a/src/src.ino +++ b/src/src.ino @@ -251,9 +251,23 @@ void setup() { .idle_core_mask = (1 << 0) | (1 << 1), // Watch both cores .trigger_panic = true // Reset on timeout }; - esp_task_wdt_init(&wdt_config); - esp_task_wdt_add(NULL); // Add current task to watchdog - logPrintf("Watchdog enabled: %ds timeout\n", WDT_TIMEOUT_SEC); + // Arduino core 3.x starts the task watchdog itself before setup() runs + // (sdkconfig: CONFIG_ESP_TASK_WDT_INIT=1, CONFIG_ESP_TASK_WDT_TIMEOUT_S=5), + // so esp_task_wdt_init() returns ESP_ERR_INVALID_STATE here and the 5 s + // default silently stayed in force -- long enough for the WiFi reconnect + // wait to trip it and reboot the board every ~70 s (#285). Reconfigure + // the running watchdog instead, and never claim a timeout we did not get. + esp_err_t wdtErr = esp_task_wdt_init(&wdt_config); + if (wdtErr == ESP_ERR_INVALID_STATE) { + wdtErr = esp_task_wdt_reconfigure(&wdt_config); + } + esp_err_t wdtAddErr = esp_task_wdt_add(NULL); // Subscribe loopTask + if (wdtErr == ESP_OK && wdtAddErr == ESP_OK) { + logPrintf("Watchdog enabled: %ds timeout\n", WDT_TIMEOUT_SEC); + } else { + logPrintf("Watchdog: config %s, subscribe %s -- core default timeout stays in force\n", + esp_err_to_name(wdtErr), esp_err_to_name(wdtAddErr)); + } // Initialize activity timer for display sleep lastActivityTime = millis(); From 715c393a1d87964d950e14929d293ba7e7710c8f Mon Sep 17 00:00:00 2001 From: Strycher Date: Tue, 8 Sep 2026 00:30:46 -0400 Subject: [PATCH 07/13] diag(#283): when the UM board's I2C scan is empty, log idle line levels, rescan swapped and at 10 kHz Temporary diagnostic for the dead I2C1 bus: one boot log says whether SDA/SCL are held low, crossed, or too slow. To be removed once the cause is known. --- src/src.ino | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/src.ino b/src/src.ino index 51f3a1c..367b775 100644 --- a/src/src.ino +++ b/src/src.ino @@ -423,6 +423,28 @@ void scanI2C() { int total = scanI2CBus(Wire, "I2C1 (Wire)"); #ifdef FC_I2C2_SDA total += scanI2CBus(Wire1, "I2C2 (Wire1)"); +#endif +#if defined(ARDUINO_FEATHERS3) + if (total == 0) { + // Diagnostic (#283): devices are on the bus but nothing answered. One + // boot log says whether the lines are held low, crossed, or too slow: + // idle levels with the peripheral detached, a rescan with SDA/SCL + // swapped, and a rescan at 10 kHz. Then the bus is restored. + Wire.end(); + pinMode(SDA, INPUT_PULLUP); + pinMode(SCL, INPUT_PULLUP); + delay(5); + logPrintf("[I2C] idle levels with I2C1 detached: SDA(GPIO%d)=%d SCL(GPIO%d)=%d (1 = released)\n", + SDA, digitalRead(SDA), SCL, digitalRead(SCL)); + Wire.begin(SCL, SDA); // deliberately crossed + scanI2CBus(Wire, "I2C1 with SDA/SCL SWAPPED (diagnostic)"); + Wire.end(); + Wire.begin(); + Wire.setClock(10000); + scanI2CBus(Wire, "I2C1 at 10 kHz (diagnostic)"); + Wire.end(); + Wire.begin(); // back to the variant's SDA/SCL at the default clock + } #endif logPrintf(" Total devices: %d\n\n", total); } From 6684672498a8ae411a84d0cf46c0216ed27f3ec5 Mon Sep 17 00:00:00 2001 From: Strycher Date: Tue, 8 Sep 2026 01:15:30 -0400 Subject: [PATCH 08/13] docs(#283): display module wire-by-wire table; how the first boot's blank panel and dead I2C resolved The dead I2C1 (SCL held low) and blank panel were display wires placed per the retracted 05:46 table. Records the corrected holes per module pin, the diagnostic that found it, and the 2026-09-08 01:13 state: TFT, OLED, RTC, SD, GPS, nine I2C devices up; FRAM and touch still open; OLED glitch #286. --- docs/HARDWARE.md | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/docs/HARDWARE.md b/docs/HARDWARE.md index f16436f..a53fa4f 100644 --- a/docs/HARDWARE.md +++ b/docs/HARDWARE.md @@ -294,6 +294,32 @@ SDA/SCL, but TFT_BL lands on GPIO 5 and BUTTON_A on GPIO 1, so nothing clashes. TFT_RST, and the UM build uses `TFT_RST=14`. If the display never comes out of reset on the UM board, this is the first wire to check. +### Display module, wire by wire (Hosyond MSP3526, ST7796U + FT6336U) + +Written 2026-09-08 after a display wire was found in hole `1` (BUTTON_A). SDI (MOSI) +carries data *into* the panel; SDO (MISO) carries data *out* of it. Not the same line. The +firmware never reads from the panel, so SDO is the one wire that can stay off; MISO is used +by the SD card and FRAM. Module pin names are the standard ones for this board; the module's +own header order is not recorded here — read the labels on the module, not the positions. + +| Module pin | Signal | UM hole (GPIO) | Adafruit hole | +|---|---|---|---| +| VCC | 3.3 V, always on | `3V3.1` (16-pin header pos 2) — **not** `LDO2` | `3V` | +| GND | ground | `GND` | `GND` | +| CS | TFT_CS | `17` | `A0` | +| RESET | TFT_RST | `14` | `A2` | +| DC / RS | TFT_DC | `18` | `A1` | +| SDI (MOSI) | SPI data in | `MO` (35) | `MO` | +| SCK | SPI clock | `SCK` (36) | `SCK` | +| LED | backlight PWM, TFT_BL | `5` | `A5` | +| SDO (MISO) | SPI data out, unused by firmware | `MI` (37) or unconnected | `MI` | +| CTP_SDA | touch I2C data | `SDA` (8) | `SDA` | +| CTP_SCL | touch I2C clock | `SCL` (9) | `SCL` | +| CTP_INT | touch interrupt, CTP_INT | `6` | `A4` | +| CTP_RST | touch reset, not driven by firmware | as on the Adafruit setup (typically 3.3 V or tied to RESET) | same | + +Nothing from the display belongs in hole `1`; that is BUTTON_A. + ### Corrections from the first boot (2026-09-07, #283 / #284) Source for all of these: UM's FeatherS3D pinout card, @@ -316,7 +342,14 @@ Source for all of these: UM's FeatherS3D pinout card, IO4, blue LED IO13, RGB LED IO40, VBUS detect IO34. IO0 and IO3 are strapping pins. - **First run results** (image 8af7ce5): TFT init and LVGL completed and the backlight lit, but the panel stayed blank, the I2C1 scan found zero devices (the stacked OLED wing - included), and the first SD mount never returned. Investigation continues in #283/#284. + included), and the first SD mount never returned. The SD hang was two SPI drivers on one + controller (#284, fixed). The blank panel and the dead I2C1 were **wiring**: a diagnostic + boot (77ab753) showed SCL held low at the chip with the peripheral detached, and the + display wires turned out to follow the retracted 05:46 table above (TFT_CS in hole `1`, + RST in `3`, CTP_INT in `5`, BL in `6`). Rewired per the wire-by-wire table on 2026-09-08 + 01:13: TFT drawing, OLED and RTC on the bus, SD mounted, GPS fix, nine I2C1 devices, + watchdog at 30 s (#285). Still open at that point: FRAM (lost after the last move) and + the FT6336U touch at 0x38. OLED boot glitch and layout: #286. ## I2C Device Map From 6946e0e0e90326681a8de19d68e4ad9331ea2e67 Mon Sep 17 00:00:00 2001 From: Strycher Date: Tue, 8 Sep 2026 01:50:50 -0400 Subject: [PATCH 09/13] docs(#283): one-page UM bench wiring list, board labels only; warn that the Adafruit pin tables are not UM holes Adafruit GPIO numbers collide with UM silkscreen numbers (14, 17, 18) and that cost the 2026-09-08 bench session hours. um-bench-wiring.md is the single list to wire from. --- docs/HARDWARE.md | 8 ++++++ docs/um-bench-wiring.md | 62 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 docs/um-bench-wiring.md diff --git a/docs/HARDWARE.md b/docs/HARDWARE.md index a53fa4f..74bb9d4 100644 --- a/docs/HARDWARE.md +++ b/docs/HARDWARE.md @@ -41,6 +41,14 @@ ## Pin Assignments +> ⚠ **These tables are the Adafruit 5477 board.** Every number in them is an Adafruit +> GPIO, and the Adafruit GPIO numbers collide with the UM board's silkscreen numbers +> (Adafruit GPIO 14 = CTP_INT, UM hole `14` = TFT RESET; Adafruit GPIO 17 = TFT_DC, UM +> hole `17` = TFT_CS). **Do not wire the UM board from these tables.** The UM board's +> wiring, in silkscreen labels only, is under *UM FeatherS3 rewire (2026-09-05)* → +> *Display module, wire by wire* and the bench sheet there. Reading these Adafruit +> numbers as UM holes cost the 2026-09-08 bench session several hours. + ### I2C Bus (STEMMA QT) | Pin | Function | |-----|----------| diff --git a/docs/um-bench-wiring.md b/docs/um-bench-wiring.md new file mode 100644 index 0000000..63b0a6a --- /dev/null +++ b/docs/um-bench-wiring.md @@ -0,0 +1,62 @@ +# UM FeatherS3D bench wiring — the only list + +Labels below are exactly what is printed on the UM board. No GPIO numbers, no Adafruit +names. If a number here is not printed on the board, it does not belong here. +Firmware: env `um_feathers3`, branch `fc/283-um-feathers3-env`. + +## Display module (Hosyond 3.5" ST7796U + FT6336U touch) + +| Module pin (as printed on the module) | UM hole | +|---|---| +| VCC | `3V3` next to `RST` | +| GND | `GND` | +| CS | `17` | +| RS (DC, D/C, RS/DC — same pin) | `18` | +| RESET (RST) | `14` | +| SDI (MOSI) | `MO` | +| SCK | `SCK` | +| LED | `5`, or leave off | +| SDO (MISO) | leave off | +| CTP_SDA | `SDA` | +| CTP_SCL | `SCL` | +| CTP_INT | `6` | +| CTP_RST | `3V3` | + +## FRAM breakout (Adafruit SPI FRAM) + +| Breakout pin | UM hole | +|---|---| +| VIN | `3V3` | +| GND | `GND` | +| SCK | `SCK` | +| MOSI | `MO` | +| MISO | `MI` | +| CS | `12` | +| WP, HOLD | leave off | + +## Stacked on the board's own headers, no wires + +OLED FeatherWing, Adalogger FeatherWing, GPS FeatherWing. They pick up SD chip select, +buttons, RTC, OLED and GPS by position. If SD, RTC and OLED all drop out together on a +boot, the stack is not seated. + +## STEMMA QT sensor chain + +Into the STEMMA connector at the `SDA` / `SCL` end of the board. Not the other connector. + +## Holes that get nothing + +`0` · `1` · `3` · `7` · `10` · `11` · `33` · `38` · `5V` · `En` · `LDO2` + +(`1`, `3`, `33`, `38` are used by the stacked wings by position; `LDO2` is a switched output.) + +## What the boot log should say when it is all right + +``` +I2C1 (Wire): 10 device(s) ← 9 seen so far + touch 0x38 +Initializing OLED... OK +Initializing FT6336U touch... OK +Initializing SD card... OK +Initializing FRAM... OK +Initializing RTC (PCF8523)... OK +``` From 7bb8c56f01b24b11736d4c0131ef52f8b9583545 Mon Sep 17 00:00:00 2001 From: Strycher Date: Tue, 8 Sep 2026 02:08:43 -0400 Subject: [PATCH 10/13] docs(#283): display SDO goes to MI, as it did on the Adafruit setup --- docs/um-bench-wiring.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/um-bench-wiring.md b/docs/um-bench-wiring.md index 63b0a6a..fc32e48 100644 --- a/docs/um-bench-wiring.md +++ b/docs/um-bench-wiring.md @@ -16,7 +16,7 @@ Firmware: env `um_feathers3`, branch `fc/283-um-feathers3-env`. | SDI (MOSI) | `MO` | | SCK | `SCK` | | LED | `5`, or leave off | -| SDO (MISO) | leave off | +| SDO (MISO) | `MI` (as on the Adafruit setup; the firmware never reads the panel, but keep what worked) | | CTP_SDA | `SDA` | | CTP_SCL | `SCL` | | CTP_INT | `6` | From c9ede7b530a8116fbd084f86c3c0dfc95f33dfc9 Mon Sep 17 00:00:00 2001 From: Strycher Date: Tue, 8 Sep 2026 02:13:27 -0400 Subject: [PATCH 11/13] fix(#283): touch init logs the real CTP_INT pin, not a literal 14 --- src/touch.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/touch.cpp b/src/touch.cpp index b1fc4d5..bcc99a5 100644 --- a/src/touch.cpp +++ b/src/touch.cpp @@ -28,5 +28,5 @@ void initTouch() { pinMode(CTP_INT, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(CTP_INT), touchISR, CHANGE); - logPrintln("OK (interrupt on GPIO 14)"); + logPrintf("OK (interrupt on GPIO %d)\n", CTP_INT); } From 80aa48216f2afd4bb9e78cc36bf5d9e3a4280859 Mon Sep 17 00:00:00 2001 From: Strycher Date: Tue, 8 Sep 2026 02:17:23 -0400 Subject: [PATCH 12/13] docs(#283): every peripheral verified on the UM board at 02:16; wire from um-bench-wiring.md only --- docs/HARDWARE.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/HARDWARE.md b/docs/HARDWARE.md index 74bb9d4..a3b7c2f 100644 --- a/docs/HARDWARE.md +++ b/docs/HARDWARE.md @@ -356,8 +356,11 @@ Source for all of these: UM's FeatherS3D pinout card, display wires turned out to follow the retracted 05:46 table above (TFT_CS in hole `1`, RST in `3`, CTP_INT in `5`, BL in `6`). Rewired per the wire-by-wire table on 2026-09-08 01:13: TFT drawing, OLED and RTC on the bus, SD mounted, GPS fix, nine I2C1 devices, - watchdog at 30 s (#285). Still open at that point: FRAM (lost after the last move) and - the FT6336U touch at 0x38. OLED boot glitch and layout: #286. + watchdog at 30 s (#285). At 02:16 the same night, after the display and FRAM wires were + redone from `docs/um-bench-wiring.md`, **every peripheral was up**: TFT, touch (0x38), + OLED, SD, FRAM, RTC, GPS, BME688, SHT41, IMU and magnetometer, fuel gauge; ten devices on + I2C1. Wire the UM board from that page and nothing else. OLED boot glitch and layout: #286. + Fuel-gauge percentage reads 140 % on the [D] and needs its own compensation: follow-up. ## I2C Device Map From 91fd9bc09e6da0d8c895ff927ee4c4eca668efa4 Mon Sep 17 00:00:00 2001 From: Strycher Date: Tue, 8 Sep 2026 02:21:40 -0400 Subject: [PATCH 13/13] docs(#283): lock the UM bench wiring; CLAUDE.md pin assignments for both boards um-bench-wiring.md is the authoritative UM wire list (owner, 2026-09-08 02:20). HARDWARE.md points at it and marks the derivation tables record-only. CLAUDE.md now lists both boards' GPIO views, names the UM board as the bench board, and corrects the Adafruit GPS pins (38/39, not 5/6). Owner also verified the TFT sleep timeout on the UM board. --- CLAUDE.md | 25 ++++++++++++++++++++----- docs/HARDWARE.md | 9 +++++++-- docs/um-bench-wiring.md | 7 ++++++- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index fa0d1e0..2c9b382 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -70,9 +70,9 @@ Pre-migration commit SHAs quoted in old issues refer to the archive, not here. | Component | Value | |-----------|-------| -| MCU | Adafruit ESP32-S3 Feather 4MB Flash 2MB PSRAM (PID 5477) | -| Display | Hosyond 3.5" ST7796U IPS TFT 480x320 (MSP3526), TFT_eSPI **rotation 3** — the panel is mounted 180° from TFT_eSPI's assumption, so landscape is the complement of the nominal rotation (#157). Configured via `build_flags`, not `User_Setup.h` (#184) | -| Touch | FT6336U capacitive on I2C `0x38` (CTP_INT = GPIO14, FALLING edge) | +| MCU | **Two boards build from this tree (#283).** Bench since 2026-09-05: **UM FeatherS3D** (Adafruit PID 6399, 16 MB flash, 8 MB QSPI PSRAM), env `um_feathers3`, registry `um_feather`. Original: Adafruit ESP32-S3 Feather 4MB Flash 2MB PSRAM (PID 5477), env `feather_s3`, registry `field_compass` | +| Display | Hosyond 3.5" ST7796U IPS TFT 480x320 (MSP3526), TFT_eSPI **rotation 3** — the panel is mounted 180° from TFT_eSPI's assumption, so landscape is the complement of the nominal rotation (#157). Configured via `build_flags`, not `User_Setup.h` (#184). On the ESP32-S3 TFT_eSPI needs `USE_FSPI_PORT` or it writes to address 0 (#284) | +| Touch | FT6336U capacitive on I2C `0x38` (CTP_INT: GPIO 6 on the UM board, GPIO 14 on the Adafruit; CHANGE interrupt) | | IMU | LSM6DSOX + LIS3MDL (STEMMA QT) — heading reference is X+ | | GPS | PA1616D (MT3339) — Serial1 @ 9600, CR1220 backup, PMTK101 hot restart | | SD | Adalogger FeatherWing (SD_CS = GPIO10) | @@ -83,11 +83,26 @@ Pre-migration commit SHAs quoted in old issues refer to the archive, not here. ### Pin Assignments -- **I2C:** SDA=GPIO3, SCL=GPIO4 (STEMMA QT) +**UM FeatherS3D (the bench board) — wire it from [`docs/um-bench-wiring.md`](docs/um-bench-wiring.md) +and nothing else.** That page is silkscreen labels only and was locked by the owner on +2026-09-08 after every peripheral came up. The GPIO view of the same wiring, as the +firmware sees it (`src/fc_config.h` UM block + `platformio.ini` `[env:um_feathers3]`): + +- **I2C1:** SDA=8, SCL=9 (header + first STEMMA QT); **I2C2:** SDA=16, SCL=15 (second STEMMA QT, LDO2-powered) +- **SPI:** SCK=36, MOSI=35, MISO=37 +- **TFT:** CS=17, DC=18, RST=14, BL=5 +- **SD/FRAM/Touch:** SD_CS=3, FRAM_CS=12, CTP_INT=6 +- **Buttons:** A=1, B=38, C=33 · **LDO2 enable:** 39 · **GPS UART:** RX=44, TX=43 + +**Adafruit ESP32-S3 Feather (PID 5477), env `feather_s3`** — these numbers are Adafruit GPIOs +and collide with the UM silkscreen numbers (14, 17, 18 mean different things). Do not wire +the UM board from this list: + +- **I2C:** SDA=3, SCL=4 (STEMMA QT) - **SPI:** SCK=36, MOSI=35, MISO=37 - **TFT:** CS=18, DC=17, RST=16, BL=8 (PWM-dimmable) - **SD/FRAM/Touch:** SD_CS=10, FRAM_CS=15, CTP_INT=14 -- **GPS Serial1:** RX=GPIO5, TX=GPIO6 +- **Buttons:** A=9, B=6, C=5 · **GPS Serial1:** RX=38, TX=39 (variant `RX`/`TX`; an earlier line here said 5/6, which was wrong) ## Repository Layout (#186) diff --git a/docs/HARDWARE.md b/docs/HARDWARE.md index a3b7c2f..0e32bb5 100644 --- a/docs/HARDWARE.md +++ b/docs/HARDWARE.md @@ -127,6 +127,11 @@ The Feather form factor breaks out 21 GPIO pins on its headers. **18 are in use; ## UM FeatherS3 rewire (2026-09-05) — bench sheet and resulting pin changes +> ✅ **LOCKED WIRING (owner, 2026-09-08 02:20 EDT): [`docs/um-bench-wiring.md`](um-bench-wiring.md) +> is the authoritative wire list for the UM board — silkscreen labels only, every +> peripheral verified up from the boot log at 02:16.** Everything else in this section is +> the record of how that list was reached and why; do not wire from it. + **Status.** On 2026-09-05 the breadboard moved from the Adafruit 5477 Feather to a **UM FeatherS3D** (Adafruit PID 6399, ESP32-S3 N16R8: 16 MB flash, 8 MB PSRAM), wired per the bench sheet below. It is registered in the pio-flash registry as `um_feather` @@ -169,7 +174,7 @@ not header positions**, and that table was retracted at 05:59. Never derive UM h positions from the variant's analog aliases. (The same mistake was repeated on 2026-09-07 and is the reason this section exists.) -### Bench sheet — unplug from, plug into (06:02) +### Bench sheet — unplug from, plug into (06:02) — RECORD ONLY, superseded by `um-bench-wiring.md` ⚠ Adafruit has two `3V` pins. The UM board has `0` — GPIO 0, the BOOT strapping pin — in the second one's position. Both UM 3.3 V outputs are `3V3.1` (position 2) and `3V3.2` @@ -320,7 +325,7 @@ own header order is not recorded here — read the labels on the module, not the | SDI (MOSI) | SPI data in | `MO` (35) | `MO` | | SCK | SPI clock | `SCK` (36) | `SCK` | | LED | backlight PWM, TFT_BL | `5` | `A5` | -| SDO (MISO) | SPI data out, unused by firmware | `MI` (37) or unconnected | `MI` | +| SDO (MISO) | SPI data out, unused by firmware | `MI` (37), as on the Adafruit setup | `MI` | | CTP_SDA | touch I2C data | `SDA` (8) | `SDA` | | CTP_SCL | touch I2C clock | `SCL` (9) | `SCL` | | CTP_INT | touch interrupt, CTP_INT | `6` | `A4` | diff --git a/docs/um-bench-wiring.md b/docs/um-bench-wiring.md index fc32e48..4e48cab 100644 --- a/docs/um-bench-wiring.md +++ b/docs/um-bench-wiring.md @@ -1,8 +1,13 @@ # UM FeatherS3D bench wiring — the only list +**LOCKED by the owner on 2026-09-08 02:20 EDT.** Every peripheral on this list was verified +up from the board's own boot log at 02:16 (#283). Change this page only with a new +verified boot log and the owner's word. + Labels below are exactly what is printed on the UM board. No GPIO numbers, no Adafruit names. If a number here is not printed on the board, it does not belong here. -Firmware: env `um_feathers3`, branch `fc/283-um-feathers3-env`. +Firmware: env `um_feathers3`; the GPIO view of the same wiring is in `CLAUDE.md` +→ Pin Assignments and `src/fc_config.h`. ## Display module (Hosyond 3.5" ST7796U + FT6336U touch)