VersaMCU/README.md
2026-03-29 14:47:13 +02:00

144 lines
5.5 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.
## 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 |
| 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 7162)
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 |