# 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 1 Next pattern index (0xFF = loop forever, 0-8 = chain to that index) 109 1 Loop count (how many full cycles before chaining, 0 = use next_pattern as-is) 110 2 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. ### Pattern Chaining A pattern can specify a successor via `next_pattern_index`. When set to anything other than 0xFF, the pattern's engine programs should use finite-count `branch(0, N)` instead of `branch(0, 0)` (infinite loop). When all engines stop (detected by polling LP5562 STATUS register 0x0C for engine interrupt bits), the MCU loads the next pattern. The LP5562 has no interrupt output pin — the MCU must poll STATUS periodically. During the idle loop, a poll every ~500ms is sufficient and costs negligible power vs. the LED current. Chain examples: - `next=0xFF`: Loop forever (default, current behavior) - `next=1, loops=0`: Play once, then switch to pattern 1 - `next=0, loops=3`: Play 3 full cycles, then switch to pattern 0 (can create A→B→A→B sequences) - Circular chains (A→B→A) create alternating pattern sequences ## 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.