# VersaMCU – Architektur-Übersicht ## Ziel-Hardware | Merkmal | Wert | |---|---| | MCU | ATSAMD21G17D (Cortex-M0+, 48 MHz) | | Flash | 128 KB (davon 512 B am Ende für NVM-Config reserviert) | | 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) | ## Loop-Ablauf ``` 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 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) ``` 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+). ## 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 SerialUSB (CDC, PC → Board) └─► poll_vendor() ├─► CButton.set_override() / clear_override() / set_base() └─► Config/Makro-Transfer (chunked, 6 B/Paket) ``` ## Komponenten-Übersicht | Datei | Verantwortung | |---|---| | `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 | ## Key-ID-Schema ``` key_id 0–3 : Encoder-SW-Buttons (COL_0 × ROW_0–3), kein LED key_id 4 : nicht belegt (COL_0 × ROW_4) key_id 5–24 : MX-Buttons (COL_1–4 × ROW_0–4), je ein WS2812-LED ``` LED-Index folgt serpentiner Verdrahtung: `LED_INDEX(col, row)`. ## Invarianten / Constraints - **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 (~10–20× 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.