VersaMCU/doc/06_nvm_config.md

4.2 KiB
Raw Blame History

NVM-Konfiguration

Dateien: config/nvm_config.h, config/nvm_config.cpp

Flash-Layout (5 Rows, 0x1FB000x1FFFF)

Row Adresse Größe Inhalt
Macro Row 0 0x1FB00 256 B SMacroTable Bytes 0255
Macro Row 1 0x1FC00 256 B SMacroTable Bytes 256511
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 7739
7 1 active_profile (02)
8 1 global_brightness (0255)
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 (0255)
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 7739 (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

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.