forked from jappel/VersaMCU
Compare commits
1 commit
master
...
fix/versap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0461f6565b |
17 changed files with 122 additions and 580 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,9 +1,6 @@
|
|||
# PlatformIO
|
||||
.pio/
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
|
||||
# VS Code
|
||||
.vscode/
|
||||
|
||||
|
|
|
|||
35
README.md
35
README.md
|
|
@ -15,7 +15,6 @@ ATSAMD21G17D mit PlatformIO und dem Arduino-SAMD-Framework.
|
|||
| LEDs | 20 WS2812B mit Base-/Override-Farbe und 7 Animationsmodi |
|
||||
| Persistenz | Config und Makros im internen Flash |
|
||||
| Recovery | Werksreset über zwei Tasten |
|
||||
| Firmware-Update | SWD (Atmel-ICE) oder USB (UF2-Bootloader, `bootloader/`) |
|
||||
|
||||
Die drei Fader-Pins sind im Board-Variant definiert, werden von der aktuellen
|
||||
Firmware aber noch nicht eingelesen.
|
||||
|
|
@ -30,7 +29,7 @@ Firmware aber noch nicht eingelesen.
|
|||
| Encoder | 4× Quadratur über EIC-Interrupts |
|
||||
| LEDs | 20× WS2812B an `PB22` |
|
||||
| USB | Native USB als HID + CDC Composite Device |
|
||||
| Programmer | Atmel-ICE/CMSIS-DAP über SWD, oder USB über den UF2-Bootloader |
|
||||
| Programmer | Atmel-ICE/CMSIS-DAP über SWD, standardmäßig ohne Bootloader |
|
||||
|
||||
## Schnellstart
|
||||
|
||||
|
|
@ -47,30 +46,9 @@ Das Standard-Environment `versapad` baut für `boards/versapad_nobl.json`. Der
|
|||
Upload wird durch `upload_openocd.py` über das von PlatformIO installierte
|
||||
OpenOCD ausgeführt.
|
||||
|
||||
Alternativ lässt sich die App-Firmware nach einem einmaligen
|
||||
SWD-Bootloader-Flash auch über USB aktualisieren, ganz ohne Atmel-ICE:
|
||||
|
||||
```bash
|
||||
cd bootloader && pio run -e versapad_bootloader --target upload # einmalig
|
||||
pio run -e versapad_usb --target upload # danach jedes App-Update
|
||||
```
|
||||
|
||||
`bootloader/` ist ein eigenständiges PlatformIO-Projekt (eigene
|
||||
`platformio.ini`, kein Arduino-Framework). Für die PlatformIO-IDE-Buttons in
|
||||
VS Code müsste der Ordner separat als eigener Workspace geöffnet werden; über
|
||||
die Kommandozeile reicht `cd bootloader && pio run ...` im selben Fenster.
|
||||
|
||||
Sobald der Bootloader installiert ist, **verweigert `versapad --target
|
||||
upload` den normalen Upload** — dieser Weg schreibt ab `0x0000` und würde den
|
||||
Bootloader sonst kommentarlos überschreiben (`upload_openocd.py` prüft das
|
||||
vorher automatisch). Bauen und Debuggen über `versapad` bleiben uneingeschränkt
|
||||
möglich, nur der Upload ist betroffen. Für App-Updates danach `versapad_usb`
|
||||
verwenden; bewusst zurück zu reinem SWD-Betrieb geht über
|
||||
`pio run -e versapad -t erase-bootloader-and-flash`.
|
||||
|
||||
Details, Speicherlayout und die Bootloader-Aktivierung (kein physischer
|
||||
Reset-Taster auf dieser Platine) stehen in
|
||||
[10_usb_bootloader.md](doc/10_usb_bootloader.md).
|
||||
Das in `platformio.ini` auskommentierte Bootloader-Environment ist derzeit
|
||||
nicht als Produktionsziel unterstützt. Details stehen unter
|
||||
[bekannte Einschränkungen](doc/09_known_limitations.md).
|
||||
|
||||
## Laufzeitmodell
|
||||
|
||||
|
|
@ -141,13 +119,9 @@ VersaMCU/
|
|||
|-- AGENTS.md # Kontext und Richtlinien für Coding-LLMs
|
||||
|-- README.md
|
||||
|-- platformio.ini
|
||||
|-- upload_openocd.py # SWD-Upload-Hook (env:versapad)
|
||||
|-- uf2conv.py # .bin -> .uf2 Konverter (App-Firmware)
|
||||
|-- upload_uf2.py # USB-Upload-Hook (env:versapad_usb)
|
||||
|-- boards/ # PlatformIO-Boarddefinitionen
|
||||
|-- variants/versapad/ # Pinmapping und Linker-Skripte
|
||||
|-- doc/ # Architektur- und Protokolldokumentation
|
||||
|-- bootloader/ # UF2-Bootloader (eigenes PlatformIO-Projekt)
|
||||
`-- src/
|
||||
|-- main.cpp
|
||||
|-- CMainController.* # Orchestrierung
|
||||
|
|
@ -183,4 +157,3 @@ Binärverträge, Änderungsregeln und die minimale Verifikation.
|
|||
- [CDC-Protokoll](doc/07_serial_protocol.md)
|
||||
- [Entwicklung und Einstieg](doc/08_development.md)
|
||||
- [Bekannte Einschränkungen](doc/09_known_limitations.md)
|
||||
- [USB-Bootloader](doc/10_usb_bootloader.md)
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@
|
|||
"core": "arduino",
|
||||
"variant": "versapad",
|
||||
"cpu": "cortex-m0plus",
|
||||
"extra_flags": "-DARDUINO_SAMD_ZERO -DARM_MATH_CM0PLUS -D__SAMD21G18A__",
|
||||
"extra_flags": "-DARDUINO_SAMD_ZERO -DARM_MATH_CM0PLUS -D__SAMD21G17D__",
|
||||
"f_cpu": "48000000L",
|
||||
"hwids": [
|
||||
["0x239A", "0x0042"]
|
||||
["0x239A", "0x0011"]
|
||||
],
|
||||
"mcu": "samd21g17d",
|
||||
"usb_product": "VersaPad v2",
|
||||
|
|
@ -21,9 +21,14 @@
|
|||
"name": "VersaPad v2 (USB bootloader)",
|
||||
"upload": {
|
||||
"maximum_ram_size": 16384,
|
||||
"maximum_size": 121600,
|
||||
"maximum_size": 122880,
|
||||
"disable_flushing": true,
|
||||
"native_usb": true,
|
||||
"offset": "0x2000",
|
||||
"protocol": "custom"
|
||||
"protocol": "sam-ba",
|
||||
"require_upload_port": true,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "",
|
||||
"vendor": "Custom"
|
||||
|
|
|
|||
5
bootloader/.gitignore
vendored
5
bootloader/.gitignore
vendored
|
|
@ -1,5 +0,0 @@
|
|||
.pio
|
||||
.vscode/.browse.c_cpp.db*
|
||||
.vscode/c_cpp_properties.json
|
||||
.vscode/launch.json
|
||||
.vscode/ipch
|
||||
|
|
@ -2,11 +2,9 @@
|
|||
|
||||
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.
|
||||
|
||||
Speicherlayout, Bootloader-Aktivierung, Bedienung und Hardwaretest-Ergebnisse
|
||||
stehen in [`../doc/10_usb_bootloader.md`](../doc/10_usb_bootloader.md). Diese
|
||||
Datei beschreibt nur das Bootloader-Unterprojekt selbst.
|
||||
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.
|
||||
|
||||
## Herkunft
|
||||
|
||||
|
|
@ -25,12 +23,6 @@ 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
|
||||
|
|
@ -45,26 +37,15 @@ da nur `0x0000..0x1FFF` beschrieben wird)
|
|||
pio run -e versapad_bootloader --target upload
|
||||
```
|
||||
|
||||
## Achtung bei manuellem SWD-Flashen von Bootloader UND App
|
||||
## Bekannte Einschränkungen
|
||||
|
||||
`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.
|
||||
- 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.
|
||||
- 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`.
|
||||
|
|
|
|||
67
bootloader/TESTING.md
Normal file
67
bootloader/TESTING.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Bootloader-Testflash -- Anleitung für Hardware-Zugriff
|
||||
|
||||
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.
|
||||
|
||||
**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.
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
## 1. Repo auf diesem Branch auschecken
|
||||
|
||||
```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.
|
||||
|
|
@ -15,16 +15,9 @@
|
|||
#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.
|
||||
*
|
||||
* 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. */
|
||||
* bootloader enumerates as a different USB device than the app. */
|
||||
#define USB_VID 0x239A
|
||||
#define USB_PID 0x0043
|
||||
#define USB_PID 0x0011
|
||||
|
||||
/* 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
|
||||
|
|
@ -36,21 +29,4 @@
|
|||
//#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
|
||||
|
|
|
|||
|
|
@ -87,38 +87,6 @@ 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
|
||||
*
|
||||
|
|
@ -126,13 +94,6 @@ static bool boot_key_pressed(void) {
|
|||
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};
|
||||
|
|
@ -206,14 +167,6 @@ 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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ pio run -e versapad --target upload
|
|||
Der Upload nutzt `upload_openocd.py`, das das von PlatformIO installierte
|
||||
OpenOCD mit `interface/cmsis-dap.cfg` und `target/at91samdXX.cfg` startet.
|
||||
|
||||
Für App-Updates über USB (nach einem einmaligen Bootloader-Flash) gibt es
|
||||
zusätzlich `env:versapad_usb`, siehe
|
||||
[10_usb_bootloader.md](10_usb_bootloader.md).
|
||||
Das in `platformio.ini` nur als Beispiel enthaltene Environment
|
||||
`versapad_usb` ist auskommentiert und mit dem aktuellen NVM-/Linker-Layout
|
||||
nicht als unterstützt anzusehen.
|
||||
|
||||
## Was beim Start passiert
|
||||
|
||||
|
|
@ -63,7 +63,6 @@ und NVM-Schreibvorgängen. Es gibt keinen Scheduler und keine Threads.
|
|||
| LEDs | `05_led_system.md` | `CButton.*`, `hal/ws2812.*` |
|
||||
| Persistente Config | `06_nvm_config.md` | `config/nvm_config.*`, Linker-Skripte |
|
||||
| Host-Protokoll | `07_serial_protocol.md` | `hal/usb_serial.*`, Controller |
|
||||
| USB-Bootloader | `10_usb_bootloader.md` | `bootloader/`, `variants/versapad/linker_scripts/gcc/flash_with_bootloader.ld`, `boards/versapad.json`, `uf2conv.py`, `upload_uf2.py` |
|
||||
|
||||
## Verifikation
|
||||
|
||||
|
|
|
|||
|
|
@ -3,33 +3,19 @@
|
|||
Diese Liste beschreibt den aktuellen Implementierungsstand nach den
|
||||
Robustheitskorrekturen. Sie ist keine Liste bereits umgesetzter Features.
|
||||
|
||||
## USB-Bootloader hat einen eingeschränkten Gültigkeitsbereich
|
||||
## Bootloader-Ziel bleibt nicht unterstützt
|
||||
|
||||
Der UF2-Bootloader (`bootloader/`, App-Environment `env:versapad_usb`,
|
||||
Details in [10_usb_bootloader.md](10_usb_bootloader.md)) ist auf echter
|
||||
Hardware verifiziert und ergänzt den SWD-Weg, ersetzt ihn aber nicht: der
|
||||
Bootloader selbst muss weiterhin einmalig per Atmel-ICE/SWD geflasht werden.
|
||||
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.
|
||||
|
||||
Verbleibende Einschränkungen:
|
||||
|
||||
- `uf2conv.py`/`upload_uf2.py` (Repo-Root) suchen das `VERSABOOT`-Laufwerk
|
||||
nur über die Windows-API, keine macOS/Linux-Unterstützung.
|
||||
- Die Bootloader-USB-PID (`0x0043`) ist kollisionsfrei verifiziert, aber kein
|
||||
offiziell registrierter Wert unter Adafruits VID `0x239A`.
|
||||
- Kein Software-Trigger, um aus der laufenden App heraus in den
|
||||
Bootloader-Modus zu wechseln — nur der physische Weg (Kabel ziehen, Taste
|
||||
halten, wieder einstecken).
|
||||
- Bootloader-Flash-Auslastung ~90 % (7364 von 8192 Byte), wenig Puffer für
|
||||
Änderungen am Bootloader selbst.
|
||||
|
||||
Sowohl `boards/versapad_nobl.json` (SWD-Ziel) als auch `boards/versapad.json`
|
||||
(USB-Bootloader-Ziel) benennen die MCU als `samd21g17d`, setzen für den
|
||||
Die aktive Boarddatei benennt die MCU als `samd21g17d`, setzt für den
|
||||
Arduino-Core aber weiterhin das Kompatibilitätsmakro `__SAMD21G18A__`. Der
|
||||
PlatformIO-Build meldet korrekt 128 KiB physischen Flash, 16 KiB RAM und
|
||||
129.792 Byte (`versapad_nobl`) beziehungsweise 121.600 Byte
|
||||
(`versapad`, abzüglich 8 KiB Bootloader) nutzbaren Firmwarebereich. Vor
|
||||
device-spezifischen Core-Änderungen sollte die historische Makro-Abweichung
|
||||
trotzdem geprüft werden.
|
||||
129.792 Byte nutzbaren Firmwarebereich. Vor device-spezifischen
|
||||
Core-Änderungen sollte die historische Makro-Abweichung trotzdem geprüft
|
||||
werden.
|
||||
|
||||
## Event-Queue hat eine feste Kapazität
|
||||
|
||||
|
|
|
|||
|
|
@ -1,134 +0,0 @@
|
|||
# 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.
|
||||
|
|
@ -17,7 +17,6 @@ Bereiche stehen ausdrücklich in
|
|||
| [07_serial_protocol.md](07_serial_protocol.md) | 8-Byte-Protokoll, Config-/Makro-Transfer, ACK/NACK |
|
||||
| [08_development.md](08_development.md) | Setup, Build, Einstieg nach Änderungstyp, Verifikation |
|
||||
| [09_known_limitations.md](09_known_limitations.md) | Aktuelle technische Einschränkungen und Risiken |
|
||||
| [10_usb_bootloader.md](10_usb_bootloader.md) | UF2-Bootloader, Speicherlayout, Tastencheck-Einstieg, USB-Flashweg |
|
||||
|
||||
Die Repository-weiten Richtlinien und der kompakte LLM-Kontext stehen in
|
||||
[`../AGENTS.md`](../AGENTS.md).
|
||||
|
|
@ -30,4 +29,3 @@ Die Repository-weiten Richtlinien und der kompakte LLM-Kontext stehen in
|
|||
- Action-Semantik und HID-Hold: [03_action_engine.md](03_action_engine.md)
|
||||
- CDC-Protokoll und Chunk-Zahlen: [07_serial_protocol.md](07_serial_protocol.md)
|
||||
- bekannte Risiken vor strukturellen Änderungen: [09_known_limitations.md](09_known_limitations.md)
|
||||
- Firmware per USB statt SWD flashen: [10_usb_bootloader.md](10_usb_bootloader.md)
|
||||
|
|
|
|||
|
|
@ -22,11 +22,8 @@ upload_protocol = custom
|
|||
extra_scripts = upload_openocd.py
|
||||
debug_tool = openocd
|
||||
|
||||
; ── USB UF2 (nur wenn bootloader/ geflasht ist) ────────────────────────────────
|
||||
; Der eigene Bootloader spricht UF2/Massenspeicher, kein SAM-BA. upload_uf2.py
|
||||
; erzeugt aus firmware.bin ein .uf2 und kopiert es aufs VERSABOOT-Laufwerk.
|
||||
[env:versapad_usb]
|
||||
extends = common
|
||||
board = versapad
|
||||
upload_protocol = custom
|
||||
extra_scripts = upload_uf2.py
|
||||
; ── USB SAM-BA (nur wenn Bootloader geflasht ist) ─────────────────────────────
|
||||
; [env:versapad_usb]
|
||||
; extends = common
|
||||
; board = versapad
|
||||
; upload_protocol = sam-ba
|
||||
|
|
|
|||
83
uf2conv.py
83
uf2conv.py
|
|
@ -1,83 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Minimal .bin -> .uf2 converter for the VersaMCU UF2 bootloader.
|
||||
|
||||
Standalone reimplementation of the block format microsoft/uf2-samdx1's
|
||||
uf2conv.py produces (256-byte payload per 512-byte block); see
|
||||
bootloader/inc/uf2format.h for the struct this has to match on the device
|
||||
side. No external dependencies, Python 3 only.
|
||||
|
||||
Usage:
|
||||
python uf2conv.py <input.bin> <output.uf2> [--base 0x2000] [--family 0x68ed2b88]
|
||||
"""
|
||||
import argparse
|
||||
import struct
|
||||
|
||||
UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
|
||||
UF2_MAGIC_START1 = 0x9E5D5157 # randomly selected, must match the device
|
||||
UF2_MAGIC_END = 0x0AB16F30 # ditto
|
||||
UF2_FLAG_FAMILYID_PRESENT = 0x00002000
|
||||
|
||||
SAMD21_FAMILY_ID = 0x68ED2B88 # bootloader/inc/uf2format.h, #ifdef SAMD21
|
||||
PAYLOAD_SIZE = 256 # bytes per block; matches the upstream uf2conv.py convention
|
||||
|
||||
|
||||
def convert(bin_path: str, uf2_path: str, base_addr: int, family_id: int) -> int:
|
||||
with open(bin_path, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
# Pad to a whole number of blocks; the bootloader writes payloadSize
|
||||
# bytes per block regardless of how much of it is real firmware.
|
||||
if len(data) % PAYLOAD_SIZE != 0:
|
||||
data += b"\x00" * (PAYLOAD_SIZE - len(data) % PAYLOAD_SIZE)
|
||||
num_blocks = len(data) // PAYLOAD_SIZE
|
||||
|
||||
blocks = []
|
||||
for block_no in range(num_blocks):
|
||||
offset = block_no * PAYLOAD_SIZE
|
||||
chunk = data[offset : offset + PAYLOAD_SIZE]
|
||||
header = struct.pack(
|
||||
"<IIIIIIII",
|
||||
UF2_MAGIC_START0,
|
||||
UF2_MAGIC_START1,
|
||||
UF2_FLAG_FAMILYID_PRESENT,
|
||||
base_addr + offset,
|
||||
PAYLOAD_SIZE,
|
||||
block_no,
|
||||
num_blocks,
|
||||
family_id,
|
||||
)
|
||||
padding = b"\x00" * (476 - PAYLOAD_SIZE)
|
||||
footer = struct.pack("<I", UF2_MAGIC_END)
|
||||
blocks.append(header + chunk + padding + footer)
|
||||
|
||||
with open(uf2_path, "wb") as f:
|
||||
f.write(b"".join(blocks))
|
||||
|
||||
return num_blocks
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("input", help="Path to the raw firmware .bin")
|
||||
parser.add_argument("output", help="Path to write the .uf2 to")
|
||||
parser.add_argument(
|
||||
"--base",
|
||||
type=lambda s: int(s, 0),
|
||||
default=0x2000,
|
||||
help="Flash base address the .bin was linked for (default: 0x2000, matches "
|
||||
"variants/versapad/linker_scripts/gcc/flash_with_bootloader.ld)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--family",
|
||||
type=lambda s: int(s, 0),
|
||||
default=SAMD21_FAMILY_ID,
|
||||
help="UF2 family ID (default: SAMD21, 0x68ed2b88)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
num_blocks = convert(args.input, args.output, args.base, args.family)
|
||||
print(f"Wrote {args.output}: {num_blocks} blocks, base 0x{args.base:08x}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -2,118 +2,23 @@ Import("env")
|
|||
import os
|
||||
import subprocess
|
||||
|
||||
# Real, already-built bootloader image used as the reference for the presence
|
||||
# check below -- a raw synthetic probe blob turned out unreliable with
|
||||
# verify_image (silent no-op on tiny files), whereas verify_image against a
|
||||
# real firmware .bin has been solid throughout this project's bring-up.
|
||||
BOOTLOADER_BIN = os.path.join(
|
||||
"bootloader", ".pio", "build", "versapad_bootloader", "firmware.bin"
|
||||
)
|
||||
|
||||
|
||||
def _openocd_paths(env):
|
||||
def upload_via_openocd(source, target, env):
|
||||
pkg_dir = env.PioPlatform().get_package_dir("tool-openocd")
|
||||
return (
|
||||
os.path.join(pkg_dir, "bin", "openocd.exe"),
|
||||
os.path.join(pkg_dir, "scripts"),
|
||||
)
|
||||
openocd = os.path.join(pkg_dir, "bin", "openocd.exe")
|
||||
scripts = os.path.join(pkg_dir, "scripts")
|
||||
firmware = str(source[0]) # .elf path
|
||||
|
||||
|
||||
def _run_openocd(env, extra_cmd, capture=False):
|
||||
openocd, scripts = _openocd_paths(env)
|
||||
cmd = [
|
||||
openocd,
|
||||
"-s", scripts,
|
||||
"-f", "interface/cmsis-dap.cfg",
|
||||
"-f", "target/at91samdXX.cfg",
|
||||
"-c", extra_cmd,
|
||||
"-c", 'program "{}" verify reset; shutdown'.format(firmware.replace("\\", "/"))
|
||||
]
|
||||
|
||||
print(" ".join(cmd))
|
||||
if capture:
|
||||
return subprocess.run(cmd, capture_output=True, text=True)
|
||||
return subprocess.run(cmd)
|
||||
|
||||
|
||||
def _bootloader_present(env):
|
||||
bootloader_bin = env.subst(
|
||||
os.path.join("$PROJECT_DIR", BOOTLOADER_BIN)
|
||||
)
|
||||
if not os.path.isfile(bootloader_bin):
|
||||
print("WARNING: {} not found (build it with".format(BOOTLOADER_BIN))
|
||||
print("'cd bootloader && pio run -e versapad_bootloader') -- can't check")
|
||||
print("for an installed bootloader, refusing to flash as a precaution.")
|
||||
print("Use 'pio run -e versapad -t erase-bootloader-and-flash' to override.")
|
||||
return True
|
||||
|
||||
result = _run_openocd(
|
||||
env,
|
||||
'init; reset halt; verify_image "{}" 0x0; shutdown'.format(
|
||||
bootloader_bin.replace("\\", "/")
|
||||
),
|
||||
capture=True,
|
||||
)
|
||||
output = result.stdout + result.stderr
|
||||
if "checksum mismatch" in output or "diff " in output:
|
||||
return False # something else is at 0x0000 -- not this bootloader
|
||||
if "halted due to debug-request" in output and "Error" not in output:
|
||||
return True # verify_image is silent on a match; absence of a
|
||||
# mismatch/error after a successful connect means it matched
|
||||
print("WARNING: could not read flash to check for an installed bootloader")
|
||||
print("(inconclusive) -- refusing to flash as a precaution.")
|
||||
print("Use 'pio run -e versapad -t erase-bootloader-and-flash' to override.")
|
||||
return True
|
||||
|
||||
|
||||
def _flash(env, firmware):
|
||||
result = _run_openocd(
|
||||
env, 'program "{}" verify reset; shutdown'.format(firmware.replace("\\", "/"))
|
||||
)
|
||||
result = subprocess.run(cmd)
|
||||
if result.returncode != 0:
|
||||
env.Exit(1)
|
||||
|
||||
|
||||
def upload_via_openocd(source, target, env):
|
||||
firmware = str(source[0]) # .elf path
|
||||
|
||||
# This script is shared with bootloader/platformio.ini's own upload
|
||||
# (env:versapad_bootloader), which legitimately writes 0x0000 every time
|
||||
# -- the guard below only makes sense for the app-without-bootloader
|
||||
# target (env:versapad).
|
||||
if env["PIOENV"] == "versapad" and _bootloader_present(env):
|
||||
print("=" * 78)
|
||||
print("REFUSING TO FLASH: a UF2 bootloader looks like it's installed at 0x0000.")
|
||||
print("")
|
||||
print("This target (env:versapad) writes the app starting at 0x0000 and")
|
||||
print("would silently overwrite it -- the board would lose its USB flashing")
|
||||
print("path (see doc/10_usb_bootloader.md).")
|
||||
print("")
|
||||
print("Use instead:")
|
||||
print(" pio run -e versapad_usb --target upload # flash over USB, keeps the bootloader")
|
||||
print("or, if you deliberately want to erase the bootloader and go back to")
|
||||
print("standalone SWD-only operation:")
|
||||
print(" pio run -e versapad -t erase-bootloader-and-flash")
|
||||
print("=" * 78)
|
||||
env.Exit(1)
|
||||
|
||||
_flash(env, firmware)
|
||||
|
||||
|
||||
env.Replace(UPLOADCMD=upload_via_openocd)
|
||||
|
||||
|
||||
def erase_bootloader_and_flash(*_args, **_kwargs):
|
||||
firmware = env.subst(os.path.join("$BUILD_DIR", "${PROGNAME}.elf"))
|
||||
print("Overwriting 0x0000..0x1FAFF -- any installed UF2 bootloader will be erased.")
|
||||
_flash(env, firmware)
|
||||
|
||||
|
||||
env.AddCustomTarget(
|
||||
name="erase-bootloader-and-flash",
|
||||
dependencies=["buildprog"],
|
||||
actions=[erase_bootloader_and_flash],
|
||||
title="Erase bootloader + flash (standalone SWD)",
|
||||
description=(
|
||||
"Unconditionally overwrites 0x0000..0x1FAFF, wiping any installed UF2 "
|
||||
"bootloader. Use only to return to standalone SWD-only operation."
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,72 +0,0 @@
|
|||
"""PlatformIO upload hook for env:versapad_usb.
|
||||
|
||||
The board's actual bootloader (bootloader/, UF2/mass-storage) does not speak
|
||||
SAM-BA, so upload_protocol=sam-ba (the board.json default, inherited from an
|
||||
Arduino-Zero-style template) does not work here. This converts the built
|
||||
.bin to .uf2 and copies it onto the VERSABOOT mass-storage volume instead --
|
||||
Windows only for now, matching the rest of this project's dev environment.
|
||||
"""
|
||||
import ctypes
|
||||
import os
|
||||
import string
|
||||
import sys
|
||||
|
||||
Import("env")
|
||||
|
||||
sys.path.insert(0, env.subst("$PROJECT_DIR"))
|
||||
import uf2conv
|
||||
|
||||
VOLUME_LABEL = "VERSABOOT"
|
||||
DRIVE_UNKNOWN = 0
|
||||
DRIVE_NO_ROOT_DIR = 1
|
||||
|
||||
|
||||
def find_versaboot_drive(label=VOLUME_LABEL):
|
||||
for letter in string.ascii_uppercase:
|
||||
root = f"{letter}:\\"
|
||||
drive_type = ctypes.windll.kernel32.GetDriveTypeW(root)
|
||||
if drive_type in (DRIVE_UNKNOWN, DRIVE_NO_ROOT_DIR):
|
||||
continue
|
||||
vol_name_buf = ctypes.create_unicode_buffer(261)
|
||||
fs_name_buf = ctypes.create_unicode_buffer(261)
|
||||
ok = ctypes.windll.kernel32.GetVolumeInformationW(
|
||||
ctypes.c_wchar_p(root),
|
||||
vol_name_buf,
|
||||
ctypes.sizeof(vol_name_buf),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
fs_name_buf,
|
||||
ctypes.sizeof(fs_name_buf),
|
||||
)
|
||||
if ok and vol_name_buf.value == label:
|
||||
return root
|
||||
return None
|
||||
|
||||
|
||||
def upload_via_uf2(source, target, env):
|
||||
build_dir = env.subst("$BUILD_DIR")
|
||||
bin_path = os.path.join(build_dir, "firmware.bin")
|
||||
uf2_path = os.path.join(build_dir, "firmware.uf2")
|
||||
|
||||
if not os.path.isfile(bin_path):
|
||||
print(f"error: {bin_path} not found (expected as a normal build product)")
|
||||
env.Exit(1)
|
||||
|
||||
num_blocks = uf2conv.convert(bin_path, uf2_path, base_addr=0x2000, family_id=uf2conv.SAMD21_FAMILY_ID)
|
||||
print(f"Wrote {uf2_path}: {num_blocks} blocks")
|
||||
|
||||
drive = find_versaboot_drive()
|
||||
if drive is None:
|
||||
print(f"error: no drive labeled '{VOLUME_LABEL}' found.")
|
||||
print("Hold the bottom-right Cherry MX key while plugging in USB to enter the bootloader.")
|
||||
env.Exit(1)
|
||||
|
||||
dest = os.path.join(drive, "firmware.uf2")
|
||||
print(f"Copying {uf2_path} -> {dest}")
|
||||
with open(uf2_path, "rb") as src, open(dest, "wb") as dst:
|
||||
dst.write(src.read())
|
||||
print("Upload triggers a reset into the app on the device side; no further action needed.")
|
||||
|
||||
|
||||
env.Replace(UPLOADCMD=upload_via_uf2)
|
||||
|
|
@ -6,8 +6,7 @@ SEARCH_DIR(.)
|
|||
|
||||
MEMORY
|
||||
{
|
||||
rom (rx) : ORIGIN = 0x00002000, LENGTH = 0x0001DB00 /* 118.75K – Firmware (128K - 8K bootloader - 1.25K NVM) */
|
||||
nvm (rx) : ORIGIN = 0x0001FB00, LENGTH = 0x00000500 /* 1.25K – Makros + Config, same layout as flash_without_bootloader.ld */
|
||||
rom (rx) : ORIGIN = 0x00002000, LENGTH = 0x0001E000 /* 120K (128K - 8K bootloader) */
|
||||
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00004000 /* 16K */
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue