Files
xblink/docs/DEVELOPMENT_PLAN.md
michael bae64c1e3a 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>
2026-03-03 09:32:30 -08:00

14 KiB
Raw Blame History

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:

    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:

    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

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

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

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
  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