11#!/usr/bin/env python3
22# Copyright (c) Microsoft Corporation
33# SPDX-License-Identifier: MIT
4- # Copied from 7a9e1f4 of https://github.com/microsoft/uf2/blob/master/utils/uf2conv.py
4+ # Copied from 27e322f of https://github.com/microsoft/uf2/blob/master/utils/uf2conv.py
55# pylint: skip-file
66import sys
77import struct
1010import os
1111import os .path
1212import argparse
13+ import json
14+ from time import sleep
1315
1416
1517UF2_MAGIC_START0 = 0x0A324655 # "UF2\n"
1618UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected
1719UF2_MAGIC_END = 0x0AB16F30 # Ditto
1820
19- families = {
20- 'SAMD21' : 0x68ed2b88 ,
21- 'SAML21' : 0x1851780a ,
22- 'SAMD51' : 0x55114460 ,
23- 'NRF52' : 0x1b57745f ,
24- 'STM32F0' : 0x647824b6 ,
25- 'STM32F1' : 0x5ee21072 ,
26- 'STM32F2' : 0x5d1a0a2e ,
27- 'STM32F3' : 0x6b846188 ,
28- 'STM32F4' : 0x57755a57 ,
29- 'STM32F7' : 0x53b80f00 ,
30- 'STM32G0' : 0x300f5633 ,
31- 'STM32G4' : 0x4c71240a ,
32- 'STM32H7' : 0x6db66082 ,
33- 'STM32L0' : 0x202e3a91 ,
34- 'STM32L1' : 0x1e1f432d ,
35- 'STM32L4' : 0x00ff6919 ,
36- 'STM32L5' : 0x04240bdf ,
37- 'STM32WB' : 0x70d16653 ,
38- 'STM32WL' : 0x21460ff0 ,
39- 'ATMEGA32' : 0x16573617 ,
40- 'MIMXRT10XX' : 0x4FB2D5BD ,
41- 'LPC55' : 0x2abc77ec ,
42- 'GD32F350' : 0x31D228C6 ,
43- 'ESP32S2' : 0xbfdd4eee ,
44- 'RP2040' : 0xe48bff56
45- }
46-
4721INFO_FILE = "/INFO_UF2.TXT"
4822
4923appstartaddr = 0x2000
@@ -59,14 +33,19 @@ def is_hex(buf):
5933 w = buf [0 :30 ].decode ("utf-8" )
6034 except UnicodeDecodeError :
6135 return False
62- if w [0 ] == ':' and re .match (b "^[:0-9a-fA-F\r \n ]+$" , buf ):
36+ if w [0 ] == ':' and re .match (rb "^[:0-9a-fA-F\r\n]+$" , buf ):
6337 return True
6438 return False
6539
6640def convert_from_uf2 (buf ):
6741 global appstartaddr
42+ global familyid
6843 numblocks = len (buf ) // 512
6944 curraddr = None
45+ currfamilyid = None
46+ families_found = {}
47+ prev_flag = None
48+ all_flags_same = True
7049 outp = []
7150 for blockno in range (numblocks ):
7251 ptr = blockno * 512
@@ -82,9 +61,13 @@ def convert_from_uf2(buf):
8261 if datalen > 476 :
8362 assert False , "Invalid UF2 data size at " + ptr
8463 newaddr = hd [3 ]
85- if curraddr == None :
86- appstartaddr = newaddr
64+ if (hd [2 ] & 0x2000 ) and (currfamilyid == None ):
65+ currfamilyid = hd [7 ]
66+ if curraddr == None or ((hd [2 ] & 0x2000 ) and hd [7 ] != currfamilyid ):
67+ currfamilyid = hd [7 ]
8768 curraddr = newaddr
69+ if familyid == 0x0 or familyid == hd [7 ]:
70+ appstartaddr = newaddr
8871 padding = newaddr - curraddr
8972 if padding < 0 :
9073 assert False , "Block out of order at " + ptr
@@ -94,9 +77,38 @@ def convert_from_uf2(buf):
9477 assert False , "Non-word padding size at " + ptr
9578 while padding > 0 :
9679 padding -= 4
97- outp += b"\x00 \x00 \x00 \x00 "
98- outp .append (block [32 : 32 + datalen ])
80+ outp .append (b"\x00 \x00 \x00 \x00 " )
81+ if familyid == 0x0 or ((hd [2 ] & 0x2000 ) and familyid == hd [7 ]):
82+ outp .append (block [32 : 32 + datalen ])
9983 curraddr = newaddr + datalen
84+ if hd [2 ] & 0x2000 :
85+ if hd [7 ] in families_found .keys ():
86+ if families_found [hd [7 ]] > newaddr :
87+ families_found [hd [7 ]] = newaddr
88+ else :
89+ families_found [hd [7 ]] = newaddr
90+ if prev_flag == None :
91+ prev_flag = hd [2 ]
92+ if prev_flag != hd [2 ]:
93+ all_flags_same = False
94+ if blockno == (numblocks - 1 ):
95+ print ("--- UF2 File Header Info ---" )
96+ families = load_families ()
97+ for family_hex in families_found .keys ():
98+ family_short_name = ""
99+ for name , value in families .items ():
100+ if value == family_hex :
101+ family_short_name = name
102+ print ("Family ID is {:s}, hex value is 0x{:08x}" .format (family_short_name ,family_hex ))
103+ print ("Target Address is 0x{:08x}" .format (families_found [family_hex ]))
104+ if all_flags_same :
105+ print ("All block flag values consistent, 0x{:04x}" .format (hd [2 ]))
106+ else :
107+ print ("Flags were not all the same" )
108+ print ("----------------------------" )
109+ if len (families_found ) > 1 and familyid == 0x0 :
110+ outp = []
111+ appstartaddr = 0x0
100112 return b"" .join (outp )
101113
102114def convert_to_carray (file_content ):
@@ -170,11 +182,10 @@ def convert_from_hex_to_uf2(buf):
170182 upper = ((rec [4 ] << 8 ) | rec [5 ]) << 16
171183 elif tp == 2 :
172184 upper = ((rec [4 ] << 8 ) | rec [5 ]) << 4
173- assert (upper & 0xffff ) == 0
174185 elif tp == 1 :
175186 break
176187 elif tp == 0 :
177- addr = upper | ( rec [1 ] << 8 ) | rec [2 ]
188+ addr = upper + (( rec [1 ] << 8 ) | rec [2 ])
178189 if appstartaddr == None :
179190 appstartaddr = addr
180191 i = 4
@@ -201,19 +212,21 @@ def get_drives():
201212 "get" , "DeviceID," , "VolumeName," ,
202213 "FileSystem," , "DriveType" ])
203214 for line in to_str (r ).split ('\n ' ):
204- words = re .split ('\s+' , line )
215+ words = re .split (r '\s+' , line )
205216 if len (words ) >= 3 and words [1 ] == "2" and words [2 ] == "FAT" :
206217 drives .append (words [0 ])
207218 else :
208- rootpath = "/media"
219+ searchpaths = [ "/media" ]
209220 if sys .platform == "darwin" :
210- rootpath = "/Volumes"
221+ searchpaths = [ "/Volumes" ]
211222 elif sys .platform == "linux" :
212- tmp = rootpath + "/" + os .environ ["USER" ]
213- if os .path .isdir (tmp ):
214- rootpath = tmp
215- for d in os .listdir (rootpath ):
216- drives .append (os .path .join (rootpath , d ))
223+ searchpaths += ["/media/" + os .environ ["USER" ], '/run/media/' + os .environ ["USER" ]]
224+
225+ for rootpath in searchpaths :
226+ if os .path .isdir (rootpath ):
227+ for d in os .listdir (rootpath ):
228+ if os .path .isdir (rootpath ):
229+ drives .append (os .path .join (rootpath , d ))
217230
218231
219232 def has_info (d ):
@@ -228,7 +241,7 @@ def has_info(d):
228241def board_id (path ):
229242 with open (path + INFO_FILE , mode = 'r' ) as file :
230243 file_content = file .read ()
231- return re .search ("Board-ID: ([^\r \n ]*)" , file_content ).group (1 )
244+ return re .search (r "Board-ID: ([^\r\n]*)" , file_content ).group (1 )
232245
233246
234247def list_drives ():
@@ -242,36 +255,57 @@ def write_file(name, buf):
242255 print ("Wrote %d bytes to %s" % (len (buf ), name ))
243256
244257
258+ def load_families ():
259+ # The expectation is that the `uf2families.json` file is in the same
260+ # directory as this script. Make a path that works using `__file__`
261+ # which contains the full path to this script.
262+ filename = "uf2families.json"
263+ pathname = os .path .join (os .path .dirname (os .path .abspath (__file__ )), filename )
264+ with open (pathname ) as f :
265+ raw_families = json .load (f )
266+
267+ families = {}
268+ for family in raw_families :
269+ families [family ["short_name" ]] = int (family ["id" ], 0 )
270+
271+ return families
272+
273+
245274def main ():
246275 global appstartaddr , familyid
247276 def error (msg ):
248- print (msg )
277+ print (msg , file = sys . stderr )
249278 sys .exit (1 )
250- parser = argparse .ArgumentParser (description = 'Convert to UF2 or flash directly.' ,
251- allow_abbrev = False )
279+ parser = argparse .ArgumentParser (description = 'Convert to UF2 or flash directly.' )
252280 parser .add_argument ('input' , metavar = 'INPUT' , type = str , nargs = '?' ,
253281 help = 'input file (HEX, BIN or UF2)' )
254- parser .add_argument ('-b' , '--base' , dest = 'base' , type = str ,
282+ parser .add_argument ('-b' , '--base' , dest = 'base' , type = str ,
255283 default = "0x2000" ,
256284 help = 'set base address of application for BIN format (default: 0x2000)' )
257- parser .add_argument ('-o' , '--output' , metavar = "FILE" , dest = 'output' , type = str ,
285+ parser .add_argument ('-f' , '--family' , dest = 'family' , type = str ,
286+ default = "0x0" ,
287+ help = 'specify familyID - number or name (default: 0x0)' )
288+ parser .add_argument ('-o' , '--output' , metavar = "FILE" , dest = 'output' , type = str ,
258289 help = 'write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible' )
259- parser .add_argument ('-d' , '--device' , dest = "device_path" ,
290+ parser .add_argument ('-d' , '--device' , dest = "device_path" ,
260291 help = 'select a device path to flash' )
261- parser .add_argument ('-l' , '--list' , action = 'store_true' ,
292+ parser .add_argument ('-l' , '--list' , action = 'store_true' ,
262293 help = 'list connected devices' )
263- parser .add_argument ('-c' , '--convert' , action = 'store_true' ,
294+ parser .add_argument ('-c' , '--convert' , action = 'store_true' ,
264295 help = 'do not flash, just convert' )
265- parser .add_argument ('-D' , '--deploy' , action = 'store_true' ,
296+ parser .add_argument ('-D' , '--deploy' , action = 'store_true' ,
266297 help = 'just flash, do not convert' )
267- parser .add_argument ('-f' , '--family' , dest = 'family' , type = str ,
268- default = "0x0" ,
269- help = 'specify familyID - number or name (default: 0x0)' )
270- parser .add_argument ('-C' , '--carray' , action = 'store_true' ,
298+ parser .add_argument ('-w' , '--wait' , action = 'store_true' ,
299+ help = 'wait for device to flash' )
300+ parser .add_argument ('-C' , '--carray' , action = 'store_true' ,
271301 help = 'convert binary file to a C array, not UF2' )
302+ parser .add_argument ('-i' , '--info' , action = 'store_true' ,
303+ help = 'display header information from UF2, do not convert' )
272304 args = parser .parse_args ()
273305 appstartaddr = int (args .base , 0 )
274306
307+ families = load_families ()
308+
275309 if args .family .upper () in families :
276310 familyid = families [args .family .upper ()]
277311 else :
@@ -291,33 +325,40 @@ def error(msg):
291325 ext = "uf2"
292326 if args .deploy :
293327 outbuf = inpbuf
294- elif from_uf2 :
328+ elif from_uf2 and not args . info :
295329 outbuf = convert_from_uf2 (inpbuf )
296330 ext = "bin"
331+ elif from_uf2 and args .info :
332+ outbuf = ""
333+ convert_from_uf2 (inpbuf )
297334 elif is_hex (inpbuf ):
298335 outbuf = convert_from_hex_to_uf2 (inpbuf .decode ("utf-8" ))
299336 elif args .carray :
300337 outbuf = convert_to_carray (inpbuf )
301338 ext = "h"
302339 else :
303340 outbuf = convert_to_uf2 (inpbuf )
304- print ("Converting to %s, output size: %d, start address: 0x%x" %
305- (ext , len (outbuf ), appstartaddr ))
341+ if not args .deploy and not args .info :
342+ print ("Converted to %s, output size: %d, start address: 0x%x" %
343+ (ext , len (outbuf ), appstartaddr ))
306344 if args .convert or ext != "uf2" :
307- drives = []
308345 if args .output == None :
309346 args .output = "flash." + ext
310- else :
311- drives = get_drives ()
312-
313347 if args .output :
314348 write_file (args .output , outbuf )
315- else :
349+ if ext == "uf2" and not args .convert and not args .info :
350+ drives = get_drives ()
316351 if len (drives ) == 0 :
317- error ("No drive to deploy." )
318- for d in drives :
319- print ("Flashing %s (%s)" % (d , board_id (d )))
320- write_file (d + "/NEW.UF2" , outbuf )
352+ if args .wait :
353+ print ("Waiting for drive to deploy..." )
354+ while len (drives ) == 0 :
355+ sleep (0.1 )
356+ drives = get_drives ()
357+ elif not args .output :
358+ error ("No drive to deploy." )
359+ for d in drives :
360+ print ("Flashing %s (%s)" % (d , board_id (d )))
361+ write_file (d + "/NEW.UF2" , outbuf )
321362
322363
323364if __name__ == "__main__" :
0 commit comments