# VersaMCU Firmware für das VersaPad v2 Macro-Pad. Läuft auf einem **ATSAMD21G17D** (Cortex-M0+), entwickelt mit PlatformIO + Arduino-Framework. ## Voraussetzungen - [PlatformIO](https://platformio.org/) (CLI oder VS Code Extension) - **Atmel-ICE** Debugger/Programmer (SWD-Verbindung zur PCB) - [OpenOCD](https://openocd.org/) – wird von PlatformIO automatisch installiert ## Flashen ```bash pio run --target upload ``` Der Upload läuft via OpenOCD über SWD. Kein Bootloader nötig – der Chip wird direkt programmiert. ## Projekt-Struktur ``` VersaMCU/ ├── platformio.ini – Build- und Upload-Konfiguration ├── upload_openocd.py – Benutzerdefiniertes Upload-Script (OpenOCD) ├── boards/ │ ├── versapad.json – Board-Definition mit Bootloader │ └── versapad_nobl.json – Board-Definition ohne Bootloader (aktiv) ├── variants/versapad/ │ ├── variant.h/.cpp – Pin-Mapping für den SAMD21G17D │ └── linker_scripts/ – Linkerscript (kein Bootloader, NVM-Reservierung) └── src/ ├── main.cpp – Arduino setup()/loop() ├── CMainController.h/.cpp – Zentraler Orchestrator ├── CButton.h/.cpp – Button-Modell: LED-Schichten, Action, Animationen ├── CEventQueue.h/.cpp – Ring-Buffer FIFO (16 Slots, kein Heap) ├── SEvent.h – Event-Typen (KEY_DOWN/UP, ENC_CW/CCW) ├── config/ │ ├── pins.h – Pin-Nummern (Arduino-Nummern aus variant.h) │ ├── action.h – ActionType-Enum + SAction-Struct (packed) │ └── nvm_config.h/.cpp – NVM-Config: Laden, Speichern, CRC16, Defaults └── hal/ ├── matrix.h/.cpp – 5×5-Matrix-Scan, 10 ms Debounce ├── encoder.h/.cpp – Quadratur-Dekodierung via EIC-Interrupts ├── ws2812.h/.cpp – WS2812-LED-Treiber (Adafruit NeoPixel, bit-bang) ├── usb_hid.h/.cpp – HID Keyboard + Consumer Control └── usb_serial.h/.cpp – CDC Serial bidirektional, 8-Byte-Pakete ``` ## Architektur ### Loop-Ablauf ``` loop() ├── matrix_scan() → matrix_cb() → CEventQueue.push() │ (Encoder-ISRs laufen asynchron) → CEventQueue.push() ├── poll_vendor() → Serial-Pakete von VersaGUI verarbeiten ├── processEvents() → Queue leeren, Aktionen ausführen └── updateLEDs() → Dirty-CButtons → WS2812-Buffer → show() ``` ### CButton – LED-Schichten Jeder MX-Button hat zwei LED-Schichten: - **base**: Konfigurierte Idle-Farbe (aus NVM) - **override**: Temporär von VersaGUI gesetzt (Benachrichtigungen etc.) Aktive Farbe = `override` wenn aktiv, sonst `base`. `clear_override()` kehrt sofort zu `base` zurück. ### LED-Animationen | Animation | Verhalten | |---|---| | `STATIC` | Feste Farbe | | `BLINK` | Binäres An/Aus | | `PULSE` | Lineares Dreieck 0→255→0 | | `FADE_IN / FADE_OUT` | Einmaliges Ein-/Ausblenden | | `COLOR_CYCLE` | Hue-Sweep (Regenbogen), ignoriert base/override | | `COLOR_FADE` | Crossfade zu Zielfarbe | Alle Berechnungen in Integer-Arithmetik (kein FPU auf Cortex-M0+). **Idle-Zustand:** Alle 20 MX-LEDs zeigen einen rotierenden Regenbogen (`COLOR_CYCLE`, 4 s/Runde, 40 % Helligkeit, gleichmäßig phasenverschoben). ### Serial-Protokoll (8 Bytes, fixed) ``` Byte 0: Command/Event-ID Byte 1: key_id (Button 0–24 oder Encoder 0–3) Byte 2: r / Daten-Byte A Byte 3: g / Daten-Byte B Byte 4: b Byte 5–7: reserviert (0x00) ``` | ID | Richtung | Bedeutung | |---|---|---| | 0x01 | PC→Board | LED-Override setzen | | 0x02 | PC→Board | LED-Override löschen | | 0x03 | PC→Board | LED-Base setzen | | 0x05 | PC→Board | Ping | | 0x10 | PC→Board | Config-Begin (Chunks-Anzahl) | | 0x11 | PC→Board | Config-Data (Chunk-Index + 6B Nutzdaten) | | 0x12 | PC→Board | Config-Commit (CRC prüfen + NVM schreiben) | | 0x13 | PC→Board | Config-Read (Board sendet NVM-Config zurück) | | 0x81 | Board→PC | KEY_DOWN | | 0x82 | Board→PC | KEY_UP | | 0x83 | Board→PC | ENC_CW | | 0x84 | Board→PC | ENC_CCW | | 0x85 | Board→PC | Pong | | 0x90 | Board→PC | Config-ACK | | 0x91 | Board→PC | Config-NACK | | 0x92 | Board→PC | Config-Begin (Dump-Start, Chunks-Anzahl) | | 0x93 | Board→PC | Config-Data (Chunk-Index + 6B) | | 0x94 | Board→PC | Config-End | ### NVM-Config-Layout (163 Bytes, packed) ``` Offset 0 4B Magic 0x56503202 Offset 4 1B Version 1 Offset 5 2B CRC16-CCITT (über Bytes 7–162) Offset 7 60B mx_actions[20] – je 3B: type(1B) + data(2B) Offset 67 36B enc_actions[4][3] – je 3B Offset103 20B led_r[20] Offset123 20B led_g[20] Offset143 20B led_b[20] ``` Gespeichert in den letzten 512 Bytes des Flashs (0x1FE00), via Linkerscript reserviert. Bei ungültigem Magic/CRC werden Defaults geladen (alle Tasten: NONE, LEDs: warm-weiß). ## Bekannte Fallstricke | Problem | Lösung | |---|---| | Kaltstart hängt (XOSC32KRDY) | `-DCRYSTALLESS` in build_flags pflicht | | Adafruit NeoPixel ZeroDMA inkompatibel | Standard bit-bang Library verwenden | | WS2812 Pegel 3.3V statt 5V | LED 0 marginal OK, ab LED 1 selbst-regenerierend. Fix nächste PCB-Rev: Level-Shifter | | `ws2812_show()` blockiert ~600 µs | Dirty-Flag-Pattern: nur aufrufen wenn nötig, nie aus ISR | | SAction muss `__attribute__((packed))` haben | Ohne packed: 4B statt 3B → CRC-Mismatch beim Config-Laden | | Windows HID-Descriptor-Cache | Bei PID-Änderung Board neu einstecken |