# NVM-Konfiguration **Dateien:** `config/nvm_config.h`, `config/nvm_config.cpp` ## Flash-Layout (5 Rows, 0x1FB00–0x1FFFF) | Row | Adresse | Größe | Inhalt | |---|---|---|---| | Macro Row 0 | `0x1FB00` | 256 B | SMacroTable Bytes 0–255 | | Macro Row 1 | `0x1FC00` | 256 B | SMacroTable Bytes 256–511 | | Config Row 0 | `0x1FD00` | 256 B | Globaler Header + Profil 0 (teilweise) | | Config Row 1 | `0x1FE00` | 256 B | Profil 0 (Rest) + Profil 1 (teilweise) | | Config Row 2 | `0x1FF00` | 256 B | Profil 1 (Rest) + Profil 2 + 28 B Reserve | Alle Rows sind im Linkerscript vom Code-Bereich ausgeschlossen. Config und Makros liegen in vollständig getrennten, zusammenhängenden Row-Blöcken. ## SDeviceConfig – Byte-Layout (740 Byte, packed) ### Globaler Header (32 B, Offset 0) | Offset | Größe | Feld | |---|---|---| | 0 | 4 | `magic` = `0x56503203` ('VP2\x03') | | 4 | 1 | `version` = 3 | | 5 | 2 | `crc` – CRC16-CCITT über Bytes 7–739 | | 7 | 1 | `active_profile` (0–2) | | 8 | 1 | `global_brightness` (0–255) | | 9 | 4 | `enc_sensitivity[4]` (1 B pro Encoder, Default 1) | | 13 | 19 | Reserve | ### Pro Profil (236 B, Offset `32 + idx × 236`) | Offset | Größe | Feld | |---|---|---| | 0 | 60 | `mx_actions[20]` – 20 × 3 B SAction | | 60 | 36 | `enc_actions[4][3]` – 12 × 3 B SAction | | 96 | 20 | `led_r[20]` | | 116 | 20 | `led_g[20]` | | 136 | 20 | `led_b[20]` | | 156 | 20 | `led_brightness[20]` – per-LED Helligkeit (0–255) | | 176 | 20 | `led_anim[20]` – LEDAnim-Typ als uint8_t | | 196 | 40 | `led_period_ms[20]` – uint16_t little-endian | Gesamt: 32 B Header + 3 × 236 B Profile = **740 B**. `__attribute__((packed))` ist zwingend. Ohne packed wäre SAction 4 B statt 3 B, was `sizeof(SDeviceConfig)` um 32 B vergrößert und die C#-Deserialisierung zerstört. ## CRC16-CCITT - Polynom: `0x1021`, Init: `0xFFFF` - Berechnet über Bytes 7–739 (ab `active_profile`, nach dem `crc`-Feld selbst) - Sichert alle Nutzdaten einschließlich `active_profile` > **Wichtig bei PROFILE_SWITCH:** `active_profile` liegt im CRC-Bereich. Nach jeder Änderung muss `cfg.crc = nvm_config_crc(cfg)` aufgerufen werden bevor gespeichert wird — sonst lädt `nvm_config_load()` die Defaults. ## Lese-Logik ``` memcpy aus Flash-Adresse 0x1FD00 (740 B) if magic != 0x56503203: Defaults laden, return false if version != 3: Defaults laden, return false if crc != crc(cfg): Defaults laden, return false if active_profile >= 3: active_profile = 0 return true ``` Kein Absturz bei ungültiger Config – Defaults greifen immer. ## Defaults - Alle Aktionen: `NONE` - LEDs: warm-weiß (R=80, G=40, B=0), `led_brightness=255` - Animation: `COLOR_CYCLE` (Typ 5), Period 4000 ms - `active_profile = 0`, `global_brightness = 255`, `enc_sensitivity = 1` ## Schreib-Logik (nvm_config_save) `nvm_config_save()` gibt `bool` zurück. `false` bedeutet NVM-Timeout — der NVM-Controller hat nicht rechtzeitig READY gemeldet (beobachtet nach bestimmten Bootloader/Flash-Zyklen auf SAMD21). SAMD21 NVM: Row = 256 B = 4 Pages à 64 B. Ablauf: 1. `NVMCTRL->CTRLB.bit.MANW = 1` (manueller Schreib-Modus) 2. 3 Rows löschen (`NVMCTRL_CTRLA_CMD_ER`) — bei Fehler: `return false` 3. Für jede der 12 Pages à 64 B: - Page-Buffer löschen (`NVMCTRL_CTRLA_CMD_PBC`) - 64 B als `uint32_t*` in Page-Buffer schreiben - Page programmieren (`NVMCTRL_CTRLA_CMD_WP`) — bei Fehler: `return false` 4. `return true` ### nvm_wait() – Timeout ```cpp static bool nvm_wait() { uint32_t timeout = 48000000UL / 4 * 400 / 1000; // ≈ 4 800 000 Iterationen ≈ 400 ms while (!NVMCTRL->INTFLAG.bit.READY) { if (--timeout == 0) return false; } return true; } ``` Der Timeout verhindert ein dauerhaftes Einfrieren des Boards wenn NVMCTRL aus unbekanntem Grund nicht READY meldet. Bei Timeout sendet das Board `CONFIG_NACK` statt zu hängen. > `NVMCTRL->ADDR.reg = addr / 2` – NVMCTRL erwartet Wort-Adresse (16-Bit-Worte), nicht Byte-Adresse. > **Aligned-Buffer-Pflicht**: `nvm_write_page` castet `data` zu `const uint32_t*`. Der Puffer muss `__attribute__((aligned(4)))` sein. Packed Structs sind nicht garantiert aligned → immer via lokalem `uint8_t buf[] __attribute__((aligned(4)))` + `memcpy` übergeben.