#!/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 [--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( " 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()