uf2conv.py 12 KB

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