VersaMCU/README.md

480 lines
23 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.
## 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) |
### Button-Matrix
- **5×5-Matrix** (COL_04 × ROW_04) = 25 logische Keys
- **20 Cherry MX Switches** (COL_14 × ROW_04), je eine WS2812-LED
- **4 Encoder-SW-Buttons** (COL_0 × ROW_03), 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 (PA16PA23), 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
```bash
pio run --target upload
```
Der Upload läuft via OpenOCD über SWD. Kein Bootloader nötig der Chip wird direkt programmiert.
---
## 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) | ✅ |
### 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 14) | ✅ |
| 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
```
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
### 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 (~1020× 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 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 |
| 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 7222)
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 € | ~810 € |
**Warum SAME54?**
- Direkter Upgrade-Pfad: Arduino-Framework, PlatformIO, gleiche HAL-Konzepte
- Ethernet-MAC integriert → nur externer PHY nötig (z.B. **KSZ8081** oder **LAN8720A**, ~12 €)
- 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. 216 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 | 13 € |
| Ethernet PHY | LAN8720A oder KSZ8081 | 12 € |
| 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.