# Running the pin-mapping tests under stock MicroPython (EP-2350 "Ting")

Goal: temporarily replace the TE firmware with **stock MicroPython for the
RP2350A** so the `machine` module is available and no competing firmware is
driving the I²C bus, then run the T1–T3 scans from `pinmapping_findings.md`.

The chip is a confirmed **RP2350A** (GPIO0–29 only). The tests address pins by
GPIO number, so the generic Pico 2 build is sufficient — no custom board
definition is required for `I2C.scan()`.

> **Status (2026-07-17):** The board uses a **custom Teenage Engineering
> bootloader**, not the RP2350 ROM BOOTSEL — so **picotool cannot be used** for
> this board in its normal update mode (see below). The viable path is
> **drag-and-drop onto the `TING BOOT` drive**. Two hardware unknowns remain to
> be settled in a future session: the **crystal frequency** and the **QSPI flash
> chip / CS pin** (needed for worst-case recovery).

---

## Key discovery: the bootloader is TE's own, not the RP2350 ROM

Entering update mode (**hold the handle + double-click the small button above
USB-C**, per <https://teenage.engineering/guides/ep-2350>) mounts a drive called
**`TING BOOT`** with `INFO_UF2.TXT`:

```
UF2 Bootloader v1.0
Model: EP-2350
Board-ID: 830002036254102695
```

and enumerates on USB as:

```
ID 2367:0620 Teenage Engineering EP-2350 Boot
```

- USB VID **`2367` = Teenage Engineering** (not `2e8a` = Raspberry Pi). The RP2350
  boot ROM's strings (`Model: Raspberry Pi RP2350`) live in silicon and cannot be
  changed — so this is a **flash-resident custom TE bootloader**, i.e. TE's
  firmware-update mode, **not** the chip's ROM BOOTSEL.
- **Consequence: `picotool` will never see this mode** (it needs the ROM's
  PICOBOOT vendor interface, which a custom bootloader doesn't expose). `sudo`
  does not help. Therefore **the OTP secure-boot check and the flash backup are
  NOT possible from here.** The earlier picotool-based plan does not apply to
  this board.

### But the format is standard, so drag-drop may just work

TE's own guide says the update drive appears as **"`TING BOOT` or `rp2350`"** and
you drag the *same* firmware file to either. That means TE firmware UF2s are
**standard RP2350 UF2s** (base `0x10000000`, family `0xe48bff59` = RP2350 ARM-S)
— **identical in format to the stock MicroPython `RPI_PICO2` UF2**. So the
`TING BOOT` bootloader may well accept the MicroPython file. It also confirms the
**RP2350 ROM BOOTSEL (`rp2350`, USB `2e8a:000f`) is reachable on this board** (USB
is wired to the ROM) — that is the picotool-capable, guaranteed-recovery path if
ever needed.

---

## The exact firmware to grab

**`RPI_PICO2-20260406-v1.28.0.uf2`** — current stable (v1.28.0), **ARM** build
for the RP2350A.

- Direct: `https://micropython.org/resources/firmware/RPI_PICO2-20260406-v1.28.0.uf2`
- Page:   `https://micropython.org/download/RPI_PICO2/`

Deliberately **not**:

- `RPI_PICO2-RISCV` — wrong core (we want the Cortex-M33 / ARM build, matching
  the TE firmware's UF2 family `0xe48bff59` = RP2350 ARM-S).
- `RPI_PICO2-W` — pulls in CYW43 wireless this board doesn't have.
- the v1.29 previews — no reason to chase a nightly for an `I2C.scan()`.

Download on the host:

```bash
cd ~/Downloads
wget https://micropython.org/resources/firmware/RPI_PICO2-20260406-v1.28.0.uf2
```

---

## Risk assessment for the drag-drop route

Since we cannot pre-check OTP or back up flash, understand the failure modes
before trying. **Good news: recovery does NOT depend on `TING BOOT` surviving** —
once stock MicroPython runs, `machine.bootloader()` reboots the chip straight
into the `rp2350` ROM BOOTSEL, from which you drag the TE UF2 to restore.

Dragging the MicroPython UF2 onto `TING BOOT` can end three ways:

1. **Rejected** (TE bootloader checks a signature/magic) → the drive simply
   re-mounts as `TING BOOT`. **Zero harm.** It's locked → fall back to read-only
   T4.
2. **Accepted and boots** → MicroPython REPL appears; run the scan; restore later
   via `machine.bootloader()` → drag TE UF2. Clean. (This outcome also
   *empirically proves OTP secure-boot is NOT enforced* — an unsigned image ran.)
3. **Accepted but does NOT enumerate** — the one real hazard. If the EP-2350's
   crystal isn't 12 MHz, MicroPython flashes but never comes up as USB serial, so
   `machine.bootloader()` is unreachable, and `TING BOOT` may be gone too.
   **Recovery then requires the hardware route** (below).

### ⚠️ Prerequisites still to settle (future session)

- **Crystal frequency.** Stock `RPI_PICO2` assumes a **12 MHz XOSC**. If the
  EP-2350's crystal differs, expect outcome #3. Verify from schematic/BOM before
  relying on this.
- **QSPI flash chip + CS pin location.** Needed for the hardware recovery route.
  Identify the flash IC and its CS pad on the (already-opened) board.

**Do not attempt the reflash until you are prepared for the hardware-recovery
fallback**, i.e. you can briefly ground the QSPI flash CS pin at power-up to
force the `rp2350` ROM BOOTSEL. If you are not willing to probe the flash chip,
use the read-only T4 route instead (zero brick risk).

---

## Procedure — drag-and-drop route

```
1. Enter TE update mode: remove the lower lid, connect USB-C, HOLD the handle,
   then double-click the small button above the USB-C port.
   -> a drive named "TING BOOT" appears. Keep holding the handle per TE's guide.

2. Copy the MicroPython UF2 onto that drive:
      cp ~/Downloads/RPI_PICO2-20260406-v1.28.0.uf2 "/media/<user>/TING BOOT/"
      sync
   Then watch:
     - Drive re-mounts as TING BOOT, unchanged      -> REJECTED (outcome #1). Go to T4.
     - Board reboots and a USB SERIAL device appears -> SUCCESS  (outcome #2). Go to step 3.
     - Board reboots but NOTHING enumerates          -> outcome #3. Hardware recovery.

3. Run the scan over the new MicroPython REPL (see below).
```

---

## Run the scan (paste over the REPL)

Use the existing `scratch_repl.py` driver, or any serial terminal. On stock
firmware the `machine` module is back:

```python
from machine import Pin, I2C, SoftI2C
i2c = I2C(1, sda=Pin(14), scl=Pin(15), freq=100_000)
print([hex(a) for a in i2c.scan()])       # expect 0x18 (accel) + 0x1b or 0x54 (codec)
s = SoftI2C(scl=Pin(15), sda=Pin(14), freq=100_000)
print([hex(a) for a in s.scan()])         # cross-check
```

Success = the scan returns the NAU88L21 address (`0x1b` = CSB0, `0x54` = CSB1),
which proves GPIO14/15 and reveals the CSB strap. See T2/T3 in
`pinmapping_findings.md` for positively identifying the codec by ID register and
disambiguating BCLK/LRCLK/MCLK.

---

## Restore the TE firmware when done

**If MicroPython is running (normal case):**

```python
import machine
machine.bootloader()      # reboots into the rp2350 ROM BOOTSEL (USB 2e8a:000f)
```

Then drag `ep-2350_firmware_1_0_8.uf2` onto the `rp2350` drive (or, since this is
real ROM BOOTSEL, `picotool load ep-2350_firmware_1_0_8.uf2 -x` now works).

**Alternatively**, if `TING BOOT` still enters via handle + double-click after the
foreign flash, just drag the TE UF2 there as usual.

---

## Hardware recovery (outcome #3 — MicroPython flashed but no USB)

Only needed if the reflash left the board with no working firmware and no way
into a bootloader:

1. Power off. **Momentarily ground the QSPI flash chip's CS pin** while applying
   power. With no readable image, the RP2350 ROM falls through to USB boot and
   presents the **`rp2350`** drive (`2e8a:000f`).
2. Release CS, then restore TE firmware: drag `ep-2350_firmware_1_0_8.uf2` onto
   `rp2350`, or `picotool load ep-2350_firmware_1_0_8.uf2 -x`.

> Requires knowing the flash IC and CS pad location — **still to be identified**
> (see prerequisites). Do not attempt the reflash without this recovery path
> understood.

---

## Zero-risk alternative: read-only T4 probes

If any of the above risk is unacceptable, **stay on the TE firmware** and run the
read-only passive probes (T4 in `pinmapping_findings.md`): re-run the scratch
dump scripts, which never disturb the bus. The I²C = GPIO14/15 mapping is already
High-confidence; T1/T2 only add the codec's direct ACK and the CSB strap. Do
**not** bit-bang or reconfigure I2C1 live — both attempts crashed the running
firmware because its own driver was mid-transaction.
