Initial project scaffold for xblink NFC-powered LED implant

Set up Rust embedded project targeting SAMD21 (thumbv6m-none-eabi) with
XIAO M0 BSP for dev board prototyping. Includes LP5562 EN pin control
on D0/A0, project documentation (README, CLAUDE.md), phased status
tracker, and detailed development plan covering LP5562 driver integration,
NTAG5Link I2C driver, pattern storage format, power management, NFC
communication protocol, recovery mode, and firmware update strategy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
michael
2026-03-03 09:32:30 -08:00
commit bae64c1e3a
9 changed files with 1342 additions and 0 deletions

344
docs/DEVELOPMENT_PLAN.md Normal file
View File

@@ -0,0 +1,344 @@
# xblink Development Plan
## Overview
xblink is an NFC-powered LED implant. Development proceeds in phases, starting with dev board prototyping (XIAO M0 + LP5562EVM + NTAG5Link Click) and progressing to a custom SAMD21E PCB suitable for implantation.
LED controller code is modular via a `LedController` trait — the LP5562 is the primary target, but the architecture supports other LED driver ICs and simple GPIO PWM for single-color LEDs.
---
## Phase 1: LP5562 Driver Integration
**Goal**: Get LP5562EVM running RGBW LED patterns from the XIAO M0.
**Hardware**: XIAO M0 ↔ LP5562EVM via I2C (A4/A5) + EN on D0/A0.
### Tasks
1. **Copy LP5562 driver** from `../ntag5-samd21-lp562/src/lp5562.rs` into `src/led/lp5562.rs`. This is a complete 757-line driver covering all LP5562 features: direct PWM, current control, engine programming, LED mapping, power-save, and clock config. It is generic over `embedded_hal::i2c::I2c` (1.0).
2. **Define `LedController` trait** in `src/led/mod.rs`:
```rust
pub trait LedController {
type Error;
fn set_channel(&mut self, channel: u8, brightness: u8) -> Result<(), Self::Error>;
fn set_all_channels(&mut self, values: &[u8]) -> Result<(), Self::Error>;
fn all_off(&mut self) -> Result<(), Self::Error>;
fn channel_count(&self) -> u8;
fn supports_engines(&self) -> bool;
fn load_pattern(&mut self, pattern: &[u8]) -> Result<(), Self::Error>;
fn stop_pattern(&mut self) -> Result<(), Self::Error>;
}
```
3. **Add GPIO EN pin** to LP5562 wrapper. The LP5562EVM EN pin is connected to D0/A0 for hardware power control:
```rust
pub struct Lp5562Gpio<I2C, EN> {
lp: Lp5562<I2C>,
en_pin: EN,
}
```
This wrapper manages the hardware enable alongside the I2C driver, allowing complete power-down for energy savings.
4. **Test sequences**:
- Direct PWM: set individual channel colors, verify RGBW output
- Current control: set 2-5mA per channel (suitable for EH power budget)
- Engine programs: breathing pattern (ramp up/down with branch loop)
- Engine programs: RGB color cycle (3 engines with synchronized triggers)
- Engine programs: heartbeat pulse (fast ramp, slow decay)
### Reference Code
The sibling project's `main.rs` (lines 49-114) provides a complete working example:
- LP5562 init: `enable()` → 1ms delay → `init_direct_control(Internal)` → `set_all_current(100, 100, 100, 100)`
- Color cycling loop with `set_all_pwm()` and delay
- This exact pattern works with the LP5562EVM
---
## Phase 2: NTAG5Link Driver (MCU-side I2C)
**Goal**: Read and write NTAG5Link EEPROM and SRAM from the SAMD21 over I2C.
**Hardware**: XIAO M0 ↔ NTAG5Link Click via I2C (shared bus with LP5562).
**Important**: The MCU accesses the NTAG5Link as a standard I2C slave at address 0x54. This is plain register read/write — completely different from the NFC-side ISO15693 custom commands (0xD2 READ_SRAM, 0xD4 WRITE_I2C, etc.) used by the Python `ntag5sensor` tooling.
### Driver Design
```rust
pub struct Ntag5Link<I2C> {
i2c: I2C,
addr: u8, // 0x54 default
}
```
**EEPROM access** (2048 bytes, 512 blocks of 4 bytes each):
- Read: I2C write 2-byte block address, then I2C read 4 bytes
- Write: I2C write 2-byte block address + 4 bytes data
- Bulk read: iterate blocks into caller-provided buffer
**SRAM access** (256 bytes, 64 blocks of 4 bytes):
- Same read/write pattern as EEPROM, different address range (0xF8-0xFF)
- Used as NFC ↔ I2C mailbox for phone communication
**Session registers** (7 bytes at 0x00-0x06):
- Single-byte address, read/write 1 byte
- Include EH status, field detection, arbiter mode control
### NTAG5Link Configuration
Must configure NTAG5Link for our use case (via NFC-side config write before first use, or via I2C config registers):
| Setting | Value | Constant (from ntag5link.py) |
|---------|-------|------------------------------|
| 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 register bits 2:1 = 10 |
| EH current | 9.0mA+ | EH_CONFIG register bits 6:4 |
---
## Phase 3: Pattern Storage Format
**Goal**: Define a compact binary format for LED patterns stored in NTAG5Link EEPROM.
### Format Specification
```
Pattern Header (16 bytes):
[0-3] Magic: 0x58 0x42 0x4C 0x4B ("XBLK")
[4] Version: 0x01
[5] Flags:
bit 0: has engine 1 program
bit 1: has engine 2 program
bit 2: has engine 3 program
bit 3: has direct color values
bit 4-7: reserved
[6] Number of pattern entries (1-255)
[7] Current setting for all channels (0-255 → 0-25.5mA)
[8-9] Reserved
[10-13] Direct color values (R, G, B, W) — used when flag bit 3 set
[14-15] CRC-16 of bytes 0-13 + all entry data
Pattern Entry (2 + N*2 bytes each):
[0] Entry type:
0x01 = Engine program (LP5562 engine commands)
0x02 = Direct PWM keyframes
[1] Length in 16-bit words (1-16 for engine programs)
[2..N] Command words (big-endian, LP5562 engine format)
Engine Assignment Block (4 bytes, after all entries):
[0] Engine 1 → entry index (0xFF = unused)
[1] Engine 2 → entry index (0xFF = unused)
[2] Engine 3 → entry index (0xFF = unused)
[3] LED mapping byte (LP5562 LED_MAP register 0x70 format)
```
**Size**: A typical 3-engine pattern with 16 commands each: 16 + 3×(2 + 32) + 4 = **122 bytes**. Even complex multi-pattern configs fit comfortably in the 2048-byte EEPROM.
### Built-in Default Patterns
Hardcoded in firmware as fallbacks:
- **Solid color**: Direct PWM, no engines needed
- **Breathing**: 1 engine — ramp up, ramp down, branch to start
- **RGB cycle**: 3 engines — staggered phase with trigger synchronization
- **Heartbeat**: 1 engine — fast ramp up, slow exponential decay
- **Rainbow fade**: 3 engines — slow ramp with offset phases
---
## Phase 4: Power Management
**Goal**: Minimize MCU power draw so LP5562 gets maximum current budget from energy harvesting.
### Sleep Architecture
1. After boot and LP5562 engine programming, enter SAMD21E **STANDBY** mode (~5µA)
2. LP5562 runs autonomously using its internal 256Hz oscillator — no MCU involvement
3. Wake sources via External Interrupt Controller (EIC):
- **NTAG5Link FD pin**: NFC field detection or SRAM write indication
- **Hall sensor GPIO**: Recovery trigger (magnet proximity)
### Power Budget Analysis
| Component | Active | Sleep/Idle | Notes |
|-----------|--------|------------|-------|
| SAMD21E | 3-5mA @ 48MHz | ~5µA standby | Sleep ASAP after boot |
| LP5562 | 0.5mA quiescent + LED current | Power-save mode | LEDs dominate budget |
| LP5562 LEDs (4ch) | 2-5mA × 4 = 8-20mA | 0mA (PS_EN) | Must stay within EH budget |
| NTAG5Link | ~100µA | ~1µA standby | Minimal contributor |
| **Total (steady state)** | **9-21mA** | **~5µA** | EH max: ~12.5mA |
**Key insight**: The system budget is tight. At 12.5mA EH and ~0.6mA quiescent (LP5562 + NTAG5), that leaves ~12mA for LEDs. At 3mA per channel, 4 channels = 12mA — right at the limit. Consider:
- Running fewer channels simultaneously
- Using LP5562 engine PWM duty cycling to reduce average current
- Pulsed patterns (breathing, heartbeat) inherently use less average current than solid-on
### Energy Harvesting Configuration
Set via NTAG5Link config registers (one-time setup via PCSC reader):
- Voltage: 3.0V (EH_CONFIG bits 2:1 = 10)
- Current threshold: start at 9.0mA, increase if patterns are dim
- Mode: Low field strength (CONFIG_0 bits 3:2 = 10) — better for phone NFC
- FD pin: SRAM write indication mode (for NFC communication wake)
---
## Phase 5: NFC Communication Protocol
**Goal**: Enable phone-to-MCU communication through NTAG5Link SRAM mailbox.
### SRAM Mailbox Protocol
The NTAG5Link's 256-byte SRAM is accessible from both NFC (phone) and I2C (MCU) sides. We use it as a command/response mailbox.
```
Command Frame (NFC → MCU, written to SRAM by phone):
[0] Command byte:
0x01 = Write pattern (followed by pattern data)
0x02 = Read current pattern info
0x03 = Set direct color (4 bytes RGBW follow)
0x04 = Get firmware version
0x05 = Enter bootloader (reserved)
[1] Sequence number (for request/response matching)
[2-3] Payload length (little-endian, max 252)
[4-255] Payload data
Response Frame (MCU → NFC, written to SRAM by MCU):
[0] Status:
0x00 = OK
0x01 = Error (error code in payload)
0x02 = Busy
0x03 = Ready for next chunk
[1] Sequence number (echoed from command)
[2-3] Payload length (little-endian)
[4-255] Payload data
```
### Chunked Transfer
For patterns larger than 252 bytes (unlikely but supported):
- Command 0x01 payload starts with chunk header: `[chunk_index: u8, total_chunks: u8, ...]`
- MCU accumulates chunks in RAM
- After final chunk, MCU writes complete pattern to EEPROM
- MCU responds with 0x03 (ready for next chunk) or 0x00 (complete)
### Communication Flow
1. Phone writes command frame to NTAG5Link SRAM via NFC
2. NTAG5Link asserts FD pin (SRAM write indication)
3. SAMD21E wakes from STANDBY via EIC interrupt
4. MCU reads SRAM via I2C, processes command
5. MCU pauses LP5562 if needed (set engines to Hold)
6. MCU performs action (update pattern, read info, etc.)
7. MCU writes response frame to SRAM via I2C
8. MCU resumes LP5562 engines if paused
9. MCU returns to STANDBY
10. Phone reads response from SRAM via NFC
---
## Phase 6: Recovery and Safe Mode
**Goal**: Provide a way to recover from bad patterns or firmware issues post-implantation.
### Hall Sensor Recovery
A hall effect sensor connected to a SAMD21E GPIO pin (EIC-capable) provides recovery triggering by holding a magnet near the implant.
**Boot-time check**:
1. On every boot, before reading EEPROM, check hall sensor GPIO state
2. If asserted (magnet present): enter recovery mode
3. If not asserted: normal boot, read EEPROM pattern
**Recovery mode behavior**:
- Load hardcoded default pattern: solid white at 25% brightness (~1.5mA/ch)
- Do NOT read EEPROM pattern (in case it's corrupted or causes issues)
- Set recovery flag in NTAG5Link SRAM so phone app can detect recovery state
- Remain awake (do not enter STANDBY) to allow firmware update operations
- Optionally: blink onboard LED in recognizable recovery pattern
**Runtime trigger**: Hall sensor also serves as EIC wake source during sleep. Possible uses:
- Cycle to next stored pattern
- Enter diagnostic mode
- Force re-read of EEPROM pattern
---
## Phase 7: Firmware Update Strategy
### Analysis
| Approach | Pros | Cons | Feasibility |
|----------|------|------|-------------|
| **NFC pass-through bootloader** | True OTA, no physical access | Complex, slow (256B SRAM chunks for 256KB flash = thousands of transactions), custom bootloader needed | Possible but hard |
| **UF2 bootloader (USB)** | Fast, well-established, standard tooling | Requires USB access — impossible once implanted | Dev phase only |
| **Hybrid (recommended)** | Best of both: UF2 for dev, NFC OTA deferred | Two bootloader systems to maintain | Practical |
### Recommended Approach
1. **Development phase**: Use UF2 bootloader on XIAO M0 for rapid iteration. Flash with `cargo hf2 --release`.
2. **Pre-implant**: Thoroughly test final firmware via UF2. Ensure pattern system works perfectly since that's the primary update mechanism post-implant.
3. **Post-implant updates**: The pattern storage in NTAG5Link EEPROM provides the most common update path — new patterns without new firmware. This covers the majority of use cases.
4. **Future NFC OTA**: Implement minimal NFC pass-through bootloader in a later phase if needed. Would require:
- ~8KB reserved flash for bootloader code (BOOTPROT fuse)
- Custom chunked transfer protocol over SRAM mailbox
- CRC verification before committing new firmware
- Fallback to known-good firmware on verification failure
---
## Phase 8: Custom PCB (SAMD21E)
### Porting from XIAO M0 to SAMD21E18A
| Change | From (XIAO M0) | To (Custom PCB) |
|--------|----------------|------------------|
| BSP crate | `xiao_m0 = "0.13"` | `atsamd-hal = { version = "0.17", features = ["samd21e"] }` |
| Runtime | Included in BSP | Add `cortex-m-rt = "0.7"` |
| Linker script | Included in BSP | Custom `memory.x` for SAMD21E18A |
| Package | SAMD21G18A (48-pin TQFP) | SAMD21E18A (32-pin QFN, 5×5mm) |
| SERCOM | SERCOM0 (BSP-configured) | Remap to available SERCOM on E variant |
| Pins | BSP pin names (a0, a4, a5, led0) | Direct PAC pin references |
### PCB Design Targets
| Component | Package | Size |
|-----------|---------|------|
| SAMD21E18A | QFN-32 | 5×5mm |
| LP5562 | QFN-16 | 3×3mm |
| NTAG5Link (NTP53x2) | XSON-8 | 1.45×1mm |
| Hall sensor | SOT-23 | 2.9×1.6mm |
| NFC antenna | PCB trace loop | ~12-15mm diameter |
Target form factor: disc under 15mm diameter or capsule under 12×30mm.
---
## Phase 9: Companion App
### React Native App
- NFC communication via platform-native APIs (Android NFC, iOS Core NFC)
- ISO15693 NTAG5Link access for SRAM mailbox protocol
- Pattern editor: visual LED color/pattern picker
- Pattern upload: serialize to XBLK format, send via SRAM mailbox
- Status display: firmware version, current pattern info, recovery state
- Testing: use VivoKey RawNFC and DT NFC Identifier as reference
---
## 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 | Always pause LP5562 before NTAG5 I2C access, resume after |
| EEPROM write endurance (~100K cycles) | Low | Use SRAM for transient communication, EEPROM only for pattern persistence |
| Boot time too slow for user experience | Low | Estimated ~15-20ms total fast enough to appear instant |
| NFC coupling distance too short | Medium | Optimize antenna, use low field strength EH mode, accept 1-3cm range |
| Firmware bug post-implant with no OTA | High | Hall sensor recovery, thorough pre-implant testing, pattern-only updates cover most needs |

126
docs/STATUS.md Normal file
View File

@@ -0,0 +1,126 @@
# xblink Project Status
**Current Phase**: Phase 0 — Project Setup
**Last Updated**: 2026-03-03
---
## Phase 0 — Project Setup
- [x] Initialize git repository
- [x] Initialize Rust project with `cargo init`
- [x] Configure `.cargo/config.toml` for thumbv6m-none-eabi
- [x] Set up `Cargo.toml` with XIAO M0 BSP and embedded-hal 1.0
- [x] Create `.gitignore`
- [x] Create initial `src/main.rs` (blink + LP5562 EN pin on D0/A0)
- [x] Create project directory structure (`src/led/`, `src/ntag5/`, `src/pattern/`, `docs/`)
- [x] Create README.md
- [x] Create CLAUDE.md
- [x] Create docs/STATUS.md (this file)
- [x] Create docs/DEVELOPMENT_PLAN.md
- [x] Verify `cargo build --release` compiles
- [x] Initial git commit
## Phase 1 — LP5562 Driver Integration
- [ ] Copy LP5562 driver from `../ntag5-samd21-lp562/src/lp5562.rs`
- [ ] Define `LedController` trait in `src/led/mod.rs`
- [ ] Wrap LP5562 driver to implement `LedController`
- [ ] Add GPIO EN pin control to LP5562 wrapper (D0/A0 hardware enable)
- [ ] Test LP5562 direct PWM on XIAO M0 + LP5562EVM
- [ ] Test LP5562 engine programs (breathing, color cycling)
- [ ] Implement `SimplePwm` driver as second `LedController` implementation
## Phase 2 — NTAG5Link Driver (MCU-side I2C Slave)
- [ ] Implement NTAG5Link register map (`src/ntag5/registers.rs`)
- [ ] Implement EEPROM block read/write via I2C (`src/ntag5/eeprom.rs`)
- [ ] Implement SRAM mailbox read/write (`src/ntag5/sram.rs`)
- [ ] Implement session register access
- [ ] Test EEPROM read/write with XIAO M0 + NTAG5Link Click board
- [ ] Test SRAM mailbox read/write
- [ ] Verify round-trip: write pattern via PCSC reader, read back on MCU
## Phase 3 — Pattern Storage Format
- [ ] Design binary pattern format (header + engine programs + LED mapping)
- [ ] Document format in `docs/PATTERN_FORMAT.md`
- [ ] Implement pattern deserializer (`src/pattern/mod.rs`)
- [ ] Implement LP5562 engine program builder from pattern data (`src/pattern/engine.rs`)
- [ ] Implement pattern serializer (for MCU-side EEPROM writes)
- [ ] Test: write pattern via PCSC, MCU reads and programs LP5562
## Phase 4 — Power Management
- [ ] Characterize energy harvesting power budget with oscilloscope
- [ ] Determine optimal EH voltage/current settings (target: 3.0V, 9.0mA+)
- [ ] Implement SAMD21E STANDBY sleep mode
- [ ] Configure EIC wake on NTAG5Link FD pin
- [ ] Configure EIC wake on hall sensor GPIO
- [ ] Test full boot → program → sleep → wake cycle
- [ ] Measure total system current draw in each state
## Phase 5 — NFC Communication Protocol
- [ ] Design SRAM mailbox protocol (command/response frames)
- [ ] Implement MCU-side protocol handler
- [ ] Implement chunked transfer for patterns > 252 bytes
- [ ] Extend ntag5sensor Python tooling with xblink protocol commands
- [ ] Test pattern upload via PCSC reader
- [ ] Test with VivoKey RawNFC app
## Phase 6 — Recovery and Safe Mode
- [ ] Implement hall sensor GPIO input with debounce
- [ ] Implement recovery mode detection at boot
- [ ] Recovery behavior: hardcoded default pattern, skip EEPROM, stay awake
- [ ] Set recovery flag in SRAM for phone app detection
- [ ] Test recovery mode end-to-end
## Phase 7 — Firmware Update Strategy
- [ ] Evaluate NFC pass-through bootloader feasibility (see DEVELOPMENT_PLAN.md)
- [ ] Evaluate UF2 + NFC hybrid approach
- [ ] Implement chosen update mechanism
- [ ] Test firmware update end-to-end
- [ ] Document update procedure
## Phase 8 — Custom PCB (SAMD21E)
- [ ] Port from `xiao_m0` BSP to bare `atsamd-hal` with `samd21e` feature
- [ ] Remap pin assignments for SAMD21E18A (32-pin QFN)
- [ ] Provide custom `memory.x` linker script
- [ ] Design PCB schematic (SAMD21E + NTAG5Link + LP5562 + hall sensor + antenna)
- [ ] PCB layout for implant form factor
- [ ] Fabricate and test prototype PCB
## Phase 9 — Companion App
- [ ] React Native project setup
- [ ] NFC communication layer (ISO15693 via platform APIs)
- [ ] Pattern editor UI
- [ ] Pattern upload via NFC
- [ ] Firmware version check / status display
---
## Open Questions
| Question | Status | Notes |
|----------|--------|-------|
| Energy harvesting power budget | TBD | Need oscilloscope measurements with LP5562EVM + NTAG5 Click |
| Optimal LED current per channel | TBD | Must fit within EH budget, probably 2-5mA/ch |
| Firmware update strategy | Under evaluation | NFC pass-through vs UF2 hybrid, see DEVELOPMENT_PLAN.md |
| NTAG5Link I2C slave address | Assumed 0x54 | Verify with Click board when jumper available |
| SAMD21E18A package sourcing | Not started | Verify QFN-32 availability for custom PCB phase |
| Antenna design | Not started | Loop antenna for 13.56MHz, PCB-integrated or wire coil |
## Hardware Inventory
| Item | Status | Notes |
|------|--------|-------|
| Seeed XIAO M0 | Available | Dev board MCU (SAMD21G18A) |
| LP5562EVM | Available | TI eval module, RGBW LEDs, I2C addr 0x30 |
| NTAG5 Link Click | Available, partially wired | Missing I2C jumper to XIAO |
| Hall effect sensor | Not available | Need to source |
| ACR1552 PCSC reader | Available | For ntag5sensor Python tooling |