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:
@@ -1,7 +1,7 @@
|
||||
# xblink Project Status
|
||||
|
||||
**Current Milestone**: M7 — Sleep/Wake
|
||||
**Last Updated**: 2026-03-05
|
||||
**Current Milestone**: GPIO-Direct LED Pivot (replacing LP5562)
|
||||
**Last Updated**: 2026-03-07
|
||||
|
||||
---
|
||||
|
||||
@@ -76,16 +76,37 @@
|
||||
- [x] I2C bus swapping for LP5562 reprogramming after pattern updates
|
||||
- [ ] Hardware test: flash and verify with PCSC reader / phone app (pending FD pin wiring)
|
||||
|
||||
## GPIO-Direct LED Pivot (2026-03-07)
|
||||
|
||||
LP5562 requires VDD >= 2.7V, incompatible with 1.8V NFC energy harvesting.
|
||||
Replaced with SAMD21E GPIO-direct PWM driving 6 red LEDs.
|
||||
See `docs/plans/2026-03-06-gpio-led-pivot.md` for full design.
|
||||
|
||||
### LED Pivot Implementation
|
||||
|
||||
- [x] Design GPIO-direct PWM architecture (`docs/plans/2026-03-06-gpio-led-pivot.md`)
|
||||
- [x] Implement software pattern engine with waveform LUTs (`src/pattern/mod.rs`)
|
||||
- [x] Implement XBLK v2 EEPROM format (16-byte entries, playlist support)
|
||||
- [x] Implement TCC PWM driver for 6 LED channels (`src/led/pwm.rs`)
|
||||
- [x] Implement TC4 50Hz animation timer ISR
|
||||
- [x] Implement power governor (proportional brightness scaling)
|
||||
- [x] Update main.rs for GPIO-direct boot flow
|
||||
- [x] Update SRAM mailbox protocol for v2 format (`src/ntag5/sram.rs`)
|
||||
- [x] Update Python serializer (`tools/xblk_serialize.py`)
|
||||
- [x] Verify `cargo build --release` compiles
|
||||
- [ ] Hardware test: wire LEDs to A1/A2/D2/D3, flash and verify animations
|
||||
- [ ] Measure power consumption with multimeter
|
||||
|
||||
## Group C — Power + Recovery
|
||||
|
||||
**Hardware needed**: + Hall sensor + multimeter
|
||||
|
||||
### M7: Sleep/Wake
|
||||
|
||||
- [ ] Configure SAMD21 EIC for FD pin wake
|
||||
- [ ] Implement STANDBY sleep after LP5562 programming
|
||||
- [ ] Verify LP5562 keeps running during MCU sleep
|
||||
- [ ] Measure current: active vs standby vs total system
|
||||
- [x] Configure SAMD21 EIC for FD pin wake
|
||||
- [x] Implement IDLE sleep during animation (TC4 ISR drives LEDs)
|
||||
- [x] Implement STANDBY sleep when no pattern active
|
||||
- [ ] Measure current: IDLE (animating) vs STANDBY vs total system
|
||||
|
||||
### M8: Recovery Mode
|
||||
|
||||
@@ -96,28 +117,17 @@
|
||||
|
||||
### M9: Power Characterization
|
||||
|
||||
- [ ] Measure current at various LED current settings
|
||||
- [ ] Test NTAG5Link EH output with phone NFC
|
||||
- [ ] Find optimal brightness vs EH budget balance
|
||||
- [ ] Measure current at various LED brightness / resistor values
|
||||
- [ ] Test NTAG5Link EH output with phone NFC at 1.8V
|
||||
- [ ] Tune power governor budget for optimal brightness
|
||||
- [ ] Document real power numbers
|
||||
|
||||
## Group D — Abstraction + Polish
|
||||
|
||||
### M10: LedController Trait
|
||||
|
||||
- [ ] Define trait based on proven usage patterns from Groups A-C
|
||||
- [ ] Implement for LP5562 wrapper
|
||||
- [ ] Refactor main.rs to trait-based API
|
||||
|
||||
### M11: SimplePwm Driver
|
||||
|
||||
- [ ] Implement only if single-color LED hardware is available
|
||||
|
||||
## Group E — Future
|
||||
## Group D — Future
|
||||
|
||||
### Custom PCB (SAMD21E)
|
||||
|
||||
- [ ] Port to `atsamd-hal` with `samd21e` feature
|
||||
- [ ] Add LEDs 2-5 on PA06/PA07 (freed from I2C conflict on custom PCB)
|
||||
- [ ] Custom `memory.x` linker script
|
||||
- [ ] PCB design and fabrication
|
||||
|
||||
@@ -135,23 +145,21 @@
|
||||
|
||||
| Question | Status | Notes |
|
||||
|----------|--------|-------|
|
||||
| Energy harvesting power budget | TBD | Multimeter measurements in M9 |
|
||||
| Optimal LED current per channel | TBD | Probably 2-5mA/ch, verify in M9 |
|
||||
| Energy harvesting power budget | TBD | Multimeter measurements at 1.8V |
|
||||
| Optimal LED resistor value | TBD | 10-47 ohm, affects brightness vs power |
|
||||
| Power governor budget default | Set to 50 | Configurable via companion app |
|
||||
| Firmware update strategy | Deferred | UF2 for dev, NFC OTA evaluated later |
|
||||
| NTAG5Link I2C slave address | Assumed 0x54 | Verify in M4 with Click board |
|
||||
| Pattern binary format | Deferred to M5 | Design after M2-M3 engine experience |
|
||||
| Hall sensor + EN circuit | Designed | Wired-AND: hall + MCU open-drain on EN with 1M pull-up. See `docs/plans/2026-03-03-hall-en-design.md` |
|
||||
| Hall sensor part selection | Decided | DRV5032FB (SOT-23, 8.4mT, prototype) → DRV5032FE (X2SON 1x1mm, final PCB) |
|
||||
| EN pull-up value | Decided | 1M — zero steady-state draw, 3µA when EN low, ~10µs rise time |
|
||||
| Hall sensor part selection | Decided | DRV5032FB (SOT-23, 8.4mT, prototype) |
|
||||
|
||||
## Hardware Inventory
|
||||
|
||||
| Item | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| Seeed XIAO M0 | Available | Dev board MCU (SAMD21G18A) |
|
||||
| LP5562EVM | Available | TI eval module, RGBW LEDs, I2C addr 0x30 |
|
||||
| Mini-USB cable | Available | USB-C, used for flashing XIAO M0 |
|
||||
| LP5562EVM | Available (unused) | Removed from design (VDD > 2.7V) |
|
||||
| NTAG5 Link Click | Available, partially wired | Missing I2C jumper to XIAO |
|
||||
| Hall effect sensor | Not available | Need to source — TI DRV5032FB (SOT-23) for prototype |
|
||||
| Low-Vf red LEDs | Need to source | Kingbright APTD1608 or similar, Vf ~1.7V |
|
||||
| Resistors (10-47 ohm) | Need to source | Current limiting for GPIO-direct LEDs |
|
||||
| Hall effect sensor | Not available | Need to source — TI DRV5032FB (SOT-23) |
|
||||
| ACR1552 PCSC reader | Available | For ntag5sensor Python tooling |
|
||||
| Multimeter | Available | For power budget measurements |
|
||||
|
||||
144
docs/plans/2026-03-06-gpio-led-pivot.md
Normal file
144
docs/plans/2026-03-06-gpio-led-pivot.md
Normal file
@@ -0,0 +1,144 @@
|
||||
# GPIO-Direct LED Pivot
|
||||
|
||||
**Date**: 2026-03-06
|
||||
**Status**: Design complete, implementation pending
|
||||
|
||||
## Why
|
||||
|
||||
The LP5562 LED driver IC requires VDD >= 2.7V. Our NTAG5Link NFC energy harvesting antenna produces only 1.8V. No workaround exists -- a boost converter would exceed the power budget. We're replacing the LP5562 with SAMD21E GPIO-direct PWM driving 6 low-Vf red LEDs through current-limiting resistors.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **Remove**: LP5562 from BOM, `src/led/lp5562.rs` driver, LP5562 engine opcode pattern format
|
||||
- **Add**: TCC hardware PWM driver (`src/led/pwm.rs`), software pattern engine, XBLK v2 EEPROM format
|
||||
- **Keep**: NTAG5Link driver, SRAM mailbox protocol, EEPROM storage, EIC wake, hall sensor design
|
||||
|
||||
---
|
||||
|
||||
## Hardware
|
||||
|
||||
**LEDs**: 6x Kingbright APTD1608 (0603, red, Vf ~1.7V at 1mA). 0.1V headroom at 1.8V supply. Arranged in a line.
|
||||
|
||||
**PWM outputs**: TCC0 (4 channels) + TCC1 (2 channels) = 6 independent PWM channels.
|
||||
|
||||
**Pin assignments** (SAMD21E, TCC-capable pins):
|
||||
|
||||
| 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 |
|
||||
|
||||
*PA06/PA07 conflict with I2C (A4/A5) on XIAO M0. Dev board testing uses 4 LEDs on non-conflicting pins. Custom PCB (SAMD21E) has no conflict.
|
||||
|
||||
**Resistors**: 10-47 ohm series per LED. At 1.8V supply, Vf=1.7V: I = 0.1V/47ohm = ~2mA (safe). Lower resistance = brighter but more current. Governor handles the budget.
|
||||
|
||||
## Power Governor
|
||||
|
||||
The NTAG5 drops out entirely (hard power loss) when current budget is exceeded. The governor prevents this by running every ISR tick before writing TCC registers:
|
||||
|
||||
```
|
||||
raw[6] = pattern engine computes brightness 0-255 per LED
|
||||
total = sum(raw[0..6])
|
||||
if total > BUDGET:
|
||||
scale = BUDGET * 256 / total // fixed-point
|
||||
for i in 0..6:
|
||||
raw[i] = raw[i] * scale / 256
|
||||
write raw values to TCC CC registers
|
||||
```
|
||||
|
||||
**BUDGET**: Stored in XBLK v2 header (1 byte, units of ~0.1mA steps). Default ~50 (5mA total LED budget). Configurable via companion app or SRAM mailbox command.
|
||||
|
||||
**Phase splitting** is the key optimization: stagger LED phase offsets so peaks don't align. A 6-LED sine wave with 60-degree phase offsets has roughly constant total brightness, maximizing perceived brightness within the budget.
|
||||
|
||||
## Software Pattern Engine
|
||||
|
||||
Replaces LP5562's hardware execution engines. Runs in TC4 ISR at 50Hz.
|
||||
|
||||
**PatternState** (per-pattern, loaded from EEPROM):
|
||||
|
||||
```rust
|
||||
struct PatternState {
|
||||
waveform: Waveform, // sine, triangle, square, heartbeat
|
||||
cycle_len: u8, // ticks per full cycle (1-255 = 20ms-5.1s)
|
||||
phase: [u8; 6], // phase offset per LED (0-255 = 0-360 degrees)
|
||||
envelope: [u8; 6], // max brightness per LED (governor input)
|
||||
tick: u16, // current animation tick (runtime, not stored)
|
||||
}
|
||||
```
|
||||
|
||||
**Waveform lookup tables** (const, in flash):
|
||||
- `SINE_LUT[256]`: 8-bit sine quarter-wave, mirrored at runtime
|
||||
- `TRIANGLE_LUT[256]`: linear ramp up/down
|
||||
- `HEARTBEAT_LUT[256]`: double-pulse cardiac shape
|
||||
|
||||
**ISR flow** (~40us at 8MHz):
|
||||
1. Increment `tick`, wrap at `cycle_len * 256`
|
||||
2. For each LED: `phase_pos = (tick + phase[i] * cycle_len) % (cycle_len * 256)`
|
||||
3. Look up `waveform[phase_pos]`, scale by `envelope[i]`
|
||||
4. Apply governor scaling
|
||||
5. Write 6 TCC CC registers
|
||||
|
||||
**MCU sleep**: IDLE mode (not STANDBY) during animation. CPU halts between interrupts, peripherals (TCC, TC4) keep running. ~0.5mA at 1.8V/8MHz. CPU active only ~40us per 20ms tick = 0.2% duty cycle.
|
||||
|
||||
## XBLK v2 EEPROM Format
|
||||
|
||||
**Storage region**: Last 1KB of NTAG5 EEPROM (blocks 0x100-0x1FF), beyond normal NFC/NDEF access.
|
||||
|
||||
### Header (16 bytes)
|
||||
|
||||
```
|
||||
Offset Size Field
|
||||
0x00 4 magic "XBLK"
|
||||
0x04 1 version 0x02
|
||||
0x05 1 flags bit 0: has_playlist
|
||||
0x06 1 pattern_count number of pattern entries
|
||||
0x07 1 active_index pattern to load on boot
|
||||
0x08 1 budget power governor budget
|
||||
0x09 1 playlist_count number of playlist entries (0 if no playlist)
|
||||
0x0A 4 reserved
|
||||
0x0E 2 crc16 over bytes 0x00-0x0D
|
||||
```
|
||||
|
||||
### Pattern Entry (16 bytes each, immediately after header)
|
||||
|
||||
```
|
||||
Offset Size Field
|
||||
0x00 1 waveform (0=sine, 1=triangle, 2=square, 3=heartbeat)
|
||||
0x01 1 cycle_len (in 50Hz ticks, 1-255 = 20ms-5.1s)
|
||||
0x02 6 phase[6] (0-255 phase offset per LED, maps to 0-360 degrees)
|
||||
0x08 6 envelope[6] (max brightness per LED, 0-255)
|
||||
0x0E 1 repeat_count (times to play before advancing playlist, 0xFF=forever)
|
||||
0x0F 1 reserved
|
||||
```
|
||||
|
||||
### Playlist Table (after all pattern entries, if flags bit 0 set)
|
||||
|
||||
Each playlist entry is 1 byte = pattern index. Sequence plays in order, loops back to start. Max 32 entries.
|
||||
|
||||
Example: patterns [breathe=0, chase=1, flash=2] with playlist [1, 2, 2, 0] plays: chase, flash, flash, breathe, chase, ...
|
||||
|
||||
**Capacity**: 1024B - 16B header - 32B playlist = 976B for patterns. At 16B each: 61 patterns.
|
||||
|
||||
## Sleep and Power-on Behavior
|
||||
|
||||
Three MCU states:
|
||||
|
||||
1. **STANDBY** (~2uA) -- No animation. Wakes on FD pin (EIC) for SRAM commands or hall sensor tap.
|
||||
2. **ANIMATING** (~0.5mA MCU + LED current) -- IDLE sleep, TC4 ISR at 50Hz updates TCC duty cycles.
|
||||
3. **COMMAND PROCESSING** -- Fully awake, handling SRAM mailbox. Returns to ANIMATING or STANDBY.
|
||||
|
||||
**Power-on sequence**:
|
||||
1. NTAG5 EH powers up, VOUT rises, SAMD21 boots
|
||||
2. Read EEPROM header -- if valid XBLK v2, load pattern at `active_index`
|
||||
3. If playlist exists, start playlist from entry 0
|
||||
4. Configure TC4 (50Hz), TCC0/TCC1 (PWM), start animation
|
||||
5. Enter IDLE sleep (animation runs via ISR)
|
||||
6. On FD interrupt, wake fully, process SRAM command, resume
|
||||
|
||||
**Playlist advancement**: When `repeat_count` cycles complete for current pattern, ISR loads next playlist entry. If playlist wraps, loop from start. Hall sensor tap: advance to next playlist entry (or next pattern if no playlist).
|
||||
|
||||
**Pattern cycling (no playlist)**: Hall sensor increments `active_index`, loads next pattern. Wrapping past last pattern goes to STANDBY (LEDs off).
|
||||
Reference in New Issue
Block a user