VersaMCU/README.md

259 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.
---
## Funktionsumfang (Anforderungskatalog)
### 1 Hardware-Plattform
| # | Anforderung | Status |
|---|-------------|--------|
| 1.1 | Ziel-MCU: **ATSAMD21G17D** (Cortex-M0+, 48 MHz, 128 KB Flash, 16 KB RAM) | ✅ |
| 1.2 | Framework: **Arduino + PlatformIO**, kein Bootloader (Direktflash via SWD/Atmel-ICE) | ✅ |
| 1.3 | USB-Enumeration ohne externen Quarz (`-DCRYSTALLESS`, DFLL48M nutzt USB-SOF als Referenz) | ✅ |
| 1.4 | Benutzerdefiniertes Board-Profil (`versapad_nobl.json`) mit korrekten Flash/RAM-Limits | ✅ |
### 2 Tasten-Matrix
| # | Anforderung | Status |
|---|-------------|--------|
| 2.1 | **5×5-Matrix-Scan** (25 Keys, davon 20 MX-Buttons + 4 Encoder-SW + 1 NC) | ✅ |
| 2.2 | **10 ms Software-Debounce** pro Taste (Flanken-Erkennung) | ✅ |
| 2.3 | KEY_DOWN- und KEY_UP-Events werden in die Event-Queue geschrieben | ✅ |
| 2.4 | Matrix-Scan im **Loop-Kontext** (kein ISR, kein Heap) | ✅ |
### 3 Encoder
| # | Anforderung | Status |
|---|-------------|--------|
| 3.1 | **4 Quadratur-Encoder** (A/B-Phasen per EIC-Interrupt) | ✅ |
| 3.2 | **Richtungserkennung**: CW / CCW via 2-Bit-Greycode-Auswertung | ✅ |
| 3.3 | Encoder-Events im ISR-Kontext direkt in Event-Queue (interrupt-sicher, kein Heap) | ✅ |
| 3.4 | Encoder-SW-Tasten über Matrix-Scan (gleicher Pfad wie MX-Buttons) | ✅ |
### 4 Aktions-Engine
| # | Anforderung | Status |
|---|-------------|--------|
| 4.1 | **ActionType NONE**: kein HID-Event beim Drücken | ✅ |
| 4.2 | **ActionType HID_KEY**: USB-HID-Tastendruck (Keycode + Modifier-Byte) | ✅ |
| 4.3 | **ActionType HID_CONSUMER**: USB Consumer Control (Play/Pause, Lautstärke …) | ✅ |
| 4.4 | **ActionType HOST_COMMAND**: Event-ID an VersaGUI senden, App führt aus | ✅ |
| 4.5 | **ActionType MACRO**: Sequenz aus bis zu 4 HID-Key-Schritten aus NVM-Tabelle abspielen | ✅ |
| 4.6 | Jede Aktion ausführbar **ohne laufende VersaGUI** (lokal per HID/Makro) | ✅ |
### 5 Makro-System
| # | Anforderung | Status |
|---|-------------|--------|
| 5.1 | **32 Makro-Slots**, je **4 Steps** (Keycode + Modifier-Byte) = 256 Byte gesamt | ✅ |
| 5.2 | Steps mit `keycode = 0` werden übersprungen (variable Makrolänge 14) | ✅ |
| 5.3 | Timing: 10 ms Key-Down-Dauer + 20 ms Pause zwischen Steps | ✅ |
| 5.4 | Makro-Tabelle in **separater NVM-Row** (Row 1, 0x1FF00, 256 Byte) | ✅ |
| 5.5 | Makro-Tabelle wird beim Start aus NVM geladen; gelöschter Flash (0xFF) → leere Tabelle | ✅ |
### 6 LED-System (WS2812)
| # | Anforderung | Status |
|---|-------------|--------|
| 6.1 | **20 WS2812-LEDs**, serpentiner Verdrahtung; Adafruit-NeoPixel bit-bang Treiber | ✅ |
| 6.2 | **2-Schicht-Modell** pro Button: `base` (Idle) + `override` (temporär von GUI) | ✅ |
| 6.3 | **STATIC**: feste Farbe aus NVM | ✅ |
| 6.4 | **BLINK**: binäres An/Aus mit konfigurierbarer Halbperiode | ✅ |
| 6.5 | **PULSE**: lineares Helligkeitsdreieck (0→255→0), kein Float | ✅ |
| 6.6 | **COLOR_CYCLE** (Regenbogen): Hue-Sweep über 6 Segmente, ignoriert base/override | ✅ |
| 6.7 | **COLOR_FADE**: einmaliger RGB-Crossfade zu Zielfarbe | ✅ |
| 6.8 | Phasenversatz beim Start: Regenbogen-LEDs sind gleichmäßig über die Periode verteilt | ✅ |
| 6.9 | `ws2812_show()` nur bei dirty-Flag aufgerufen (~600 µs Blockzeit vermieden) | ✅ |
| 6.10 | Alle Animationen in **Integer-Arithmetik** (kein FPU auf M0+) | ✅ |
### 7 Konfigurations-Speicherung (NVM)
| # | Anforderung | Status |
|---|-------------|--------|
| 7.1 | Config-Layout **Version 2**, 223 Byte packed, mit Magic `0x56503202` + CRC16-CCITT | ✅ |
| 7.2 | Config gespeichert in **NVM Row 0** (0x1FE00, 256 Byte, via Linkerscript reserviert) | ✅ |
| 7.3 | Makro-Tabelle in **NVM Row 1** (0x1FF00, 256 Byte) | ✅ |
| 7.4 | NVM-Schreiben: Row löschen + 4 Pages à 64 Byte manuell schreiben (MANW=1) | ✅ |
| 7.5 | Bei ungültigem Magic / falscher Version / CRC-Fehler → **Defaults** laden (kein Crash) | ✅ |
| 7.6 | Defaults: alle Aktionen NONE, LEDs warm-weiß, Animation Regenbogen 4 s | ✅ |
### 8 Serial-Kommunikation mit VersaGUI
| # | Anforderung | Status |
|---|-------------|--------|
| 8.1 | **CDC Serial** (USB), kein Treiber nötig; 8-Byte-Festlängen-Pakete | ✅ |
| 8.2 | **Ring-Buffer** (256 Byte = 32 Pakete) für eingehende Bytes; kein Datenverlust bei Burst | ✅ |
| 8.3 | **Ping / Pong** (0x05 / 0x85) zur Verbindungsdiagnose | ✅ |
| 8.4 | **Config-Transfer PC→Board**: BEGIN(0x10) → 38×DATA(0x11) → COMMIT(0x12) → ACK/NACK | ✅ |
| 8.5 | **Config-Dump Board→PC**: auf READ(0x13) → BEGIN(0x92) → 38×DATA(0x93) → END(0x94) | ✅ |
| 8.6 | **Makro-Transfer PC→Board**: BEGIN(0x20) → 43×DATA(0x21) → COMMIT(0x22) → ACK(0x95) | ✅ |
| 8.7 | **Makro-Dump Board→PC**: auf READ(0x23) → BEGIN(0x96) → 43×DATA(0x97) → END(0x98) | ✅ |
| 8.8 | Config-COMMIT validiert Magic + Version + CRC; bei Fehler **NACK** ohne NVM-Schreiben | ✅ |
| 8.9 | Alle Sende-Pakete nur wenn `SerialUSB` aktiv (DTR-Check verhindert stilles Verwerfen) | ✅ |
### 9 Nicht implementiert / Roadmap
| # | Anforderung | Status |
|---|-------------|--------|
| 9.1 | **Fader/Potentiometer**: 3× ADC-Kanäle auf Board vorhanden, HAL nicht implementiert | 🔲 TODO |
| 9.2 | **HOST_COMMAND-Payload**: Board sendet Command-ID, App-Seite führt aus (halbfertig) | 🔲 TODO |
| 9.3 | **FADE_IN / FADE_OUT** per GUI konfigurierbar (Firmware vorhanden, kein GUI-Eintrag) | 🔲 TODO |
---
## 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 024 oder Encoder 03)
Byte 2: r / Daten-Byte A
Byte 3: g / Daten-Byte B
Byte 4: b
Byte 57: 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 |
| 0x20 | PC→Board | Makro-Begin (Chunk-Anzahl = 43) |
| 0x21 | PC→Board | Makro-Data (Chunk-Index + 6B Nutzdaten) |
| 0x22 | PC→Board | Makro-Commit (in NVM schreiben) |
| 0x23 | PC→Board | Makro-Read (Board sendet Tabelle zurück) |
| 0x90 | Board→PC | Config-ACK |
| 0x91 | Board→PC | Config-NACK (CRC/Magic/Version ungültig) |
| 0x92 | Board→PC | Config-Begin (Dump-Start, Chunks-Anzahl) |
| 0x93 | Board→PC | Config-Data (Chunk-Index + 6B) |
| 0x94 | Board→PC | Config-End |
| 0x95 | Board→PC | Makro-ACK |
| 0x96 | Board→PC | Makro-Begin (Dump-Start) |
| 0x97 | Board→PC | Makro-Data (Chunk-Index + 6B) |
| 0x98 | Board→PC | Makro-End |
### NVM-Config-Layout (Version 2, 223 Bytes, packed)
```
Offset 0 4B Magic 0x56503202
Offset 4 1B Version 2
Offset 5 2B CRC16-CCITT (über Bytes 7222)
Offset 7 60B mx_actions[20] je 3B: type(1B) + data(2B)
Offset 67 36B enc_actions[4][3] je 3B
Offset 103 20B led_r[20]
Offset 123 20B led_g[20]
Offset 143 20B led_b[20]
Offset 163 20B led_anim[20] LEDAnim-Typ (uint8_t)
Offset 183 40B led_period_ms[20] Animationsperiode in ms (uint16_t, LE)
```
**NVM Row 0** (0x1FE00, 256 Byte): Config (223B genutzt, 33B Padding)
**NVM Row 1** (0x1FF00, 256 Byte): Makro-Tabelle (32 Slots × 4 Steps × 2B)
Via Linkerscript reserviert. Bei ungültigem Magic/Version/CRC werden Defaults geladen.
## 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 |