Pivot from LP5562 to GPIO-direct PWM LEDs, add NTAG5 EH provisioning

LP5562 requires 2.7V min but NFC energy harvesting produces only 1.8V.
New architecture: SAMD21 drives 6 red LEDs directly via TCC0/TCC1
hardware PWM through current-limiting resistors.

Key changes:
- Add TCC PWM driver (src/led/pwm.rs) for 6 GPIO-direct LED channels
- Rewrite pattern engine: software waveform LUTs (sine/triangle/square/
  heartbeat) replace LP5562 hardware execution engines
- Add XBLK v2 EEPROM format with 16-byte pattern entries + playlist
- Add TC4 50Hz ISR for animation, power governor for current budget
- Add NTAG5 EH provisioning: persistent config + session trigger
- Critical finding: SRAM passthrough in persistent EEPROM blocks all
  NFC access when MCU unpowered — CONFIG_1 must be session-only
- Add provision_eh.py PCSC tool for ACR1552 reader
- Update CLAUDE.md and STATUS.md for new architecture

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
michael
2026-03-07 10:44:12 -08:00
parent f437e4e281
commit 5cd728c298
11 changed files with 1459 additions and 1099 deletions

View File

@@ -1,6 +1,6 @@
# 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.
Embedded Rust firmware for a battery-free, NFC-powered LED implant. The NTAG5Link harvests energy from an NFC field (1.8V) and provides EEPROM storage for LED patterns. A SAMD21E MCU reads patterns from EEPROM, drives 6 red LEDs directly via TCC hardware PWM through current-limiting resistors, and updates duty cycles from a 50Hz timer ISR. The MCU stays in IDLE sleep during animation.
## Build Commands
@@ -19,8 +19,9 @@ cargo hf2 --release
- 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)
- LEDs: 6x low-Vf red LEDs (Kingbright APTD1608, Vf ~1.7V) driven via TCC PWM + resistors
- NFC: NTAG5Link Click board (MikroElektronika)
- LP5562: removed from design (requires 2.7V min, incompatible with 1.8V EH)
## Dependencies
@@ -37,12 +38,13 @@ When porting to bare SAMD21E: replace `xiao_m0` with `atsamd-hal = { version = "
```
src/
main.rs # Entry point: boot, pattern load, sleep/wake loop
main.rs # Entry point: boot, EEPROM load, TC4 ISR animation, 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+)
mod.rs # LED module re-exports
pwm.rs # TCC0/TCC1 hardware PWM driver for 6 GPIO-direct LEDs
lp5562.rs # LP5562 driver (legacy, kept for reference)
ntag5/ # NTAG5Link I2C slave driver
pattern/ # XBLK v2 format, software pattern engine, waveform LUTs
```
## Architecture Conventions
@@ -55,23 +57,32 @@ src/
- **`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
## LED PWM Architecture
The LP5562 driver does NOT enforce timing delays internally — the caller is responsible:
6 LEDs driven directly by SAMD21 TCC hardware PWM through current-limiting resistors (10-47 ohm).
- **>= 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
| LED | TCC | Channel | SAMD21E Pin | XIAO Pin |
|-----|------|---------|-------------|----------|
| 0 | TCC0 | WO[0] | PA04 | A1 |
| 1 | TCC0 | WO[1] | PA05 | A2 |
| 2 | TCC0 | WO[2] | PA06 | A3* |
| 3 | TCC0 | WO[3] | PA07 | A4* |
| 4 | TCC1 | WO[0] | PA10 | D2 |
| 5 | TCC1 | WO[1] | PA11 | D3 |
## Key I2C Addresses
*PA06/PA07 conflict with I2C on XIAO M0. Dev board uses 4 LEDs (0,1,4,5).
Both devices share the same I2C bus (A4/A5). Non-conflicting addresses — no bus arbitration issues.
- **Animation**: TC4 ISR at 50Hz computes brightness from PatternEngine, writes TCC CC registers
- **Power governor**: Proportional scaling ensures total LED current stays within NTAG5 EH budget
- **Sleep**: IDLE mode during animation (~0.5mA MCU), STANDBY when no pattern active (~2uA)
| Device | Address | Notes |
| --------- | ------- | ------------------------------------------ |
| LP5562 | 0x30 | ADDR_SEL pins both low (LP5562EVM default) |
| NTAG5Link | 0x54 | Default NTP53x2 I2C slave address |
## Key I2C Address
I2C bus (A4/A5) is used for NTAG5 only (LP5562 removed from design).
| Device | Address | Notes |
| --------- | ------- | --------------------------------- |
| NTAG5Link | 0x54 | Default NTP53x2 I2C slave address |
## NTAG5Link I2C Register Map (MCU-side)
@@ -93,24 +104,25 @@ Key config constants (from `../ntag5sensor/vicinity/ntag5link.py`):
## 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.
| XIAO Pin | Connection | Function |
| -------- | --------------------- | ------------------------------------------- |
| A1 (PA04)| LED 0 + resistor | TCC0/WO[0] PWM output |
| A2 (PA05)| LED 1 + resistor | TCC0/WO[1] PWM output |
| A4 (SDA) | NTAG5 SDA | I2C data |
| A5 (SCL) | NTAG5 SCL | I2C clock |
| D2 (PA10)| LED 4 + resistor | TCC1/WO[0] PWM output |
| D3 (PA11)| LED 5 + resistor | TCC1/WO[1] PWM output |
| A1 (PA04)| NTAG5 FD pin | Field detect / SRAM write indication (EIC) |
| TBD | Hall sensor output | EIC wake, pattern cycling |
## Testing
- **Hardware**: XIAO M0 + LP5562EVM + NTAG5Link Click (when jumpers available)
- **Hardware**: XIAO M0 + NTAG5Link Click + 4 LEDs with resistors on A1/A2/D2/D3
- **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)
- `../ntag5-samd21-lp562/` — Original LP5562 driver (legacy reference)
- `../ntag5sensor/` — NTAG5Link command reference (`vicinity/ntag5link.py`), I2C patterns (`vicinity/i2cbase.py`)