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>
72 lines
2.4 KiB
Python
72 lines
2.4 KiB
Python
"""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)
|