Added config, added factory reset functionality

This commit is contained in:
2026-04-18 23:59:48 +02:00
parent 433d61c29f
commit 802ab858e1
9 changed files with 718 additions and 778 deletions
+81 -57
View File
@@ -1,83 +1,107 @@
# VersaMCU Architekturbersicht
# VersaMCU - Architekturuebersicht
## Ziel-Hardware
## Zielplattform
| Merkmal | Wert |
|---|---|
| MCU | ATSAMD21G17D (Cortex-M0+, 48 MHz) |
| Flash | 128 KB (davon 512 B am Ende für NVM-Config reserviert) |
| MCU | ATSAMD21G17D, Cortex-M0+, 48 MHz |
| Flash | 128 KB |
| RAM | 16 KB |
| FPU | Keine alle Berechnungen in Integer-Arithmetik |
| USB | Native USB, DFLL48M via USB-SOF-Kalibrierung (`-DCRYSTALLESS`) |
| Framework | Arduino + PlatformIO, kein Bootloader (Direktflash via SWD/Atmel-ICE) |
| FPU | keine, deshalb Integer-Arithmetik |
| USB | HID Keyboard + Consumer + CDC Serial |
| Toolchain | PlatformIO + Arduino Core |
## Loop-Ablauf
## Setup und Loop
```
```text
setup()
├── macro_config_load() Makro-Tabelle aus NVM in RAM laden
├── init_buttons() CButton-Objekte aus NVM initialisieren
├── usb_hid_init() HID-Descriptor (No-Op, läuft via global ctor)
├── usb_serial_init() CDC Serial öffnen
├── matrix_init(cb) 5×5-Matrix + Debounce-Zustand
└── encoder_init(cb) EIC-Interrupts für 4 Encoder
macro_config_load()
nvm_config_load()
init_buttons()
usb_hid_init()
usb_serial_init()
matrix_init(cb)
encoder_init(cb)
loop() [~20 ms Iteration]
├── matrix_scan() Debounce-Zustand prüfen → Events in Queue
├── poll_vendor() CDC-Pakete vom PC verarbeiten (LED-Cmds, Config, Makros)
├── processEvents() Queue leeren: Aktionen ausführen, HOST_COMMAND melden
└── updateLEDs() Dirty-CButtons → WS2812-Buffer → show() (nur wenn dirty)
loop()
matrix_scan()
poll_vendor()
processEvents()
check_factory_reset()
updateLEDs()
```
Encoder-ISRs laufen asynchron (CHANGE-Interrupt auf A und B) und schreiben direkt in die Event-Queue. Die Queue ist interrupt-sicher (keine Locks nötig auf Single-Core-M0+).
Die Reihenfolge ist absichtlich simpel:
- Eingaben einsammeln
- CDC-Kommandos vom Host verarbeiten
- Event-Queue leeren
- Sonderlogik fuer den Werksreset pruefen
- LED-Frame nur bei Bedarf rendern
## Datenfluss
```
HAL-Callbacks (matrix_cb, encoder_cb)
└─► CEventQueue (16 Slots, Ring-Buffer, kein Heap)
└─► processEvents()
├─► CButton.on_press() / on_release() [Hooks, aktuell leer]
├─► execute_action() → USB HID / Makro-Ablauf
└─► usb_serial_send() HOST_COMMAND-Events an PC
```text
matrix_scan / encoder ISR
-> EventQueue
-> processEvents()
-> execute_action_down / execute_action_up
-> usb_hid_*
-> usb_serial_send() fuer HOST_COMMAND
SerialUSB (CDC, PC → Board)
└─► poll_vendor()
├─► CButton.set_override() / clear_override() / set_base()
└─► Config/Makro-Transfer (chunked, 6 B/Paket)
CDC Serial
-> poll_vendor()
-> Config/Makros einlesen oder dumpen
-> LED-Overrides setzen/loeschen
LED-Render
-> CButton.render_led()
-> ws2812_set()
-> ws2812_show() nur wenn dirty
```
## Komponenten-Übersicht
## Zentrale Komponenten
| Datei | Verantwortung |
| Datei | Aufgabe |
|---|---|
| `main.cpp` | `setup()` / `loop()` ruft nur CMainController auf |
| `CMainController` | Zentraler Orchestrator, hält alle CButton-Instanzen |
| `CButton` | LED-Layering, Animations-Engine, Action-Referenz |
| `CEventQueue` | ISR-sicherer Ring-Buffer, 16 Events |
| `hal/matrix` | 5×5-Matrix-Scan, 10 ms Debounce |
| `hal/encoder` | Quadratur-Dekodierung via EIC-ISR |
| `hal/ws2812` | Thin Wrapper um Adafruit NeoPixel (bit-bang) |
| `hal/usb_hid` | HID Keyboard + Consumer Control |
| `hal/usb_serial` | CDC bidirektional, 8-Byte-Pakete, Ring-Buffer |
| `config/nvm_config` | SDeviceConfig: laden, speichern, CRC16, Defaults |
| `config/macro_config` | SMacroTable: laden, speichern (NVM Row 1) |
| `config/action` | SAction-Struct + ActionType-Enum |
| `main.cpp` | startet den Controller |
| `CMainController.*` | Orchestrator fuer Inputs, Actions, Serial, LEDs |
| `CButton.*` | LED-Zustand, Animationen, Action-Referenz |
| `CEventQueue.*` | ISR-sicherer Ringbuffer |
| `config/nvm_config.*` | Config v3 laden, speichern, Defaults |
| `config/macro_config.*` | Makros laden, speichern |
| `hal/matrix.*` | 5x5-Matrixscan mit Debounce |
| `hal/encoder.*` | Encoder-ISR und Drehrichtung |
| `hal/usb_hid.*` | Keyboard- und Consumer-HID |
| `hal/usb_serial.*` | CDC-Paketpfad |
| `hal/ws2812.*` | WS2812-Treiber |
## Key-ID-Schema
```
key_id 03 : Encoder-SW-Buttons (COL_0 × ROW_03), kein LED
key_id 4 : nicht belegt (COL_0 × ROW_4)
key_id 524 : MX-Buttons (COL_14 × ROW_04), je ein WS2812-LED
```text
0..3 = Encoder-SW
4 = unbenutzt
5..24 = MX-Buttons
```
LED-Index folgt serpentiner Verdrahtung: `LED_INDEX(col, row)`.
Die beiden Werksreset-Tasten sind:
## Invarianten / Constraints
- `key_id 9` = unten links
- `key_id 24` = unten rechts
- **Kein Heap**: kein `new`/`malloc` alle Objekte statisch oder als Felder in CMainController.
- **Kein Float**: Cortex-M0+ hat keine FPU; Float würde per Software emuliert (~1020× langsamer).
- **Packed Structs**: `SAction` und `SDeviceConfig` sind `__attribute__((packed))` damit die Byte-Größen mit der C#-Serialisierung in VersaGUI übereinstimmen.
- **Aligned NVM-Writes**: `nvm_write_page` castet Pointer zu `uint32_t*`; Puffer müssen vor dem Aufruf in `__attribute__((aligned(4)))`-Variablen kopiert werden (sonst HardFault auf M0+).
- **DTR-Check**: `usb_serial_send()` prüft ob SerialUSB aktiv ist, bevor Bytes gesendet werden.
## Werksreset im Ablauf
Der Werksreset ist keine PC-Funktion, sondern Teil der Firmware:
- sobald beide Reset-Tasten gleichzeitig gehalten werden, werden ihre normalen Actions unterdrueckt
- falls bereits ein HID-Hold aktiv war, wird er sofort freigegeben
- nach 5 Sekunden gemeinsamer Haltezeit wird Default-Config + leere Makro-Tabelle in NVM geschrieben
- danach folgt ein kurzes rotes Feedback-Blinken
## Invarianten
- kein Heap
- keine Floats
- `packed` fuer serielle und NVM-relevante Structs
- NVM-Schreibpuffer muessen 4-Byte-aligned sein
- `usb_serial_send()` sendet nur bei aktiver CDC-Verbindung