forked from micropython/micropython
-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathmake-af-csv.py
286 lines (239 loc) · 10.7 KB
/
make-af-csv.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
#!/usr/bin/env python
"""
Generates MCU alternate-function (AF) definition files for stm32 parts
using the official sources from:
https://github.com/STMicroelectronics/STM32_open_pin_data
Usage:
$ python3 make-af-csv.py <mcu_name>
Example:
$ python3 make-af-csv.py stm32wb55
"""
import json
import re
import sys
import urllib.request
import xml.etree.ElementTree as ET
from collections import defaultdict
from io import BytesIO
from pathlib import Path
# https://docs.github.com/en/rest/git/trees?apiVersion=2022-11-28#get-a-tree
# Note this API has a limit of 60 (unauthenticated) requests per hour.
repo_url = "https://api.github.com/repos/STMicroelectronics/STM32_open_pin_data/git/trees/"
# Raw xml files can be downloaded here
xml_url = "https://raw.githubusercontent.com/STMicroelectronics/STM32_open_pin_data/master/mcu/"
mcu_list_all = None
def generate_af_csv(target: str) -> None:
"""
Generates an AF CSV file based on the given target.
Parameters:
- target (str): The mcu to generate the AF CSV for.
"""
global mcu_list_all
print("Generating AF file for:", target)
cpu = target.rstrip("x").upper()
if mcu_list_all is None:
with urllib.request.urlopen(repo_url + "master") as response:
repo_list_all = json.load(response)["tree"]
mcu_list_url = [entry for entry in repo_list_all if entry["path"] == "mcu"][0]["url"]
with urllib.request.urlopen(mcu_list_url + "?recursive=1") as response:
mcu_list_all = json.load(response)["tree"]
# Get list of all mcu files matching the target
mcu_list = [mcu for mcu in mcu_list_all if mcu.get("path").upper().startswith(cpu)]
if not mcu_list:
raise SystemExit('ERROR: Could not find mcu "{}" on: \n{}'.format(cpu, mcu_list_url))
# Check the mcu definition files for one of the matching mcus,
# pick largest to include all possible pins.
gpio_file_url = None
mcu_xml_len = 0
mcu_url = None
for mcu in mcu_list:
mcu_xml_url = xml_url + mcu["path"]
with urllib.request.urlopen(mcu_xml_url) as response:
length = int(response.headers["content-length"])
if length > mcu_xml_len:
mcu_xml_len = length
mcu_url = mcu_xml_url
with urllib.request.urlopen(mcu_url) as response:
mcuxmlstr = response.read()
print("Downloaded:", mcu_url)
# The STM xml files have a dummy namespace declared which confuses ElementTree.
# This wrapper iter removes them.
it = ET.iterparse(BytesIO(mcuxmlstr))
for _, el in it:
_, _, el.tag = el.tag.rpartition("}") # strip ns
root = it.root
# This mcu definition file declares which pins have ADC pins. Gather them now.
adc_pins = dict()
for pin in root.findall("./Pin"):
for sig in pin.findall("./Signal"):
sig_name = sig.get("Name")
if sig_name.startswith("ADC") and "_IN" in sig_name:
# On some parts they omit the 1 on the first ADC peripheral
sig_name = sig_name.replace("ADC_", "ADC1_")
index, channel = sig_name.split("_")
pname = pin_name(pin.get("Name"))
dual_pad_pin = pin.get("Name").endswith("_C")
if dual_pad_pin:
# https://github.com/micropython/micropython/pull/13764
pname += "_C"
# Keep just the ADC index and IN channel.
n_index = re.search("\d+", index)[0]
if pname in adc_pins:
if channel not in adc_pins[pname]:
adc_pins[pname][channel] = n_index
else:
# Merge ADC unit together, eg ADC1 and ADC3 becomes ADC13
adc_pins[pname][channel] = "".join(
sorted(adc_pins[pname][channel] + n_index)
)
else:
adc_pins[pname] = {channel: n_index}
# The mcu definition xml file includes a reference to the GPIO definition file
# matching the chip. This is the file with AF data.
for detail in root.findall("./IP[@Name='GPIO']"):
gpio_file = [
gpio
for gpio in mcu_list_all
if gpio["path"].startswith("IP/GPIO") and detail.get("Version") in gpio["path"]
][0]
gpio_file_url = xml_url + gpio_file["path"]
break
if gpio_file_url is None:
raise SystemError("ERROR: Could not find GPIO details in {}".format(mcu_xml_url))
with urllib.request.urlopen(gpio_file_url) as response:
xml_data = response.read()
print("Downloaded:", gpio_file_url)
# Parse all the alternate function mappings from the xml file.
it = ET.iterparse(BytesIO(xml_data))
for _, el in it:
_, _, el.tag = el.tag.rpartition("}")
root = it.root
af_domains = set()
mapping = defaultdict(list)
for pin in root.findall("./GPIO_Pin"):
pname = pin_name(pin.get("Name"))
if pname is None:
continue
_ = mapping[pname] # Ensure pin gets captured / listed
for pin_signal in pin.findall("./PinSignal"):
for specific_param in pin_signal.findall("SpecificParameter"):
if specific_param.get("Name") != "GPIO_AF":
continue
signal_name = pin_signal.get("Name")
af_fn = specific_param.find("./PossibleValue").text
mapping[pname].append((signal_name, af_fn))
af_domains.add(af_fn.split("_")[1])
# Note: "AF0" though "AF15" are somewhat standard across STM range,
# however not all chips use all domains. List them all for consistency however.
af_domains = ["AF%s" % i for i in range(16)] + ["ADC"]
# Format the AF data into appropriate CSV file.
# First row in CSV is the heading, going through all the AF domains
heading = ["Port", "Pin"] + af_domains
col_width = [4] * len(heading)
# Second csv row lists the peripherals handled by that column
category_parts = [defaultdict(set) for _ in range(len(heading))]
# Third row onwards are the individual pin function mappings
rows = []
for pin in sorted(mapping, key=pin_int):
functions = mapping[pin]
row = [""] * len(heading)
row[0] = "Port%s" % pin[1]
row[1] = pin
for signal, af in functions:
# Some signals are loose about whether 1 is declared or not.
signal = signal.replace("CAN_", "CAN1_")
signal = signal.replace("I2S_", "I2S1_")
# / is used later to delineate multiple functions
signal = signal.replace("/", "_")
if signal.startswith("ETH"):
signal = eth_remap(signal)
column = heading.index(re.search("(AF\d+)", af).group(1))
peripheral = re.search("([A-Z1-9]*[A-Z]+)(\d*)(_|$)", signal)
category_parts[column][peripheral[1]].add(peripheral[2])
signal = signal.replace("SYS_", "")
signal = signal.replace("-", "/")
if row[column]:
if signal not in row[column].split("/"):
# multiple af signals per pin
row[column] = "/".join((row[column], signal))
else:
row[column] = signal
if pin in adc_pins:
row[-1] = "/".join(("ADC{}_{}".format(i, ch) for ch, i in adc_pins[pin].items()))
for i, val in enumerate(row):
col_width[i] = max(col_width[i], len(val))
rows.append(row)
if (dual_pad := pin + "_C") in adc_pins:
# Add extra row for dual-pad ADC entry.
row_c = [""] * len(row)
row_c[0] = row[0]
row_c[1] = dual_pad
row_c[-1] = "/".join(
("ADC{}_{}".format(i, ch) for ch, i in adc_pins[dual_pad].items())
)
rows.append(row_c)
# Simplify the category sets, eg TIM1/TIM2 -> TIM1/2
category_head = ["" for _ in range(len(heading))]
for i, periph in enumerate(category_parts):
periphs = []
for key, vals in periph.items():
if len(vals) > 1:
if "" in vals:
vals.remove("")
vals.add("1")
periphs.append(key + "/".join(sorted(vals, key=int)))
else:
periphs.append(key + vals.pop())
category_head[i] = "/".join(sorted(periphs))
col_width[i] = max(col_width[i], len(category_head[i]))
category_head[-1] = "ADC"
# Sort the rows by the first two columns; port and pin number
rows.sort(key=lambda row: (row[0], pin_int(row[1])))
output_file = Path(__file__).parent / ("%s_af.csv" % target.lower())
with output_file.open("w") as out:
print("# This was auto-generated by make-af.csv.py", file=out)
padded_heading = [val.ljust(col_width[i]) for i, val in enumerate(heading)]
print(",".join(padded_heading).strip(), file=out)
categories = [val.ljust(col_width[i]) for i, val in enumerate(category_head)]
print(",".join(categories).rstrip(), file=out)
for row in rows:
padded_row = [val.ljust(col_width[i]) for i, val in enumerate(row)]
print(",".join(padded_row).strip(), file=out)
print("Written:", output_file.resolve())
def pin_int(pname: str):
# Takes a Pin (or AF) name like PA5, PD15 or AF4 and returns the integer component
return int(re.search("[PA][A-Z](\d+)", pname).group(1))
def pin_name(pin: str):
# Filters out just the pin name, eg PB12 from provided pin string
return (re.search("P[A-Z]\d+", pin) or [None])[0]
def eth_remap(signal):
# The AF signal names in stm xml's generally match the names used in datasheets,
# except for ethernet RMII / MII ones...
# This mapping was generated from comparing xml data to datasheet on STM32H573
eth_signals = {
"ETH_MDC": "ETH_MDC",
"ETH_MDIO": "ETH_MDIO",
"ETH_PPS_OUT": "ETH_PPS_OUT",
"ETH_CRS": "ETH_MII_CRS",
"ETH_COL": "ETH_MII_COL",
"ETH_TX_ER": "ETH_MII_TX_ER",
"ETH_RXD2": "ETH_MII_RXD2",
"ETH_RXD3": "ETH_MII_RXD3",
"ETH_TXD3": "ETH_MII_TXD3",
"ETH_RX_ER": "ETH_MII_RX_ER",
"ETH_TXD2": "ETH_MII_TXD2",
"ETH_TX_CLK": "ETH_MII_TX_CLK",
"ETH_RX_CLK": "ETH_MII_RX_CLK",
"ETH_RX_DV": "ETH_MII_RX_DV",
"ETH_REF_CLK": "ETH_RMII_REF_CLK",
"ETH_CRS_DV": "ETH_RMII_CRS_DV",
"ETH_TX_EN": "ETH_MII_TX_EN/ETH_RMII_TX_EN",
"ETH_TXD0": "ETH_MII_TXD0/ETH_RMII_TXD0",
"ETH_TXD1": "ETH_MII_TXD1/ETH_RMII_TXD1",
"ETH_RXD0": "ETH_MII_RXD0/ETH_RMII_RXD0",
"ETH_RXD1": "ETH_MII_RXD1/ETH_RMII_RXD1",
}
return eth_signals.get(signal, signal)
if __name__ == "__main__":
for target in sys.argv[1:]:
generate_af_csv(target)