8 Step macro and profile switching fully working

This commit is contained in:
2026-04-13 22:34:54 +02:00
parent 098a166a9f
commit 433d61c29f
10 changed files with 204 additions and 193 deletions
+76 -99
View File
@@ -2,113 +2,33 @@
**Dateien:** `config/nvm_config.h`, `config/nvm_config.cpp`
## Flash-Layout
## Flash-Layout (5 Rows, 0x1FB000x1FFFF)
| Row | Adresse | Größe | Inhalt |
|---|---|---|---|
| Row 0 | `0x1FE00` | 256 B | SDeviceConfig (223 B genutzt, 33 B Padding) |
| Row 1 | `0x1FF00` | 256 B | SMacroTable (256 B, komplett genutzt) |
| 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 |
Beide Rows sind im Linkerscript vom Code-Bereich ausgeschlossen.
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 (223 Byte, packed)
## SDeviceConfig Byte-Layout (740 Byte, packed)
| Offset | Größe | Feld |
|---|---|---|
| 0 | 4 | `magic` = `0x56503202` ('VP2\x02') |
| 4 | 1 | `version` = 2 |
| 5 | 2 | `crc` CRC16-CCITT über Bytes 7222 |
| 7 | 60 | `mx_actions[20]` 20 × 3 B SAction |
| 67 | 36 | `enc_actions[4][3]` 12 × 3 B SAction |
| 103 | 20 | `led_r[20]` |
| 123 | 20 | `led_g[20]` |
| 143 | 20 | `led_b[20]` |
| 163 | 20 | `led_anim[20]` LEDAnim-Typ als uint8_t |
| 183 | 40 | `led_period_ms[20]` uint16_t, little-endian |
| **223** | — | Ende des genutzten Bereichs |
`__attribute__((packed))` ist zwingend. Ohne packed wäre SAction 4 B statt 3 B (Alignment-Padding), was `sizeof(SDeviceConfig)` um 32 B vergrößert und die C#-Deserialisierung in VersaGUI zerstört.
## CRC16-CCITT
- Polynom: `0x1021`, Init: `0xFFFF`
- Berechnet über Bytes 7248 (ab `mx_actions`, nach dem `crc`-Feld selbst)
- Sichert Datenintegrität nach NVM-Schreiben und bei Versionswechsel
## Lese-Logik
```
memcpy aus Flash-Adresse 0x1FE00
if magic != 0x56503202: Defaults laden, return false
if version != 2: Defaults laden, return false
if crc != crc(cfg): Defaults laden, return false
return true
```
Kein Absturz bei ungültiger Config Defaults greifen immer.
## Defaults
- Alle Aktionen: `NONE`
- LEDs: warm-weiß (R=80, G=40, B=0)
- Animation: `COLOR_CYCLE` (Typ 5), Period 4000 ms
## Schreib-Logik (NVM-Mechanik)
SAMD21 NVM: Row = 256 B = 4 Pages à 64 B. Schreiben erfordert:
1. `NVMCTRL->CTRLB.bit.MANW = 1` (manueller Schreib-Modus, kein Auto-Write)
2. Row löschen (`NVMCTRL_CTRLA_CMD_ER`)
3. Page-Buffer löschen (`NVMCTRL_CTRLA_CMD_PBC`)
4. 64 B als `uint32_t*` in Page-Buffer schreiben
5. Page programmieren (`NVMCTRL_CTRLA_CMD_WP`)
6. Schritte 35 viermal (für alle 4 Pages)
> `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 lokalen `uint8_t buf[256] __attribute__((aligned(4)))` + `memcpy` übergeben.
---
## Geplante Erweiterung: NVM v3
### Motivation
Das bisherige Layout (2 Rows, 512 B) stößt an mehrere Grenzen:
- **Makro-Steps zu kurz** — 4 Steps reichen für komplexe Shortcuts (z.B. Excel-Ribbon-Navigation: Alt → Buchstabe → Buchstabe → ...) nicht aus. Ziel: 8 Steps.
- **Keine Profile** — Eine einzige Config erlaubt keine Umschaltung zwischen Layouts (z.B. Coding vs. Tabellenkalkulation). Ziel: 3 unabhängige Profile.
- **Keine Helligkeitssteuerung** — Weder global noch pro LED einstellbar. Beide Ebenen sollen konfigurierbar werden.
- **Encoder-Sensitivity** — Schrittweite pro Encoder soll konfigurierbar sein.
Das bisherige Layout hat außerdem Config und Macros in denselben Adressbereich gemischt (`0x1FE00` Config, `0x1FF00` Macros). Das neue Layout trennt beide Bereiche sauber.
### Neues 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 (Bytes 0255) |
| Config Row 1 | `0x1FE00` | 256 B | Profil 0 (Rest) + Profil 1 (Bytes 256511) |
| Config Row 2 | `0x1FF00` | 256 B | Profil 1 (Rest) + Profil 2 + Reserve (Bytes 512767) |
Macros und Config liegen in vollständig getrennten, jeweils zusammenhängenden Row-Blöcken.
### Config-Inhalt (768 B, davon 740 B genutzt, 28 B Reserve)
**Globaler Header (32 B, Offset 0):**
### 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 alle Nutzdaten (ab Byte 7) |
| 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) |
| 9 | 4 | `enc_sensitivity[4]` (1 B pro Encoder, Default 1) |
| 13 | 19 | Reserve |
**Pro Profil (236 B, Offset `32 + idx × 236`):**
### Pro Profil (236 B, Offset `32 + idx × 236`)
| Offset | Größe | Feld |
|---|---|---|
@@ -117,14 +37,71 @@ Macros und Config liegen in vollständig getrennten, jeweils zusammenhängenden
| 96 | 20 | `led_r[20]` |
| 116 | 20 | `led_g[20]` |
| 136 | 20 | `led_b[20]` |
| 156 | 20 | `led_brightness[20]` ← neu |
| 176 | 20 | `led_anim[20]` |
| 196 | 40 | `led_period_ms[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 |
### Makro-Tabelle (512 B)
Gesamt: 32 B Header + 3 × 236 B Profile = **740 B**.
32 Slots × **8 Steps** × 2 B = 512 B. Gegenüber v2 doppelt so viele Steps (4 → 8), Slot-Anzahl und Struktur bleiben gleich. Siehe [04_macro_system.md](04_macro_system.md).
`__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.
### Migration von v2
## CRC16-CCITT
Beim Laden: wenn `magic` oder `version` nicht zu v3 passen, werden Defaults geladen (kein Migrations-Pfad von v2 → v3, da das Layout inkompatibel ist). Eine einmalige Neukonfiguration nach dem Firmware-Update ist nötig.
- 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
```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.