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

5
.cargo/config.toml Normal file
View File

@@ -0,0 +1,5 @@
[build]
target = "thumbv6m-none-eabi"
[target.thumbv6m-none-eabi]
rustflags = ["-C", "link-arg=-Tlink.x", "-C", "link-arg=-nmagic"]

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

107
CLAUDE.md Normal file
View File

@@ -0,0 +1,107 @@
# 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
```
## 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 # LedController trait + re-exports
lp5562.rs # LP5562 driver (from ../ntag5-samd21-lp562/)
simple_pwm.rs # Single-color LED driver (GPIO PWM)
ntag5/
mod.rs # NTAG5Link I2C slave driver (MCU-side)
registers.rs # Register map and config constants
eeprom.rs # EEPROM read/write
sram.rs # SRAM mailbox for NFC pass-through
pattern/
mod.rs # Pattern format, parser, serializer
engine.rs # LP5562 engine program builder from patterns
```
## 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.
- **LedController trait**: LED controllers implement the `LedController` trait defined in `src/led/mod.rs`. This enables modular support for LP5562, single-color PWM, and future LED driver ICs.
- **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.
## Key I2C Addresses
| 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 | Hardware enable (GPIO, active high) |
| TBD | NTAG5 FD pin | Field detect / SRAM write indication (EIC wake) |
| TBD | Hall sensor | Recovery mode trigger (EIC wake) |
## 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/` — LP5562 driver source (`src/lp5562.rs`), reference `main.rs` boot sequence
- `../ntag5sensor/` — NTAG5Link command reference (`vicinity/ntag5link.py`), I2C patterns (`vicinity/i2cbase.py`)

572
Cargo.lock generated Normal file
View File

@@ -0,0 +1,572 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aes"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
"opaque-debug",
]
[[package]]
name = "atsamd-hal"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84fba26d12e032c93fb0de8c0e0ff34fafea35192f61cd0fc247838d78cda22e"
dependencies = [
"aes",
"atsamd-hal-macros",
"atsamd21g",
"bitfield",
"bitflags",
"cipher",
"cortex-m",
"embedded-hal 0.2.7",
"embedded-hal 1.0.0",
"embedded-hal-nb",
"embedded-io",
"fugit",
"modular-bitfield",
"nb 1.1.0",
"num-traits",
"opaque-debug",
"paste",
"rand_core",
"seq-macro",
"typenum",
"vcell",
"void",
]
[[package]]
name = "atsamd-hal-macros"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b27ccb060a908ff8a40acde574902a6bd283d3420dd4ffcdb6a327225f55e098"
dependencies = [
"litrs",
"phf",
"phf_codegen",
"serde",
"serde_yaml",
]
[[package]]
name = "atsamd21g"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dc7fa02c53f5039898dc16880423b13e5f27238466f3999790ff79330c8c00d"
dependencies = [
"cortex-m",
"cortex-m-rt",
"critical-section",
"vcell",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bare-metal"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3"
dependencies = [
"rustc_version",
]
[[package]]
name = "bitfield"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cipher"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array",
]
[[package]]
name = "cortex-m"
version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9"
dependencies = [
"bare-metal",
"bitfield",
"critical-section",
"embedded-hal 0.2.7",
"volatile-register",
]
[[package]]
name = "cortex-m-rt"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6"
dependencies = [
"cortex-m-rt-macros",
]
[[package]]
name = "cortex-m-rt-macros"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "critical-section"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "embedded-hal"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff"
dependencies = [
"nb 0.1.3",
"void",
]
[[package]]
name = "embedded-hal"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89"
[[package]]
name = "embedded-hal-nb"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605"
dependencies = [
"embedded-hal 1.0.0",
"nb 1.1.0",
]
[[package]]
name = "embedded-io"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "fugit"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e639847d312d9a82d2e75b0edcc1e934efcc64e6cb7aa94f0b1fbec0bc231d6"
dependencies = [
"gcd",
]
[[package]]
name = "gcd"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a"
[[package]]
name = "generic-array"
version = "0.14.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "hashbrown"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]]
name = "itoa"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2"
[[package]]
name = "libc"
version = "0.2.182"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112"
[[package]]
name = "litrs"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed"
dependencies = [
"proc-macro2",
]
[[package]]
name = "modular-bitfield"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74"
dependencies = [
"modular-bitfield-impl",
"static_assertions",
]
[[package]]
name = "modular-bitfield-impl"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "nb"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f"
dependencies = [
"nb 1.1.0",
]
[[package]]
name = "nb"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "opaque-debug"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "panic-halt"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812"
[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "phf"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_codegen"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
dependencies = [
"phf_generator",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_shared"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
"siphasher",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "seq-macro"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.117",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "siphasher"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "typenum"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "vcell"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "void"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
[[package]]
name = "volatile-register"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc"
dependencies = [
"vcell",
]
[[package]]
name = "xblink"
version = "0.1.0"
dependencies = [
"cortex-m",
"embedded-hal 1.0.0",
"panic-halt",
"xiao_m0",
]
[[package]]
name = "xiao_m0"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79c0b0e12f25458aee8004c8f6a3a5f2a6e670cace39950191f8478ae3ee41a8"
dependencies = [
"atsamd-hal",
"cortex-m-rt",
]

14
Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "xblink"
version = "0.1.0"
edition = "2021"
[dependencies]
xiao_m0 = "0.13"
panic-halt = "0.2"
cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
embedded-hal = "1.0.0"
[profile.release]
opt-level = "z"
lto = true

130
README.md Normal file
View File

@@ -0,0 +1,130 @@
# xblink
NFC-powered LED implant using NTAG5Link, SAMD21E, and LP5562.
A battery-free subdermal LED implant that harvests energy from an NFC field. An NTAG5Link provides power and storage, a SAMD21E microcontroller reads stored LED patterns and programs the LP5562 LED driver's autonomous execution engines, then sleeps. Patterns are updated wirelessly via NFC from a phone.
## Hardware Overview
```
I2C (0x30)
Phone/Reader ))) NFC ((( NTAG5Link ---------> LP5562 -----> RGBW LEDs
| | |
| VOUT | FD pin | EN
| (EH) | |
v v |
+--SAMD21E--+ |
| D0/A0 ----------------+ (GPIO enable)
| A4/A5 (I2C bus) ------+
| GPIO <--- Hall Sensor
+----------+
```
All power comes from NTAG5Link energy harvesting — no battery.
## Components
| Component | Part | Role | I2C Addr | Key Specs |
|-----------|------|------|----------|-----------|
| NFC + Power | NXP NTAG5Link (NTP53x2) | NFC interface, energy harvesting, pattern storage | 0x54 (slave) | 2048B EEPROM, 256B SRAM, ISO15693, 1.8/2.4/3.0V EH output |
| MCU | SAMD21E18A | Firmware (Rust), pattern loading, sleep/wake | — | 256KB flash, 32KB RAM, ARM Cortex-M0+, 32-pin QFN |
| LED Driver | TI LP5562 | RGBW LED control with autonomous patterns | 0x30 | 4ch RGBW, 3 execution engines, 16 cmds/engine, 0-25.5mA/ch |
| Recovery | Hall effect sensor | Safe mode / recovery trigger | — | GPIO input with EIC wake from sleep |
**Dev board**: Seeed XIAO M0 (SAMD21G18A) + LP5562EVM + MikroElektronika NTAG5 Link Click
## Architecture
### Boot Sequence
1. NFC field detected by NTAG5Link
2. VOUT powers SAMD21E and LP5562 (MCU drives EN pin high on D0/A0)
3. SAMD21E boots, checks hall sensor GPIO (asserted = recovery mode)
4. Normal boot: reads LED pattern from NTAG5Link EEPROM via I2C
5. Programs LP5562 execution engines with pattern data
6. Enters STANDBY sleep (~5uA)
7. LP5562 runs patterns autonomously using internal oscillator
8. NFC field drops → VOUT drops → clean power-off
### Pattern Update Sequence
1. Phone sends new pattern data to NTAG5Link via NFC
2. NTAG5Link signals MCU via FD pin (SRAM write indication)
3. SAMD21E wakes from sleep via EIC interrupt
4. Pauses LP5562 engines (set exec to Hold)
5. Reads new pattern from NTAG5Link SRAM mailbox via I2C
6. Writes pattern to NTAG5Link EEPROM for persistence
7. Reprograms LP5562 engines with new pattern
8. Returns to STANDBY sleep
### Recovery Mode
If the hall sensor is asserted at boot (magnet held near implant):
- Loads a hardcoded default pattern (solid white, low brightness)
- Ignores EEPROM pattern data
- Sets recovery flag in SRAM for phone app detection
- Remains awake for firmware update if supported
## Power Budget
All power from NTAG5Link energy harvesting. Budget is constrained and TBD pending prototype measurements.
| Parameter | Value | Notes |
|-----------|-------|-------|
| EH max current | ~12.5mA | Highest threshold setting |
| EH voltage | 3.0V | Required for LP5562 (2.7-5.5V) and SAMD21E (1.62-3.63V) |
| LP5562 per channel | 0-25.5mA | Must limit to ~2-5mA/ch for EH budget |
| SAMD21E active | ~3-5mA | During boot and pattern programming |
| SAMD21E standby | ~5uA | After LP5562 engines are running |
## Getting Started
### Toolchain Setup
```bash
# Install Rust
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source $HOME/.cargo/env
# Add ARM Cortex-M0+ target
rustup target add thumbv6m-none-eabi
# Install cargo-hf2 for UF2 flashing (XIAO M0)
cargo install cargo-hf2
```
On Linux, add udev rule for XIAO bootloader (vendor ID 0x2886):
```bash
echo 'SUBSYSTEM=="usb", ATTR{idVendor}=="2886", MODE="0666"' | sudo tee /etc/udev/rules.d/99-xiao.rules
sudo udevadm control --reload-rules
```
### Build and Flash
```bash
cargo build --release
# Put XIAO in bootloader mode (double-tap RST), then:
cargo hf2 --release
```
## Dev Board Wiring
Current prototype (XIAO M0 + LP5562EVM):
| XIAO Pin | LP5562EVM | Function |
|----------|-----------|----------|
| A4 (SDA) | SDA | I2C data |
| A5 (SCL) | SCL | I2C clock |
| D0/A0 | EN | Hardware enable (GPIO) |
| 3V3 | VCC | Power |
| GND | GND | Ground |
## Project Status
See [docs/STATUS.md](docs/STATUS.md) for current progress.
## Related Projects
- `../ntag5-samd21-lp562/` — LP5562 Rust driver prototype and SAMD21 test firmware
- `../ntag5sensor/` — Python NTAG5Link tooling (PCSC reader, ISO15693, sensor interface)

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 |

43
src/main.rs Normal file
View File

@@ -0,0 +1,43 @@
#![no_std]
#![no_main]
use panic_halt as _;
use xiao_m0 as bsp;
use bsp::entry;
use bsp::hal;
use bsp::pac;
use hal::clock::GenericClockController;
use hal::delay::Delay;
use hal::prelude::*;
use pac::{CorePeripherals, Peripherals};
#[entry]
fn main() -> ! {
let mut peripherals = Peripherals::take().unwrap();
let core = CorePeripherals::take().unwrap();
let mut clocks = GenericClockController::with_external_32kosc(
peripherals.GCLK,
&mut peripherals.PM,
&mut peripherals.SYSCTRL,
&mut peripherals.NVMCTRL,
);
let pins = bsp::Pins::new(peripherals.PORT);
let mut delay = Delay::new(core.SYST, &mut clocks);
// On-board LED for status indication
let mut led: bsp::Led0 = pins.led0.into_push_pull_output();
// LP5562 hardware enable on D0/A0
let mut lp_en = pins.a0.into_push_pull_output();
lp_en.set_high().unwrap(); // Enable LP5562
loop {
led.toggle().unwrap();
delay.delay_ms(500u16);
}
}