# USB-Bootloader (UF2) Zweiter Firmware-Update-Weg neben SWD: ein UF2-Bootloader unter `bootloader/` erlaubt, die App-Firmware über USB zu aktualisieren, ohne Atmel-ICE. Der Bootloader selbst wird einmalig per SWD geflasht; danach reicht für App-Updates ein PlatformIO-Befehl. ## Speicherlayout | Bereich | Adresse | Größe | Environment | |---|---|---|---| | Bootloader | `0x00000..0x01FFF` | 8 KiB | `versapad_bootloader` (in `bootloader/`) | | App-Firmware | `0x02000..0x1FAFF` | 118,75 KiB | `versapad_usb` | | NVM (Makros + Config) | `0x1FB00..0x1FFFF` | 1,25 KiB | — | Gleiches NVM-Layout wie beim SWD-Ziel `versapad_nobl` (siehe [06_nvm_config.md](06_nvm_config.md)); nur die Firmware-Obergrenze sinkt um die 8 KiB Bootloader. Linkerskript: `variants/versapad/linker_scripts/gcc/flash_with_bootloader.ld`, Boarddatei: `boards/versapad.json`. ## Bootloader-Einstieg ohne Reset-Taster Die Platine hat keinen dedizierten Reset-/Boot-Taster. Statt eines physischen Pins wird die unterste rechte Cherry-MX-Taste (`key_id 24`, COL4 × ROW4, siehe [01_matrix.md](01_matrix.md)) beim Boot geprüft: `check_start_application()` in `bootloader/src/main.c` treibt ROW4 kurz auf LOW und liest COL4 zurück — noch vor der App-Adress-Validierung, funktioniert also auch bei kaputter oder fehlender App-Firmware. Bedienung: USB-Kabel ziehen, Taste gedrückt halten, wieder einstecken (Taste weiter halten) → Board bootet in den Massenspeicher-Modus (`VERSABOOT`). Ohne gehaltene Taste startet die App normal. ## Bauen und Flashen Bootloader einmalig per SWD (überschreibt nur `0x0000..0x1FFF`): ```bash cd bootloader pio run -e versapad_bootloader --target upload ``` Danach App-Firmware per USB (Board vorher wie oben in den Bootloader-Modus versetzen): ```bash pio run -e versapad_usb --target upload ``` Baut die App, wandelt `firmware.bin` über `uf2conv.py` (Repo-Root, eigene minimale Implementierung, kein Upstream-Code) in `firmware.uf2` und kopiert es über `upload_uf2.py` aufs erkannte `VERSABOOT`-Laufwerk. Der Bootloader erkennt den Schreibzugriff und springt selbständig in die neue App. `upload_uf2.py` sucht das Laufwerk aktuell nur über die Windows-API (`GetVolumeInformationW`) — keine macOS/Linux-Unterstützung. ## Schutz gegen versehentliches Überschreiben (env:versapad) `env:versapad` (SWD, `boards/versapad_nobl.json`) schreibt die App-Firmware ab `0x0000` und überschreibt damit einen installierten Bootloader kommentarlos — genau das ist am 2026-08-05 während der Entwicklung passiert (zweimal). `upload_openocd.py` prüft das seither vor jedem `versapad`-Upload: - vergleicht per `verify_image` gegen die lokal gebaute `bootloader/.pio/build/versapad_bootloader/firmware.bin` - Bootloader erkannt → Upload wird verweigert, mit Hinweis auf `versapad_usb` oder den expliziten Override - lässt sich die Prüfung nicht eindeutig durchführen (z. B. `bootloader/` noch nicht gebaut, oder die SWD-Verbindung antwortet nicht) → wird sicherheitshalber ebenfalls verweigert, nicht durchgelassen - gilt nur für `env:versapad` — `env:versapad_bootloader` nutzt dasselbe Skript (`extra_scripts = ../upload_openocd.py`) und schreibt bewusst immer auf `0x0000`, ungeprüft Bewusstes Überschreiben (zurück zu reinem SWD-Betrieb ohne Bootloader): ```bash pio run -e versapad -t erase-bootloader-and-flash ``` ## Herkunft und Details Der Bootloader ist abgeleitet und stark eingekürzt aus [microsoft/uf2-samdx1](https://github.com/microsoft/uf2-samdx1) (MIT), auf den `SAMD21G17D`-Chip dieses Boards zugeschnitten. Herkunft, Lizenz und vollständige Implementierungsdetails stehen in `bootloader/README.md`. ## Hardwaretest (2026-08-05) und gefundene Bugs Kompletter Weg auf echter Hardware verifiziert: Bootloader-Flash, USB-Enumeration, Tastencheck-Einstieg, App-Firmware per `.uf2` schreiben, automatischer Rücksprung in die neue App. Details, Fehlerbilder und Fixes stehen in `bootloader/README.md` unter "Hardwaretest"; kurz zusammengefasst: - **Fehlende Synchronisationsbarriere beim Sprung Bootloader→App.** Der Sprung (`__set_MSP` → `SCB->VTOR` → `bx`) faultete bei jedem eigenständigen Boot. Fix: `__DSB(); __ISB();` vor dem `bx` in `check_start_application()` — von ARM für dieses Pattern vorgeschrieben, fehlte im vendorten Code. - **USB-PID-Kollision.** Die ursprünglich übernommene PID `0x0011` ist Adafruits eigene Gemma-M0-Bootloader-PID; Windows-Rechner mit installiertem Adafruit-Treiber banden das Board fälschlich als `Adafruit Circuit Playground`-COM-Port statt als Massenspeicher. Fix: eigene PID `0x0043`. - **OpenOCD-Falle beim manuellen SWD-Debuggen:** `program datei.elf verify` ohne explizite Zieladresse hat wiederholt den Bootloader mit rohen ELF-Datei-Bytes überschrieben, sobald Bootloader- und App-Flash im selben OpenOCD-Aufruf kombiniert wurden. Bei manuellem Flashen beider Bereiche immer `.bin` mit expliziter Adresse und getrennte OpenOCD-Aufrufe verwenden — Details in `bootloader/README.md`, "Achtung bei manuellem SWD-Flashen". - **`env:versapad` überschreibt den Bootloader kommentarlos.** Ein normaler `pio run -e versapad --target upload` (der alte, gewohnte SWD-Weg für App-Updates) schreibt ab `0x0000` und hat den Bootloader dabei zweimal ohne jede Warnung zerstört. Fix: automatischer Presence-Check in `upload_openocd.py`, siehe "Schutz gegen versehentliches Überschreiben" oben. ## Bekannte Einschränkungen - Bootloader-Flash-Auslastung ~90 % (7364 von 8192 Byte) — wenig Puffer für Änderungen am Bootloader selbst. - Kein Status-LED-Feedback im Bootloader-Modus (nur WS2812-Kette, dafür ist der Original-Code nicht ausgelegt). - USB-PID `0x0043` ist kollisionsfrei verifiziert, aber kein offiziell registrierter Wert unter Adafruits VID `0x239A`. - `upload_uf2.py` ist Windows-only (siehe oben). - Kein Software-Trigger, um aus der laufenden App heraus in den Bootloader zurückzuspringen — nur der physische Kabel-raus/Taste-halten/Kabel-rein-Weg. Wäre über einen neuen CDC-Befehl (siehe [07_serial_protocol.md](07_serial_protocol.md)) nachrüstbar, der dieselbe `DBL_TAP_MAGIC`-RAM-Adresse setzt und `NVIC_SystemReset()` aufruft.