keyboard/qmk/lib/chibios-contrib/tools/mx2board.py

423 lines
14 KiB
Python
Raw Normal View History

#!/usr/bin/python
# -*- coding: utf-8 -*-
__author__ = 'Fabien Poussin'
__version__ = '0.3'
from xml.etree import ElementTree as etree
from jinja2 import Template
from os.path import expanduser, sep, dirname, abspath
from argparse import ArgumentParser
from traceback import print_exc
import re
import pprint
pretty_print = pprint.PrettyPrinter(indent=2)
def pprint(*kwargs):
pretty_print.pprint(kwargs)
PIN_MODE_INPUT = "PIN_MODE_INPUT({0})"
PIN_MODE_OUTPUT = "PIN_MODE_OUTPUT({0})"
PIN_MODE_ALTERNATE = "PIN_MODE_ALTERNATE({0})"
PIN_MODE_ANALOG = "PIN_MODE_ANALOG({0})"
PIN_ODR_LOW = "PIN_ODR_LOW({0})"
PIN_ODR_HIGH = "PIN_ODR_HIGH({0})"
PIN_OTYPE_PUSHPULL = "PIN_OTYPE_PUSHPULL({0})"
PIN_OTYPE_OPENDRAIN = "PIN_OTYPE_OPENDRAIN({0})"
PIN_OSPEED_VERYLOW = "PIN_OSPEED_VERYLOW({0})"
PIN_OSPEED_LOW = "PIN_OSPEED_LOW({0})"
PIN_OSPEED_MEDIUM = "PIN_OSPEED_MEDIUM({0})"
PIN_OSPEED_HIGH = "PIN_OSPEED_HIGH({0})"
PIN_PUPDR_FLOATING = "PIN_PUPDR_FLOATING({0})"
PIN_PUPDR_PULLUP = "PIN_PUPDR_PULLUP({0})"
PIN_PUPDR_PULLDOWN = "PIN_PUPDR_PULLDOWN({0})"
PIN_AFIO_AF = "PIN_AFIO_AF({0}, {1})"
PIN_ASCR_DISABLED = "PIN_ASCR_DISABLED({0})"
PIN_ASCR_ENABLED = "PIN_ASCR_ENABLED({0})"
PIN_LOCKR_DISABLED = "PIN_LOCKR_DISABLED({0})"
PIN_LOCKR_ENABLED = "PIN_LOCKR_ENABLED({0})"
FMT = '{0}'
FMT_DEF = '({0})'
PIN_CONF_LIST = ['MODER', 'OTYPER', 'OSPEEDR', 'PUPDR', 'ODR', 'ASCR', 'LOCKR']
PIN_CONF_LIST_AF = ['AFRL', 'AFRH']
DEFAULT_PAD = {"SIGNAL": "UNUSED",
"LABEL": "",
"MODER": PIN_MODE_ANALOG,
"OTYPER": PIN_OTYPE_PUSHPULL,
"OSPEEDR": PIN_OSPEED_VERYLOW,
"PUPDR": PIN_PUPDR_FLOATING,
"ODR": PIN_ODR_LOW,
"ASCR": PIN_ASCR_DISABLED,
"LOCKR": PIN_LOCKR_DISABLED}
PIN_MODE_TRANSLATE = {"GPIO_MODE_AF_PP": PIN_MODE_ALTERNATE,
"GPIO_MODE_ANALOG": PIN_MODE_ANALOG,
"GPIO_MODE_INPUT": PIN_MODE_INPUT,
"GPIO_MODE_OUTPUT": PIN_MODE_OUTPUT,
"GPIO_MODE_OUTPUT_PP": PIN_MODE_OUTPUT,
"GPIO_MODE_OUTPUT_OD": PIN_MODE_OUTPUT}
PIN_OTYPE_TRANSLATE = {"GPIO_MODE_OUTPUT_PP": PIN_OTYPE_PUSHPULL,
"GPIO_MODE_OUTPUT_OD": PIN_OTYPE_OPENDRAIN}
PIN_OSPEED_TRANSLATE = {"GPIO_SPEED_FREQ_LOW": PIN_OSPEED_VERYLOW,
"GPIO_SPEED_FREQ_MEDIUM": PIN_OSPEED_LOW,
"GPIO_SPEED_FREQ_HIGH": PIN_OSPEED_MEDIUM,
"GPIO_SPEED_FREQ_VERY_HIGH": PIN_OSPEED_HIGH
}
PIN_PUPDR_TRANSLATE = {"GPIO_NOPULL": PIN_PUPDR_FLOATING,
"GPIO_PULLUP": PIN_PUPDR_PULLUP,
"GPIO_PULLDOWN": PIN_PUPDR_PULLDOWN}
PIN_ODR_TRANSLATE = {"GPIO_PIN_SET": PIN_ODR_HIGH,
"GPIO_PIN_CLEAR": PIN_ODR_LOW,
"GPIO_PIN_RESET": PIN_ODR_LOW }
parser = ArgumentParser(description='Generate ChibiOS GPIO header file from STM32CubeMX project files.')
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument('-m', '--mx', default='', type=str, help='STM32CubeMX path. Invalid if -g is used.')
group.add_argument('-g', '--gpio', default='', type=str, help='STM32CubeMX Gpio file, if you don\'t have STM32CubeMX installed. Invalid if -m is used.')
parser.add_argument('-p', '--project', required=True, type=str, help="STM32CubeMX Project file")
parser.add_argument('-o', '--output', default='board_gpio.h', type=str, help='Output file name')
def open_xml(filename):
# Remove namespace
with open(filename, 'r') as xmlfile:
xml = re.sub(' xmlns="[^"]+"', '', xmlfile.read(), count=1)
return etree.fromstring(xml)
def char_range(c1, c2):
"""Generates the characters from `c1` to `c2`, inclusive."""
for c in range(ord(c1), ord(c2)+1):
yield chr(c)
def get_gpio_file(proj_file, mx_path):
mcu_name = None
gpio_file = None
path = None
mcu_info = None
print('Opening ' + proj_file)
with open(proj_file, 'r') as f:
proj_data = f.readlines()
for l in proj_data:
if l.startswith('Mcu.Name'):
print('MCU is ' + l.split('=')[-1].strip())
mcu_name = '{}.xml'.format(l.split('=')[-1].strip())
if not mcu_name:
print('Could not find MCU name in project file')
exit(1)
if "STM32F1" in mcu_name:
print('STM32F1xx are not compatible with this script. (old GPIO)')
exit(1)
found = False
for p in (mx_path,
'{0}{1}STM32CubeMX'.format(expanduser("~"), sep),
'C:{0}Program Files{0}STMicroelectronics{0}STM32Cube{0}STM32CubeMX'.format(sep)):
if not p:
continue
try:
path = '{1}{0}db{0}mcu{0}'.format(sep, p)
mcu_info = open_xml(path + mcu_name)
found = True
break
except IOError:
continue
if not found:
print('Could not find GPIO file')
exit(1)
print('Opened ' + path)
for ip in mcu_info.findall("IP"):
if ip.attrib['Name'] == 'GPIO':
gpio_file = '{0}{2}IP{2}GPIO-{1}_Modes.xml'.format(path,
ip.attrib['Version'],
sep)
return gpio_file
def read_gpio(filename):
gpio = {'ports': {}, 'defaults': {}, 'modes': {}}
print('Opening GPIO file ' + filename)
root = open_xml(filename)
gpio['defaults']['GPIO_Mode'] = 'GPIO_MODE_ANALOG'
for modes in root.findall("RefParameter"):
try:
name = modes.attrib['Name']
gpio['defaults'][name] = modes.attrib['DefaultValue']
gpio['modes'][name] = []
except KeyError as e:
continue
if 'GPIO_' not in name:
continue
for m in modes.findall("PossibleValue"):
prop_val = m.attrib['Value']
gpio['modes'][name].append(prop_val)
for pin in root.findall('GPIO_Pin'):
try:
port = pin.attrib['Name'][1]
num = pin.attrib['Name'][2:]
# remove notes from pin name (e.g. PH0 - OSC_IN)
num = num.split('-')[0].strip()
num = int(num)
if port not in gpio['ports']:
gpio['ports'][port] = {}
if num not in gpio['ports'][port]:
gpio['ports'][port][num] = {}
except ValueError as e:
continue
for s in pin.findall('PinSignal'):
try:
af = s.find('SpecificParameter/PossibleValue').text
af = int(''.join(af.split('_')[1])[2:])
gpio['ports'][port][num][s.attrib['Name']] = af
except ValueError as e:
print_exc(e)
except AttributeError as e:
print_exc(e)
return gpio
# Extract signals from IOC
def read_project(gpio, filename):
print('Opening project file ' + filename)
with open(filename, 'r') as mx_file:
tmp = mx_file.readlines()
pads = {}
mcu = 'Unknown'
for l in tmp:
if l.startswith('Mcu.Name'):
mcu = l.split('=')[-1].strip()
# Default pad mode is analog, here we assign proper mode and speed.
for p in gpio['ports'].keys():
pads[p] = {}
for i in range(0, 16):
pads[p][i] = DEFAULT_PAD.copy()
pads[p][i]['PUPDR'] = PIN_PUPDR_TRANSLATE[gpio['defaults']['GPIO_PuPdOD']]
pads[p][i]['OTYPER'] = PIN_OTYPE_TRANSLATE[gpio['defaults']['GPIO_ModeDefaultOutputPP']]
pads[p][i]['OSPEEDR'] = PIN_OSPEED_TRANSLATE[gpio['defaults']['GPIO_Speed']]
for t in tmp:
if re.search(r"^P[A-Z]\d{1,2}(-OSC.+)?\.", t, re.M):
split = t.split('=')
pad_name = split[0].split(".")[0]
pad_port = pad_name[1:2]
pad_num = int(pad_name[2:4].replace('.', '').replace('-', ''))
pad_prop = split[0].split(".")[-1]
prop_value = split[-1].rstrip('\r\n')
if pad_prop == "Signal":
if 'S_TIM' in prop_value:
prop_value = prop_value[2:]
if prop_value.startswith('ADC') \
or 'DAC' in prop_value \
or 'OSC' in prop_value:
pads[pad_port][pad_num]["MODER"] = PIN_MODE_ANALOG
pads[pad_port][pad_num]["SIGNAL"] = prop_value
elif 'GPIO_Output' == prop_value:
pads[pad_port][pad_num]["MODER"] = PIN_MODE_OUTPUT
elif 'GPIO_Input' == prop_value:
pads[pad_port][pad_num]["MODER"] = PIN_MODE_INPUT
else:
# workaround for different names in project and gpio defs
if "FSMC" in prop_value:
prop_value = re.sub(r"FSMC_D([0-9]+)_DA[0-9]+",
r"FSMC_D\1", prop_value)
pads[pad_port][pad_num]["SIGNAL"] = prop_value
pads[pad_port][pad_num]["MODER"] = PIN_MODE_ALTERNATE
pads[pad_port][pad_num]["OSPEEDR"] = PIN_OSPEED_MEDIUM
elif pad_prop == "GPIO_Mode":
pads[pad_port][pad_num]["MODER"] = PIN_MODE_TRANSLATE[prop_value]
elif pad_prop == "GPIO_Label":
pads[pad_port][pad_num]["LABEL"] = prop_value
elif pad_prop == "GPIO_PuPd":
pads[pad_port][pad_num]["PUPDR"] = PIN_PUPDR_TRANSLATE[prop_value]
elif pad_prop == "GPIO_ModeDefaultOutputPP":
pads[pad_port][pad_num]["OTYPER"] = PIN_OTYPE_TRANSLATE[prop_value]
pads[pad_port][pad_num]["MODER"] = PIN_MODE_OUTPUT
elif pad_prop == "GPIO_Speed":
pads[pad_port][pad_num]["OSPEEDR"] = PIN_OSPEED_TRANSLATE[prop_value]
elif pad_prop == "PinState":
pads[pad_port][pad_num]["ODR"] = PIN_ODR_TRANSLATE[prop_value]
elif pad_prop == "Mode":
if "I2C" in prop_value:
pads[pad_port][pad_num]["OTYPER"] = PIN_OTYPE_OPENDRAIN
return mcu, pads
# Add defines for all pins with labels
def gen_defines(project):
defines = {}
for port_key in sorted(project.keys()):
for pad_key in sorted(project[port_key].keys()):
pad_data = project[port_key][pad_key]
if pad_data['SIGNAL'] != 'UNUSED' and not pad_data['LABEL']:
pad_data['LABEL'] = pad_data['SIGNAL']
pad_data['LABEL'] = pad_data['LABEL'].replace('-', '_')
label = pad_data['LABEL']
signal = pad_data['SIGNAL']
if not label:
continue
defines['PORT_'+label] = 'GPIO' + port_key
defines['PAD_'+label] = pad_key
defines['LINE_'+label] = 'PAL_LINE(GPIO' + port_key
defines['LINE_'+label] += ', ' + str(pad_key) + 'U)'
match = re.search(r"TIM(\d+)_CH(\d)$", signal)
if match:
timer = match.group(1)
ch_num = int(match.group(2))
defines['TIM_' + label] = timer
defines['CCR_' + label] = 'CCR' + timer[-1]
defines['PWMD_' + label] = 'PWMD' + timer[-1]
defines['ICUD_' + label] = 'ICUD' + timer[-1]
defines['CHN_' + label] = ch_num - 1
continue
match = re.search(r"ADC(\d*)_IN(\d+)$", signal)
if match:
adc = match.group(1)
if len(adc) == 0:
adc = 1
defines['ADC_' + label] = adc
defines['CHN_' + label] = match.group(2)
continue
match = re.search(r"USART(\d+)_[RT]X$", signal)
if match:
defines['USART_' + label] = match.group(1)
continue
match = re.search(r"COMP_DAC(\d+)_group", signal)
if match:
defines['DAC_' + label] = match.group(1)
continue
match = re.search(r"I2C(\d)_S(CL|DA)", signal)
if match:
defines['I2C_' + label] = match.group(1)
continue
match = re.search(r"SPI(\d)_(MOSI|MISO|SCK|NSS)", signal)
if match:
defines['SPI_' + label] = match.group(1)
continue
match = re.search(r"CAN(\d*)_[RT]X", signal)
if match:
can = match.group(1)
if len(can) == 0:
can = 1
defines['CAN_' + label] = can
continue
return defines
# Each Port (A.B.C...)
def gen_ports(gpio, project):
ports = {}
for port_key in sorted(project.keys()):
ports[port_key] = {}
# Each property (mode, output/input...)
for conf in PIN_CONF_LIST:
ports[port_key][conf] = []
for pin in project[port_key]:
out = project[port_key][pin][conf]
out = out.format(pin)
ports[port_key][conf].append(out)
conf = PIN_CONF_LIST_AF[0]
ports[port_key][conf] = []
for pin in range(0, 8):
try:
af = project[port_key][pin]['SIGNAL']
out = PIN_AFIO_AF.format(pin, gpio['ports'][port_key][pin][af])
except KeyError as e:
out = PIN_AFIO_AF.format(pin, 0)
ports[port_key][conf].append(out)
conf = PIN_CONF_LIST_AF[1]
ports[port_key][conf] = []
for pin in range(8, 16):
try:
af = project[port_key][pin]['SIGNAL']
out = PIN_AFIO_AF.format(pin, gpio['ports'][port_key][pin][af])
except KeyError:
out = PIN_AFIO_AF.format(pin, 0)
ports[port_key][conf].append(out)
return ports
if __name__ == '__main__':
args = parser.parse_args()
cur_path = dirname(abspath(__file__))
if args.gpio:
gpio = read_gpio(args.gpio)
else:
gpio_file = get_gpio_file(args.project, args.mx)
gpio = read_gpio(gpio_file)
mcu, proj = read_project(gpio, args.project)
defines = gen_defines(proj)
ports = gen_ports(gpio, proj)
with open(cur_path + '/templates/board_gpio.tpl', 'r') as tpl_file:
tpl = tpl_file.read()
template = Template(tpl)
defines_sorted = []
for d in sorted(defines.keys()):
defines_sorted.append((d, defines[d]))
ports_sorted = []
for p in sorted(ports.keys()):
ports_sorted.append((p, ports[p]))
template.stream(defines=defines_sorted, ports=ports_sorted, ascr_lockr='L4' in mcu).dump(args.output)
print('File generated at ' + args.output)