VersaMCU
Firmware für das VersaPad v2 Macro-Pad. Läuft auf einem ATSAMD21G17D (Cortex-M0+), entwickelt mit PlatformIO + Arduino-Framework.
Voraussetzungen
- PlatformIO (CLI oder VS Code Extension)
- Atmel-ICE Debugger/Programmer (SWD-Verbindung zur PCB)
- OpenOCD – wird von PlatformIO automatisch installiert
Flashen
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 |
Description
Languages
C++
76.3%
C
23%
Python
0.7%