diff --git a/boards/versapad.json b/boards/versapad.json index ee0f346..308ad44 100644 --- a/boards/versapad.json +++ b/boards/versapad.json @@ -10,9 +10,9 @@ "extra_flags": "-DARDUINO_SAMD_ZERO -DARM_MATH_CM0PLUS -D__SAMD21G18A__", "f_cpu": "48000000L", "hwids": [ - ["0x239A", "0x0011"] + ["0x239A", "0x0042"] ], - "mcu": "samd21g18a", + "mcu": "samd21g17d", "usb_product": "VersaPad v2", "usb_manufacturer": "Custom" }, @@ -20,8 +20,8 @@ "frameworks": ["arduino"], "name": "VersaPad v2 (USB bootloader)", "upload": { - "maximum_ram_size": 32768, - "maximum_size": 253952, + "maximum_ram_size": 16384, + "maximum_size": 120832, "disable_flushing": true, "native_usb": true, "offset": "0x2000", diff --git a/bootloader/README.md b/bootloader/README.md index 03853af..f02c84a 100644 --- a/bootloader/README.md +++ b/bootloader/README.md @@ -3,8 +3,19 @@ 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 (Doppel-Tap Reset), Board erscheint als USB-Laufwerk `VERSABOOT`, -`.uf2`-Datei drauf kopieren. +aktivieren, Board erscheint als USB-Laufwerk `VERSABOOT`, `.uf2`-Datei drauf +kopieren. + +Der Bootloader selbst (Flash, USB-Enumeration, Massenspeicher-Modus, +Rücksprung in die App) ist auf echter Hardware verifiziert, siehe +[Hardwaretest](#hardwaretest-2026-08-05). Der App-seitige `.uf2`-Schreibweg +(Firmware tatsächlich über das Laufwerk aktualisieren) ist noch nicht gebaut, +siehe [Bekannte Einschränkungen](#bekannte-einschränkungen). + +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 @@ -37,15 +48,72 @@ da nur `0x0000..0x1FFF` beschrieben wird) 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. + +## 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. + ## Bekannte Einschränkungen -- Flash-Auslastung ~89 % (7292 von 8192 Byte). Wenig Puffer für Änderungen. -- Kein physischer Boot-Pin definiert, nur Doppel-Tap-Reset (RAM-Magic-Wert). - Ein Hardware-Fallback-Pin wäre für die Wiederherstellung bei kaputter - App-Firmware sinnvoll, ist aber noch nicht eingerichtet. +- 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 `0x0011` ist unverifiziert übernommen (siehe - `../doc/09_known_limitations.md`), noch nicht auf echter Hardware getestet. -- Ungetestet auf echter Hardware, siehe Branch `feature/usb-bootloader`. +- 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. +- Die App-Firmware erzeugt noch keine `.uf2`-Datei, nur `.bin`/`.elf` + (SWD-Weg). Der geplante Weg über das `VERSABOOT`-Laufwerk ist damit noch + nicht nutzbar. +- `env:versapad_usb` in der Haupt-`platformio.ini` verwendet + `upload_protocol = sam-ba` — das klassische Arduino/Atmel-SAM-BA-Protokoll, + nicht das UF2/Massenspeicher-Verfahren dieses Bootloaders. Für echtes + USB-Flashen wird stattdessen ein `.bin`→`.uf2`-Konvertierungsschritt plus + einfaches Kopieren aufs Laufwerk benötigt, kein spezielles Upload-Protokoll. diff --git a/bootloader/TESTING.md b/bootloader/TESTING.md index b2bb167..2bc26b6 100644 --- a/bootloader/TESTING.md +++ b/bootloader/TESTING.md @@ -1,67 +1,65 @@ -# Bootloader-Testflash -- Anleitung für Hardware-Zugriff +# Bootloader-Testflash -- Ergebnis -Der UF2-Bootloader (siehe [README.md](README.md)) ist fertig gebaut, aber noch -nie auf echter Hardware gelaufen. Diese Anleitung ist für jemanden mit -Atmel-ICE-Zugriff aufs VersaPad-v2-Board, um das einmal zu testen. +Der UF2-Bootloader (siehe [README.md](README.md)) wurde am 2026-08-05 auf +einem echten VersaPad-v2-Board über Atmel-ICE/SWD getestet. Zusammenfassung +der Ergebnisse steht im [Hardwaretest-Abschnitt der README](README.md#hardwaretest-2026-08-05). +Dieses Dokument hält den Testablauf und die Antworten auf die ursprünglichen +Prüffragen fest. -**Wichtig zur Sicherheit:** Dieser Flash schreibt nur `0x0000..0x1FFF` -(8 KiB). Die App-Firmware liegt ab `0x2000` und bleibt unangetastet. Falls -etwas schiefgeht, ist das jederzeit per SWD neu beschreibbar, das Board kann -dabei nicht dauerhaft "gebrickt" werden, solange der Atmel-ICE-Zugriff -funktioniert. +**Sicherheitsrahmen, der eingehalten wurde:** Der Bootloader-Flash schreibt +nur `0x0000..0x1FFF` (8 KiB). Die App-Firmware ab `0x2000` blieb dabei +unangetastet. Über SWD war der Chip jederzeit neu beschreibbar; ein Brick war +zu keinem Zeitpunkt möglich, solange der Atmel-ICE-Zugriff funktionierte. ## Voraussetzungen -- Board per Atmel-ICE/SWD angeschlossen, genau wie beim normalen - App-Firmware-Flashen (`pio run -e versapad --target upload` in - `doc/08_development.md`) -- PlatformIO Core installiert. Falls nicht: - ```bash - pip install -U platformio +- Board per Atmel-ICE/SWD angeschlossen, wie beim normalen + App-Firmware-Flashen (`pio run -e versapad --target upload`, + siehe `../doc/08_development.md`) +- PlatformIO Core (hier: PlatformIO-IDE-penv unter + `%USERPROFILE%\.platformio\penv\Scripts\pio.exe`) + +## Ablauf + +1. `git checkout feature/usb-bootloader` +2. `cd bootloader && pio run -e versapad_bootloader` — Build sauber + (89,9 % Flash, 7364/8192 Byte) +3. `pio run -e versapad_bootloader --target upload` — Flash + Verify über + Atmel-ICE/SWD + +## Ursprüngliche Prüffragen und Antworten + +- **Ist der Flash-Befehl ohne Fehler durchgelaufen?** Ja, `** Verified OK **` + bei jedem Flash. +- **Erscheint nach dem Bootloader-Einstieg ein Laufwerk `VERSABOOT`?** Ja. + Getestete Board-Revision hat **keinen physischen Reset-/Boot-Taster** + (Custom-PCB) — der ursprünglich geplante Doppel-Tap-Reset-Test war damit + nicht durchführbar. Stattdessen wurde ein Hardware-Bootloader-Einstieg über + eine gehaltene Cherry-MX-Taste ergänzt, siehe + [README.md](README.md#hardware-bootloader-einstieg-ohne-reset-taster). + Mit dieser Ergänzung: gehaltene Taste beim Power-On → `VERSABOOT`. +- **Was steht in `INFO_UF2.TXT`?** ``` + UF2 Bootloader versapad-1 SFHWRO + Model: VersaPad v2 + Board-ID: SAMD21G17D-VersaPad-v2 + ``` +- **Reagiert das Board normal als App-Firmware, wenn man es ohne gehaltene + Taste ansteckt?** Ja, nach dem in der README beschriebenen DSB/ISB-Fix. + Vor dem Fix: nein, siehe unten. +- **Ungewöhnliches?** Ja, ein echter Bug: siehe + [Hardwaretest-Abschnitt der README](README.md#hardwaretest-2026-08-05) für + die Fehlersuche (Bootloader-eigener Sprung faultete zuverlässig, obwohl vom + Debugger injizierte identische Register-/VTOR-Werte einwandfrei liefen) und + den Fix (fehlende `__DSB()`/`__ISB()` vor dem `bx` in + `check_start_application()`). +- **USB-Identität korrekt, kein Fremdtreiber-Konflikt?** Nach PID-Wechsel von + `0x0011` auf `0x0043` ja (siehe README, "Bekannte Einschränkungen" zum + ursprünglichen Adafruit-PID-Konflikt). -## 1. Repo auf diesem Branch auschecken +## Noch NICHT getestet -```bash -git clone https://git.jappel.io/jappel/VersaMCU.git -cd VersaMCU -git checkout feature/usb-bootloader -``` - -## 2. Bootloader bauen und flashen - -```bash -cd bootloader -pio run -e versapad_bootloader --target upload -``` - -Der erste Aufruf lädt automatisch den ARM-Toolchain und OpenOCD herunter -(einmalig, braucht Internet). Der Build sollte ohne Fehler durchlaufen -(erwartete Größe: ~7,3 von 8 KB Flash). - -## 3. Prüfen, ob der Bootloader läuft - -Nach dem Flash: Board kurz vom USB trennen und wieder anstecken, dann -**Reset-Taste zweimal kurz hintereinander drücken** (Doppel-Tap, wie bei -Arduino-Zero-artigen Boards üblich). - -Erwartung: Das Board sollte sich als **USB-Massenspeicher-Laufwerk namens -`VERSABOOT`** melden (in Windows z. B. im Explorer als neues Laufwerk). - -## 4. Ergebnis zurückmelden - -Bitte notieren und zurückgeben: - -- Ist der Flash-Befehl ohne Fehler durchgelaufen? (ggf. komplette - Fehlermeldung kopieren) -- Erscheint nach dem Doppel-Tap-Reset ein Laufwerk `VERSABOOT`? -- Falls ja: was steht in der Datei `INFO_UF2.TXT` auf dem Laufwerk? -- Falls nein: reagiert das Board überhaupt noch normal als App-Firmware - (Tasten/LEDs), wenn man es normal per USB ansteckt ohne Doppel-Tap? -- Alles, was ungewöhnlich aussieht (USB-Gerät mit falschem Namen, Windows - fragt nach Treiber, Laufwerk lässt sich nicht öffnen, etc.) - -Noch NICHT testen: Firmware tatsächlich über das `VERSABOOT`-Laufwerk -flashen. Der Firmware-Build erzeugt noch keine `.uf2`-Datei (nur `.bin` -für den bisherigen SWD-Weg), das ist der nächste Schritt, nachdem der -Bootloader selbst bestätigt läuft. +Firmware tatsächlich über das `VERSABOOT`-Laufwerk flashen. Der +Firmware-Build der App erzeugt noch keine `.uf2`-Datei (nur `.bin`/`.elf` für +den SWD-Weg) — das ist der nächste Schritt, siehe README, "Bekannte +Einschränkungen". diff --git a/bootloader/include/board_config.h b/bootloader/include/board_config.h index 68a3f4e..9ef09f0 100644 --- a/bootloader/include/board_config.h +++ b/bootloader/include/board_config.h @@ -15,9 +15,16 @@ #define BOARD_ID "SAMD21G17D-VersaPad-v2" /* Same VID as the app firmware (platformio.ini), distinct PID so the - * bootloader enumerates as a different USB device than the app. */ + * bootloader enumerates as a different USB device than the app. + * + * NOT 0x0011: that's Adafruit's own Gemma M0 bootloader PID (this file's + * upstream template), and Windows machines with an Adafruit driver already + * installed silently bind it as a "Adafruit Circuit Playground" COM port + * instead of exposing the VERSABOOT mass-storage volume. Confirmed on real + * hardware 2026-08-05. 0x0043 is app PID (0x0042) + 1, not a known + * third-party assignment. */ #define USB_VID 0x239A -#define USB_PID 0x0011 +#define USB_PID 0x0043 /* No plain GPIO status LED on this board, only a WS2812 chain on PB22. * The bootloader's LED_PIN code just toggles a digital output, that would @@ -29,4 +36,21 @@ //#define BOARD_RGBLED_CLOCK_PIN //#define BOARD_RGBLED_DATA_PIN +/* Hardware bootloader entry, no dedicated reset/boot button on this PCB. + * Reuses the bottom-right Cherry MX key (key_id 24 in the app firmware, + * see src/config/pins.h) as a boot-hold key: held while the board resets + * or powers up -> stay in the bootloader. Checked in check_start_application() + * before the app is even validated, so it works regardless of app firmware + * state. + * + * Pin numbers are PORT-flat (group*32 + pin), matching PINOP()/PINCFG() in + * uf2.h: PA15 = 15, PA08 = 8. + * + * Matrix wiring (src/config/pins.h, src/hal/matrix.cpp): key_id 24 = COL4 x + * ROW4. COL4 has an external 10k pull-up to 3V3 (idle HIGH); ROW4 is driven + * LOW during the check, matching the app's own scan polarity. + */ +#define BOOT_KEY_ROW_PIN 15 // PA15 -- ROW4, driven LOW during the check +#define BOOT_KEY_COL_PIN 8 // PA08 -- COL4, read back; LOW = key pressed + #endif diff --git a/bootloader/src/main.c b/bootloader/src/main.c index c4f6f9a..e646dcd 100644 --- a/bootloader/src/main.c +++ b/bootloader/src/main.c @@ -87,6 +87,38 @@ extern int8_t led_tick_step; #define RESET_CONTROLLER RSTC #endif +#if defined(BOOT_KEY_ROW_PIN) && defined(BOOT_KEY_COL_PIN) +/** + * \brief Check whether the boot-hold key (bottom-right Cherry MX button) is + * held down. Drives its matrix row low and reads its matrix column back, + * the same polarity the app firmware's own matrix scan uses. + */ +static bool boot_key_pressed(void) { + PORT_PINCFG_Type col_cfg = {0}; + col_cfg.bit.PMUXEN = false; + col_cfg.bit.INEN = true; // external 10k pull-up already on the board + + PORT_PINCFG_Type row_cfg = {0}; + row_cfg.bit.PMUXEN = false; + row_cfg.bit.DRVSTR = true; + + PINCFG(BOOT_KEY_COL_PIN) = col_cfg.reg; + PINOP(BOOT_KEY_COL_PIN, DIRCLR); // column stays an input + + PINOP(BOOT_KEY_ROW_PIN, OUTCLR); // pre-set drive level before enabling output + PINCFG(BOOT_KEY_ROW_PIN) = row_cfg.reg; + PINOP(BOOT_KEY_ROW_PIN, DIRSET); // row -> output, driving low + + for (volatile int i = 0; i < 200; i++) { + } // let the row settle through the diode/pull-up RC + + bool pressed = (PINIP(BOOT_KEY_COL_PIN) == 0); + + PINOP(BOOT_KEY_ROW_PIN, DIRCLR); // release row back to high-Z + return pressed; +} +#endif + /** * \brief Check the application startup condition * @@ -94,6 +126,13 @@ extern int8_t led_tick_step; static void check_start_application(void) { uint32_t app_start_address; +#if defined(BOOT_KEY_ROW_PIN) && defined(BOOT_KEY_COL_PIN) + if (boot_key_pressed()) { + /* Stay in bootloader */ + return; + } +#endif + // Check if there is an IO which will hold us inside the bootloader. #if defined(HOLD_PIN) && defined(HOLD_STATE) PORT_PINCFG_Type pincfg = {0}; @@ -167,6 +206,14 @@ static void check_start_application(void) { /* Rebase the vector table base address */ SCB->VTOR = ((uint32_t)APP_START_ADDRESS & SCB_VTOR_TBLOFF_Msk); + /* Ensure the MSP/VTOR writes are visible before jumping; without these + * barriers the app's first fetch can race the pipeline (observed on + * real hardware: identical MSP/VTOR/PC values injected by a halted + * debugger boot fine, but the bootloader's own running jump hard-faults + * every time). */ + __DSB(); + __ISB(); + /* Jump to application Reset Handler in the application */ asm("bx %0" ::"r"(app_start_address)); } diff --git a/doc/09_known_limitations.md b/doc/09_known_limitations.md index be5152d..4e16e02 100644 --- a/doc/09_known_limitations.md +++ b/doc/09_known_limitations.md @@ -6,9 +6,23 @@ Robustheitskorrekturen. Sie ist keine Liste bereits umgesetzter Features. ## Bootloader-Ziel bleibt nicht unterstützt Das aktive Ziel `versapad_nobl` reserviert den kompletten Bereich -`0x1FB00..0x1FFFF` für Makros und Config. Das auskommentierte -USB-Bootloader-Environment verwendet dagegen weiterhin eine historische -Board-/Linker-Konfiguration und ist nicht als Produktionsziel verifiziert. +`0x1FB00..0x1FFFF` für Makros und Config. + +Ein USB-Bootloader-Pfad wird im Branch `feature/usb-bootloader` aufgebaut +(`bootloader/`, App-Environment `env:versapad_usb`). Der Bootloader selbst +(UF2, `bootloader/`) ist dort auf echter Hardware verifiziert — inklusive +eines gefundenen und behobenen Bugs beim Sprung in die App (fehlende +`__DSB()`/`__ISB()` vor dem `bx`, siehe `bootloader/README.md`, +"Hardwaretest"). `env:versapad_usb`s Linkerskript +(`variants/versapad/linker_scripts/gcc/flash_with_bootloader.ld`) reserviert +inzwischen denselben NVM-Bereich wie `flash_without_bootloader.ld`. + +Trotzdem noch kein Produktionsziel: `env:versapad_usb` verwendet +`upload_protocol = sam-ba`, das klassische Arduino/Atmel-SAM-BA-Protokoll — +das spricht der UF2-Bootloader nicht. Die App-Firmware erzeugt außerdem noch +keine `.uf2`-Datei. Vor einem Merge nach `master` fehlen also noch die +`.uf2`-Erzeugung und ein Ende-zu-Ende-Test des tatsächlichen USB-Flashwegs +(Datei aufs `VERSABOOT`-Laufwerk kopieren). Die aktive Boarddatei benennt die MCU als `samd21g17d`, setzt für den Arduino-Core aber weiterhin das Kompatibilitätsmakro `__SAMD21G18A__`. Der diff --git a/platformio.ini b/platformio.ini index 5375d4a..3b3df48 100644 --- a/platformio.ini +++ b/platformio.ini @@ -23,7 +23,7 @@ extra_scripts = upload_openocd.py debug_tool = openocd ; ── USB SAM-BA (nur wenn Bootloader geflasht ist) ───────────────────────────── -; [env:versapad_usb] -; extends = common -; board = versapad -; upload_protocol = sam-ba +[env:versapad_usb] +extends = common +board = versapad +upload_protocol = sam-ba diff --git a/variants/versapad/linker_scripts/gcc/flash_with_bootloader.ld b/variants/versapad/linker_scripts/gcc/flash_with_bootloader.ld index f79fcbc..889a441 100644 --- a/variants/versapad/linker_scripts/gcc/flash_with_bootloader.ld +++ b/variants/versapad/linker_scripts/gcc/flash_with_bootloader.ld @@ -6,7 +6,8 @@ SEARCH_DIR(.) MEMORY { - rom (rx) : ORIGIN = 0x00002000, LENGTH = 0x0001E000 /* 120K (128K - 8K bootloader) */ + rom (rx) : ORIGIN = 0x00002000, LENGTH = 0x0001D900 /* 118.25K – Firmware (128K - 8K bootloader - 1.25K NVM) */ + nvm (rx) : ORIGIN = 0x0001FB00, LENGTH = 0x00000500 /* 1.25K – Makros + Config, same layout as flash_without_bootloader.ld */ ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00004000 /* 16K */ }