VersaMCU/bootloader/README.md
Julian Appel d325297063 Add end-to-end USB flashing for the app firmware via UF2
Adds uf2conv.py (minimal, dependency-free .bin -> .uf2 converter
matching bootloader/inc/uf2format.h's block layout) and upload_uf2.py,
a PlatformIO upload hook for env:versapad_usb that finds the mounted
VERSABOOT volume and copies the converted firmware onto it.

env:versapad_usb previously used upload_protocol=sam-ba, the classic
Arduino/Atmel protocol -- the actual bootloader speaks UF2/mass
storage, not SAM-BA, so that upload path never worked. Switched to
upload_protocol=custom with the new hook, and cleaned the now-unused
SAM-BA-specific fields out of boards/versapad.json.

Verified end to end on real hardware: pio run -e versapad_usb
--target upload builds, converts, copies to the VERSABOOT drive, and
the bootloader jumps into the freshly written app on its own.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-05 21:35:19 +02:00

138 lines
6.9 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 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.
## 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.
- **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.