@@ -41,73 +41,33 @@ def db_get_family(db, chip_id):
4141
4242 return None
4343
44- def lookup_part_number (family , series , flash_size_kb ):
45- flash = family .get ("flash" , {})
46- size_key = str (flash_size_kb )
47-
48- # Simple format:
49- # "flash": { "512": "GD32F207XEXX" }
50- part_number = flash .get (size_key )
51- if isinstance (part_number , str ):
52- return part_number
53-
54- # Nested format with exact resolved series:
55- # "flash": { "GD32F407": { "512": "GD32F407XEXX" } }
56- # This is used after a probe has changed the generic family series
57- # "GD32F407/GD32F450" into the concrete series "GD32F407".
58- series_flash = flash .get (series )
59- if isinstance (series_flash , dict ):
60- part_number = series_flash .get (size_key )
61- if isinstance (part_number , str ):
62- return part_number
63-
64- # Fallback for generic or unresolved series names, for example when
65- # series is still "GD32F407/GD32F450" or when probing was not available.
66- # Search all nested flash maps and return the part when the flash size is
67- # unambiguous.
68- matches = []
69- for value in flash .values ():
70- if not isinstance (value , dict ):
71- continue
72- part_number = value .get (size_key )
73- if isinstance (part_number , str ):
74- matches .append (part_number )
75-
76- if len (matches ) == 1 :
77- return matches [0 ]
78-
79- return "Unknown"
80-
8144def lookup_device (db , chip_id , flash_size_kb , flasher = None ):
8245 family = db_get_family (db , chip_id )
8346 if family is None :
8447 return {
48+ "identifier" : "Unknown" ,
8549 "series" : "Unknown" ,
86- "part_number" : "Unknown"
50+ "part_number" : "Unknown" ,
8751 }
8852
89- series = family .get ("series" , "Unknown" )
90-
91- probes = family .get ("probes" , {})
53+ identifier = None
54+ device = family
9255
93- if "tli_apb2en_bit26" in probes and flasher is not None :
94- probe = probes ["tli_apb2en_bit26" ]
95-
96- detected = flasher .probe_register_bit (
97- int (probe ["address" ], 0 ),
98- int (probe ["bit" ])
99- )
56+ if flasher is not None :
57+ identifier = flasher .get_identifier ()
10058
101- if detected is True :
102- series = probe .get ("set" , series )
103- elif detected is False :
104- series = probe .get ("clear" , series )
59+ identifiers = family .get ("identifiers" , {})
60+ if identifier is not None and isinstance (identifiers , dict ):
61+ device = identifiers .get (identifier , family )
10562
106- part_number = lookup_part_number (family , series , flash_size_kb )
63+ series = device .get ("series" , family .get ("series" , "Unknown" ))
64+ flash = device .get ("flash" , family .get ("flash" , {}))
65+ part_number = flash .get (str (flash_size_kb ), "Unknown" )
10766
10867 return {
68+ "identifier" : identifier or "Unknown" ,
10969 "series" : series ,
110- "part_number" : part_number
70+ "part_number" : part_number ,
11171 }
11272
11373
@@ -118,6 +78,7 @@ class GD32Flasher:
11878 CMD_GET = 0x00
11979 CMD_GET_VERSION = 0x01
12080 CMD_GET_ID = 0x02
81+ CMD_GET_IDENTIFIER = 0x06
12182 CMD_READ_MEMORY = 0x11
12283 CMD_GO = 0x21
12384 CMD_WRITE_MEMORY = 0x31
@@ -171,18 +132,78 @@ def enter_bootloader(self):
171132 return False
172133
173134 def _try_sync (self , attempts = 3 ):
174- for _ in range (attempts ):
175- self .port .write (bytes ([0x7F ]))
176- time .sleep (0.1 )
177- resp = self .port .read (1 )
178- if resp and resp [0 ] == self .ACK :
179- return True
180- return False
135+ original_timeout = self .port .timeout
136+
137+ try :
138+ for attempt in range (1 , attempts + 1 ):
139+ self .port .reset_input_buffer ()
140+
141+ print (f" Synchronization attempt { attempt } " )
142+ self .port .write (b"\x7F " )
143+ self .port .flush ()
144+
145+ deadline = time .monotonic () + 1.0
146+
147+ while time .monotonic () < deadline :
148+ self .port .timeout = max (
149+ deadline - time .monotonic (),
150+ 0.01
151+ )
152+
153+ response = self .port .read (1 )
154+ if not response :
155+ break
156+
157+ value = response [0 ]
158+ print (f" Synchronization RX: 0x{ value :02X} " )
159+
160+ if value == self .ACK :
161+ return True
162+
163+ if value == self .NACK :
164+ break
165+
166+ if value == 0x7F :
167+ # Possible local echo.
168+ continue
169+
170+ time .sleep (0.05 )
171+
172+ return False
173+
174+ finally :
175+ self .port .timeout = original_timeout
181176
182177 def _send_command (self , cmd ):
183- self .port .write (bytes ([cmd , cmd ^ 0xFF ]))
184- resp = self .port .read (1 )
185- return resp and resp [0 ] == self .ACK
178+ command = bytes ([cmd , cmd ^ 0xFF ])
179+
180+ print (
181+ f" Command TX: "
182+ f"0x{ command [0 ]:02X} 0x{ command [1 ]:02X} "
183+ )
184+
185+ self .port .write (command )
186+ self .port .flush ()
187+
188+ response = self .port .read (1 )
189+
190+ if not response :
191+ print (f" Command 0x{ cmd :02X} : timeout" )
192+ return False
193+
194+ print (f" Command RX: 0x{ response [0 ]:02X} " )
195+
196+ if response [0 ] == self .ACK :
197+ return True
198+
199+ if response [0 ] == self .NACK :
200+ print (f" Command 0x{ cmd :02X} : NACK" )
201+ else :
202+ print (
203+ f" Command 0x{ cmd :02X} : unexpected response"
204+ )
205+
206+ return False
186207
187208 def _wait_ack (self ):
188209 resp = self .port .read (1 )
@@ -217,6 +238,57 @@ def get_id(self):
217238 self ._wait_ack ()
218239 return chip_id .hex ()
219240
241+ def get_identifier (self ):
242+ """Return the four-character GD32 device identifier.
243+
244+ Command 0x06 may return more than four payload bytes on newer
245+ devices. The first four bytes contain the printable identifier;
246+ any remaining bytes are vendor-specific extension data.
247+ """
248+ if not self ._send_command (self .CMD_GET_IDENTIFIER ):
249+ return None
250+
251+ length_data = self .port .read (1 )
252+ if len (length_data ) != 1 :
253+ print (" Identifier: timeout while reading payload length" )
254+ return None
255+
256+ length = length_data [0 ]
257+ if length < 4 or length > 32 :
258+ print (f" Identifier: invalid payload length { length } " )
259+ return None
260+
261+ payload = self .port .read (length )
262+ if len (payload ) != length :
263+ print (
264+ f" Identifier: expected { length } payload bytes, "
265+ f"received { len (payload )} "
266+ )
267+ return None
268+
269+ if not self ._wait_ack ():
270+ print (" Identifier: missing final ACK" )
271+ return None
272+
273+ identifier_data = payload [:4 ]
274+ if not all (0x20 <= value <= 0x7E for value in identifier_data ):
275+ print (
276+ " Identifier: first four bytes are not printable ASCII: "
277+ + identifier_data .hex (" " ).upper ()
278+ )
279+ return None
280+
281+ identifier = identifier_data .decode ("ascii" )
282+
283+ if length > 4 :
284+ extension = payload [4 :]
285+ print (
286+ f" Identifier payload: { payload .hex (' ' ).upper ()} "
287+ f"(extension: { extension .hex (' ' ).upper ()} )"
288+ )
289+
290+ return identifier
291+
220292 def read_memory (self , address , length ):
221293 if not 1 <= length <= 256 :
222294 raise ValueError ("length must be 1..256" )
@@ -404,6 +476,7 @@ def print_device_info(flasher, gd32_db):
404476 device = lookup_device (gd32_db , chip_id , size_kb , flasher )
405477
406478 print (f"Chip ID : { chip_id } " )
479+ print (f"Identifier : { device ['identifier' ]} " )
407480
408481 if device :
409482 print (f"Series : { device ['series' ]} " )
@@ -482,6 +555,7 @@ def main():
482555 parser .add_argument ("--monitor" , action = "store_true" , help = "Start UART monitor" )
483556 parser .add_argument ("--get-version" , action = "store_true" , help = "Read bootloader version" )
484557 parser .add_argument ("--get-id" , action = "store_true" , help = "Read chip ID" )
558+ parser .add_argument ("--get-identifier" , action = "store_true" , help = "Read device identifier" )
485559 parser .add_argument ("--get-uid" , action = "store_true" , help = "Read chip UID" )
486560 parser .add_argument ("--get-size" , action = "store_true" , help = "Read flash size, series and part number" )
487561 parser .add_argument ("--db" , default = "gd32.json" , help = "GD32 JSON database file" )
@@ -513,6 +587,7 @@ def main():
513587 needs_bootloader = any ([
514588 args .get_version ,
515589 args .get_id ,
590+ args .get_identifier ,
516591 args .get_uid ,
517592 args .get_size ,
518593 args .mass_erase ,
@@ -550,6 +625,13 @@ def main():
550625 print (f"Chip ID: { chip_id } " )
551626 chip_id_already_printed = True
552627
628+ if args .get_identifier and not args .get_size :
629+ identifier = flasher .get_identifier ()
630+ if identifier is None :
631+ print ("Failed to read device identifier" )
632+ return 1
633+ print (f"Identifier: { identifier } " )
634+
553635 if args .get_size :
554636 if not print_device_info (flasher , gd32_db ):
555637 return 1
0 commit comments