uf2conv.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. #!/usr/bin/env python3
  2. import sys
  3. import struct
  4. import subprocess
  5. import re
  6. import os
  7. import os.path
  8. import argparse
  9. UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
  10. UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
  11. UF2_MAGIC_END = 0x0AB16F30 # Ditto
  12. families = {
  13. 'SAMD21': 0x68ed2b88,
  14. 'SAML21': 0x1851780a,
  15. 'SAMD51': 0x55114460,
  16. 'NRF52': 0x1b57745f,
  17. 'STM32F0': 0x647824b6,
  18. 'STM32F1': 0x5ee21072,
  19. 'STM32F2': 0x5d1a0a2e,
  20. 'STM32F3': 0x6b846188,
  21. 'STM32F4': 0x57755a57,
  22. 'STM32F7': 0x53b80f00,
  23. 'STM32G0': 0x300f5633,
  24. 'STM32G4': 0x4c71240a,
  25. 'STM32H7': 0x6db66082,
  26. 'STM32L0': 0x202e3a91,
  27. 'STM32L1': 0x1e1f432d,
  28. 'STM32L4': 0x00ff6919,
  29. 'STM32L5': 0x04240bdf,
  30. 'STM32WB': 0x70d16653,
  31. 'STM32WL': 0x21460ff0,
  32. 'ATMEGA32': 0x16573617,
  33. 'MIMXRT10XX': 0x4FB2D5BD,
  34. 'LPC55': 0x2abc77ec,
  35. 'GD32F350': 0x31D228C6,
  36. 'ESP32S2': 0xbfdd4eee,
  37. 'RP2040': 0xe48bff56
  38. }
  39. INFO_FILE = "/INFO_UF2.TXT"
  40. appstartaddr = 0x2000
  41. familyid = 0x0
  42. def is_uf2(buf):
  43. w = struct.unpack("<II", buf[0:8])
  44. return w[0] == UF2_MAGIC_START0 and w[1] == UF2_MAGIC_START1
  45. def is_hex(buf):
  46. try:
  47. w = buf[0:30].decode("utf-8")
  48. except UnicodeDecodeError:
  49. return False
  50. if w[0] == ':' and re.match(b"^[:0-9a-fA-F\r\n]+$", buf):
  51. return True
  52. return False
  53. def convert_from_uf2(buf):
  54. global appstartaddr
  55. numblocks = len(buf) // 512
  56. curraddr = None
  57. outp = []
  58. for blockno in range(numblocks):
  59. ptr = blockno * 512
  60. block = buf[ptr:ptr + 512]
  61. hd = struct.unpack(b"<IIIIIIII", block[0:32])
  62. if hd[0] != UF2_MAGIC_START0 or hd[1] != UF2_MAGIC_START1:
  63. print("Skipping block at " + ptr + "; bad magic")
  64. continue
  65. if hd[2] & 1:
  66. # NO-flash flag set; skip block
  67. continue
  68. datalen = hd[4]
  69. if datalen > 476:
  70. assert False, "Invalid UF2 data size at " + ptr
  71. newaddr = hd[3]
  72. if curraddr == None:
  73. appstartaddr = newaddr
  74. curraddr = newaddr
  75. padding = newaddr - curraddr
  76. if padding < 0:
  77. assert False, "Block out of order at " + ptr
  78. if padding > 10*1024*1024:
  79. assert False, "More than 10M of padding needed at " + ptr
  80. if padding % 4 != 0:
  81. assert False, "Non-word padding size at " + ptr
  82. while padding > 0:
  83. padding -= 4
  84. outp += b"\x00\x00\x00\x00"
  85. outp.append(block[32 : 32 + datalen])
  86. curraddr = newaddr + datalen
  87. return b"".join(outp)
  88. def convert_to_carray(file_content):
  89. outp = "const unsigned long bindata_len = %d;\n" % len(file_content)
  90. outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {"
  91. for i in range(len(file_content)):
  92. if i % 16 == 0:
  93. outp += "\n"
  94. outp += "0x%02x, " % file_content[i]
  95. outp += "\n};\n"
  96. return bytes(outp, "utf-8")
  97. def convert_to_uf2(file_content):
  98. global familyid
  99. datapadding = b""
  100. while len(datapadding) < 512 - 256 - 32 - 4:
  101. datapadding += b"\x00\x00\x00\x00"
  102. numblocks = (len(file_content) + 255) // 256
  103. outp = []
  104. for blockno in range(numblocks):
  105. ptr = 256 * blockno
  106. chunk = file_content[ptr:ptr + 256]
  107. flags = 0x0
  108. if familyid:
  109. flags |= 0x2000
  110. hd = struct.pack(b"<IIIIIIII",
  111. UF2_MAGIC_START0, UF2_MAGIC_START1,
  112. flags, ptr + appstartaddr, 256, blockno, numblocks, familyid)
  113. while len(chunk) < 256:
  114. chunk += b"\x00"
  115. block = hd + chunk + datapadding + struct.pack(b"<I", UF2_MAGIC_END)
  116. assert len(block) == 512
  117. outp.append(block)
  118. return b"".join(outp)
  119. class Block:
  120. def __init__(self, addr):
  121. self.addr = addr
  122. self.bytes = bytearray(256)
  123. def encode(self, blockno, numblocks):
  124. global familyid
  125. flags = 0x0
  126. if familyid:
  127. flags |= 0x2000
  128. hd = struct.pack("<IIIIIIII",
  129. UF2_MAGIC_START0, UF2_MAGIC_START1,
  130. flags, self.addr, 256, blockno, numblocks, familyid)
  131. hd += self.bytes[0:256]
  132. while len(hd) < 512 - 4:
  133. hd += b"\x00"
  134. hd += struct.pack("<I", UF2_MAGIC_END)
  135. return hd
  136. def convert_from_hex_to_uf2(buf):
  137. global appstartaddr
  138. appstartaddr = None
  139. upper = 0
  140. currblock = None
  141. blocks = []
  142. for line in buf.split('\n'):
  143. if line[0] != ":":
  144. continue
  145. i = 1
  146. rec = []
  147. while i < len(line) - 1:
  148. rec.append(int(line[i:i+2], 16))
  149. i += 2
  150. tp = rec[3]
  151. if tp == 4:
  152. upper = ((rec[4] << 8) | rec[5]) << 16
  153. elif tp == 2:
  154. upper = ((rec[4] << 8) | rec[5]) << 4
  155. assert (upper & 0xffff) == 0
  156. elif tp == 1:
  157. break
  158. elif tp == 0:
  159. addr = upper | (rec[1] << 8) | rec[2]
  160. if appstartaddr == None:
  161. appstartaddr = addr
  162. i = 4
  163. while i < len(rec) - 1:
  164. if not currblock or currblock.addr & ~0xff != addr & ~0xff:
  165. currblock = Block(addr & ~0xff)
  166. blocks.append(currblock)
  167. currblock.bytes[addr & 0xff] = rec[i]
  168. addr += 1
  169. i += 1
  170. numblocks = len(blocks)
  171. resfile = b""
  172. for i in range(0, numblocks):
  173. resfile += blocks[i].encode(i, numblocks)
  174. return resfile
  175. def to_str(b):
  176. return b.decode("utf-8")
  177. def get_drives():
  178. drives = []
  179. if sys.platform == "win32":
  180. r = subprocess.check_output(["wmic", "PATH", "Win32_LogicalDisk",
  181. "get", "DeviceID,", "VolumeName,",
  182. "FileSystem,", "DriveType"])
  183. for line in to_str(r).split('\n'):
  184. words = re.split('\s+', line)
  185. if len(words) >= 3 and words[1] == "2" and words[2] == "FAT":
  186. drives.append(words[0])
  187. else:
  188. rootpath = "/media"
  189. if sys.platform == "darwin":
  190. rootpath = "/Volumes"
  191. elif sys.platform == "linux":
  192. tmp = rootpath + "/" + os.environ["USER"]
  193. if os.path.isdir(tmp):
  194. rootpath = tmp
  195. for d in os.listdir(rootpath):
  196. drives.append(os.path.join(rootpath, d))
  197. def has_info(d):
  198. try:
  199. return os.path.isfile(d + INFO_FILE)
  200. except:
  201. return False
  202. return list(filter(has_info, drives))
  203. def board_id(path):
  204. with open(path + INFO_FILE, mode='r') as file:
  205. file_content = file.read()
  206. return re.search("Board-ID: ([^\r\n]*)", file_content).group(1)
  207. def list_drives():
  208. for d in get_drives():
  209. print(d, board_id(d))
  210. def write_file(name, buf):
  211. with open(name, "wb") as f:
  212. f.write(buf)
  213. print("Wrote %d bytes to %s" % (len(buf), name))
  214. def main():
  215. global appstartaddr, familyid
  216. def error(msg):
  217. print(msg)
  218. sys.exit(1)
  219. parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.')
  220. parser.add_argument('input', metavar='INPUT', type=str, nargs='?',
  221. help='input file (HEX, BIN or UF2)')
  222. parser.add_argument('-b' , '--base', dest='base', type=str,
  223. default="0x2000",
  224. help='set base address of application for BIN format (default: 0x2000)')
  225. parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str,
  226. help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible')
  227. parser.add_argument('-d' , '--device', dest="device_path",
  228. help='select a device path to flash')
  229. parser.add_argument('-l' , '--list', action='store_true',
  230. help='list connected devices')
  231. parser.add_argument('-c' , '--convert', action='store_true',
  232. help='do not flash, just convert')
  233. parser.add_argument('-D' , '--deploy', action='store_true',
  234. help='just flash, do not convert')
  235. parser.add_argument('-f' , '--family', dest='family', type=str,
  236. default="0x0",
  237. help='specify familyID - number or name (default: 0x0)')
  238. parser.add_argument('-C' , '--carray', action='store_true',
  239. help='convert binary file to a C array, not UF2')
  240. args = parser.parse_args()
  241. appstartaddr = int(args.base, 0)
  242. if args.family.upper() in families:
  243. familyid = families[args.family.upper()]
  244. else:
  245. try:
  246. familyid = int(args.family, 0)
  247. except ValueError:
  248. error("Family ID needs to be a number or one of: " + ", ".join(families.keys()))
  249. if args.list:
  250. list_drives()
  251. else:
  252. if not args.input:
  253. error("Need input file")
  254. with open(args.input, mode='rb') as f:
  255. inpbuf = f.read()
  256. from_uf2 = is_uf2(inpbuf)
  257. ext = "uf2"
  258. if args.deploy:
  259. outbuf = inpbuf
  260. elif from_uf2:
  261. outbuf = convert_from_uf2(inpbuf)
  262. ext = "bin"
  263. elif is_hex(inpbuf):
  264. outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8"))
  265. elif args.carray:
  266. outbuf = convert_to_carray(inpbuf)
  267. ext = "h"
  268. else:
  269. outbuf = convert_to_uf2(inpbuf)
  270. print("Converting to %s, output size: %d, start address: 0x%x" %
  271. (ext, len(outbuf), appstartaddr))
  272. if args.convert or ext != "uf2":
  273. drives = []
  274. if args.output == None:
  275. args.output = "flash." + ext
  276. else:
  277. drives = get_drives()
  278. if args.output:
  279. write_file(args.output, outbuf)
  280. else:
  281. if len(drives) == 0:
  282. error("No drive to deploy.")
  283. for d in drives:
  284. print("Flashing %s (%s)" % (d, board_id(d)))
  285. write_file(d + "/NEW.UF2", outbuf)
  286. if __name__ == "__main__":
  287. main()