144 lines
5.5 KiB
Markdown
144 lines
5.5 KiB
Markdown
# 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 |
|