Files
xblink/CLAUDE.md
michael f437e4e281 Remove application-layer CRC from SRAM mailbox protocol
ISO15693 already provides CRC-16 at the transport layer, making
the application-layer CRC redundant. Simplifies header from 6 to
4 bytes, removes crc16_continue/verify_crc, and drops STATUS_BAD_CRC.
Also updates design doc and fixes markdown table formatting.

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

6.0 KiB

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

# 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<E> enum wrapping underlying I2C error with From<E> 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

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)