Skip to content

Instantly share code, notes, and snippets.

@gmarull
Last active June 23, 2020 13:07
Embed
What would you like to do?
Utility to autogenerate Zephyr DT pinctrl files for all STM32 microcontrollers.
"""
Utility to autogenerate Zephyr DT pinctrl files for all STM32 microcontrollers.
Usage::
python3 stm32_pinctrl_gen.py -p /path/to/STM32CubeMX -o /path/to/output_dir
Copyright (c) 2020 Teslabs Engineering S.L.
SPDX-License-Identifier: Apache-2.0
"""
import argparse
import glob
import logging
import os
import re
import shutil
import xml.etree.ElementTree as ET
logger = logging.getLogger(__name__)
NS = "{http://mcd.rou.st.com/modules.php?name=mcu}"
"""MCU XML namespace."""
PINCTRL_TEMPLATE = """
/*
* WARNING: Autogenerated file using stm32_pinctrl_gen.py
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <dt-bindings/pinctrl/stm32-pinctrl.h>
#include "../pinctrl_st_stm32.h"
/ {{
\tsoc {{
\t\tpinctrl: pin-controller@{pinctrl_addr:08X} {{
{pinctrl_entries}
\t\t}};
\t}};
}};
"""
"""pinctrl file template."""
PINCTRL_GROUP_TEMPLATE = "\t\t\t/* {group} */\n"
"""pinctrl group template."""
PINCTRL_ENTRY_TEMPLATE = "\t\t\tDT_STM32_{signal}({port}, {pin}, {periph}, AF{af})\n"
"""pinctrl entry template."""
PINCTRL_MAPPINGS = [
{
"name": "UART_CTS",
"matches": [
{"re": r"UART(\d+)_CTS", "periph": "uart"},
{"re": r"USART(\d+)_CTS", "periph": "usart"},
],
},
{
"name": "UART_RTS",
"matches": [
{"re": r"UART(\d+)_RTS", "periph": "uart"},
{"re": r"USART(\d+)_RTS", "periph": "usart"},
],
},
{
"name": "UART_TX",
"matches": [
{"re": r"UART(\d+)_TX", "periph": "uart"},
{"re": r"USART(\d+)_TX", "periph": "usart"},
],
},
{
"name": "UART_RX",
"matches": [
{"re": r"UART(\d+)_RX", "periph": "uart"},
{"re": r"USART(\d+)_RX", "periph": "usart"},
],
},
]
"""pinctrl mappings (according to ``pinctrl_st_stm32.h`` header)."""
PINCTRL_ADDR = {
"stm32f0": 0x48000000,
"stm32f1": 0x40010800,
"stm32f2": 0x40020000,
"stm32f3": 0x48000000,
"stm32f4": 0x40020000,
"stm32f7": 0x40020000,
"stm32g0": 0x50000000,
"stm32g4": 0x48000000,
"stm32h7": 0x58020000,
"stm32l0": 0x50000000,
"stm32l4": 0x48000000,
"stm32l5": 0x42020000,
"stm32mp1": 0x50002000,
"stm32wb": 0x48000000,
}
"""pinctrl peripheral address for each family."""
def get_gpio_ip_afs(cube_path):
"""Obtain all GPIO IP alternate functions.
Example output::
{
"STM32L4P_gpio_v1_0": {
"PA2": {
"EVENTOUT": 15,
"LPUART1_TX": 8,
...
},
...
},
...
}
Args:
cube_path: Path to CubeMX package.
Returns:
Dictionary of alternate functions.
"""
ip_path = os.path.join(cube_path, "db", "mcu", "IP")
if not os.path.exists(ip_path):
raise FileNotFoundError(f"IP DB folder '{ip_path}' does not exist")
results = dict()
for gpio_file in glob.glob(os.path.join(ip_path, "GPIO-*_Modes.xml")):
m = re.search(r"GPIO-(.*)_Modes.xml", gpio_file)
gpio_ip = m.group(1)
gpio_ip_entries = dict()
results[gpio_ip] = gpio_ip_entries
gpio_tree = ET.parse(gpio_file)
gpio_root = gpio_tree.getroot()
for pin in gpio_root.findall(NS + "GPIO_Pin"):
pin_name = pin.get("Name")
pin_entries = dict()
gpio_ip_entries[pin_name] = pin_entries
for signal in pin.findall(NS + "PinSignal"):
signal_name = signal.get("Name")
param = signal.find(NS + "SpecificParameter")
if not param:
# NOTE: Only notify error if not F1 IP. F1 may use re-mapping
# which is not supported.
if "STM32F1" not in gpio_ip:
logger.error(
f"Missing signal {signal_name} parameters (ip: {gpio_ip})"
)
continue
value = param.find(NS + "PossibleValue")
if not param:
logger.error(f"Missing signal {signal_name} value (ip: {gpio_ip})")
continue
m = re.search(r"^GPIO_AF(\d+)_[A-Z0-9]+", value.text)
if not m:
# NOTE: Only notify error if not F1 IP. F1 may use re-mapping
# definitions which are not supported.
if "STM32F1" not in gpio_ip:
logger.error(
f"Unexpected AF format: {value.text} (ip: {gpio_ip})"
)
continue
af_n = int(m.group(1))
pin_entries[signal_name] = af_n
return results
def get_mcu_signals(cube_path, gpio_ip_afs):
"""Obtain all MCU signals.
Example output::
{
"STM32WB": [
{
"name": "STM32WB30CEUx"
"pins: [
{
"name": "PA0",
"signals" : [
{
"name": "ADC1_IN5",
"af": 8
},
...
]
},
...
]
},
...
]
}
Args:
cube_path: Path to CubeMX.
gpio_ip_afs: GPIO IP alternate functions.
Returns:
Dictionary with all MCU signals.
"""
mcus_path = os.path.join(cube_path, "db", "mcu")
if not os.path.exists(mcus_path):
raise FileNotFoundError(f"MCU DB folder '{mcus_path}' does not exist")
results = dict()
for mcu_file in glob.glob(os.path.join(mcus_path, "STM32*.xml")):
mcu_tree = ET.parse(mcu_file)
mcu_root = mcu_tree.getroot()
# obtain family, reference and GPIO IP
family = mcu_root.get("Family")
ref = mcu_root.get("RefName")
gpio_ip_version = None
for ip in mcu_root.findall(NS + "IP"):
if ip.get("Name") == "GPIO":
gpio_ip_version = ip.get("Version")
break
if not gpio_ip_version:
logger.error(f"GPIO IP version not specified (mcu: {mcu_file})")
continue
if gpio_ip_version not in gpio_ip_afs:
logger.error(f"GPIO IP version {gpio_ip_version} not available")
continue
gpio_ip = gpio_ip_afs[gpio_ip_version]
# create reference entry on its family
if family not in results:
family_entries = list()
results[family] = family_entries
else:
family_entries = results[family]
pin_entries = list()
family_entries.append({"name": ref, "pins": pin_entries})
# process all pins
for pin in mcu_root.findall(NS + "Pin"):
if pin.get("Type") != "I/O":
continue
m = re.search(r"^P[A-Z]\d+", pin.get("Name"))
if not m:
continue
pin_name = m.group(0)
if pin_name not in gpio_ip:
continue
pin_afs = gpio_ip[pin_name]
pin_signals = list()
pin_entries.append({"name": pin_name, "signals": pin_signals})
# process all pin signals
for signal in pin.findall(NS + "Signal"):
if signal.get("Name") == "GPIO":
continue
signal_name = signal.get("Name")
if signal_name not in pin_afs:
continue
pin_af = pin_afs[signal_name]
pin_signals.append({"name": signal_name, "af": pin_af})
return results
def main(cube_path, output):
"""Entry point.
Args:
cube_path: CubeMX path.
output: Output directory.
"""
gpio_ip_afs = get_gpio_ip_afs(cube_path)
mcu_signals = get_mcu_signals(cube_path, gpio_ip_afs)
if os.path.exists(output):
shutil.rmtree(output)
os.makedirs(output)
for family, refs in mcu_signals.items():
# obtain family pinctrl address
pinctrl_addr = PINCTRL_ADDR.get(family.lower())
if not pinctrl_addr:
continue
# create directory for each family
family_dir = os.path.join(output, family.lower()[5:])
if not os.path.exists(family_dir):
os.makedirs(family_dir)
# process each reference
for ref in refs:
entries = dict()
# process each pin in the current reference
for pin in ref["pins"]:
# obtain pin port (A, B, ...) and number (0, 1, ...)
m = re.search(r"^P([A-Z])(\d+)$", pin["name"])
if not m:
logger.error(f"Unexpected pin name format: {pin['name']}")
continue
pin_port = m.group(1).lower()
pin_number = int(m.group(2))
# process each pin available signal (matched against mapping table)
for signal in pin["signals"]:
for mapping in PINCTRL_MAPPINGS:
for match in mapping["matches"]:
m = re.search(match["re"], signal["name"])
if not m:
continue
periph_number = m.group(1)
if mapping["name"] not in entries:
entries[mapping["name"]] = list()
entries[mapping["name"]].append(
{
"port": pin_port,
"pin": pin_number,
"periph": f"{match['periph']}{periph_number}",
"af": signal["af"],
}
)
if not entries:
continue
# format entries (grouped and sorted)
formatted_entries = ""
for name, signals in entries.items():
formatted_entries += PINCTRL_GROUP_TEMPLATE.format(group=name)
sorted_signals = sorted(
signals, key=lambda entry: (entry["port"], entry["pin"])
)
for signal in sorted_signals:
formatted_entries += PINCTRL_ENTRY_TEMPLATE.format(
signal=name,
port=signal["port"],
pin=signal["pin"],
periph=signal["periph"],
af=signal["af"],
)
formatted_entries += "\n"
# write pinctrl file
ref_file = os.path.join(family_dir, ref["name"].lower() + "-pinctrl.dtsi")
with open(ref_file, "w") as f:
f.write(
PINCTRL_TEMPLATE.format(
pinctrl_addr=pinctrl_addr,
pinctrl_entries=formatted_entries[:-1],
)
)
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-p", "--cube-path", required=True, help="CubeMX path")
parser.add_argument("-o", "--output", required=True, help="Output directory")
args = parser.parse_args()
main(args.cube_path, args.output)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment