#!/usr/bin/env python3 import sys import xmltodict import re import json import os from collections import OrderedDict from glob import glob from stm32data import yaml, header from stm32data.yaml import DecimalInt, HexInt from stm32data.util import removeprefix, removesuffix def corename(d): # print("CHECKING CORENAME", d) if m := re.match('.*Cortex-M(\d+)(\+?)\s*(.*)', d): name = "cm" + str(m.group(1)) if m.group(2) == "+": name += "p" if m.group(3) == "secure": name += "s" return name def children(x, key): r = x.get(key) if r is None: return [] if type(r) is list: return r return [r] def expand_name(name): if '(' not in name: return [name] prefix, suffix = name.split('(') letters, suffix = suffix.split(')') return [prefix + x + suffix for x in letters.split('-')] # ======================================== # ======================================== FAKE_PERIPHERALS = [ # These are real peripherals but with special handling 'NVIC', 'GPIO', 'DMA', # I2S is just SPI on disguise 'I2S1', 'I2S2', 'I2S3', 'I2S4', 'I2S5', 'I2S6', 'I2S7', 'I2S8', # These are software libraries 'FREERTOS', 'PDM2PCM', 'FATFS', # 'CRC', 'LIBJPEG', 'MBEDTLS', 'LWIP', 'USB_HOST', 'USB_DEVICE', 'GUI_INTERFACE', 'TRACER_EMB', ] perimap = [ ('.*:USART:sci2_v1_1', 'usart_v1/USART'), ('.*:USART:sci2_v1_2_F1', 'usart_v1/USART'), ('.*:USART:sci2_v1_2', 'usart_v1/USART'), ('.*:USART:sci2_v2_0', 'usart_v2/USART'), ('.*:USART:sci2_v2_1', 'usart_v2/USART'), ('.*:USART:sci2_v2_2', 'usart_v2/USART'), ('.*:USART:sci3_v1_0', 'usart_v2/USART'), ('.*:USART:sci3_v1_1', 'usart_v2/USART'), ('.*:USART:sci3_v1_2', 'usart_v2/USART'), ('.*:USART:sci3_v2_0', 'usart_v2/USART'), ('.*:USART:sci3_v2_1', 'usart_v2/USART'), ('.*:UART:sci2_v2_1', 'usart_v2/USART'), ('.*:UART:sci2_v3_0', 'usart_v2/USART'), ('.*:UART:sci2_v3_1', 'usart_v2/USART'), ('.*:RNG:rng1_v1_1', 'rng_v1/RNG'), ('.*:RNG:rng1_v2_0', 'rng_v1/RNG'), ('.*:RNG:rng1_v2_1', 'rng_v1/RNG'), ('.*:RNG:rng1_v3_1', 'rng_v1/RNG'), ('.*:SPI:spi2_v1_4', 'spi_f1/SPI'), ('.*:SPI:spi2s1_v2_2', 'spi_v1/SPI'), ('.*:SPI:spi2s1_v3_2', 'spi_v2/SPI'), ('.*:SPI:spi2s1_v3_3', 'spi_v2/SPI'), ('.*:SPI:spi2s1_v3_5', 'spi_v2/SPI'), ('.*:SUBGHZSPI:.*', 'spi_v2/SPI'), ('.*:SPI:spi2s1_v3_1', 'spi_v2/SPI'), ('.*:SPI:spi2s2_v1_1', 'spi_v3/SPI'), ('.*:SPI:spi2s2_v1_0', 'spi_v3/SPI'), ('.*:I2C:i2c1_v1_5', 'i2c_v1/I2C'), ('.*:I2C:i2c2_v1_1', 'i2c_v2/I2C'), ('.*:I2C:i2c2_v1_1F7', 'i2c_v2/I2C'), ('.*:DAC:dacif_v1_1', 'dac_v1/DAC'), ('.*:DAC:dacif_v2_0', 'dac_v2/DAC'), ('.*:DAC:dacif_v3_0', 'dac_v2/DAC'), ('.*:ADC:aditf2_v1_1', 'adc_v2/ADC'), ('.*:ADC:aditf5_v2_0', 'adc_v3/ADC'), ('STM32G0.*:ADC:.*', 'adc_g0/ADC'), ('STM32G0.*:ADC_COMMON:.*', 'adccommon_v3/ADC_COMMON'), ('.*:ADC_COMMON:aditf2_v1_1', 'adccommon_v2/ADC_COMMON'), ('.*:ADC_COMMON:aditf5_v2_0', 'adccommon_v3/ADC_COMMON'), ('.*:ADC_COMMON:aditf4_v3_0_WL', 'adccommon_v3/ADC_COMMON'), ('.*:DCMI:cci_v2_0', 'dcmi_v1/DCMI'), ('STM32F0.*:SYS:.*', 'syscfg_f0/SYSCFG'), ('STM32F4.*:SYS:.*', 'syscfg_f4/SYSCFG'), ('STM32F7.*:SYS:.*', 'syscfg_f7/SYSCFG'), ('STM32L4.*:SYS:.*', 'syscfg_l4/SYSCFG'), ('STM32L0.*:SYS:.*', 'syscfg_l0/SYSCFG'), ('STM32L1.*:SYS:.*', 'syscfg_l1/SYSCFG'), ('STM32G0.*:SYS:.*', 'syscfg_g0/SYSCFG'), ('STM32H7.*:SYS:.*', 'syscfg_h7/SYSCFG'), ('STM32U5.*:SYS:.*', 'syscfg_u5/SYSCFG'), ('STM32WB.*:SYS:.*', 'syscfg_wb/SYSCFG'), ('STM32WL5.*:SYS:.*', 'syscfg_wl5/SYSCFG'), ('STM32WLE.*:SYS:.*', 'syscfg_wle/SYSCFG'), ('.*:IWDG:iwdg1_v2_0', 'iwdg_v2/IWDG'), ('.*:WWDG:wwdg1_v1_0', 'wwdg_v1/WWDG'), ('.*:JPEG:jpeg1_v1_0', 'jpeg_v1/JPEG'), ('.*:LPTIM:F7_lptimer1_v1_1', 'lptim_v1/LPTIM'), ('.*:LTDC:lcdtft1_v1_1', 'ltdc_v1/LTDC'), ('.*:MDIOS:mdios1_v1_0', 'mdios_v1/MDIOS'), ('.*:QUADSPI:quadspi1_v1_0', 'quadspi_v1/QUADSPI'), ('.*:RTC:rtc2_v2_6', 'rtc_v2/RTC'), ('.*:SAI:sai1_v1_1', 'sai_v1/SAI'), ('.*:SDMMC:sdmmc_v1_3', 'sdmmc_v1/SDMMC'), ('.*:SPDIFRX:spdifrx1_v1_0', 'spdifrx_v1/SPDIFRX'), ('.*:USB_OTG_FS:otgfs1_v1_2', 'otgfs_v1/OTG_FS'), ('.*:USB_OTG_HS:otghs1_v1_1', 'otghs_v1/OTG_HS'), ('STM32F0.0.*:RCC:.*', 'rcc_f0x0/RCC'), ('STM32F0.*:RCC:.*', 'rcc_f0/RCC'), ('STM32F1.*:RCC:.*', 'rcc_f1/RCC'), ('STM32F2.*:RCC:.*', 'rcc_f2/RCC'), ('STM32F3.*:RCC:.*', 'rcc_f3/RCC'), ('STM32F410.*:RCC:.*', 'rcc_f410/RCC'), ('STM32F4.*:RCC:.*', 'rcc_f4/RCC'), ('STM32F7.*:RCC:.*', 'rcc_f7/RCC'), ('STM32G0.*:RCC:.*', 'rcc_g0/RCC'), ('STM32G4.*:RCC:.*', 'rcc_g4/RCC'), ('STM32H7[AB].*:RCC:.*', 'rcc_h7ab/RCC'), ('STM32H7.*:RCC:.*', 'rcc_h7/RCC'), ('STM32L0.*:RCC:.*', 'rcc_l0/RCC'), ('STM32L1.*:RCC:.*', 'rcc_l1/RCC'), ('STM32L4.*:RCC:.*', 'rcc_l4/RCC'), ('STM32L5.*:RCC:.*', 'rcc_l5/RCC'), ('STM32U5.*:RCC:.*', 'rcc_u5/RCC'), ('STM32WB.*:RCC:.*', 'rcc_wb/RCC'), ('STM32WL5.*:RCC:.*', 'rcc_wl5/RCC'), ('STM32WLE.*:RCC:.*', 'rcc_wle/RCC'), ('STM32F1.*:AFIO:.*', 'afio_f1/AFIO'), ('STM32L5.*:EXTI:.*', 'exti_l5/EXTI'), ('STM32G0.*:EXTI:.*', 'exti_g0/EXTI'), ('STM32H7.*:EXTI:.*', 'exti_h7/EXTI'), ('STM32U5.*:EXTI:.*', 'exti_u5/EXTI'), ('STM32WB.*:EXTI:.*', 'exti_w/EXTI'), ('STM32WL5.*:EXTI:.*', 'exti_w/EXTI'), ('STM32WLE.*:EXTI:.*', 'exti_wle/EXTI'), ('.*:EXTI:.*', 'exti_v1/EXTI'), ('.*:STM32L0_crs_v1_0', 'crs_l0/CRS'), ('.*SDMMC:sdmmc2_v1_0', 'sdmmc_v2/SDMMC'), ('.*:STM32G0_pwr_v1_0', 'pwr_g0/PWR'), ('STM32H7(42|43|53|50).*:STM32H7_pwr_v1_0', 'pwr_h7/PWR'), ('.*:STM32H7_pwr_v1_0', 'pwr_h7smps/PWR'), ('.*:STM32F4_pwr_v1_0', 'pwr_f4/PWR'), ('.*:STM32F7_pwr_v1_0', 'pwr_f7/PWR'), ('.*:STM32L1_pwr_v1_0', 'pwr_l1/PWR'), ('.*:STM32U5_pwr_v1_0', 'pwr_u5/PWR'), ('.*:STM32WL_pwr_v1_0', 'pwr_wl5/PWR'), ('.*:STM32H7_flash_v1_0', 'flash_h7/FLASH'), ('.*:STM32F0_flash_v1_0', 'flash_f0/FLASH'), ('.*:STM32F1_flash_v1_0', 'flash_f1/FLASH'), ('.*:STM32F4_flash_v1_0', 'flash_f4/FLASH'), ('.*:STM32F7_flash_v1_0', 'flash_f7/FLASH'), ('.*:STM32L4_flash_v1_0', 'flash_l4/FLASH'), ('STM32F7.*:ETH:ETH:ethermac110_v2_0', 'eth_v1c/ETH'), ('.*ETH:ethermac110_v3_0', 'eth_v2/ETH'), ('.*LPTIM\d.*:G0xx_lptimer1_v1_4', 'lptim_g0/LPTIM'), ('STM32F7.*:TIM1:.*', 'timer_v1/TIM_ADV'), ('STM32F7.*:TIM8:.*', 'timer_v1/TIM_ADV'), ('.*TIM\d.*:gptimer.*', 'timer_v1/TIM_GP16'), ('.*:STM32F0_dbgmcu_v1_0', 'dbgmcu_f0/DBGMCU'), ('.*:STM32F1_dbgmcu_v1_0', 'dbgmcu_f1/DBGMCU'), ('.*:STM32F2_dbgmcu_v1_0', 'dbgmcu_f2/DBGMCU'), ('.*:STM32F3_dbgmcu_v1_0', 'dbgmcu_f3/DBGMCU'), ('.*:STM32F4_dbgmcu_v1_0', 'dbgmcu_f4/DBGMCU'), ('.*:STM32F7_dbgmcu_v1_0', 'dbgmcu_f7/DBGMCU'), ('.*:STM32G0_dbgmcu_v1_0', 'dbgmcu_g0/DBGMCU'), ('.*:STM32G4_dbgmcu_v1_0', 'dbgmcu_g4/DBGMCU'), ('.*:STM32H7_dbgmcu_v1_0', 'dbgmcu_h7/DBGMCU'), ('.*:STM32L0_dbgmcu_v1_0', 'dbgmcu_l0/DBGMCU'), ('.*:STM32L1_dbgmcu_v1_0', 'dbgmcu_l1/DBGMCU'), ('.*:STM32L4_dbgmcu_v1_0', 'dbgmcu_l4/DBGMCU'), ('.*:STM32U5_dbgmcu_v1_0', 'dbgmcu_u5/DBGMCU'), ('.*:STM32WB_dbgmcu_v1_0', 'dbgmcu_wb/DBGMCU'), ('.*:STM32WL_dbgmcu_v1_0', 'dbgmcu_wl/DBGMCU'), ('.*:IPCC:v1_0', 'ipcc_v1/IPCC'), ('.*:DMAMUX:v1', 'dmamux_v1/DMAMUX'), ('.*:BDMA:DMA', 'bdma_v1/DMA'), ('STM32H7.*:DMA2D:DMA2D:dma2d1_v1_0', 'dma2d_v2/DMA2D'), ('.*:DMA2D:dma2d1_v1_0', 'dma2d_v1/DMA2D'), ('STM32L4[PQRS].*:.*:DMA', 'bdma_v1/DMA'), # L4+ ('STM32L[04].*:.*:DMA', 'bdma_v2/DMA'), # L0, L4 non-plus (since plus is handled above) ('STM32F030.C.*:.*:DMA', 'bdma_v2/DMA'), # Weird F0 ('STM32F09.*:.*:DMA', 'bdma_v2/DMA'), # Weird F0 ('STM32F[247].*:.*:DMA', 'dma_v2/DMA'), ('STM32H7.*:.*:DMA', 'dma_v1/DMA'), ('.*:DMA', 'bdma_v1/DMA'), ('.*:CAN:bxcan1_v1_1.*', 'can_bxcan/CAN'), # stm32F4 CRC peripheral # ("STM32F4*:CRC:CRC:crc_f4") # v1: F1, F2, F4, L1 # v2, adds INIT reg: F0 # v3, adds POL reg: F3, F7, G0, G4, H7, L0, L4, L5, WB, WL ('.*:CRC:integtest1_v1_0', 'crc_v1/CRC'), ('STM32L[04].*:CRC:integtest1_v2_0', 'crc_v3/CRC'), ('.*:CRC:integtest1_v2_0', 'crc_v2/CRC'), ('.*:CRC:integtest1_v2_2', 'crc_v3/CRC'), ] # Device address overrides, in case of missing from headers address_overrides = { 'STM32F412VE:GPIOF_BASE': 0x40021400, 'STM32F412VE:GPIOG_BASE': 0x40021800, 'STM32F412VG:GPIOF_BASE': 0x40021400, 'STM32F412VG:GPIOG_BASE': 0x40021800, 'STM32L151CB-A:GPIOF_BASE': 0x40021800, 'STM32L151CB-A:GPIOG_BASE': 0x40021C00, } def lookup_address(defines, name, d): if addr := defines.get(d): return addr elif addr := address_overrides.get(name + ':' + d): return addr def match_peri(peri): for r, block in perimap: if re.match('^' + r + '$', peri): if block == '': return None return block return None def find_af(gpio_af, peri_name, pin_name, signal_name): if gpio_af in af: if pin_name in af[gpio_af]: if peri_name + '_' + signal_name in af[gpio_af][pin_name]: return af[gpio_af][pin_name][peri_name + '_' + signal_name] return None all_mcu_files = {} per_mcu_files = {} def parse_documentations(): print("linking files and documents") with open('sources/mcufinder/files.json', 'r') as j: files = json.load(j) for file in files['Files']: file_id = file['id_file'] if file_id not in all_mcu_files: all_mcu_files[file_id] = OrderedDict({ 'name': file['name'], 'title': file['title'], 'url': file['URL'], 'type': file['type'], }) with open('sources/mcufinder/mcus.json', 'r') as j: mcus = json.load(j) for mcu in mcus['MCUs']: rpn = mcu['RPN'] if rpn not in per_mcu_files: per_mcu_files[rpn] = [] for file in mcu['files']: per_mcu_files[rpn].append(file['file_id']) def documents_for(chip_name, type): docs = [] for id in per_mcu_files[chip_name]: if id in all_mcu_files: file = all_mcu_files[id] if file['type'] == type: docs.append(OrderedDict({ 'title': file['title'], 'name': file['name'], 'url': file['url'], })) return docs def chip_name_from_package_name(x): name_map = [ ('(STM32L1....).x([AX])', '\\1-\\2'), ('(STM32G0....).xN', '\\1'), ('(STM32F412..).xP', '\\1'), ('(STM32L4....).xP', '\\1'), ('(STM32WB....).x[AE]', '\\1'), ('(STM32G0....).xN', '\\1'), ('(STM32L5....).x[PQ]', '\\1'), ('(STM32L0....).xS', '\\1'), ('(STM32H7....).xQ', '\\1'), ('(STM32U5....).xQ', '\\1'), ('(STM32......).x', '\\1'), ] for a, b in name_map: r, n = re.subn('^' + a + '$', b, x) if n != 0: return r raise Exception("bad name: {}".format(x)) memories_map = { 'flash': [ 'FLASH', 'FLASH_BANK1', 'FLASH_BANK2', 'D1_AXIFLASH', 'D1_AXIICP', ], 'ram': [ 'SRAM', 'SRAM1', 'SRAM2', 'D1_AXISRAM', 'D1_ITCMRAM', 'D1_DTCMRAM', 'D1_AHBSRAM', 'D2_AXISRAM', 'D3_BKPSRAM', 'D3_SRAM' ], } def cleanup_pin_name(pin_name): if p := parse_pin_name(pin_name): return f'P{p[0]}{p[1]}' def parse_pin_name(pin_name): if len(pin_name) < 3: return None if pin_name[0] != 'P': return None port = pin_name[1] if not port.isalpha(): return None pin = pin_name[2:] i = 0 while i < len(pin) and pin[i].isnumeric(): i += 1 if i == 0: return None pin = int(pin[:i]) return port, pin def parse_chips(): os.makedirs('data/chips', exist_ok=True) chips = {} for f in sorted(glob('sources/cubedb/mcu/STM32*.xml')): if 'STM32MP' in f: continue if len(sys.argv) > 1: if not sys.argv[1] in f: continue print(f) r = xmltodict.parse(open(f, 'rb'))['Mcu'] package_names = expand_name(r['@RefName']) package_rams = r['Ram'] package_flashs = r['Flash'] die = r['Die'] if type(package_rams) != list: package_rams = [package_rams] * len(package_names) if type(package_flashs) != list: package_flashs = [package_flashs] * len(package_names) for package_i, package_name in enumerate(package_names): chip_name = chip_name_from_package_name(package_name) flash = OrderedDict({ 'bytes': DecimalInt(int(package_flashs[package_i]) * 1024), 'regions': {}, }) ram = OrderedDict({ 'bytes': DecimalInt(int(package_rams[package_i]) * 1024), 'regions': {}, }) gpio_af = next(filter(lambda x: x['@Name'] == 'GPIO', r['IP']))['@Version'] gpio_af = removesuffix(gpio_af, '_gpio_v1_0') dma = next(filter(lambda x: x['@Name'] == 'DMA', r['IP']), None) bdma = next(filter(lambda x: x['@Name'] == 'BDMA', r['IP']), None) nvic = next(filter(lambda x: x['@Name'] == 'NVIC', r['IP']), None) if nvic is None: nvic = next(filter(lambda x: x['@Name'] == 'NVIC1', r['IP']), None) nvic = nvic['@Version'] if dma is not None: dma = dma['@Version'] if bdma is not None: bdma = bdma['@Version'] rcc = next(filter(lambda x: x['@Name'] == 'RCC', r['IP']))['@Version'] rcc = removesuffix(rcc, '-rcc_v1_0') rcc = removesuffix(rcc, '_rcc_v1_0') core = r['Core'] family = r['@Family'] cores = [] if isinstance(core, list): for core in core: cores.append(OrderedDict( { 'name': corename(core), 'peripherals': {}, })) else: cores.append(OrderedDict( { 'name': corename(core), 'peripherals': {}, })) if chip_name not in chips: chips[chip_name] = OrderedDict({ 'name': chip_name, 'family': family, 'line': r['@Line'], 'die': die, 'device-id': None, 'packages': [], 'datasheet': None, 'reference-manual': None, 'flash': flash, 'ram': ram, 'cores': cores, 'peripherals': {}, 'pins': {}, 'application-notes': [], 'rcc': rcc, # temporarily stashing it here 'dma': dma, # temporarily stashing it here 'bdma': bdma, # temporarily stashing it here 'nvic': nvic # temporarily stashing it here }) chips[chip_name]['packages'].append(OrderedDict({ 'name': package_name, 'package': r['@Package'], })) if chip_name in per_mcu_files: if len(ds := documents_for(chip_name, 'Datasheet')) >= 1: chips[chip_name]['datasheet'] = ds[0] if len(rm := documents_for(chip_name, 'Reference manual')) >= 1: chips[chip_name]['reference-manual'] = rm[0] chips[chip_name]['application-notes'] = documents_for(chip_name, 'Application note') if 'datasheet' in chips[chip_name] and chips[chip_name]['datasheet'] is None: del chips[chip_name]['datasheet'] if 'reference-manual' in chips[chip_name] and chips[chip_name]['reference-manual'] is None: del chips[chip_name]['reference-manual'] # Some packages have some peripehrals removed because the package had to # remove GPIOs useful for that peripheral. So we merge all peripherals from all packages. peris = chips[chip_name]['peripherals'] pins = chips[chip_name]['pins'] for ip in r['IP']: pname = ip['@InstanceName'] pkind = ip['@Name'] + ':' + ip['@Version'] pkind = removesuffix(pkind, '_Cube') if pname == 'SYS': pname = 'SYSCFG' if pname == 'SUBGHZ': pname = 'SUBGHZSPI' if pname == 'SYSCFG_VREFBUF': pname = 'SYSCFG' if pname in FAKE_PERIPHERALS: continue if pname.startswith('ADC'): if not 'ADC_COMMON' in peris: peris['ADC_COMMON'] = 'ADC_COMMON:' + removesuffix(ip['@Version'], '_Cube') peris[pname] = pkind pins[pname] = [] for pin in r['Pin']: pin_name = cleanup_pin_name(pin['@Name']) if pin_name is None: continue if 'Signal' in pin: signals = [] if not type(pin['Signal']) is list: signals.append(pin['Signal']) else: signals = pin['Signal'] for signal in signals: signal_name = signal['@Name'] parts = signal_name.split('_', 1) if len(parts) == 1: continue peri_name = parts[0] signal_name = parts[1] if signal_name.startswith("EXTI"): continue if peri_name.startswith("DEBUG") and signal_name.startswith("SUBGHZSPI"): parts = signal_name.split('-', 1) if len(parts) == 2: peri_name = parts[0] signal_name = removesuffix(parts[1], "OUT") if not peri_name in pins: pins[peri_name] = [] entry = OrderedDict({ 'pin': pin_name, 'signal': signal_name, }) af_num = find_af(gpio_af, peri_name, pin_name, signal_name) if af_num is not None: entry['af'] = af_num # Some SVDs have duplicate pin definitions if entry not in pins[peri_name]: pins[peri_name].append(entry) for chip_name, chip in chips.items(): if len(sys.argv) > 1: if not chip_name.startswith(sys.argv[1]): continue print(f'* processing chip {chip_name}') rcc = chip['rcc'] del chip['rcc'] chip_dma = chip['dma'] del chip['dma'] chip_bdma = chip['bdma'] del chip['bdma'] chip_nvic = chip['nvic'] del chip['nvic'] device_id = determine_device_id(chip_name) if device_id is not None: chip['device-id'] = HexInt(device_id) else: del chip['device-id'] h = header.get_for_chip(chip_name) if h is None: raise Exception("missing header for {}".format(chip_name)) found = [] for each in memories_map['flash']: if each + '_BASE' in h['defines']['all']: if each == 'FLASH': key = 'BANK_1' elif each == 'FLASH_BANK1': key = 'BANK_1' elif each == 'FLASH_BANK2': key = 'BANK_2' else: key = each if key in found: continue found.append(key) chip['flash']['regions'][key] = OrderedDict({ 'base': HexInt(h['defines']['all'][each + '_BASE']) }) if key == 'BANK_1' or key == 'BANK_2': flash_size = determine_flash_size(chip_name) if flash_size is not None: if flash_size > chip['flash']['bytes'].val: flash_size = chip['flash']['bytes'].val chip['flash']['regions'][key]['bytes'] = DecimalInt(flash_size) found = [] for each in memories_map['ram']: if each + '_BASE' in h['defines']['all']: if each == 'D1_AXISRAM': key = 'SRAM' elif each == 'SRAM1': key = 'SRAM' else: key = each if key in found: continue found.append(key) chip['ram']['regions'][key] = OrderedDict({ 'base': HexInt(h['defines']['all'][each + '_BASE']) }) if key == 'SRAM': ram_size = determine_ram_size(chip_name) if ram_size is not None: chip['ram']['regions'][key]['bytes'] = DecimalInt(ram_size) # print("Got", len(chip['cores']), "cores") for core in chip['cores']: core_name = core['name'] if (chip_nvic + '-' + core_name) in chip_interrupts: # if there's a more specific set of irqs... chip_nvic = chip_nvic + '-' + core_name if not core_name in h['interrupts'] or not core_name in h['defines']: core_name = 'all' # print("Defining for core", core_name) # Gather all interrupts and defines for this core interrupts = h['interrupts'][core_name] defines = h['defines'][core_name] core['interrupts'] = interrupts peris = {} for pname, pkind in chip['peripherals'].items(): addr = defines.get(pname) if addr is None: if pname == 'ADC_COMMON': addr = defines.get('ADC1_COMMON') if addr is None: addr = defines.get('ADC12_COMMON') if addr is None: addr = defines.get('ADC123_COMMON') if addr is None: continue p = OrderedDict({ 'address': addr, 'kind': pkind, }) if pname in clocks[rcc]: p['clock'] = clocks[rcc][pname] if block := match_peri(chip_name + ':' + pname + ':' + pkind): p['block'] = block if pname in chip['pins']: if len(chip['pins'][pname]) > 0: p['pins'] = sorted(chip['pins'][pname], key=lambda p: (parse_pin_name(p['pin']), p['signal'])) if chip_nvic in chip_interrupts: if pname in chip_interrupts[chip_nvic]: # filter by available, because some are conditioned on p['interrupts'] = filter_interrupts(chip_interrupts[chip_nvic][pname], interrupts) peris[pname] = p family_extra = "data/extra/family/" + chip['family'] + ".yaml" if os.path.exists(family_extra): with open(family_extra) as extra_f: extra = yaml.load(extra_f) for (extra_name, extra_p) in extra['peripherals'].items(): peris[extra_name] = extra_p # Handle GPIO specially. for p in range(20): port = 'GPIO' + chr(ord('A') + p) if addr := lookup_address(defines, chip['name'], port + '_BASE'): block = 'gpio_v2/GPIO' if chip['family'] == 'STM32F1': block = 'gpio_v1/GPIO' p = OrderedDict({ 'address': addr, 'block': block, }) peris[port] = p # Handle DMA specially. for dma in ('DMA1', 'DMA2', 'BDMA'): if addr := defines.get(dma + '_BASE'): p = OrderedDict({ 'address': addr, }) if block := match_peri(chip_name + ':' + dma + ':DMA'): p['block'] = block if chip_nvic in chip_interrupts: if dma in chip_interrupts[chip_nvic]: # filter by available, because some are conditioned on p['interrupts'] = filter_interrupts(chip_interrupts[chip_nvic][dma], interrupts) peris[dma] = p # DMAMUX is not in the cubedb XMLs for dma in ('DMAMUX', 'DMAMUX1', "DMAMUX2"): if addr := defines.get(dma + '_BASE'): kind = 'DMAMUX:v1' dbg_peri = OrderedDict({ 'address': addr, 'kind': kind, }) if block := match_peri(chip_name + ':' + dma + ':' + kind): dbg_peri['block'] = block peris[dma] = dbg_peri # EXTI is not in the cubedb XMLs if addr := defines.get('EXTI_BASE'): peri = OrderedDict({ 'address': addr, 'kind': 'EXTI', }) if block := match_peri(chip_name + ':EXTI:EXTI:v1'): peri['block'] = block peris['EXTI'] = peri # FLASH is not in the cubedb XMLs if addr := defines.get('FLASH_R_BASE'): kind = 'FLASH:' + chip_name[:7] + '_flash_v1_0' flash_peri = OrderedDict({ 'address': addr, 'kind': kind, }) if block := match_peri(kind): flash_peri['block'] = block peris['FLASH'] = flash_peri # DBGMCU is not in the cubedb XMLs if addr := defines.get('DBGMCU_BASE') or defines.get('DBG_BASE'): kind = 'DBGMCU:' + chip_name[:7] + '_dbgmcu_v1_0' dbg_peri = OrderedDict({ 'address': addr, 'kind': kind, }) if block := match_peri(kind): dbg_peri['block'] = block peris['DBGMCU'] = dbg_peri # CRS is not in the cubedb XMLs if addr := defines.get('CRS_BASE'): kind = 'CRS:' + chip_name[:7] + '_crs_v1_0' crs_peri = OrderedDict({ 'address': addr, 'kind': kind, }) if block := match_peri(kind): crs_peri['block'] = block peris['CRS'] = crs_peri # PWR is not in some XMLs if 'PWR' not in peris: if addr := defines.get('PWR_BASE'): kind = 'PWR:' + chip_name[:7] + '_pwr_v1_0' pwr_peri = OrderedDict({ 'address': addr, 'kind': kind, }) if block := match_peri(kind): pwr_peri['block'] = block peris['PWR'] = pwr_peri # AFIO is not in the cubedb XMLs if addr := defines.get('AFIO_BASE'): kind = 'AFIO' afio_peri = OrderedDict({ 'address': addr, 'kind': kind, }) if block := match_peri(chip_name[:7] + ':' + kind + ':'): afio_peri['block'] = block peris['AFIO'] = afio_peri core['peripherals'] = peris if 'block' in core['peripherals']['RCC']: rcc_block = core['peripherals']['RCC']['block'] for (name, body) in core['peripherals'].items(): if 'clock' not in body: peri_clock = None if chip_name.startswith('STM32G0') and name.startswith('TIM'): peri_clock = 'APB' elif chip_name.startswith('STM32G0') and name.startswith('SYSCFG'): peri_clock = 'APB' else: peri_clock = match_peri_clock(rcc_block, name) if peri_clock is not None: core['peripherals'][name]['clock'] = peri_clock # Process DMA channels chs = {} if chip_dma in dma_channels: chs.update(dma_channels[chip_dma]['channels']) if chip_bdma in dma_channels: chs.update(dma_channels[chip_bdma]['channels']) # The dma_channels[xx] is generic for multiple chips. The current chip may have less DMAs, # so we have to filter it. chs = { name: ch for (name, ch) in chs.items() if ch['dma'] in peris } core['dma_channels'] = chs # Process peripheral - DMA channel associations if chip_dma is not None: for pname, p in peris.items(): if (peri_chs := dma_channels[chip_dma]['peripherals'].get(pname)) is not None: p['dma_channels'] = { req: [ ch for ch in req_chs if ('channel' not in ch) or ch['channel'] in chs ] for req, req_chs in peri_chs.items() } # remove all pins from the root of the chip before emitting. del chip['pins'] del chip['peripherals'] with open('data/chips/' + chip_name + '.yaml', 'w') as f: f.write(yaml.dump(chip, width=500)) af = {} def parse_gpio_af(): # os.makedirs('data/gpio_af', exist_ok=True) for f in glob('sources/cubedb/mcu/IP/GPIO-*_gpio_v1_0_Modes.xml'): if 'STM32F1' in f: continue ff = removeprefix(f, 'sources/cubedb/mcu/IP/GPIO-') ff = removesuffix(ff, '_gpio_v1_0_Modes.xml') pins = {} r = xmltodict.parse(open(f, 'rb')) for pin in r['IP']['GPIO_Pin']: pin_name = pin['@Name'] # Blacklist non-pins if pin_name == 'PDR_ON': continue # Cleanup pin name pin_name = cleanup_pin_name(pin_name) if pin_name is None: continue # Extract AFs afs = {} for signal in children(pin, 'PinSignal'): func = signal['@Name'] if func.startswith("DEBUG_SUBGHZSPI"): func = removeprefix(func, "DEBUG_") parts = func.split('-', 2) if len(parts) > 1: func = parts[0] + '_' + removesuffix(parts[1], "OUT") afn = signal['SpecificParameter']['PossibleValue'].split('_')[1] afn = int(removeprefix(afn, 'AF')) afs[func] = afn pins[pin_name] = afs # with open('data/gpio_af/'+ff+'.yaml', 'w') as f: # f.write(yaml.dump(pins)) af[ff] = pins dma_channels = {} def parse_dma(): for f in glob('sources/cubedb/mcu/IP/*DMA-*Modes.xml'): is_explicitly_bdma = False ff = removeprefix(f, 'sources/cubedb/mcu/IP/') if not (ff.startswith('B') or ff.startswith('D')): continue if ff.startswith("BDMA"): is_explicitly_bdma = True ff = removeprefix(ff, 'DMA-') ff = removeprefix(ff, 'BDMA-') ff = removesuffix(ff, '_Modes.xml') r = xmltodict.parse(open(f, 'rb'), force_list={'Mode', 'RefMode'}) chip_dma = { 'channels': {}, 'peripherals': {}, } for dma in r['IP']['ModeLogicOperator']['Mode']: dma_peri_name = dma['@Name'] if ' Context' in dma_peri_name: continue channels = dma['ModeLogicOperator']['Mode'] if len(channels) == 1: # ========== CHIP WITH DMAMUX dmamux_file = ff[5:7] if ff.startswith('STM32L4P'): dmamux_file = 'L4PQ' if ff.startswith('STM32L4S'): dmamux_file = 'L4RS' for mf in glob('data/dmamux/{}_*.yaml'.format(dmamux_file)): with open(mf, 'r') as yaml_file: y = yaml.load(yaml_file) mf = removesuffix(mf, '.yaml') dmamux = mf[mf.index('_') + 1:] # DMAMUX1 or DMAMUX2 for (request_name, request_num) in y.items(): parts = request_name.split('_') target_peri_name = parts[0] if len(parts) < 2: request = target_peri_name else: request = parts[1] if target_peri_name not in chip_dma['peripherals']: chip_dma['peripherals'][target_peri_name] = {} peri_dma = chip_dma['peripherals'][target_peri_name] if request not in peri_dma: peri_dma[request] = [] peri_dma[request].append({ "dmamux": dmamux, "request": request_num, }) dmamux = 'DMAMUX1' if is_explicitly_bdma: dmamux = 'DMAMUX2' dmamux_channel = 0 for n in dma_peri_name.split(","): n = n.strip() if result := re.match('.*' + n + '_(Channel|Stream)\[(\d+)-(\d+)\]', channels[0]['@Name']): low = int(result.group(2)) high = int(result.group(3)) # Make sure all channels numbers start at 0 if low == 1: low -= 1 high -= 1 for i in range(low, high + 1): chip_dma['channels'][n + '_CH' + str(i)] = OrderedDict({ 'dma': n, 'channel': i, 'dmamux': dmamux, 'dmamux_channel': dmamux_channel, }) dmamux_channel += 1 else: # ========== CHIP WITHOUT DMAMUX # see if we can scrape out requests requests = {} request_blocks = filter(lambda x: x['@BaseMode'] == 'DMA_Request', r['IP']['RefMode']) for block in request_blocks: name = block['@Name'] # Depending on the chip, the naming is "Channel" or "Request"... request_num = next(filter(lambda x: x['@Name'] in ('Channel', 'Request'), block['Parameter']), None) if request_num is not None: request_num = request_num['PossibleValue'] request_num = removeprefix(request_num, "DMA_CHANNEL_") request_num = removeprefix(request_num, "DMA_REQUEST_") requests[name] = int(request_num) channel_names = [] for channel in channels: channel_name = channel['@Name'] channel_name = removeprefix(channel_name, dma_peri_name + '_') channel_name = removeprefix(channel_name, "Channel") channel_name = removeprefix(channel_name, "Stream") channel_names.append(channel_name) chip_dma['channels'][dma_peri_name + '_CH' + channel_name] = OrderedDict({ 'dma': dma_peri_name, 'channel': int(channel_name), }) for target in channel['ModeLogicOperator']['Mode']: target_name = target['@Name'] original_target_name = target_name parts = target_name.split(':') target_name = parts[0] parts = target_name.split('_') target_peri_name = parts[0] if len(parts) < 2: target_requests = [target_peri_name] else: target_requests = target_name.split('_')[1].split('/') if target_name != 'MEMTOMEM': if target_peri_name not in chip_dma['peripherals']: chip_dma['peripherals'][target_peri_name] = {} peri_dma = chip_dma['peripherals'][target_peri_name] for request in target_requests: if ':' in request: request = request.split(':')[0] if request not in peri_dma: peri_dma[request] = [] entry = OrderedDict({ 'channel': dma_peri_name + '_CH' + channel_name, }) if original_target_name in requests: entry['request'] = requests[original_target_name] peri_dma[request].append(entry) # Make sure all channels numbers start at 0 if min(map(int, channel_names)) != 0: for name in channel_names: chip_dma['channels'][dma_peri_name + '_CH' + name]['channel'] -= 1 dma_channels[ff] = chip_dma clocks = {} def parse_clocks(): for f in glob('sources/cubedb/mcu/IP/RCC-*rcc_v1_0_Modes.xml'): ff = removeprefix(f, 'sources/cubedb/mcu/IP/RCC-') ff = removesuffix(ff, '_rcc_v1_0_Modes.xml') ff = removesuffix(ff, '-rcc_v1_0_Modes.xml') chip_clocks = {} r = xmltodict.parse(open(f, 'rb')) for ref in r['IP']['RefParameter']: name = ref['@Name'] if name.startswith("APB") and name.endswith("Freq_Value") and not name.endswith("TimFreq_Value") and '@IP' in ref: name = removesuffix(name, "Freq_Value") peripherals = ref['@IP'] peripherals = peripherals.split(",") for p in peripherals: chip_clocks[p] = name clocks[ff] = chip_clocks peripheral_to_clock = {} def parse_rcc_regs(): print("parsing RCC registers") for f in glob('data/registers/rcc_*'): ff = removeprefix(f, 'data/registers/rcc_') ff = removesuffix(ff, '.yaml') family_clocks = {} with open(f, 'r') as yaml_file: y = yaml.load(yaml_file) for (key, body) in y.items(): if key.startswith("fieldset/A") and key.endswith("ENR"): clock = removesuffix(key, "ENR") clock = removeprefix(clock, "fieldset/") clock = removesuffix(clock, "L") clock = removesuffix(clock, "H") for field in body['fields']: if field['name'].endswith('EN'): peri = removesuffix(field['name'], 'EN') family_clocks[peri] = clock peripheral_to_clock['rcc_' + ff + '/RCC'] = family_clocks def match_peri_clock(rcc_block, peri_name): if rcc_block in peripheral_to_clock: family_clocks = peripheral_to_clock[rcc_block] if peri_name in family_clocks: return family_clocks[peri_name] # print("found no clock for ", peri_name) if peri_name.endswith("1"): return match_peri_clock(rcc_block, removesuffix(peri_name, "1")) return None chip_interrupts = {} def parse_interrupts(): print("parsing interrupts") for f in glob('sources/cubedb/mcu/IP/NVIC*_Modes.xml'): ff = removeprefix(f, 'sources/cubedb/mcu/IP/NVIC') ff = removesuffix(ff, '_Modes.xml') chip_irqs = {} r = xmltodict.parse(open(f, 'rb')) if ff.startswith('1') or ff.startswith('2'): ff = removeprefix(ff, '1') ff = removeprefix(ff, '2') core = corename(next(filter(lambda x: x['@Name'] == 'CoreName', r['IP']['RefParameter']))['@DefaultValue']) ff = ff + "-" + core ff = removeprefix(ff, '-') irqs = next(filter(lambda x: x['@Name'] == 'IRQn', r['IP']['RefParameter'])) for irq in irqs['PossibleValue']: value = irq['@Value'] parts = value.split(':') irq_name = removesuffix(parts[0], "_IRQn") peri_names = parts[2].split(',') if len(peri_names) == 1 and peri_names[0] == '': continue elif len(peri_names) == 1 and (peri_names[0] == 'DMA' or peri_names[0].startswith("DMAL")): peri_names = [parts[3]] split = split_interrupts(peri_names, irq_name) for p in peri_names: if p not in chip_irqs: chip_irqs[p] = {} merge_peri_irq_signals(chip_irqs[p], split[p]) chip_interrupts[ff] = chip_irqs def merge_peri_irq_signals(peri_irqs, additional): for key, value in additional.items(): if key not in peri_irqs: peri_irqs[key] = [] peri_irqs[key].append(value) def split_interrupts(peri_names, irq_name): split = {} for p in peri_names: split[p] = remap_interrupt_signals(p, irq_name) return split irq_signals_map = { 'CAN': ['TX', 'RX0', 'RX1', 'SCE'], 'I2C': ['ER', 'EV'], 'TIM': ['BRK', 'UP', 'TRG', 'COM'], 'HRTIM': ['Master', 'TIMA', 'TIMB', 'TIMC', 'TIMD', 'TIME', 'TIMF'] } def remap_interrupt_signals(peri_name, irq_name): if peri_name == irq_name: return expand_all_irq_signals(peri_name, irq_name) if (peri_name.startswith('DMA') or peri_name.startswith('BDMA')) and irq_name.startswith(peri_name): return {irq_name: irq_name} if peri_name.startswith('USART') and irq_name.startswith(peri_name): return {'GLOBAL': irq_name} if peri_name in irq_name: signals = {} start = irq_name.index(peri_name) regexp = re.compile('(_[^_]+)') if match := regexp.findall(irq_name, start): for m in match: signal = removeprefix(m, '_').strip() if is_valid_irq_signal(peri_name, signal): signals[signal] = irq_name else: signals = expand_all_irq_signals(peri_name, irq_name) return signals else: return {'GLOBAL': irq_name} def is_valid_irq_signal(peri_name, signal): for prefix, signals in irq_signals_map.items(): if peri_name.startswith(prefix): return signal in signals return False def expand_all_irq_signals(peri_name, irq_name): expanded = {} for prefix, signals in irq_signals_map.items(): if peri_name.startswith(prefix): for s in irq_signals_map[prefix]: expanded[s] = irq_name return expanded return {'GLOBAL': irq_name} def filter_interrupts(peri_irqs, all_irqs): filtered = {} for signal, irqs in peri_irqs.items(): for irq in all_irqs: if irq in irqs: filtered[signal] = irq break return filtered memories = [] def parse_memories(): with open('data/memories.yaml', 'r') as yaml_file: m = yaml.load(yaml_file) for each in m: memories.append(each) def determine_ram_size(chip_name): for each in memories: for name in each['names']: if is_chip_name_match(name, chip_name): return each['ram']['bytes'] return None def determine_flash_size(chip_name): for each in memories: for name in each['names']: if is_chip_name_match(name, chip_name): return each['flash']['bytes'] return None def determine_device_id(chip_name): for each in memories: for name in each['names']: if is_chip_name_match(name, chip_name): return each['device-id'] return None def is_chip_name_match(pattern, chip_name): pattern = pattern.replace('x', '.') return re.match(pattern + ".*", chip_name) parse_memories() parse_interrupts() parse_rcc_regs() parse_documentations() parse_dma() parse_gpio_af() parse_clocks() parse_chips()