Skip to content

Commit ddf6b53

Browse files
committed
Add TRNG support and GD32 identifier-based lookup
- Add gd32_trng.h/cpp for TRNG (True Random Number Generator) support on GD32F20X/GD32F4XX devices - Refactor flash.py device lookup to use 4-char ASCII identifiers (CMD 0x06) instead of register probes - Update gd32.json to use identifier-based device entries with per-identifier series/flash mappings - Improve _try_sync and _send_command with better error handling and logging - Add --get-identifier CLI argument - Expand CI to run on all branches
1 parent 411e4b2 commit ddf6b53

5 files changed

Lines changed: 342 additions & 96 deletions

File tree

.github/workflows/c-cpp.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ name: C/C++ CI
22

33
on:
44
push:
5-
branches: [ main ]
5+
branches:
6+
- '*'
67
pull_request:
7-
branches: [ main ]
8+
branches:
9+
- '*'
810

911
jobs:
1012
build:

common/scripts/gd32/flash.py

Lines changed: 146 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -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-
8144
def 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

common/scripts/gd32/gd32.json

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,56 @@
11
{
2-
"0414": {
3-
"series": "GD32F303",
4-
"uid_base" : "",
5-
"flash": {
6-
"256": "GD32F303XCXX"
7-
}
8-
},
9-
"0418": {
10-
"series": "GD32F207",
11-
"flash": {
12-
"1024": "GD32F207XRXX"
13-
}
14-
},
15-
"0x0419": {
16-
"series": "GD32F407/GD32F450",
17-
"probes": {
18-
"tli_apb2en_bit26": {
19-
"address": "0x40021024",
20-
"bit": 26,
21-
"set": "GD32F450",
22-
"clear": "GD32F407"
23-
}
24-
},
25-
"flash": {
26-
"GD32F407": {
27-
"512": "GD32F407XEXX"
28-
}
29-
}
30-
}
31-
}
2+
"0414": {
3+
"identifiers": {
4+
"3RCF": {
5+
"series": "GD32F303",
6+
"flash": {
7+
"256": "GD32F303RCXX"
8+
}
9+
},
10+
"3RCB": {
11+
"series": "GD32F103",
12+
"flash": {
13+
"256": "GD32F103RCXX"
14+
}
15+
}
16+
}
17+
},
18+
"0418": {
19+
"identifiers": {
20+
"7RCB": {
21+
"series": "GD32F107",
22+
"flash": {
23+
"256": "GD32F107RCXX"
24+
}
25+
},
26+
"7RGC": {
27+
"series": "GD32F207",
28+
"flash": {
29+
"1024": "GD32F207RGXX"
30+
}
31+
}
32+
}
33+
},
34+
"0419": {
35+
"identifiers": {
36+
"7REE": {
37+
"series": "GD32F407",
38+
"flash": {
39+
"512": "GD32F407REXX"
40+
}
41+
},
42+
"9VIE": {
43+
"series": "GD32F450",
44+
"flash": {
45+
"2048": "GD32F450VIXX"
46+
}
47+
},
48+
"0VGN": {
49+
"series": "GD32F470",
50+
"flash": {
51+
"1024": "GD32F470VGXX"
52+
}
53+
}
54+
}
55+
}
56+
}

0 commit comments

Comments
 (0)