Add M6 SRAM mailbox design doc, implementation plan, mark M6 complete
- Protocol design: streaming single-hold NFC transfer, 7 commands - Implementation plan: 7 tasks for subagent-driven development - STATUS.md: M6 marked complete, current milestone now M7 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
# xblink Project Status
|
# xblink Project Status
|
||||||
|
|
||||||
**Current Milestone**: M6 — SRAM Mailbox
|
**Current Milestone**: M7 — Sleep/Wake
|
||||||
**Last Updated**: 2026-03-05
|
**Last Updated**: 2026-03-05
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -65,12 +65,16 @@
|
|||||||
- [x] Write Python serializer tool (`tools/xblk_serialize.py`) for PCSC pattern uploads
|
- [x] Write Python serializer tool (`tools/xblk_serialize.py`) for PCSC pattern uploads
|
||||||
- [x] Verified on hardware: self-provisioning writes XBLK, subsequent boots load from EEPROM
|
- [x] Verified on hardware: self-provisioning writes XBLK, subsequent boots load from EEPROM
|
||||||
|
|
||||||
### M6: SRAM Mailbox
|
### M6: SRAM Mailbox (COMPLETE)
|
||||||
|
|
||||||
- [ ] Implement SRAM read/write (`src/ntag5/sram.rs`)
|
- [x] Design SRAM mailbox protocol (`docs/plans/2026-03-05-sram-mailbox-protocol.md`)
|
||||||
- [ ] Design command/response protocol
|
- [x] Add NTAG5 `write_register`, SRAM read/write, FD pin configuration
|
||||||
- [ ] Implement MCU-side protocol handler
|
- [x] Implement command protocol types and CRC helpers (`src/ntag5/sram.rs`)
|
||||||
- [ ] Test pattern update via NFC without power-cycling
|
- [x] Implement all 7 command handlers (WRITE_PATTERN, GET_STATUS, SET_ACTIVE, SYNC_START/END, READ_LIBRARY/NEXT)
|
||||||
|
- [x] Streaming transfer: single NFC hold for full library sync (one pattern per SRAM round-trip)
|
||||||
|
- [x] FD pin polling in main idle loop (200ms interval, A1/PA04)
|
||||||
|
- [x] I2C bus swapping for LP5562 reprogramming after pattern updates
|
||||||
|
- [ ] Hardware test: flash and verify with PCSC reader / phone app (pending FD pin wiring)
|
||||||
|
|
||||||
## Group C — Power + Recovery
|
## Group C — Power + Recovery
|
||||||
|
|
||||||
|
|||||||
1024
docs/plans/2026-03-05-m6-sram-mailbox-impl.md
Normal file
1024
docs/plans/2026-03-05-m6-sram-mailbox-impl.md
Normal file
File diff suppressed because it is too large
Load Diff
221
docs/plans/2026-03-05-sram-mailbox-protocol.md
Normal file
221
docs/plans/2026-03-05-sram-mailbox-protocol.md
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
# M6: SRAM Mailbox Protocol Design
|
||||||
|
|
||||||
|
**Date**: 2026-03-05
|
||||||
|
**Status**: Approved — ready for implementation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The NTAG5Link's 256-byte SRAM (64 x 4-byte blocks at I2C address 0xF8-0xFF) serves as a bidirectional mailbox between the phone (NFC/RF side) and the MCU (I2C side). The MCU polls the FD pin to detect when the phone has written a command, processes it, and writes a response back to SRAM for the phone to read.
|
||||||
|
|
||||||
|
## Pin Allocation
|
||||||
|
|
||||||
|
| Pin | Function | Milestone |
|
||||||
|
|-----|----------|-----------|
|
||||||
|
| A0 (PA02) | LP5562 EN (open-drain, wired-AND with hall sensor) | M1 (done) |
|
||||||
|
| A1 (PA04) | NTAG5 FD (SRAM write / field detect, EXTINT[4]) | M6 |
|
||||||
|
| TBD | Hall sensor (EIC wake) | M8 |
|
||||||
|
|
||||||
|
## FD Pin Configuration
|
||||||
|
|
||||||
|
NTAG5 session register `FD_PIN_CFG` (0x06) set at boot to "SRAM RF write complete" mode:
|
||||||
|
- FD goes **low** when the RF side (phone) finishes writing to SRAM
|
||||||
|
- FD returns **high** when the I2C side (MCU) reads the SRAM
|
||||||
|
- Natural flow control for command/response cycles
|
||||||
|
|
||||||
|
Pin wiring: NTAG5 Click FD → XIAO A1 (PA04). Configured as GPIO input with pull-up (FD is open-drain on NTAG5).
|
||||||
|
|
||||||
|
M6 uses GPIO polling (~200ms interval in idle loop). M7 upgrades to EIC interrupt (EXTINT[4]) for wake-from-STANDBY.
|
||||||
|
|
||||||
|
## Command Packet Format
|
||||||
|
|
||||||
|
**Phone → MCU (written to SRAM, 256 bytes max):**
|
||||||
|
|
||||||
|
```
|
||||||
|
Byte Field
|
||||||
|
0 Command ID (0x01-0x7F)
|
||||||
|
1 Sequence number (0-255, incremented per packet)
|
||||||
|
2-3 Payload length (u16 LE)
|
||||||
|
4-5 CRC-16 over bytes 0-3 + payload
|
||||||
|
6-255 Payload (up to 250 bytes)
|
||||||
|
```
|
||||||
|
|
||||||
|
**MCU → Phone (written back to SRAM):**
|
||||||
|
|
||||||
|
```
|
||||||
|
Byte Field
|
||||||
|
0 0xFF (response marker — distinguishes from commands)
|
||||||
|
1 Echo sequence number
|
||||||
|
2 Status (0x00=OK, 0x01=BAD_CRC, 0x02=BAD_CMD, 0x03=EEPROM_FAIL, 0x04=INVALID_INDEX)
|
||||||
|
3 Payload length
|
||||||
|
4-5 CRC-16 over bytes 0-3 + payload
|
||||||
|
6-255 Response payload
|
||||||
|
```
|
||||||
|
|
||||||
|
The 0xFF response marker lets the phone distinguish "MCU hasn't responded yet" (byte 0 still holds old command ID) from "response ready" (byte 0 = 0xFF).
|
||||||
|
|
||||||
|
## Command Set
|
||||||
|
|
||||||
|
| ID | Name | Payload (Phone → MCU) | Response Payload (MCU → Phone) |
|
||||||
|
|----|------|----------------------|-------------------------------|
|
||||||
|
| 0x01 | WRITE_PATTERN | index (1B) + pattern (112B) | none |
|
||||||
|
| 0x02 | GET_STATUS | none | version (1B) + pattern_count (1B) + active_index (1B) + led_current (1B) + led_mode (1B) |
|
||||||
|
| 0x03 | SET_ACTIVE | index (1B) | none |
|
||||||
|
| 0x05 | SYNC_START | count (1B) + current (1B) + mode (1B) | none |
|
||||||
|
| 0x06 | SYNC_END | none | none |
|
||||||
|
| 0x07 | READ_LIBRARY | none | pattern_count (1B) + active (1B) + current (1B) + mode (1B) |
|
||||||
|
| 0x08 | READ_NEXT | none | pattern data (112B) |
|
||||||
|
|
||||||
|
## Streaming Transfer Protocol
|
||||||
|
|
||||||
|
### Write (Phone → Implant): Full Library Sync
|
||||||
|
|
||||||
|
Single continuous NFC hold. Phone writes packets back-to-back, polling for MCU response between each.
|
||||||
|
|
||||||
|
```
|
||||||
|
Phone: Write SYNC_START (count=3, current=20, mode=RGBW) → SRAM
|
||||||
|
MCU: Erase library, write new header skeleton, respond OK
|
||||||
|
Phone: Poll byte 0 until 0xFF
|
||||||
|
|
||||||
|
Phone: Write WRITE_PATTERN (index=0, 112B pattern) → SRAM
|
||||||
|
MCU: Write 28 blocks to EEPROM (~140ms), respond OK
|
||||||
|
Phone: Poll byte 0 until 0xFF
|
||||||
|
|
||||||
|
Phone: Write WRITE_PATTERN (index=1, ...) → SRAM
|
||||||
|
MCU: Write 28 blocks to EEPROM, respond OK
|
||||||
|
Phone: Poll byte 0 until 0xFF
|
||||||
|
|
||||||
|
Phone: Write WRITE_PATTERN (index=2, ...) → SRAM
|
||||||
|
MCU: Write 28 blocks to EEPROM, respond OK
|
||||||
|
Phone: Poll byte 0 until 0xFF
|
||||||
|
|
||||||
|
Phone: Write SYNC_END → SRAM
|
||||||
|
MCU: Recalculate header CRC, set active=0, reprogram LP5562, respond OK
|
||||||
|
Phone: Poll byte 0 until 0xFF → done, show success
|
||||||
|
```
|
||||||
|
|
||||||
|
**Timing**: ~150ms per pattern (28 EEPROM blocks × 5ms each + I2C overhead). 9 patterns ≈ 1.4s of EEPROM writes. Total transfer including NFC overhead: under 3 seconds.
|
||||||
|
|
||||||
|
### Read (Implant → Phone): Library Download
|
||||||
|
|
||||||
|
Phone-driven — each READ_NEXT is an explicit request for the next pattern.
|
||||||
|
|
||||||
|
```
|
||||||
|
Phone: Write READ_LIBRARY → SRAM
|
||||||
|
MCU: Read header, respond with [pattern_count, active, current, mode]
|
||||||
|
Phone: Poll byte 0 until 0xFF, read header info
|
||||||
|
|
||||||
|
Phone: Write READ_NEXT (seq=1) → SRAM
|
||||||
|
MCU: Read pattern 0 from EEPROM, write 112B to response payload
|
||||||
|
Phone: Poll byte 0 until 0xFF, read pattern 0
|
||||||
|
|
||||||
|
Phone: Write READ_NEXT (seq=2) → SRAM
|
||||||
|
MCU: Read pattern 1 from EEPROM, write 112B to response payload
|
||||||
|
Phone: Poll byte 0 until 0xFF, read pattern 1
|
||||||
|
|
||||||
|
(repeat until pattern_count exhausted)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Timing**: EEPROM reads are fast (~1ms per block). Full 9-pattern read-back under 1 second.
|
||||||
|
|
||||||
|
### Single-Pattern Update
|
||||||
|
|
||||||
|
For quick edits without replacing the whole library:
|
||||||
|
|
||||||
|
```
|
||||||
|
Phone: Write WRITE_PATTERN (index=2, 112B) → SRAM
|
||||||
|
MCU: Write to EEPROM, update header CRC
|
||||||
|
If index == active pattern: reprogram LP5562 immediately
|
||||||
|
Respond OK
|
||||||
|
```
|
||||||
|
|
||||||
|
## MCU Processing Flow
|
||||||
|
|
||||||
|
### M6 Idle Loop
|
||||||
|
|
||||||
|
```rust
|
||||||
|
loop {
|
||||||
|
delay_ms(200);
|
||||||
|
if fd_pin.is_low() {
|
||||||
|
process_sram_command(&mut ntag, &mut lp5562, &mut delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Command Processing
|
||||||
|
|
||||||
|
1. Read SRAM bytes 0-5 (header)
|
||||||
|
2. Validate command ID (0x01-0x08)
|
||||||
|
3. CRC-16 check over header + payload — if bad, write `BAD_CRC` response
|
||||||
|
4. Read remaining payload bytes based on length field
|
||||||
|
5. Execute command (see per-command logic below)
|
||||||
|
6. Write response to SRAM
|
||||||
|
7. Return to polling
|
||||||
|
|
||||||
|
### Per-Command Logic
|
||||||
|
|
||||||
|
**SYNC_START (0x05)**:
|
||||||
|
- Store target `pattern_count`, `led_current`, `led_mode` in local vars
|
||||||
|
- Write new XBLK header with count=0 (partial header, CRC updated at SYNC_END)
|
||||||
|
- Reset internal write index to 0
|
||||||
|
|
||||||
|
**WRITE_PATTERN (0x01)** (during sync or standalone):
|
||||||
|
- Validate index < 9
|
||||||
|
- Write 28 blocks to EEPROM at offset `16 + (index × 112)`, verify each block
|
||||||
|
- If standalone (not in SYNC): update header pattern count and CRC
|
||||||
|
- If `index == active_pattern`: reprogram LP5562
|
||||||
|
|
||||||
|
**SYNC_END (0x06)**:
|
||||||
|
- Update header: set pattern_count, recalculate CRC-16 over all data
|
||||||
|
- Set active_pattern = 0
|
||||||
|
- Read pattern 0, reprogram LP5562
|
||||||
|
- Clear sync state
|
||||||
|
|
||||||
|
**GET_STATUS (0x02)**:
|
||||||
|
- Read XBLK header from EEPROM
|
||||||
|
- Return `[0x01, count, active, current, mode]`
|
||||||
|
|
||||||
|
**SET_ACTIVE (0x03)**:
|
||||||
|
- Validate index < pattern_count
|
||||||
|
- Update header active_pattern byte (single EEPROM block write)
|
||||||
|
- Read new pattern, reprogram LP5562
|
||||||
|
|
||||||
|
**READ_LIBRARY (0x07)**:
|
||||||
|
- Read XBLK header
|
||||||
|
- Store pattern_count for READ_NEXT iteration
|
||||||
|
- Reset read index to 0
|
||||||
|
- Return `[count, active, current, mode]`
|
||||||
|
|
||||||
|
**READ_NEXT (0x08)**:
|
||||||
|
- Read pattern at current read index from EEPROM
|
||||||
|
- Return 112 bytes of pattern data
|
||||||
|
- Increment read index
|
||||||
|
|
||||||
|
## I2C Bus Sharing
|
||||||
|
|
||||||
|
No special handling required. LP5562 engines run autonomously on the chip's internal oscillator after programming — no ongoing I2C traffic. The MCU switches between NTAG5 (0x54) and LP5562 (0x30) by address as needed. No engine pause necessary.
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
- **Bad CRC**: Respond with `BAD_CRC` (0x01), discard command
|
||||||
|
- **Unknown command**: Respond with `BAD_CMD` (0x02)
|
||||||
|
- **EEPROM write failure**: Respond with `EEPROM_FAIL` (0x03), library may be in partial state
|
||||||
|
- **Invalid pattern index**: Respond with `INVALID_INDEX` (0x04)
|
||||||
|
- **Phone timeout**: If phone stops sending during SYNC, library is partial. Next SYNC_START will erase and restart. No persistent corruption — header CRC won't match partial data, so boot falls back to hardcoded patterns.
|
||||||
|
- **Garbage in SRAM**: Bad command ID or CRC → error response, resume polling. Each command is self-contained, no state to corrupt.
|
||||||
|
|
||||||
|
## Phone-Side Polling
|
||||||
|
|
||||||
|
After writing a command to SRAM, the phone polls byte 0 via ISO15693 READ SINGLE BLOCK:
|
||||||
|
- If byte 0 != 0xFF → MCU hasn't responded yet, poll again
|
||||||
|
- If byte 0 == 0xFF → response ready, read full response
|
||||||
|
- Sequence number in response must match sent sequence — prevents reading stale responses
|
||||||
|
- Timeout after 2 seconds per command → show error to user
|
||||||
|
|
||||||
|
## Implementation Phases
|
||||||
|
|
||||||
|
1. **NTAG5 SRAM driver** — `src/ntag5/sram.rs`: block read/write at 0xF8-0xFF
|
||||||
|
2. **FD pin setup** — Session register config at boot, GPIO input on A1
|
||||||
|
3. **Command parser** — Read SRAM, validate header/CRC, dispatch
|
||||||
|
4. **Command handlers** — WRITE_PATTERN, GET_STATUS, SET_ACTIVE first
|
||||||
|
5. **Streaming sync** — SYNC_START/END, READ_LIBRARY/NEXT
|
||||||
|
6. **Testing** — PCSC reader with Python test script, then VivoKey RawNFC app
|
||||||
Reference in New Issue
Block a user