# VersaMCU UF2-Bootloader USB-Bootloader für das VersaPad-v2-Makropad. Wird per Atmel-ICE/SWD einmalig auf den ATSAMD21G17D geflasht und belegt `0x0000..0x1FFF` (8 KiB). Danach lässt sich die App-Firmware ohne SWD über USB aktualisieren: Bootloader-Modus aktivieren, Board erscheint als USB-Laufwerk `VERSABOOT`, `.uf2`-Datei drauf kopieren. Der komplette Weg — Bootloader-Einstieg, `.uf2`-Erzeugung, Kopieren aufs Laufwerk, automatischer Rücksprung in die neu geschriebene App — ist auf echter Hardware verifiziert, siehe [Hardwaretest](#hardwaretest-2026-08-05). Diese Platine hat keinen dedizierten Reset-/Boot-Taster. Bootloader-Modus aktivieren heißt hier: unterste rechte Cherry-MX-Taste (key_id 24) beim Einstecken/Reset gedrückt halten. Siehe [Hardware-Bootloader-Einstieg](#hardware-bootloader-einstieg-ohne-reset-taster). ## Herkunft Abgeleitet und stark eingekürzt aus [microsoft/uf2-samdx1](https://github.com/microsoft/uf2-samdx1) (MIT-Lizenz, siehe [`LICENSE-uf2-samdx1`](LICENSE-uf2-samdx1)). Übernommen wurden nur die SAMD21-Bootloader-Sourcen (`src/`, `inc/`), die zugehörigen CMSIS-/ASF-Header für genau den `SAMD21G17A`-Chip (`lib/`) sowie das Linkerskript, angepasst auf den 128-KiB-Flash/16-KiB-RAM-Chip auf diesem Board (Original-Vorlagen zielen auf 256-KiB-Chips wie den SAMD21G18A). Board-Vorlage war `boards/gemma_m0` aus dem Original-Repo (crystalless, gleiche USB_VID wie unser Board). Nicht übernommen: SAMD51/SAME54-Support, der Bootloader-Selbstupdate-Pfad (`selfmain.c`, `sketch.cpp`, das `python2`-Tooling `uf2conv.py`/`gendata.py`) und das Node-/Makefile-basierte Build-System — stattdessen ein eigenständiges PlatformIO-Environment, damit dasselbe Tooling wie für die App-Firmware ausreicht. Für die App-Seite (nicht diesen Bootloader-Build) gibt es unter [`../uf2conv.py`](../uf2conv.py) eine eigene, minimale Python-3-Neuimplemen- tierung des `.bin`→`.uf2`-Konverters (kein Upstream-Code, passendes Blockformat zu `inc/uf2format.h`), eingebunden über [`../upload_uf2.py`](../upload_uf2.py) als `env:versapad_usb`-Upload-Hook. ## Build ```bash cd bootloader pio run -e versapad_bootloader ``` ## Flash (Atmel-ICE/SWD, einmalig, überschreibt ggf. vorhandene App-Firmware nicht, da nur `0x0000..0x1FFF` beschrieben wird) ```bash pio run -e versapad_bootloader --target upload ``` ## Hardware-Bootloader-Einstieg ohne Reset-Taster Die Platine hat keinen dedizierten Reset-/Boot-Taster (Custom-PCB-Design). Der klassische UF2-"Doppel-Tap-Reset" braucht aber gar keinen physischen Pin — `check_start_application()` in `src/main.c` prüft ein RAM-Flag (`DBL_TAP_PTR`/`DBL_TAP_MAGIC`), das bei jedem Reset gesetzt wird, unabhängig von der Reset-Quelle. Statt eines eigenen Tasters wird die unterste rechte Cherry-MX-Taste (`key_id 24` in der App-Firmware, siehe `../src/config/pins.h`) missbraucht: - `key_id 24` liegt auf COL4 (`PA08`) × ROW4 (`PA15`) - `boot_key_pressed()` in `src/main.c` treibt ROW4 kurz auf LOW und liest COL4 zurück — kein voller Matrixscan nötig, nur ein Drei-Pin-Check - der Check läuft ganz am Anfang von `check_start_application()`, noch vor der App-Adress-Validierung und vor der RCAUSE-/DBL-TAP-Logik: funktioniert also auch bei kaputter/gelöschter App-Firmware und bei normalem Power-On (Stecker ziehen/reinstecken), kein Software-Trigger in der App nötig - Pin-Konstanten stehen in `include/board_config.h` (`BOOT_KEY_ROW_PIN`/`BOOT_KEY_COL_PIN`) Bedienung: USB-Kabel ziehen, unterste rechte Taste gedrückt halten, Kabel wieder einstecken (Taste dabei weiter halten) → Board bootet direkt in `VERSABOOT`. Ohne gehaltene Taste startet die App normal. ## App-Firmware per USB flashen (nach dem einmaligen Bootloader-Flash) ```bash # Board zuerst in den Bootloader-Modus versetzen: USB ziehen, unterste # rechte Taste halten, wieder einstecken (siehe oben) pio run -e versapad_usb --target upload ``` Baut die App-Firmware (Repo-Root, nicht `bootloader/`), erzeugt `firmware.uf2` und kopiert es aufs `VERSABOOT`-Laufwerk. Kein Atmel-ICE mehr nötig. ## Achtung bei manuellem SWD-Flashen von Bootloader UND App `upload_openocd.py` (App, `env:versapad`) und der Bootloader-Upload (oben) sind sicher, weil sie jeweils nur ihren eigenen Bereich anfassen. **Beim manuellen Debuggen über OpenOCD-Kommandozeile aber Vorsicht:** `openocd -c "program datei.elf verify"` **ohne explizite Zieladresse** hat sich in dieser Kombination aus OpenOCD-Version/CMSIS-DAP-Adapter/Target-Skript als unzuverlässig erwiesen — statt die im ELF hinterlegten Sektionsadressen (`0x2000` für die App) zu nutzen, landeten die rohen Datei-Bytes teils direkt ab Flash-Adresse `0x0000` und haben damit den frisch geschriebenen Bootloader sofort wieder überschrieben (bestätigt am 2026-08-05: Byte 0 an Adresse `0x0000` war `0x7f`, der Beginn der ELF-Magic `\x7fELF` — die rohe Datei, kein Firmware-Code). Passierte zuverlässig, wenn Bootloader- und App-Flash im selben OpenOCD-Aufruf kombiniert wurden. **Regel für manuelles SWD-Flashen beider Bereiche:** immer die `.bin`-Datei verwenden (nie `.elf`) und die Zieladresse **immer explizit angeben** (`program firmware.bin 0x0 verify` für den Bootloader, `program firmware.bin 0x2000 verify` für die App), und Bootloader- und App-Schreibvorgang **in getrennten OpenOCD-Aufrufen**, nicht in einer gemeinsamen `-c`-Kommandokette. Im Zweifel danach mit `dump_image` in einer frischen, unabhängigen Sitzung verifizieren. ## Hardwaretest (2026-08-05) Erster vollständiger Hardwaretest auf einem echten VersaPad-v2-Board über Atmel-ICE/SWD. Ergebnisse: - Bootloader-Build/-Flash/-Verify laufen sauber (89,9 % Flash, 7364/8192 Byte). - USB-Enumeration und `VERSABOOT`-Massenspeicher-Modus funktionieren nach dem PID-Fix (siehe unten) korrekt, inklusive korrektem `INFO_UF2.TXT`. - Der Tastencheck (oben) funktioniert wie vorgesehen: gehaltene Taste beim Boot → `VERSABOOT`, sonst normaler App-Start. - **Kritischer Bug gefunden und behoben:** Der Sprung vom Bootloader in die App (`__set_MSP` → `SCB->VTOR` → `bx`) führte bei jedem echten, eigenständigen Boot (auch bei komplett getrenntem Debugger) zu einem Hard-Fault/Lockup der CPU. Identische Register-/VTOR-Werte, vom Debugger bei angehaltener CPU direkt injiziert, liefen dagegen einwandfrei — das grenzte den Fehler auf die *Ausführung* der Sprungsequenz selbst ein, nicht auf falsche Werte. Fix: `__DSB(); __ISB();` zwischen dem `SCB->VTOR`-Schreib- zugriff und dem `bx` in `check_start_application()` (`src/main.c`) — von ARM für genau dieses Bootloader-Pattern vorgeschrieben, hat im vendorten Code gefehlt. Nach dem Fix bootet die App-Firmware zuverlässig, mit und ohne angeschlossenen Debugger. - **Zweiter Bug beim manuellen Debuggen gefunden:** Beim anschließenden manuellen SWD-Debugging (Suche nach der Ursache des Tastencheck-Problems, siehe oben) hat `openocd -c "program app.elf verify"` ohne explizite Zieladresse wiederholt den Bootloader mit rohen ELF-Datei-Bytes überschrieben, sobald Bootloader- und App-Flash im selben OpenOCD-Aufruf kombiniert wurden — siehe "Achtung bei manuellem SWD-Flashen" oben. Nach einem vollständigen Chip-Erase und getrennten `.bin`-Flashes mit expliziten Adressen liefen beide Bereiche wieder zuverlässig, Tastencheck und App-Start bestätigt funktionsfähig. - **Kompletter USB-Flashweg getestet:** `pio run -e versapad_usb --target upload` (App-Firmware, Repo-Root) baut `firmware.bin`, wandelt es über [`../uf2conv.py`](../uf2conv.py) in `firmware.uf2` und kopiert es über [`../upload_uf2.py`](../upload_uf2.py) automatisch aufs erkannte `VERSABOOT`-Laufwerk. Der Bootloader erkennt den Schreibzugriff und springt danach selbständig in die neue App — kein manuelles Auswerfen oder Reset nötig. Voraussetzung: Board zuvor per gehaltener Taste (siehe oben) in den Bootloader-Modus versetzt. ## Bekannte Einschränkungen - Flash-Auslastung ~90 % (7364 von 8192 Byte). Wenig Puffer für Änderungen. - Kein Status-LED-Feedback im Bootloader-Modus, da die Platine nur eine WS2812-Kette (kein einfaches GPIO-LED oder DotStar) hat und der Original-Code dafür nicht ausgelegt ist. - USB_PID war ursprünglich `0x0011` (Adafruit Gemma M0s eigene Bootloader-PID, unverändert aus der Vorlage übernommen) — kollidierte auf Rechnern mit installiertem Adafruit-Treiber, wurde als `Adafruit Circuit Playground`-COM-Port statt als Massenspeicher gebunden. Verifiziert am 2026-08-05, seither `0x0043`. Bleibt ein Wert ohne echte Registrierung (kein offiziell zugeteilter PID unter Adafruits VID); ein sauber eigener VID (z. B. über pid.codes) wäre die langfristig korrekte Lösung, ist aber nicht Teil dieses Branches. - [`../upload_uf2.py`](../upload_uf2.py) sucht das `VERSABOOT`-Laufwerk aktuell nur über die Windows-API (`GetVolumeInformationW`) — passend zur bisherigen Dev-Umgebung dieses Projekts, aber nicht plattformübergreifend. Für macOS/Linux müsste die Laufwerkssuche noch ergänzt werden.