Fold bootloader documentation into the main doc/ index

Adds doc/10_usb_bootloader.md as the primary reference for the UF2
bootloader (memory layout, key-based boot entry, build/flash steps,
hardware test findings, known limitations), following the existing
numbered-doc convention. Updates doc/INDEX.md, doc/09_known_limitations.md
(bootloader is no longer "not supported", just scoped), and
doc/08_development.md accordingly.

Removes bootloader/TESTING.md (its findings now live in
doc/10_usb_bootloader.md) and trims bootloader/README.md down to what
belongs with that subproject specifically: upstream attribution/license
and local build/flash commands, plus the OpenOCD manual-flashing
warning since that's implementation-specific detail that would clutter
the higher-level doc.

Updates the top-level README.md (feature table, hardware table,
quickstart, project tree, doc links) to reflect USB flashing as a
supported path alongside SWD.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Julian Appel 2026-08-05 22:49:56 +02:00
parent 3986d2effe
commit 33d9e85992
7 changed files with 153 additions and 201 deletions

104
doc/10_usb_bootloader.md Normal file
View file

@ -0,0 +1,104 @@
# 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.
## 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".
## 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.