M5 complete: boot-from-EEPROM with self-provisioning

XBLK binary format for pattern library in NTAG5 upper 1K EEPROM:
- 16-byte header (magic, version, pattern count, active index, CRC-16)
- Up to 9 × 112-byte fixed-size pattern entries
- Deserializer + serializer in src/pattern/mod.rs
- MCU self-provisions hardcoded patterns on first boot (no XBLK found)
- Subsequent boots load active pattern directly from EEPROM
- Python serializer tool for future PCSC-based pattern uploads

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
michael
2026-03-05 15:37:42 -08:00
parent acb48902be
commit a32c242859
5 changed files with 829 additions and 58 deletions

View File

@@ -1,6 +1,6 @@
# xblink Project Status
**Current Milestone**: M5Boot-from-EEPROM
**Current Milestone**: M6SRAM Mailbox
**Last Updated**: 2026-03-05
---
@@ -55,13 +55,15 @@
- [x] Verified on hardware: config check passes, NDEF readable via NFC
- [ ] Register map module (`src/ntag5/registers.rs`) — deferred, constants in mod.rs for now
### M5: Boot-from-EEPROM
### M5: Boot-from-EEPROM (COMPLETE)
- [ ] Design binary pattern format (informed by M2-M3 experience)
- [ ] Implement pattern deserializer (`src/pattern/mod.rs`)
- [ ] Implement engine program builder (`src/pattern/engine.rs`)
- [ ] Update main.rs: boot → read EEPROM → parse → program LP5562 → idle
- [ ] Write Python serializer tool for PCSC pattern uploads
- [x] Design XBLK binary pattern format (`docs/plans/2026-03-05-eeprom-pattern-format.md`)
- [x] Implement XBLK deserializer (`src/pattern/mod.rs`: parse_header, parse_pattern_entry)
- [x] Implement XBLK serializer (`src/pattern/mod.rs`: serialize_pattern_entry, serialize_header, crc16)
- [x] Update main.rs: boot → read EEPROM → parse → program LP5562 → idle, with fallback
- [x] MCU self-provisioning: write hardcoded patterns to EEPROM on first boot
- [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
### M6: SRAM Mailbox

View File

@@ -0,0 +1,106 @@
# EEPROM Pattern Library Format (M5)
## Memory Layout
The NTP53x2 has 2048 bytes (512 x 4-byte blocks) of user EEPROM.
| Region | Blocks | Bytes | Purpose |
|--------|--------|-------|---------|
| NFC/NDEF | 0-255 | 0-1023 | CC + NDEF data (phone-visible, MCU doesn't touch) |
| Pattern library | 256-511 | 1024-2047 | XBLK header + up to 9 patterns |
I2C base address for the pattern library: `0x0100` (block 256).
## Header (16 bytes, blocks 256-259)
```
Offset Size Field
0 4 Magic: "XBLK" (0x58 0x42 0x4C 0x4B)
4 1 Version: 0x01
5 1 Pattern count (1-9)
6 1 Active pattern index (0-based, wraps at count)
7 1 LED current (0-255, 0.1mA/step)
8 1 LED mode (0x00 = RGBW, 0x01 = Mono3)
9 5 Reserved (0x00)
14 2 CRC-16 over bytes 0-13 + all pattern data
```
Current and LED mode are global -- all patterns share them.
## Pattern Entry (112 bytes, 28 blocks each)
Fixed-size entries for direct seeking: `offset = 16 + (index * 112)`.
```
Offset Size Field
0 1 Engine count (0-3)
1 1 LED_MAP register value (raw LP5562 register byte)
2 4 Direct PWM [B, G, R, W] for I2C-mapped channels
6 2 Engine 1 command count (big-endian, 0 = unused)
8 32 Engine 1 commands (up to 16 x 2 bytes BE, pad with 0x0000)
40 2 Engine 2 command count
42 32 Engine 2 commands
74 2 Engine 3 command count
76 32 Engine 3 commands
108 4 Reserved
```
Max capacity: `(1024 - 16) / 112 = 9` patterns.
The 16-command-per-engine limit is a hardware constraint of the LP5562 (48 bytes engine SRAM). The `branch` command enables infinite looping, so pattern duration is unlimited.
## Boot Sequence
1. Init LP5562 (enable, clock, current)
2. Read header at `0x0100` (4 blocks)
3. If magic = "XBLK" and CRC valid:
- Read pattern at `active_pattern_index`
- Deserialize into `Pattern` struct
- Load into LP5562 engines
4. If invalid (no magic, CRC mismatch, I2C error):
- Fall back to hardcoded breathe pattern (compiled into firmware)
5. Config check via session registers, report via LED blinks (no NDEF write)
6. Sleep (future: STANDBY)
## Pattern Switching (future, hall sensor)
1. Wake on hall EIC interrupt
2. Read header, increment `active_pattern_index` (wrap at `pattern_count`)
3. Write back the single block containing the index (1 EEPROM write, ~5ms)
4. Read and load the new pattern
5. Sleep
## Error Handling
- EEPROM read fails at boot: use hardcoded fallback
- CRC mismatch: use hardcoded fallback
- Pattern data malformed (engine_count > 3, cmd_count > 16): skip to next, wrap around; if all bad, use fallback
- Active index >= pattern count: reset to 0
## Python Serializer
`tools/xblk_serialize.py` converts JSON pattern definitions to binary and writes to EEPROM via ntag5sensor/PCSC.
Input JSON:
```json
{
"current": 20,
"mode": "rgbw",
"active": 0,
"patterns": [
{
"name": "breathe",
"led_map": {"b": "engine1", "g": "engine1", "r": "engine1", "w": "direct"},
"direct_pwm": [0, 0, 0, 0],
"engines": [
[18176, 18176, 26368, 26368, 15408, 40960],
[],
[]
]
}
]
}
```
Engine commands are raw u16 values. The serializer validates constraints, builds the binary blob, computes CRC-16, and writes to EEPROM blocks 256+ via I2C.