# xblink Development Guide Embedded Rust firmware for a battery-free, NFC-powered LED implant. The NTAG5Link harvests energy from an NFC field and provides EEPROM storage for LED patterns. A SAMD21E MCU reads patterns from EEPROM, programs LP5562 execution engines, then sleeps while the LP5562 autonomously drives RGBW LEDs. ## Build Commands ```bash # Build release firmware cargo build --release # Flash via UF2 bootloader (XIAO M0: double-tap RST to enter bootloader) cargo hf2 --release # Auto-flash: watches for bootloader USB device, flashes when detected ./flash_when_ready.sh ``` ## Target - Architecture: `thumbv6m-none-eabi` (ARM Cortex-M0+) - Dev board: Seeed XIAO M0 (SAMD21G18A) — will port to bare SAMD21E18A later - LED driver: LP5562EVM (TI evaluation module, RGBW, I2C addr 0x30) - NFC: NTAG5Link Click board (MikroElektronika) ## Dependencies | Crate | Version | Purpose | |-------|---------|---------| | `xiao_m0` | 0.13 | Board support package (dev board phase) | | `panic-halt` | 0.2 | Minimal panic handler for `no_std` | | `cortex-m` | 0.7 | Cortex-M runtime, `critical-section-single-core` feature | | `embedded-hal` | 1.0.0 | Hardware abstraction traits (I2C, GPIO, delay) | When porting to bare SAMD21E: replace `xiao_m0` with `atsamd-hal = { version = "0.17", features = ["samd21e"] }` and add `cortex-m-rt = "0.7"`. ## Project Structure ``` src/ main.rs # Entry point: boot, pattern load, sleep/wake loop led/ mod.rs # LP5562 re-exports (LedController trait added later, M10) lp5562.rs # LP5562 driver (from ../ntag5-samd21-lp562/) ntag5/ # NTAG5Link I2C slave driver (M4+) pattern/ # Pattern format and engine builder (M5+) ``` ## Architecture Conventions - **`no_std` only**: No heap allocation. Fixed-size arrays and stack-only data structures. - **`embedded-hal` generics**: All hardware drivers must be generic over `embedded_hal::i2c::I2c` (1.0 traits). Never use concrete HAL types in driver code. - **Hardware-first**: Get hardware working before extracting abstractions. Traits and formats are designed after hands-on experience, not before. - **Error types**: Use `Error` enum wrapping underlying I2C error with `From` impl (see LP5562 driver pattern). - **Register addresses**: Place in a `reg` submodule as `pub const` values. - **`const fn`**: Prefer `const fn` where possible (see `EngineCommand` builder in LP5562 driver). - **No `unsafe`**: Avoid unless absolutely necessary for hardware access. ## LP5562 Timing Constraints The LP5562 driver does NOT enforce timing delays internally — the caller is responsible: - **>= 500 us** after `enable()` before any other commands - **>= 488 us** between consecutive ENABLE register writes (engine exec changes) - **>= 153 us** between consecutive OP_MODE register writes (engine mode changes) - When transitioning from Run, set exec to Hold first, then change mode ## Key I2C Addresses Both devices share the same I2C bus (A4/A5). Non-conflicting addresses — no bus arbitration issues. | Device | Address | Notes | |--------|---------|-------| | LP5562 | 0x30 | ADDR_SEL pins both low (LP5562EVM default) | | NTAG5Link | 0x54 | Default NTP53x2 I2C slave address | ## NTAG5Link I2C Register Map (MCU-side) The MCU accesses NTAG5Link as a standard I2C slave — plain register read/write, NOT the NFC-side ISO15693 custom commands used by the Python ntag5sensor tooling. | Region | Address Range | Size | Notes | |--------|--------------|------|-------| | Session registers | 0x00-0x06 | 7 bytes | Volatile, runtime config | | Configuration | 0x37-0x3F | Varies | Persistent, EEPROM-backed | | EH config | 0x3D | 1 byte | Energy harvesting voltage/current | | SRAM | 0xF8-0xFF | 256 bytes | 64 x 4-byte blocks, volatile | | EEPROM user memory | blocks 0x00-0x1FF | 2048 bytes | 512 x 4-byte blocks | Key config constants (from `../ntag5sensor/vicinity/ntag5link.py`): - `CONFIG_1_USE_CASE_CONF_I2C_SLAVE = 0 << 4` — NTAG5 as I2C slave (our mode) - `CONFIG_1_ARBITER_MODE_SRAM_PASSTHROUGH = 2 << 2` — SRAM pass-through for NFC↔I2C - `CONFIG_1_SRAM_ENABLE = 1 << 1` — Enable SRAM access - `CONFIG_0_EH_MODE_LOW_FIELD_STRENGTH = 2 << 2` — EH mode for phone NFC ## Hardware Wiring (Dev Board) | XIAO Pin | Connection | Function | |----------|------------|----------| | A4 (SDA) | LP5562 SDA, NTAG5 SDA | Shared I2C data | | A5 (SCL) | LP5562 SCL, NTAG5 SCL | Shared I2C clock | | D0/A0 | LP5562 EN line (open-drain) | Wired-AND with hall sensor, pull-up to VCC | | TBD (EIC) | Hall sensor output | EIC wake + wired-AND to LP5562 EN line | | TBD (EIC) | NTAG5 FD pin | Field detect / SRAM write indication (EIC wake) | **LP5562 EN wired-AND**: D0/A0 (open-drain) and hall sensor (open-drain, active-low) both connect to LP5562 EN with a 1M pull-up. Either can force EN low. Magnet kills LEDs at hardware level regardless of MCU state. ## Testing - **Hardware**: XIAO M0 + LP5562EVM + NTAG5Link Click (when jumpers available) - **PCSC reader**: Use `../ntag5sensor/` Python tooling with ACR1552 reader - **Phone NFC**: VivoKey RawNFC app for SRAM mailbox testing - **Phone app**: DT NFC Identifier for basic tag info ## Sibling Projects - `../ntag5-samd21-lp562/` — Original LP5562 driver + smoke test (xblink's `src/led/lp5562.rs` is synced from here) - `../ntag5sensor/` — NTAG5Link command reference (`vicinity/ntag5link.py`), I2C patterns (`vicinity/i2cbase.py`)