Compare commits

..

No commits in common. "master" and "feature/usb-bootloader" have entirely different histories.

4 changed files with 8 additions and 151 deletions

View file

@ -55,19 +55,6 @@ cd bootloader && pio run -e versapad_bootloader --target upload # einmalig
pio run -e versapad_usb --target upload # danach jedes App-Update 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 Details, Speicherlayout und die Bootloader-Aktivierung (kein physischer
Reset-Taster auf dieser Platine) stehen in Reset-Taster auf dieser Platine) stehen in
[10_usb_bootloader.md](doc/10_usb_bootloader.md). [10_usb_bootloader.md](doc/10_usb_bootloader.md).

View file

@ -1,5 +0,0 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch

View file

@ -56,30 +56,6 @@ erkennt den Schreibzugriff und springt selbständig in die neue App.
`upload_uf2.py` sucht das Laufwerk aktuell nur über die Windows-API `upload_uf2.py` sucht das Laufwerk aktuell nur über die Windows-API
(`GetVolumeInformationW`) — keine macOS/Linux-Unterstützung. (`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 ## Herkunft und Details
Der Bootloader ist abgeleitet und stark eingekürzt aus Der Bootloader ist abgeleitet und stark eingekürzt aus
@ -111,12 +87,6 @@ stehen in `bootloader/README.md` unter "Hardwaretest"; kurz zusammengefasst:
immer `.bin` mit expliziter Adresse und getrennte OpenOCD-Aufrufe immer `.bin` mit expliziter Adresse und getrennte OpenOCD-Aufrufe
verwenden — Details in `bootloader/README.md`, "Achtung bei manuellem verwenden — Details in `bootloader/README.md`, "Achtung bei manuellem
SWD-Flashen". 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 ## Bekannte Einschränkungen

View file

@ -2,118 +2,23 @@ Import("env")
import os import os
import subprocess import subprocess
# Real, already-built bootloader image used as the reference for the presence def upload_via_openocd(source, target, env):
# check below -- a raw synthetic probe blob turned out unreliable with pkg_dir = env.PioPlatform().get_package_dir("tool-openocd")
# verify_image (silent no-op on tiny files), whereas verify_image against a openocd = os.path.join(pkg_dir, "bin", "openocd.exe")
# real firmware .bin has been solid throughout this project's bring-up. scripts = os.path.join(pkg_dir, "scripts")
BOOTLOADER_BIN = os.path.join( firmware = str(source[0]) # .elf path
"bootloader", ".pio", "build", "versapad_bootloader", "firmware.bin"
)
def _openocd_paths(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"),
)
def _run_openocd(env, extra_cmd, capture=False):
openocd, scripts = _openocd_paths(env)
cmd = [ cmd = [
openocd, openocd,
"-s", scripts, "-s", scripts,
"-f", "interface/cmsis-dap.cfg", "-f", "interface/cmsis-dap.cfg",
"-f", "target/at91samdXX.cfg", "-f", "target/at91samdXX.cfg",
"-c", extra_cmd, "-c", 'program "{}" verify reset; shutdown'.format(firmware.replace("\\", "/"))
] ]
print(" ".join(cmd)) print(" ".join(cmd))
if capture: result = subprocess.run(cmd)
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("\\", "/"))
)
if result.returncode != 0: if result.returncode != 0:
env.Exit(1) 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) 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."
),
)