""" This script reads in the given CMSIS device include file (eg stm32f405xx.h), extracts relevant peripheral constants, and creates qstrs, mpz's and constants for the stm module. """ from __future__ import print_function import argparse import re # Python 2/3 compatibility import platform if platform.python_version_tuple()[0] == '2': def convert_bytes_to_str(b): return b elif platform.python_version_tuple()[0] == '3': def convert_bytes_to_str(b): try: return str(b, 'utf8') except ValueError: # some files have invalid utf8 bytes, so filter them out return ''.join(chr(l) for l in b if l <= 126) # end compatibility code # given a list of (name,regex) pairs, find the first one that matches the given line def re_match_first(regexs, line): for name, regex in regexs: match = re.match(regex, line) if match: return name, match return None, None class LexerError(Exception): def __init__(self, line): self.line = line class Lexer: re_io_reg = r'__IO uint(?P8|16|32)_t +(?P[A-Z0-9]+)' re_comment = r'(?P[A-Za-z0-9 \-/_()&]+)' re_addr_offset = r'Address offset: (?P0x[0-9A-Z]{2,3})' regexs = ( ('#define hex', re.compile(r'#define +(?P[A-Z0-9_]+) +\(?(\(uint32_t\))?(?P0x[0-9A-F]+)U?L?\)?($| */\*)')), ('#define X', re.compile(r'#define +(?P[A-Z0-9_]+) +(?P[A-Z0-9_]+)($| +/\*)')), ('#define X+hex', re.compile(r'#define +(?P[A-Za-z0-9_]+) +\(?(?P[A-Z0-9_]+) \+ (?P0x[0-9A-F]+)U?L?\)?($| +/\*)')), ('#define typedef', re.compile(r'#define +(?P[A-Z0-9_]+(ext)?) +\(\([A-Za-z0-9_]+_TypeDef \*\) (?P[A-Za-z0-9_]+)\)($| +/\*)')), ('typedef struct', re.compile(r'typedef struct$')), ('{', re.compile(r'{$')), ('}', re.compile(r'}$')), ('} TypeDef', re.compile(r'} *(?P[A-Z][A-Za-z0-9_]+)_(?P([A-Za-z0-9_]+)?)TypeDef;$')), ('IO reg', re.compile(re_io_reg + r'; */\*!< *' + re_comment + r', +' + re_addr_offset + r' *\*/')), ('IO reg array', re.compile(re_io_reg + r'\[(?P[2-8])\]; */\*!< *' + re_comment + r', +' + re_addr_offset + r'-(0x[0-9A-Z]{2,3}) *\*/')), ) def __init__(self, filename): self.file = open(filename, 'rb') self.line_number = 0 def next_match(self, strictly_next=False): while True: line = self.file.readline() line = convert_bytes_to_str(line) self.line_number += 1 if len(line) == 0: return ('EOF', None) match = re_match_first(Lexer.regexs, line.strip()) if strictly_next or match[0] is not None: return match def must_match(self, kind): match = self.next_match(strictly_next=True) if match[0] != kind: raise LexerError(self.line_number) return match def parse_file(filename): lexer = Lexer(filename) reg_defs = {} consts = {} periphs = [] while True: m = lexer.next_match() if m[0] == 'EOF': break elif m[0] == '#define hex': d = m[1].groupdict() consts[d['id']] = int(d['hex'], base=16) elif m[0] == '#define X': d = m[1].groupdict() if d['id2'] in consts: consts[d['id']] = consts[d['id2']] elif m[0] == '#define X+hex': d = m[1].groupdict() if d['id2'] in consts: consts[d['id']] = consts[d['id2']] + int(d['hex'], base=16) elif m[0] == '#define typedef': d = m[1].groupdict() if d['id2'] in consts: periphs.append((d['id'], consts[d['id2']])) elif m[0] == 'typedef struct': lexer.must_match('{') m = lexer.next_match() regs = [] while m[0] in ('IO reg', 'IO reg array'): d = m[1].groupdict() reg = d['reg'] offset = int(d['offset'], base=16) bits = int(d['bits']) comment = d['comment'] if m[0] == 'IO reg': regs.append((reg, offset, bits, comment)) else: for i in range(int(d['array'])): regs.append((reg + str(i), offset + i * bits // 8, bits, comment)) m = lexer.next_match() if m[0] == '}': pass elif m[0] == '} TypeDef': reg_defs[m[1].groupdict()['id']] = regs else: raise LexerError(lexer.line_number) return periphs, reg_defs def print_int_obj(val, needed_mpzs): if -0x40000000 <= val < 0x40000000: print('MP_ROM_INT(%#x)' % val, end='') else: print('MP_ROM_PTR(&mpz_%08x)' % val, end='') needed_mpzs.add(val) def print_periph(periph_name, periph_val, needed_qstrs, needed_mpzs): qstr = periph_name.upper() print('{ MP_ROM_QSTR(MP_QSTR_%s), ' % qstr, end='') print_int_obj(periph_val, needed_mpzs) print(' },') needed_qstrs.add(qstr) def print_regs(reg_name, reg_defs, needed_qstrs, needed_mpzs): reg_name = reg_name.upper() for r in reg_defs: qstr = reg_name + '_' + r[0] print('{ MP_ROM_QSTR(MP_QSTR_%s), ' % qstr, end='') print_int_obj(r[1], needed_mpzs) print(' }, // %s-bits, %s' % (r[2], r[3])) needed_qstrs.add(qstr) # This version of print regs groups registers together into submodules (eg GPIO submodule). # This makes the qstrs shorter, and makes the list of constants more manageable (since # they are not all in one big module) but it is then harder to compile the constants, and # is more cumbersome to access. # As such, we don't use this version. # And for the number of constants we have, this function seems to use about the same amount # of ROM as print_regs. def print_regs_as_submodules(reg_name, reg_defs, modules, needed_qstrs): mod_name_lower = reg_name.lower() + '_' mod_name_upper = mod_name_lower.upper() modules.append((mod_name_lower, mod_name_upper)) print(""" STATIC const mp_rom_map_elem_t stm_%s_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_%s) }, """ % (mod_name_lower, mod_name_upper)) needed_qstrs.add(mod_name_upper) for r in reg_defs: print(' { MP_ROM_QSTR(MP_QSTR_%s), MP_ROM_INT(%#x) }, // %s-bits, %s' % (r[0], r[1], r[2], r[3])) needed_qstrs.add(r[0]) print("""}; STATIC MP_DEFINE_CONST_DICT(stm_%s_globals, stm_%s_globals_table); const mp_obj_module_t stm_%s_obj = { .base = { &mp_type_module }, .name = MP_QSTR_%s, .globals = (mp_obj_dict_t*)&stm_%s_globals, }; """ % (mod_name_lower, mod_name_lower, mod_name_lower, mod_name_upper, mod_name_lower)) def main(): cmd_parser = argparse.ArgumentParser(description='Extract ST constants from a C header file.') cmd_parser.add_argument('file', nargs=1, help='input file') cmd_parser.add_argument('-q', '--qstr', dest='qstr_filename', default='build/stmconst_qstr.h', help='Specified the name of the generated qstr header file') cmd_parser.add_argument('--mpz', dest='mpz_filename', default='build/stmconst_mpz.h', help='the destination file of the generated mpz header') args = cmd_parser.parse_args() periphs, reg_defs = parse_file(args.file[0]) # add legacy GPIO constants that were removed when upgrading CMSIS if 'GPIO' in reg_defs and 'stm32f4' in args.file[0]: reg_defs['GPIO'].append(['BSRRL', 0x18, 16, 'legacy register']) reg_defs['GPIO'].append(['BSRRH', 0x1a, 16, 'legacy register']) modules = [] needed_qstrs = set() needed_mpzs = set() print("// Automatically generated from %s by make-stmconst.py" % args.file[0]) print("") for periph_name, periph_val in periphs: print_periph(periph_name, periph_val, needed_qstrs, needed_mpzs) for reg in ( 'ADC', #'ADC_Common', #'CAN_TxMailBox', #'CAN_FIFOMailBox', #'CAN_FilterRegister', #'CAN', 'CRC', 'DAC', 'DBGMCU', 'DMA_Stream', 'DMA', 'EXTI', 'FLASH', 'GPIO', 'SYSCFG', 'I2C', 'IWDG', 'PWR', 'RCC', 'RTC', #'SDIO', 'SPI', 'TIM', 'USART', 'WWDG', 'RNG', ): if reg in reg_defs: print_regs(reg, reg_defs[reg], needed_qstrs, needed_mpzs) #print_regs_as_submodules(reg, reg_defs[reg], modules, needed_qstrs) #print("#define MOD_STM_CONST_MODULES \\") #for mod_lower, mod_upper in modules: # print(" { MP_ROM_QSTR(MP_QSTR_%s), MP_ROM_PTR(&stm_%s_obj) }, \\" % (mod_upper, mod_lower)) print("") with open(args.qstr_filename, 'wt') as qstr_file: print('#if MICROPY_PY_STM', file=qstr_file) for qstr in sorted(needed_qstrs): print('Q({})'.format(qstr), file=qstr_file) print('#endif // MICROPY_PY_STM', file=qstr_file) with open(args.mpz_filename, 'wt') as mpz_file: for mpz in sorted(needed_mpzs): assert 0 <= mpz <= 0xffffffff print('STATIC const mp_obj_int_t mpz_%08x = {{&mp_type_int}, ' '{.neg=0, .fixed_dig=1, .alloc=2, .len=2, ' '.dig=(uint16_t*)(const uint16_t[]){%#x, %#x}}};' % (mpz, mpz & 0xffff, (mpz >> 16) & 0xffff), file=mpz_file) if __name__ == "__main__": main()