134 lines
4.9 KiB
Python
134 lines
4.9 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
make_uf2.py — 最小 UF2 封装器(RP2040)
|
|
==================================================
|
|
背景: pico-sdk 2.x 生成 UF2 依赖 picotool(FetchContent 拉 github), 本机
|
|
网络对 github 不通时走本脚本替代: elf 先经 arm-none-eabi-objcopy 转 bin,
|
|
再按 UF2 规范(Microsoft UF2 spec, family 变体)打包。
|
|
|
|
用法:
|
|
python make_uf2.py <app.bin> <out.uf2> [base_addr_hex]
|
|
python make_uf2.py --test # 内部往返自检(解包回来比对 payload)
|
|
|
|
UF2 块格式(512B/块): magic0 0x0A324655, magic1 0x9E5D5157,
|
|
flags 0x00002000(family), addr, len(≤476, 本工具固定 256), size, blockNo,
|
|
numBlocks, familyId=0xE48BFF56(RP2040), data[476], magicEnd 0x0AB16F30。
|
|
"""
|
|
import struct
|
|
import sys
|
|
|
|
MAGIC0 = 0x0A324655
|
|
MAGIC1 = 0x9E5D5157
|
|
MAGIC_END = 0x0AB16F30
|
|
FLAG_FAMILY = 0x00002000
|
|
FAMILY_RP2040 = 0xE48BFF56
|
|
RP2040_FLASH_BASE = 0x10000000
|
|
PAYLOAD = 256 # RP2040 flash 页大小, 256 对齐最稳
|
|
|
|
|
|
def bin_to_uf2(data: bytes, base: int = RP2040_FLASH_BASE,
|
|
family: int = FAMILY_RP2040) -> bytes:
|
|
"""块布局按官方 uf2conv 实现(经真机 UF2 逐块核对):
|
|
0:magic0 4:magic1 8:flags 12:targetAddr 16:payloadSize
|
|
20:blockNo 24:numBlocks 28:familyID 32:data[476] 508:magicEnd"""
|
|
if len(data) % PAYLOAD: # 补齐到整页
|
|
data = data + b'\x00' * (PAYLOAD - len(data) % PAYLOAD)
|
|
total = len(data) // PAYLOAD
|
|
out = bytearray()
|
|
for i in range(total):
|
|
chunk = data[i * PAYLOAD:(i + 1) * PAYLOAD]
|
|
block = struct.pack(
|
|
'<IIIIIIII',
|
|
MAGIC0, MAGIC1, FLAG_FAMILY,
|
|
base + i * PAYLOAD, # targetAddr
|
|
PAYLOAD, # payloadSize
|
|
i, total, # blockNo / numBlocks
|
|
family, # fileSize 槽位 = familyID
|
|
)
|
|
block += chunk
|
|
block += b'\x00' * (476 - PAYLOAD) # data 区补齐到 476
|
|
block += struct.pack('<I', MAGIC_END)
|
|
assert len(block) == 512
|
|
out += block
|
|
return bytes(out)
|
|
|
|
|
|
def uf2_to_payload(uf2: bytes):
|
|
"""解包自检用: 还原 (base, payload bytes); 非法块抛 ValueError"""
|
|
if len(uf2) % 512:
|
|
raise ValueError(f'uf2 长度 {len(uf2)} 不是 512 的倍数')
|
|
blocks = [uf2[i:i + 512] for i in range(0, len(uf2), 512)]
|
|
base = None
|
|
total = None
|
|
chunks = {}
|
|
for n, b in enumerate(blocks):
|
|
(m0, m1, flags, addr, nb, no, nt, fam) = struct.unpack_from('<8I', b)
|
|
if m0 != MAGIC0 or m1 != MAGIC1:
|
|
raise ValueError(f'块 {n} magic 错')
|
|
if struct.unpack_from('<I', b, 508)[0] != MAGIC_END:
|
|
raise ValueError(f'块 {n} 尾 magic 错')
|
|
if flags & FLAG_FAMILY and fam != FAMILY_RP2040:
|
|
raise ValueError(f'块 {n} family 非 RP2040: 0x{fam:08X}')
|
|
if nb > 476:
|
|
raise ValueError(f'块 {n} payloadSize 超限')
|
|
if base is None:
|
|
base, total = addr, nt
|
|
if no != n or nt != total:
|
|
raise ValueError(f'块 {n} 序号/总数错')
|
|
chunks[no] = (addr, b[32:32 + nb])
|
|
payload = bytearray()
|
|
expect = base
|
|
for i in range(total):
|
|
if i not in chunks:
|
|
raise ValueError(f'缺块 {i}')
|
|
addr, c = chunks[i]
|
|
if addr != expect:
|
|
raise ValueError(f'块 {i} 地址不连续: {addr:#x} != {expect:#x}')
|
|
payload += c
|
|
expect += len(c)
|
|
return base, bytes(payload)
|
|
|
|
|
|
def _selftest():
|
|
import random
|
|
rng = random.Random(7)
|
|
for size in (0, 1, 255, 256, 257, 4096, 4097, 65536):
|
|
blob = bytes(rng.randrange(256) for _ in range(size))
|
|
if size == 0:
|
|
continue # 空镜像无意义(0 块)
|
|
uf2 = bin_to_uf2(blob, base=0x10000000)
|
|
base, back = uf2_to_payload(uf2)
|
|
pad = (-size) % PAYLOAD
|
|
assert base == 0x10000000
|
|
assert back[:size] == blob and len(back) == size + pad, \
|
|
f'size={size} 往返不一致'
|
|
print(f'PASS make_uf2 往返自检 ({(size + (-size) % PAYLOAD) // PAYLOAD} 块)')
|
|
return 0
|
|
|
|
|
|
def main(argv):
|
|
if len(argv) >= 2 and argv[1] == '--test':
|
|
return _selftest()
|
|
if len(argv) < 3:
|
|
print(__doc__)
|
|
return 2
|
|
with open(argv[1], 'rb') as f:
|
|
data = f.read()
|
|
base = int(argv[3], 16) if len(argv) > 3 else RP2040_FLASH_BASE
|
|
uf2 = bin_to_uf2(data, base=base)
|
|
with open(argv[2], 'wb') as f:
|
|
f.write(uf2)
|
|
print(f'UF2: {len(uf2)} B, {len(uf2) // 512} 块, '
|
|
f'base=0x{base:08X}, payload={len(data)} B -> {argv[2]}')
|
|
# 写完即自检
|
|
with open(argv[2], 'rb') as f:
|
|
b, p = uf2_to_payload(f.read())
|
|
assert b == base and p[:len(data)] == data
|
|
print('PASS 写出文件解包往返一致')
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv))
|