Files
xblink/docs/DEVELOPMENT_PLAN.md
michael acb48902be Add pattern library, NTAG5 config check, NDEF writer (M3+M4)
M3: 5 autonomous LP5562 engine patterns (breathe, heartbeat, slow_pulse,
rgb_cycle, color_wash) with dual LED mode support (RGBW/Mono3) and
2mA/ch current for EH-realistic power budget.

M4: NTAG5Link I2C slave driver with session register reads, EEPROM
write-verify, and NDEF Type 5 text record output. Config check validates
CONFIG_0, CONFIG_1, EH_CONFIG against expected values and writes result
as NFC-readable text. Verified on hardware: config check passes, NDEF
readable via ISO15693 from phone.

Key findings from hardware testing:
- SRAM passthrough arbiter blocks NFC reads while I2C bus is active
  (resolves naturally when MCU enters STANDBY — noted in M7 plan)
- NTAG5 must be in I2C slave mode (not master) for MCU communication
- EH config mask set to 0x00 (skip check during external power testing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 15:03:31 -08:00

300 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# xblink Development Plan
## Overview
xblink is an NFC-powered LED implant. Development is organized into small milestones grouped by hardware requirements. Each milestone is scoped to roughly one session of work. Abstractions (traits, pattern formats) are deferred until we have hands-on experience with the hardware they'll abstract.
### Guiding Principles
- **Hardware-first**: Get real LEDs blinking before designing abstractions
- **YAGNI**: No traits, formats, or protocols until we need them
- **Small milestones**: Each should produce a flashable, testable result
- **Test equipment**: Multimeter for power budget, logic analyzer (optional) for I2C debugging
---
## Group A — LP5562 Only
**Hardware needed**: XIAO M0 + LP5562EVM + mini-USB cable
### M1: LED Smoke Test
**Goal**: Verify LP5562 direct PWM control works over I2C.
Copy the proven LP5562 driver from `../ntag5-samd21-lp562/src/lp5562.rs` into the project. Wire up I2C and GPIO EN in `main.rs`. Light up each RGBW channel to confirm wiring and driver work.
**Tasks**:
1. Copy `lp5562.rs``src/led/lp5562.rs`
2. Create `src/led/mod.rs` with `pub mod lp5562;` re-export
3. Update `main.rs`: I2C init (A4/A5, 400kHz), GPIO EN on D0/A0, LP5562 enable → 1ms delay → `init_direct_control(Internal)``set_all_current(100,100,100,100)` → cycle through RGBW
4. Flash and verify all 4 LED channels light up
**Reference**: Sibling project `main.rs` lines 40-114 — proven working sequence with LP5562EVM.
**Done when**: All 4 RGBW LEDs cycle on the LP5562EVM.
### M2: Engine Patterns
**Goal**: Program LP5562 execution engines to run patterns autonomously — the core value proposition.
After engine programming, the MCU can sleep while LP5562 keeps running patterns on its own internal oscillator. This milestone proves the architecture works.
**Tasks**:
1. Build engine programs using `EngineCommand` builder (already in driver):
- **Breathing**: `set_pwm(0)` → ramp up → ramp down → `branch(0, 0)` (infinite loop)
- **Heartbeat**: `set_pwm(0)` → fast ramp up → slow ramp down → wait → `branch(0, 0)`
- **Color cycle**: 3 engines with staggered phases, synchronized via `trigger()`
2. Map LED channels to engines via `set_led_mapping()`
3. Load programs, set engines to Run, verify LEDs animate without MCU loop
4. Verify MCU can `stop_engine()` and resume with a different pattern
**Done when**: LP5562 runs a breathing pattern while `main()` sits in an idle loop (no `delay_ms` driving the LEDs).
### M3: Hardcoded Pattern Library
**Goal**: Build a set of const patterns in firmware, selectable at boot.
This gives us real pattern data to inform the EEPROM storage format later (M5), without designing that format prematurely.
**Tasks**:
1. Define 4-5 patterns as `const` or `static` data:
- Solid RGBW (direct PWM, no engines)
- Breathing (single channel, 1 engine)
- Heartbeat pulse (1 engine, asymmetric ramp)
- RGB color cycle (3 engines, trigger sync)
- Rainbow fade (3 engines, staggered offset)
2. Simple pattern selector in `main.rs` (e.g., cycle through patterns on each boot, or use a compile-time `const`)
3. Reduce LED current to 20-50 (2-5mA/ch) to simulate EH power budget
**Done when**: Can flash different patterns by changing a const, each runs autonomously on LP5562.
---
## Group B — Add NTAG5Link
**Hardware needed**: + NTAG5Link Click board + I2C jumper wire
### M4: NTAG5 EEPROM Read/Write
**Goal**: Communicate with NTAG5Link over I2C as a slave device.
**Important**: The MCU accesses NTAG5Link as a standard I2C slave at address 0x54 — plain register read/write. This is NOT the NFC-side ISO15693 custom commands used by the Python ntag5sensor tooling.
**I2C bus management**: LP5562 (0x30) and NTAG5Link (0x54) share the same I2C bus (A4/A5). Both addresses are used simultaneously — the MCU talks to each device by its address. Key considerations:
- Both are I2C slave devices with non-conflicting addresses
- LP5562 engines run autonomously after programming — no ongoing I2C traffic needed
- Pause LP5562 engines (set exec to Hold) before lengthy NTAG5 I2C transactions to avoid any bus contention edge cases
- NTAG5Link's use_case must be set to `i2c_slave` (not `i2c_master`) so it doesn't drive the bus
**Tasks**:
1. Write `src/ntag5/mod.rs``Ntag5Link<I2C>` struct, generic over `embedded_hal::i2c::I2c`
2. Write `src/ntag5/registers.rs` — register map constants from `ntag5link.py`
3. Implement EEPROM block read/write (2-byte block address, 4 bytes per block)
4. Implement session register read (single-byte address)
5. Test: write known data to EEPROM via PCSC reader (ntag5sensor), read back on MCU, display result as LP5562 colors (visual verification)
**NTAG5Link config** (one-time setup via PCSC reader):
| Setting | Value | Constant |
|---------|-------|----------|
| Use case | I2C slave | `CONFIG_1_USE_CASE_CONF_I2C_SLAVE = 0 << 4` |
| Arbiter mode | SRAM pass-through | `CONFIG_1_ARBITER_MODE_SRAM_PASSTHROUGH = 2 << 2` |
| SRAM enable | On | `CONFIG_1_SRAM_ENABLE = 1 << 1` |
| EH mode | Low field strength | `CONFIG_0_EH_MODE_LOW_FIELD_STRENGTH = 2 << 2` |
| EH voltage | 3.0V | EH_CONFIG bits 2:1 = 10 |
**Done when**: MCU reads EEPROM data written by PCSC reader and displays it as LED colors.
### M5: Boot-from-EEPROM
**Goal**: Design pattern binary format (informed by M2-M3 experience) and boot from stored patterns.
By now we know exactly what engine programs look like in practice, so the format design is grounded in real usage.
**Tasks**:
1. Design binary pattern format — header + engine programs + LED mapping (see format spec below)
2. Write `src/pattern/mod.rs` — deserializer (parse EEPROM bytes → engine programs)
3. Write `src/pattern/engine.rs` — convert parsed pattern → LP5562 engine program load sequence
4. Update `main.rs` boot sequence: read EEPROM → parse pattern → program LP5562 → idle
5. Write a Python script (or extend ntag5sensor) to serialize patterns to EEPROM via PCSC
6. Test: write pattern via PCSC, power-cycle, verify LP5562 runs the pattern
**Pattern format** (preliminary — will be refined based on M2-M3 learnings):
```
Header (16 bytes):
[0-3] Magic: "XBLK" (0x58 0x42 0x4C 0x4B)
[4] Version: 0x01
[5] Flags (which engines used, direct color mode)
[6] Number of pattern entries
[7] LED current setting (0-255 → 0-25.5mA)
[8-13] Reserved / direct RGBW color values
[14-15] CRC-16
Entry (2 + N*2 bytes):
[0] Type (0x01 = engine program)
[1] Length in 16-bit words (1-16)
[2..] Engine command words (big-endian)
Assignment (4 bytes, after entries):
[0-2] Engine 1/2/3 → entry index (0xFF = unused)
[3] LED_MAP register value
```
Typical 3-engine pattern: 16 + 3×34 + 4 = **122 bytes** (fits easily in 2048B EEPROM).
**Done when**: MCU boots, reads pattern from EEPROM, LP5562 runs it autonomously.
### M6: SRAM Mailbox
**Goal**: Phone writes a new pattern via NFC, MCU picks it up and reprograms LP5562.
**Tasks**:
1. Write `src/ntag5/sram.rs` — SRAM block read/write (64 blocks × 4 bytes at 0xF8-0xFF)
2. Design SRAM mailbox command/response protocol:
```
Command (NFC → MCU): [cmd, seq, len_lo, len_hi, payload...]
Response (MCU → NFC): [status, seq, len_lo, len_hi, payload...]
Commands: 0x01=write_pattern, 0x02=get_info, 0x03=set_color, 0x04=get_version
```
3. Configure NTAG5Link FD pin for SRAM write indication
4. Implement polling or interrupt-based command detection (EIC wake comes in M7)
5. MCU receives pattern → stores to EEPROM → reprograms LP5562
6. Test with PCSC reader first, then VivoKey RawNFC app
**Done when**: Pattern update works via NFC without power-cycling the MCU.
---
## Group C — Power + Recovery
**Hardware needed**: + Hall effect sensor + multimeter
### M7: Sleep/Wake
**Goal**: MCU enters STANDBY after programming LP5562, wakes on NFC activity.
**Tasks**:
1. Configure SAMD21 EIC for NTAG5Link FD pin (wake source)
2. After LP5562 engine programming, enter STANDBY (~5µA)
3. On EIC interrupt (FD pin), wake, process SRAM mailbox, return to sleep
4. Verify LP5562 keeps running patterns during MCU sleep
5. Measure current with multimeter: active vs standby vs total system
6. Verify I2C bus releases cleanly on STANDBY entry (SDA/SCL float high via pull-ups, no bus hold). With SRAM passthrough arbiter, an active I2C bus blocks NFC-side reads — confirmed during M4 testing.
**Power budget reference**:
| Component | Active | Standby |
|-----------|--------|---------|
| SAMD21E | 3-5mA | ~5µA |
| LP5562 quiescent | ~0.5mA | ~0.5mA |
| LP5562 LEDs (4ch @ 3mA) | ~12mA | ~12mA |
| NTAG5Link | ~100µA | ~1µA |
| Hall sensor (DRV5032) | ~1.6µA | ~1.6µA |
| EN pull-up (1M @ 3V) | ~0 | ~0 (3µA only when EN low) |
| **Total** | **~17mA** | **~12.5mA** |
Key insight: LED current dominates. MCU sleep saves ~4mA. Total must stay under EH max (~12.5mA), so LED current must be limited.
**Done when**: MCU sleeps after boot, LP5562 runs patterns, MCU wakes on NFC to accept new pattern.
### M8: Recovery Mode + Pattern Swap
**Goal**: Hall sensor provides pattern cycling (tap) and safe mode entry (hold + confirm).
**Circuit**: Wired-AND on LP5562 EN — hall sensor (open-drain) + MCU GPIO (open-drain) + pull-up resistor. Magnet presence forces EN low at hardware level, independent of MCU state. Hall output also connected to MCU EIC pin for interrupt/wake. See `docs/plans/2026-03-03-hall-en-design.md`.
**Interaction model**:
- **Boot-time recovery**: Hall asserted at power-on → EN held low (hardware), MCU skips EEPROM, loads default pattern, stays awake
- **Tap (<1s)**: LEDs off instantly (hardware EN drop) → magnet removed → MCU loads next pattern → LEDs back
- **Hold (>3s + confirm)**: LEDs off (hardware) → magnet removed → MCU blinks warning (3x red) → 2s window for confirmation tap → if confirmed, enters recovery mode. No second tap → resumes normal operation
- **MCU crashed + magnet**: Hall still kills EN (hardware safety)
**Tasks**:
1. Wire hall sensor: open-drain output to EN line (wired-AND) + separate wire to EIC pin
2. Add pull-up resistor (100k-1M) on EN line
3. Configure MCU GPIO (D0/A0) as open-drain for EN control
4. Implement EIC interrupt handler for hall sensor pin
5. Implement boot-time recovery: read hall GPIO at startup, if asserted → default pattern, skip EEPROM
6. Implement runtime state machine: tap detection, hold timing, warning blink, recovery confirmation
7. Recovery behavior: load hardcoded solid white (low brightness), skip EEPROM, stay awake
8. Set recovery flag in SRAM so phone app can detect it
**Done when**: Boot-time recovery works (magnet at power-on), tap cycles patterns at runtime, hold + confirm enters runtime recovery, hardware EN kill works even if MCU is unresponsive.
### M9: Power Characterization
**Goal**: Measure real power consumption and tune EH settings.
**Tasks**:
1. Measure current draw: MCU active, MCU standby, LP5562 at various current settings
2. Test NTAG5Link EH output with phone NFC at different current thresholds
3. Find optimal balance: maximum LED brightness within EH budget
4. Document actual power budget (replaces estimates in this plan)
**Done when**: We have real numbers and documented EH configuration that works reliably.
---
## Group D — Abstraction + Polish
**Prerequisite**: Groups A-C working. Now we know the real interface needs.
### M10: LedController Trait
**Goal**: Extract trait from proven LP5562 usage patterns.
After M1-M9, we know exactly what `set_channel`, `load_pattern`, and `stop_pattern` actually need to look like. Design the trait from real experience, not speculation.
**Tasks**:
1. Define `LedController` trait in `src/led/mod.rs` based on actual usage in `main.rs`
2. Implement for LP5562 wrapper (including GPIO EN pin)
3. Refactor `main.rs` to use trait-based API
4. Verify everything still works
### M11: SimplePwm Driver
**Goal**: Single-color LED support via GPIO PWM — only if we have hardware to test.
Implement `LedController` for direct GPIO PWM. Skip this milestone entirely if we don't have single-color LEDs to validate against.
---
## Group E — Future
Deferred until Groups A-D are solid.
### Custom PCB (SAMD21E)
- Port from `xiao_m0` BSP to `atsamd-hal` with `samd21e` feature
- Add `cortex-m-rt = "0.7"`, custom `memory.x` linker script
- Remap SERCOM and pin assignments for 32-pin QFN
- PCB design: SAMD21E (5×5mm) + LP5562 (3×3mm) + NTAG5Link (1.45×1mm) + hall sensor + antenna
- Target: disc <15mm diameter or capsule <12×30mm
### Companion App (React Native)
- NFC via platform APIs (Android NFC, iOS Core NFC)
- ISO15693 access to NTAG5Link SRAM mailbox
- Pattern editor UI + upload
- Status display (firmware version, current pattern, recovery state)
### Firmware Update (NFC OTA)
- Custom bootloader in protected flash (~8KB, BOOTPROT fuse)
- Chunked firmware transfer via SRAM mailbox (256B per chunk)
- CRC verification, fallback to known-good firmware
- Development: use UF2 via USB; NFC OTA only if needed post-implant
---
## Technical Risks
| Risk | Severity | Mitigation |
|------|----------|------------|
| Power budget too tight for 4ch LED | High | Limit current to 2-3mA/ch, use pulsed patterns, run fewer channels |
| I2C bus contention (LP5562 + NTAG5) | Medium | Non-conflicting addresses (0x30 vs 0x54). Pause LP5562 engines during NTAG5 I2C bursts. Verify NTAG5 is in i2c_slave mode (not i2c_master) |
| EEPROM write endurance (~100K cycles) | Low | SRAM for transient comms, EEPROM only for pattern persistence |
| Boot time too slow | Low | Estimated ~15-20ms appears instant to user |
| NFC coupling distance too short | Medium | Optimize antenna, low field strength EH mode, accept 1-3cm |
| Firmware bug post-implant | High | Hall sensor recovery, thorough pre-implant testing, pattern-only updates |