Added config, added factory reset functionality
This commit is contained in:
@@ -1,481 +1,167 @@
|
||||
# VersaMCU
|
||||
|
||||
Firmware für das VersaPad v2 Macro-Pad.
|
||||
Läuft auf einem **ATSAMD21G17D** (Cortex-M0+), entwickelt mit PlatformIO + Arduino-Framework.
|
||||
Firmware fuer das VersaPad v2 Macro-Pad.
|
||||
Laeuft auf einem ATSAMD21G17D mit PlatformIO und Arduino-Framework.
|
||||
|
||||
## Hardware
|
||||
|
||||
| Eigenschaft | Detail |
|
||||
|---|---|
|
||||
| MCU | ATSAMD21**G17D** (Cortex-M0+, 128 KB Flash, 16 KB RAM) |
|
||||
| Taktfrequenz | 48 MHz (DFLL, intern – kein externer 32 kHz-Quarz) |
|
||||
| Framework | Arduino SAMD Core 1.8.14, PlatformIO |
|
||||
| Programmer | Atmel-ICE via SWD (kein Bootloader) |
|
||||
| MCU | ATSAMD21G17D, Cortex-M0+, 48 MHz |
|
||||
| Flash / RAM | 128 KB / 16 KB |
|
||||
| USB | Composite: HID Keyboard + Consumer + CDC Serial |
|
||||
| Matrix | 5x5 logisch, davon 20 MX-Buttons + 4 Encoder-SW + 1 unbelegt |
|
||||
| Encoder | 4x Rotary Encoder mit Quadratur via EIC-Interrupt |
|
||||
| LEDs | 20x WS2812B an `PB22` |
|
||||
| Programmer | Atmel-ICE via SWD, kein Bootloader |
|
||||
|
||||
### Button-Matrix
|
||||
|
||||
- **5×5-Matrix** (COL_0–4 × ROW_0–4) = 25 logische Keys
|
||||
- **20 Cherry MX Switches** (COL_1–4 × ROW_0–4), je eine WS2812-LED
|
||||
- **4 Encoder-SW-Buttons** (COL_0 × ROW_0–3), keine LEDs
|
||||
- COL_0 × ROW_4 = nicht belegt
|
||||
- **Dioden**: D4148 je Taste, Anode an Switch, Kathode an ROW → Ghost-Key-freies Scannen
|
||||
- **Scan-Richtung**: ROW wird OUTPUT LOW getrieben, COL-Leitungen haben 10 kΩ-Pullup nach 3V3 (werden gelesen)
|
||||
|
||||
### Rotary Encoder
|
||||
|
||||
- 4× Encoder (ENC0 = USB-nah, ENC3 = USB-fern)
|
||||
- Alle A/B-Leitungen auf EIC (PA16–PA23), Quadratur-Dekodierung via ISR + 4-State-Lookup
|
||||
- Jeder Encoder hat drei Aktionen: **CW**, **CCW**, **SW** (alle aus NVM konfigurierbar)
|
||||
|
||||
### LEDs (WS2812)
|
||||
|
||||
- 20× WS2812B, serpentinen-verdrahtet (Reihe 0: L→R, Reihe 1: R→L, …)
|
||||
- Datenleitung: **PB22 (D18)**, bit-bang via Adafruit NeoPixel Library
|
||||
- **LED_INDEX-Formel**: `row * 4 + ((row & 1) ? (4 - col) : (col - 1))`
|
||||
- **Pegelproblematik**: WS2812 sind 5V-Devices (HIGH-Schwelle ~3,5 V), SAMD21 liefert 3,3 V ohne Level-Shifter. LED 0 empfängt einen marginal gültigen Pegel; ab LED 1 regeneriert jede LED intern auf 5 V → alle weiteren problemlos. Fix nächste PCB-Revision: Level-Shifter oder Diode in der VDD-Leitung.
|
||||
|
||||
### Fader (ADC)
|
||||
|
||||
- 3× Linearpotentiometer: PA02 (A0), PA03 (A1, auch VREFA), PB08 (A2)
|
||||
- Noch nicht implementiert (siehe Roadmap)
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
## Build und Flash
|
||||
|
||||
```bash
|
||||
pio run
|
||||
pio run --target upload
|
||||
```
|
||||
|
||||
Der Upload läuft via OpenOCD über SWD. Kein Bootloader nötig – der Chip wird direkt programmiert.
|
||||
Der Upload laeuft per OpenOCD ueber SWD.
|
||||
|
||||
---
|
||||
## Laufzeitmodell
|
||||
|
||||
## 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) | ✅ |
|
||||
| 4.7 | **Hold-Modell** für HID_KEY / HID_CONSUMER: KEY_DOWN → Key-Down senden, KEY_UP → Key-Up senden (OS-Repeat ab ~500ms) | ✅ |
|
||||
| 4.8 | **Tap-Modell** für Encoder CW/CCW: atomare down+delay(10)+up Sequenz (diskrete Events, kein Hold möglich) | ✅ |
|
||||
|
||||
### 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 1–4) | ✅ |
|
||||
| 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
|
||||
`main.cpp` startet genau einen `CMainController`.
|
||||
Die Hauptschleife in `work()` ist:
|
||||
|
||||
```text
|
||||
matrix_scan()
|
||||
poll_vendor()
|
||||
processEvents()
|
||||
check_factory_reset()
|
||||
updateLEDs()
|
||||
```
|
||||
|
||||
Dabei gilt:
|
||||
|
||||
- Matrix und Encoder erzeugen `SEvent`s.
|
||||
- `processEvents()` fuehrt daraus HID, Makros, Host-Commands oder Profilwechsel aus.
|
||||
- `poll_vendor()` verarbeitet das 8-Byte-CDC-Protokoll mit Config- und Makro-Transfers.
|
||||
- `updateLEDs()` rendert nur dann zu den WS2812, wenn sich etwas geaendert hat.
|
||||
|
||||
## Action-System
|
||||
|
||||
Unterstuetzte `ActionType`s:
|
||||
|
||||
| Typ | Verhalten |
|
||||
|---|---|
|
||||
| `NONE` | keine Aktion |
|
||||
| `HID_KEY` | Keyboard-Hold ueber USB HID |
|
||||
| `HID_CONSUMER` | Media/Consumer-Hold ueber USB HID |
|
||||
| `HOST_COMMAND` | Event an die GUI per CDC Serial |
|
||||
| `MACRO` | Firmware spielt Makro-Slot komplett ab |
|
||||
| `PROFILE_SWITCH` | aktives Profil in NVM wechseln |
|
||||
|
||||
Wichtige Semantik:
|
||||
|
||||
- normale Keys und Consumer folgen dem Hold-Modell
|
||||
- Encoder `CW` / `CCW` sind immer Tap-Events
|
||||
- Makros laufen komplett in der Firmware, ohne laufende App
|
||||
|
||||
## LED-System
|
||||
|
||||
Jeder MX-Button hat:
|
||||
|
||||
- eine Base-Farbe
|
||||
- optional eine temporaere Override-Farbe
|
||||
- eine Animation
|
||||
|
||||
Aktuelle Animationsmodi:
|
||||
|
||||
- `STATIC`
|
||||
- `BLINK`
|
||||
- `PULSE`
|
||||
- `FADE_IN`
|
||||
- `FADE_OUT`
|
||||
- `COLOR_CYCLE`
|
||||
- `COLOR_FADE`
|
||||
|
||||
Die GUI nutzt derzeit vor allem `STATIC`, `BLINK`, `PULSE` und `COLOR_CYCLE`.
|
||||
|
||||
## Aktuelles NVM-Layout
|
||||
|
||||
### DeviceConfig
|
||||
|
||||
- Version: `3`
|
||||
- Magic: `0x56503203`
|
||||
- Groesse: `740` Byte
|
||||
- CRC16-CCITT ueber Bytes `7..739`
|
||||
- 3 Profile
|
||||
- globale Helligkeit
|
||||
- per-LED-Helligkeit
|
||||
|
||||
### MacroTable
|
||||
|
||||
- 32 Slots
|
||||
- 8 Steps pro Slot
|
||||
- 512 Byte gesamt
|
||||
|
||||
### Flash-Bereich
|
||||
|
||||
| Bereich | Adresse | Groesse |
|
||||
|---|---|---|
|
||||
| Makros | `0x1FB00-0x1FCFF` | 512 B |
|
||||
| Config | `0x1FD00-0x1FFFF` | 768 B, davon 740 B genutzt |
|
||||
|
||||
Config und Makros liegen in getrennten reservierten NVM-Bereichen.
|
||||
|
||||
## Werksreset
|
||||
|
||||
Die Firmware hat einen eingebauten Recovery-Pfad:
|
||||
|
||||
- unteren linken und unteren rechten MX-Button gleichzeitig 5 Sekunden halten
|
||||
- waehrend des Holds leuchten diese beiden Tasten rot
|
||||
- ihre normalen HID-Aktionen werden waehrenddessen unterdrueckt
|
||||
- bei Erfolg blinken alle LEDs kurz rot
|
||||
- danach werden Config und Makros auf Werkseinstellungen zurueckgesetzt und neu geladen
|
||||
|
||||
Reset-Inhalt:
|
||||
|
||||
- alle Aktionen `NONE`
|
||||
- alle Makro-Slots leer
|
||||
- Base-LEDs auf Defaultwerte
|
||||
- sichtbarer Idle-Zustand wieder Regenbogen
|
||||
|
||||
Wichtig:
|
||||
|
||||
- ein SWD-Reflash loescht diese NVM-Daten nicht automatisch
|
||||
- der Werksreset ist der vorgesehene Weg, um eine kaputte Konfiguration zu bereinigen
|
||||
|
||||
## Projektstruktur
|
||||
|
||||
```text
|
||||
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
|
||||
|-- platformio.ini
|
||||
|-- boards/
|
||||
|-- variants/versapad/
|
||||
`-- src/
|
||||
|-- main.cpp
|
||||
|-- CMainController.h/.cpp
|
||||
|-- CButton.h/.cpp
|
||||
|-- CEventQueue.h/.cpp
|
||||
|-- SEvent.h
|
||||
|-- config/
|
||||
| |-- action.h
|
||||
| |-- macro_config.h/.cpp
|
||||
| `-- nvm_config.h/.cpp
|
||||
`-- hal/
|
||||
|-- encoder.h/.cpp
|
||||
|-- matrix.h/.cpp
|
||||
|-- usb_hid.h/.cpp
|
||||
|-- usb_serial.h/.cpp
|
||||
`-- ws2812.h/.cpp
|
||||
```
|
||||
|
||||
## Architektur
|
||||
## Weiterfuehrende Doku
|
||||
|
||||
### Schichten
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ main.cpp │
|
||||
│ CMainController (setup / work) │
|
||||
├────────────────────────────────────────────────┤
|
||||
│ Modell │
|
||||
│ CButton (LED 2-Layer, Action, dirty-Flag) │
|
||||
│ CEventQueue (Ring-Buffer FIFO, 16 Slots) │
|
||||
├────────────────────────────────────────────────┤
|
||||
│ HAL │
|
||||
│ hal/matrix – 5×5-Scan, 10 ms Debounce │
|
||||
│ hal/encoder – Quadratur via EIC-ISR │
|
||||
│ hal/ws2812 – Adafruit NeoPixel bit-bang │
|
||||
│ hal/usb_hid – HID Keyboard + Consumer │
|
||||
│ hal/usb_serial– CDC Serial bidirektional │
|
||||
├────────────────────────────────────────────────┤
|
||||
│ Config │
|
||||
│ config/pins.h – Pin-Mapping │
|
||||
│ config/action.h – ActionType + SAction │
|
||||
│ config/nvm_config – Flash R/W, CRC16 │
|
||||
└────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Datenfluss
|
||||
|
||||
```
|
||||
HAL (matrix_scan, Encoder-ISR)
|
||||
└─► matrix_cb / encoder_cb
|
||||
└─► CEventQueue.push()
|
||||
└─► CMainController.processEvents()
|
||||
├─► CButton.on_press() / on_release()
|
||||
├─► execute_action() → usb_hid_send_key / send_consumer
|
||||
└─► usb_serial_send() (nur bei HOST_COMMAND)
|
||||
|
||||
SerialUSB (PC → Board, 8-Byte-Pakete)
|
||||
└─► CMainController.poll_vendor()
|
||||
└─► CButton.set_override() / set_base() / clear_override()
|
||||
|
||||
CMainController.updateLEDs()
|
||||
└─► CButton.render_led() → ws2812_set()
|
||||
└─► ws2812_show() (nur wenn dirty)
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
Animationen modulieren die Helligkeit oder Farbe der aktiven Schicht (base oder override). Alle Berechnungen in **Integer-Arithmetik** (Cortex-M0+ hat keine FPU).
|
||||
|
||||
| Animation | Typ | Verhalten | `period_ms`-Semantik |
|
||||
|---|---|---|---|
|
||||
| `STATIC` | – | Feste Helligkeit (Standardzustand) | – |
|
||||
| `BLINK` | Helligkeit | Binäres An/Aus, endlos | Halbperiode (An = Aus) |
|
||||
| `PULSE` | Helligkeit | Lineares Dreieck 0→255→0, endlos | Vollperiode |
|
||||
| `FADE_IN` | Helligkeit | Einmalig: schwarz → voll, dann STATIC | Dauer |
|
||||
| `FADE_OUT` | Helligkeit | Einmalig: voll → schwarz, dann STATIC + base=schwarz | Dauer |
|
||||
| `COLOR_CYCLE` | Farbe | Hue-Sweep Regenbogen, endlos, ignoriert base/override | Eine volle Runde |
|
||||
| `COLOR_FADE` | Farbe | Einmalig: Crossfade akt. Farbe → Zielfarbe, dann STATIC + base=Ziel | Dauer |
|
||||
|
||||
**API:**
|
||||
|
||||
```cpp
|
||||
set_anim(LEDAnim, period_ms, phase_offset_ms = 0)
|
||||
// Für alle Typen außer COLOR_FADE.
|
||||
// phase_offset_ms verschiebt den Startpunkt in die Vergangenheit →
|
||||
// mehrere LEDs versetzt starten (z. B. Regenbogen-Welle).
|
||||
|
||||
set_color_fade(RGB to, period_ms)
|
||||
// Startet COLOR_FADE von aktueller Farbe zu `to`.
|
||||
|
||||
clear_anim()
|
||||
// Sofort zurück zu STATIC (volle Helligkeit).
|
||||
|
||||
render_led()
|
||||
// Gibt true zurück solange dirty oder Animation läuft → ws2812_show() nötig.
|
||||
```
|
||||
|
||||
**Idle-Zustand:** Alle 20 MX-LEDs laufen mit `COLOR_CYCLE` (4 s/Runde, 40 % Helligkeit). Der Phasenversatz ist gleichmäßig über alle 20 LEDs verteilt, sodass immer ein vollständiger Regenbogen auf dem Pad liegt.
|
||||
|
||||
**Warum Bit-Bang statt DMA?**
|
||||
|
||||
WS2812-DMA auf dem SAMD21 würde einen SERCOM im SPI-Modus bei exakt 2,4 MHz benötigen, wobei jedes WS2812-Bit als 3 SPI-Bits kodiert wird (`110` = 1, `100` = 0). Das erfordert einen zusätzlichen Puffer von 20 LEDs × 24 Bit × 3 = 1440 Byte — mehr als 8 % des gesamten RAM — plus DMAC-Konfiguration und Transfer-Ende-Erkennung.
|
||||
|
||||
`ws2812_show()` blockiert ~600 µs mit gesperrten Interrupts, wird aber nur bei gesetztem dirty-Flag aufgerufen. Bei 20 ms Loop-Rate entspricht das 3 % der Loop-Zeit. Encoder-Impulse, die in dieses Fenster fallen, werden maximal um eine Loop-Iteration verzögert; bei typischen Drehgeschwindigkeiten (< 20 Rastschritte/s, Impulsabstand > 50 ms) ist das Risiko eines verlorenen Impulses praktisch null.
|
||||
|
||||
Ergebnis: Bit-Bang via Adafruit NeoPixel reicht für 20 LEDs vollständig aus, belegt keinen SERCOM und keinen zusätzlichen RAM.
|
||||
|
||||
**Warum keine Adafruit-Animationsfunktionen?**
|
||||
|
||||
Die Adafruit-NeoPixel-Library stellt ausschließlich den LED-Treiber bereit (`setPixelColor`, `show`, `fill`, `clear`). Animations-Logik (Blinken, Pulsieren, Farbverläufe) ist nicht enthalten und muss in jedem Fall selbst implementiert werden. Darüber hinaus:
|
||||
|
||||
- `Adafruit_NeoPixel::ColorHSV()` verwendet intern float-Operationen für die HSV→RGB-Konvertierung. Der Cortex-M0+ hat keine FPU; float wird per Software emuliert (~10–20× langsamer). `hue_to_rgb()` in `CButton.cpp` erreicht dasselbe Ergebnis mit reiner Integer-Arithmetik (6 lineare Segmente à 43 Hue-Einheiten).
|
||||
- Das 2-Schicht-Modell (base + override) und die dirty-Flag-gesteuerte Render-Pipeline sind projektspezifische Logik ohne Entsprechung in der Library.
|
||||
|
||||
**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 |
|
||||
| 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 7–222)
|
||||
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 |
|
||||
| `SERCOM5 CTRLB.RXEN` Sync-Bug | `while(SYNCBUSY.bit.CTRLB)` hängt vor ENABLE → nur relevant bei manueller SERCOM5-Konfiguration; nicht im Normalbetrieb |
|
||||
| `PluggableUSBModule` nicht nutzbar | `USB_SendControl` / `USB_RecvControl` nicht verlinkt in dieser Core-Version → CDC Serial statt Vendor HID für PC-Kommunikation verwenden |
|
||||
| HID-Descriptor vor USB-Enumeration registrieren | Registrierung via globalem Konstruktor (läuft vor `main()`), nicht in `setup()` |
|
||||
|
||||
---
|
||||
|
||||
## Next-Generation Hardware – MCU-Empfehlung
|
||||
|
||||
### Warum der SAMD21G17D an seine Grenzen stößt
|
||||
|
||||
| Einschränkung | Auswirkung auf geplante Features |
|
||||
|---|---|
|
||||
| **128 KB Flash** | Mehrere Profile + lange Makros + OLED-Fonts füllen den Speicher vollständig |
|
||||
| **16 KB RAM** | OLED-Framebuffer (128×64 px = 1 KB) + Profil-Puffer + Makro-Tabellen + Stack = kaum Luft |
|
||||
| **Kein FPU** | LED-Animationen erfordern Integer-Arithmetik-Workarounds; aufwändigere Effekte unwirtschaftlich |
|
||||
| **Kein Ethernet-MAC** | Ethernet nur via langsamen SPI-Chip möglich |
|
||||
| **Kein USB High-Speed** | CDC bleibt auf 12 Mbit/s (Full Speed); für schnelle Konfigurationsübertragungen ausreichend, aber kein Spielraum |
|
||||
| **WS2812 bit-bang blockiert 600 µs** | Mit mehr LEDs oder höherer Auflösung kritisch; kein DMA ohne SERCOM-Umbau |
|
||||
|
||||
---
|
||||
|
||||
### Empfehlung: Microchip SAME54P20A
|
||||
|
||||
**Primäre Empfehlung** – selber Hersteller, gleicher Toolchain-Stack, deutlich mehr Reserven.
|
||||
|
||||
| Merkmal | SAMD21G17D (aktuell) | SAME54P20A (empfohlen) |
|
||||
|---|---|---|
|
||||
| Kern | Cortex-M0+, 48 MHz | Cortex-M4F, 120 MHz |
|
||||
| **FPU** | ✗ | ✓ (single-precision) |
|
||||
| **Flash** | 128 KB | **1 MB** |
|
||||
| **RAM** | 16 KB | **256 KB** |
|
||||
| **USB** | Full Speed (12 Mbit/s) | Full Speed + optionaler HS-PHY |
|
||||
| **Ethernet MAC** | ✗ | ✓ (IEEE 802.3, braucht ext. PHY) |
|
||||
| **DMA** | 12 Kanäle | 32 Kanäle |
|
||||
| SERCOM | 6 | 8 |
|
||||
| NVM (intern) | 128 KB | 1 MB (kein ext. Flash nötig) |
|
||||
| Preis (LCSC ca.) | ~2 € | ~8–10 € |
|
||||
|
||||
**Warum SAME54?**
|
||||
- Direkter Upgrade-Pfad: Arduino-Framework, PlatformIO, gleiche HAL-Konzepte
|
||||
- Ethernet-MAC integriert → nur externer PHY nötig (z.B. **KSZ8081** oder **LAN8720A**, ~1–2 €)
|
||||
- 1 MB Flash reicht für viele Profile, lange Makros und OLED-Font-Tabellen ohne externen Flash
|
||||
- 256 KB RAM: OLED-Framebuffer, Profil-Puffer und komplexe Makro-Engines kein Problem
|
||||
- FPU: sauberere LED-Animationen, kein Integer-Workaround mehr nötig
|
||||
- DMA: WS2812 via SERCOM-SPI + DMA möglich → kein Bit-Bang, keine Interrupt-Sperre
|
||||
|
||||
---
|
||||
|
||||
### Alternative: Raspberry Pi RP2350
|
||||
|
||||
Falls Ethernet nicht zwingend auf dem MCU selbst integriert sein muss (z.B. W5500 via SPI):
|
||||
|
||||
| Merkmal | RP2350 |
|
||||
|---|---|
|
||||
| Kern | Dual Cortex-M33 oder RISC-V, 150 MHz |
|
||||
| FPU | ✓ |
|
||||
| RAM | **520 KB** SRAM |
|
||||
| Flash | Kein interner; ext. QSPI (typ. 2–16 MB) |
|
||||
| USB | Full Speed (Device + Host) |
|
||||
| Ethernet MAC | ✗ (W5500 via SPI, ~3 €) |
|
||||
| PIO | 3 × 4 PIO-Blöcke → WS2812 hardwareseitig ohne CPU |
|
||||
| Preis | ~1,50 € |
|
||||
|
||||
**Vorteil:** PIO-Blöcke übernehmen WS2812-Timing hardwareseitig (kein bit-bang, kein DMA-Setup). Sehr viel RAM für komplexe Logik. Günstiger als SAME54.
|
||||
|
||||
**Nachteil:** Kein integrierter Ethernet-MAC. W5500 übernimmt TCP/IP-Stack per SPI (ausreichend für einfaches HTTP/Telnet-Protokoll), ist aber kein vollwertiger Network-Stack.
|
||||
|
||||
---
|
||||
|
||||
### System-Architektur für PoE-Betrieb
|
||||
|
||||
PoE erfordert unabhängig vom MCU zusätzliche Hardware:
|
||||
|
||||
```
|
||||
RJ45-Buchse (mit integrierten Magnetics)
|
||||
└─► PoE-PD-Controller (z.B. TPS2372-4, AG9800)
|
||||
├─► DC/DC-Wandler → 3.3V / 5V Versorgung des Boards
|
||||
└─► Ethernet-Signal → MCU-Ethernet-MAC → ext. PHY (LAN8720A / KSZ8081)
|
||||
```
|
||||
|
||||
Der PoE-PD-Controller ist zwingend: Er verhandelt mit dem PoE-Switch (IEEE 802.3af/at), isoliert galvanisch und liefert geregelte Spannung. Typische PoE-Leistungsklasse 0 (15,4 W) reicht für MCU + LEDs + Display mehrfach aus.
|
||||
|
||||
**Empfohlene ICs:**
|
||||
|
||||
| Funktion | IC | Preis ca. |
|
||||
|---|---|---|
|
||||
| PoE PD Controller | AG9800 oder TPS2372-4 | 1–3 € |
|
||||
| Ethernet PHY | LAN8720A oder KSZ8081 | 1–2 € |
|
||||
| QSPI-Flash (falls RP2350) | W25Q128 (16 MB) | ~0,80 € |
|
||||
| OLED Controller | SSD1306 (128×64, I2C/SPI) | im Modul enthalten |
|
||||
|
||||
---
|
||||
|
||||
### Empfehlung nach Szenario
|
||||
|
||||
| Szenario | Empfehlung |
|
||||
|---|---|
|
||||
| Voller Feature-Umfang (Ethernet-MAC, PoE, Profile, OLED) | **SAME54P20A** + LAN8720A + PoE-PD |
|
||||
| Maximale RAM/Flash-Reserve, WS2812 ohne CPU-Last | **RP2350** + W5500 + PoE-PD |
|
||||
| Minimaler Footprint, kein Ethernet | **RP2040** (günstiger als RP2350, 264 KB RAM reicht für OLED + Profile) |
|
||||
|
||||
Für VersaPad v3 mit allen genannten Features ist der **SAME54P20A** die solideste Wahl: gleicher Hersteller, etablierter Toolchain, integrierter Ethernet-MAC, und der Firmware-Code von v2 (HAL-Struktur, Event-Queue, CButton-Modell) ist weitgehend übertragbar.
|
||||
- [doc/INDEX.md](doc/INDEX.md)
|
||||
- [doc/00_architecture.md](doc/00_architecture.md)
|
||||
- [doc/03_action_engine.md](doc/03_action_engine.md)
|
||||
- [doc/04_macro_system.md](doc/04_macro_system.md)
|
||||
- [doc/06_nvm_config.md](doc/06_nvm_config.md)
|
||||
- [doc/07_serial_protocol.md](doc/07_serial_protocol.md)
|
||||
|
||||
Reference in New Issue
Block a user