2026-03-29 14:47:13 +02:00
2026-03-29 14:47:13 +02:00
2026-03-29 14:47:13 +02:00
2026-03-29 14:47:13 +02:00
2026-03-29 14:47:13 +02:00
2026-03-29 14:47:13 +02:00
2026-03-29 14:47:13 +02:00
2026-03-29 14:47:13 +02:00

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 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
Description
No description provided
Readme 214 KiB
Languages
C++ 76.3%
C 23%
Python 0.7%