diff --git a/ports/esp32/boards/make-pins.py b/ports/esp32/boards/make-pins.py index 2054274690..49b10f0ce1 100755 --- a/ports/esp32/boards/make-pins.py +++ b/ports/esp32/boards/make-pins.py @@ -1,182 +1,67 @@ #!/usr/bin/env python -import argparse +import os import sys -import csv -import re -MAX_CPU_PINS = 49 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../../../tools")) +import boardgen -def parse_pin(name_str): - """Parses a string and returns a pin number.""" - if len(name_str) < 2: - raise ValueError("Expecting pin name to be at least 2 characters.") - if not name_str.startswith("GPIO"): - raise ValueError("Expecting pin name to start with GPIO") - return int(re.findall(r"\d+$", name_str)[0]) +# Pins start at zero, and the highest pin index on any ESP32* chip is 48. +NUM_GPIOS = 49 -class Pin: - def __init__(self, pin): - self.pin = pin - self.is_board = False +class Esp32Pin(boardgen.Pin): + # Required by NumericPinGenerator. + def index(self): + return int(self._cpu_pin_name[4:]) - def cpu_pin_name(self): - return "GPIO{:d}".format(self.pin) + # The IDF provides `GPIO_NUM_x = x` as an enum. + def index_name(self): + return "GPIO_NUM_{:d}".format(self.index()) - def is_board_pin(self): - return self.is_board + # Emit the combined struct which contains both the pin and irq instances. + def definition(self): + return "{ .base = { .type = &machine_pin_type }, .irq = { .base = { .type = &machine_pin_irq_type } } }" - def set_is_board_pin(self): - self.is_board = True + # This script isn't family-aware, so we always emit the maximum number of + # pins and rely on the `MICROPY_HW_ENABLE_GPIOn` macros defined in + # machine_pin.h to figure out which ones are actually available. + def enable_macro(self): + return "MICROPY_HW_ENABLE_{}".format(self._cpu_pin_name) + + # ESP32 cpu names must be "GPIOn". + @staticmethod + def validate_cpu_pin_name(cpu_pin_name): + boardgen.Pin.validate_cpu_pin_name(cpu_pin_name) + + if not cpu_pin_name.startswith("GPIO") or not cpu_pin_name[4:].isnumeric(): + raise boardgen.PinGeneratorError( + "Invalid cpu pin name '{}', must be 'GPIOn'".format(cpu_pin_name) + ) + + if not (0 <= int(cpu_pin_name[4:]) < NUM_GPIOS): + raise boardgen.PinGeneratorError("Unknown cpu pin '{}'".format(cpu_pin_name)) -class NamedPin: - def __init__(self, name, pin): - self._name = name - self._pin = pin - - def pin(self): - return self._pin - - def name(self): - return self._name - - -class Pins: +class Esp32PinGenerator(boardgen.NumericPinGenerator): def __init__(self): - self.cpu_pins = [] # list of NamedPin objects - self.board_pins = [] # list of NamedPin objects + # Use custom pin type above. + super().__init__(pin_type=Esp32Pin) - def find_pin(self, pin_name): - for pin in self.cpu_pins: - if pin.name() == pin_name: - return pin.pin() + # Pre-define the pins (i.e. don't require them to be listed in pins.csv). + for i in range(NUM_GPIOS): + self.add_cpu_pin("GPIO{}".format(i)) - def create_pins(self): - for pin_num in range(MAX_CPU_PINS): - pin = Pin(pin_num) - self.cpu_pins.append(NamedPin(pin.cpu_pin_name(), pin)) + # Only use pre-defined cpu pins (do not let board.csv create them). + def find_pin_by_cpu_pin_name(self, cpu_pin_name, create=True): + return super().find_pin_by_cpu_pin_name(cpu_pin_name, create=False) - def parse_board_file(self, filename): - with open(filename, "r") as csvfile: - rows = csv.reader(csvfile) - for row in rows: - if len(row) == 0 or row[0].startswith("#"): - # Skip empty lines, and lines starting with "#" - continue - if len(row) != 2: - raise ValueError("Expecting two entries in a row") - - cpu_pin_name = row[1] - parse_pin(cpu_pin_name) - pin = self.find_pin(cpu_pin_name) - if not pin: - raise ValueError("Unknown pin {}".format(cpu_pin_name)) - pin.set_is_board_pin() - if row[0]: # Only add board pins that have a name - self.board_pins.append(NamedPin(row[0], pin)) - - def print_cpu_table(self, out_source): - print("", file=out_source) - print( - "const machine_pin_obj_t machine_pin_obj_table[GPIO_NUM_MAX] = {", - file=out_source, - ) - for pin in self.cpu_pins: - print(" #if MICROPY_HW_ENABLE_{}".format(pin.name()), file=out_source) - print( - " [GPIO_NUM_{}] = {{ .base = {{ .type = &machine_pin_type }}, .irq = {{ .base = {{ .type = &machine_pin_irq_type }} }} }},".format( - pin.pin().pin, - ), - file=out_source, - ) - print(" #endif", file=out_source) - print("};", file=out_source) - - def print_named(self, label, named_pins, out_source): - print("", file=out_source) - print( - "STATIC const mp_rom_map_elem_t machine_pin_{:s}_pins_locals_dict_table[] = {{".format( - label - ), - file=out_source, - ) - for named_pin in named_pins: - pin = named_pin.pin() - print( - " {{ MP_ROM_QSTR(MP_QSTR_{:s}), MP_ROM_PTR(&pin_{:s}) }},".format( - named_pin.name(), pin.cpu_pin_name() - ), - file=out_source, - ) - - print("};", file=out_source) - print( - "MP_DEFINE_CONST_DICT(machine_pin_{:s}_pins_locals_dict, machine_pin_{:s}_pins_locals_dict_table);".format( - label, label - ), - file=out_source, - ) - - def print_tables(self, out_source): - self.print_cpu_table(out_source) - self.print_named("board", self.board_pins, out_source) - - def print_header(self, out_header): - # Provide #defines for each cpu pin. - for named_pin in self.cpu_pins: - pin = named_pin.pin() - n = pin.cpu_pin_name() - print("#if MICROPY_HW_ENABLE_{}".format(n), file=out_header) - print( - "#define pin_{:s} (machine_pin_obj_table[{}])".format(n, pin.pin), - file=out_header, - ) - print("#endif", file=out_header) - - # Provide #define's mapping board to cpu name. - for named_pin in self.board_pins: - if named_pin.pin().is_board_pin(): - print( - "#define pin_{:s} pin_{:s}".format( - named_pin.name(), named_pin.pin().cpu_pin_name() - ), - file=out_header, - ) - - -def main(): - parser = argparse.ArgumentParser(description="Generate board specific pin file") - parser.add_argument("--board-csv") - parser.add_argument("--prefix") - parser.add_argument("--output-source") - parser.add_argument("--output-header") - args = parser.parse_args() - - pins = Pins() - pins.create_pins() - - with open(args.output_source, "w") as out_source: - print("// This file was automatically generated by make-pins.py", file=out_source) - print("//", file=out_source) - - if args.board_csv: - print("// --board-csv {:s}".format(args.board_csv), file=out_source) - pins.parse_board_file(args.board_csv) - - if args.prefix: - print("// --prefix {:s}".format(args.prefix), file=out_source) - print("", file=out_source) - with open(args.prefix, "r") as prefix_file: - print(prefix_file.read(), end="", file=out_source) - - pins.print_tables(out_source) - - with open(args.output_header, "w") as out_header: - pins.print_header(out_header) + # This is provided by the IDF and is one more than the highest available + # GPIO num. + def cpu_table_size(self): + return "GPIO_NUM_MAX" if __name__ == "__main__": - main() + Esp32PinGenerator().main() diff --git a/ports/rp2/CMakeLists.txt b/ports/rp2/CMakeLists.txt index 7b715c31fa..4f76c5864f 100644 --- a/ports/rp2/CMakeLists.txt +++ b/ports/rp2/CMakeLists.txt @@ -479,7 +479,6 @@ set(GEN_PINS_PREFIX "${MICROPY_BOARDS_DIR}/rp2_prefix.c") set(GEN_PINS_MKPINS "${MICROPY_BOARDS_DIR}/make-pins.py") set(GEN_PINS_SRC "${CMAKE_BINARY_DIR}/pins_${MICROPY_BOARD}.c") set(GEN_PINS_HDR "${MICROPY_GENHDR_DIR}/pins.h") -set(GEN_PINS_AF_CONST "${MICROPY_GENHDR_DIR}/pins_af_const.h") if(EXISTS "${MICROPY_BOARDS_DIR}/${MICROPY_BOARD}/pins.csv") set(GEN_PINS_BOARD_CSV "${MICROPY_BOARDS_DIR}/${MICROPY_BOARD}/pins.csv") diff --git a/ports/rp2/boards/make-pins.py b/ports/rp2/boards/make-pins.py index 4cf73d9900..cbc3424705 100755 --- a/ports/rp2/boards/make-pins.py +++ b/ports/rp2/boards/make-pins.py @@ -1,331 +1,133 @@ #!/usr/bin/env python """Creates the pin file for the RP2.""" -from __future__ import print_function - -import argparse -import sys -import csv +import os import re +import sys -SUPPORTED_FN = { - "SPI": ["TX", "RX", "SCK", "CS"], - "UART": ["TX", "RX", "CTS", "RTS"], - "I2C": ["SCL", "SDA"], - "PWM": ["A", "B"], - "SIO": [""], - "PIO0": [""], - "PIO1": [""], - "GPCK": ["GPIN0", "GPOUT0", "GPIN1", "GPOUT1", "GPOUT2", "GPOUT3"], - "USB": ["OVCUR_DET", "VBUS_DET", "VBUS_EN"], -} +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../../../tools")) +import boardgen + +# This is NUM_BANK0_GPIOS. Pin indices are 0 to 29 (inclusive). +NUM_GPIOS = 30 +# Up to 10 additional extended pins (e.g. via the wifi chip). +NUM_EXT_GPIOS = 10 -def parse_pin(name_str): - """Parses a string and returns a pin number.""" - if len(name_str) < 2: - raise ValueError("Expecting pin name to be at least 2 characters.") - if not name_str.startswith("GPIO") and not name_str.startswith("EXT_GPIO"): - raise ValueError("Expecting pin name to start with EXT_/GPIO") - return int(re.findall(r"\d+$", name_str)[0]) +class Rp2Pin(boardgen.Pin): + def __init__(self, cpu_pin_name): + super().__init__(cpu_pin_name) + self._afs = [] - -def split_name_num(name_num): - num = None - for num_idx in range(len(name_num) - 1, -1, -1): - if not name_num[num_idx].isdigit(): - name = name_num[0 : num_idx + 1] - num_str = name_num[num_idx + 1 :] - if len(num_str) > 0: - num = int(num_str) - break - if name == "PIO": - name += str(num) - return name, num - - -class AlternateFunction(object): - """Holds the information associated with a pins alternate function.""" - - def __init__(self, idx, af_str): - self.idx = idx - self.af_str = af_str - - self.func = "" - self.fn_num = None - self.pin_type = "" - self.supported = False - - af_words = af_str.split("_", 1) - self.func, self.fn_num = split_name_num(af_words[0]) - if len(af_words) > 1: - self.pin_type = af_words[1] - if self.func in SUPPORTED_FN: - pin_types = SUPPORTED_FN[self.func] - if self.pin_type in pin_types: - self.supported = True - - def is_supported(self): - return self.supported - - def print(self, out_source): - """Prints the C representation of this AF.""" - if self.supported: - print(" AF", end="", file=out_source) + if self.name().startswith("EXT_"): + self._index = None + self._ext_index = int(self.name()[8:]) # "EXT_GPIOn" else: - print(" //", end="", file=out_source) - fn_num = self.fn_num - if fn_num is None: - fn_num = 0 - print( - "({:d}, {:4s}, {:d}), // {:s}".format(self.idx, self.func, fn_num, self.af_str), - file=out_source, - ) + self._index = int(self.name()[4:]) # "GPIOn" + self._ext_index = None + # Required by NumericPinGenerator. + def index(self): + return self._index -class Pin(object): - """Holds the information associated with a pin.""" + # Use the PIN() macro defined in rp2_prefix.c for defining the pin + # objects. + def definition(self): + if self._index is not None: + return "PIN({:d}, GPIO{:d}, 0, {:d}, pin_GPIO{:d}_af)".format( + self._index, self._index, len(self._afs), self.index() + ) + else: + return "PIN({:d}, EXT_GPIO{:d}, 1, 0, NULL)".format(self._ext_index, self._ext_index) - def __init__(self, pin, is_ext=False): - self.pin = pin - self.alt_fn = [] - self.alt_fn_count = 0 - self.is_board = False - self.is_ext = is_ext + # External pins need to be mutable (because they track the output state). + def is_const(self): + return self._index is not None - def cpu_pin_name(self): - return "{:s}GPIO{:d}".format("EXT_" if self.is_ext else "", self.pin) + # Add conditional macros only around the external pins based on how many + # are enabled. + def enable_macro(self): + if self._ext_index is not None: + return "(MICROPY_HW_PIN_EXT_COUNT > {:d})".format(self._ext_index) - def is_board_pin(self): - return self.is_board + def add_af(self, af_idx, _af_name, af): + if self._index is None: + raise boardgen.PinGeneratorError( + "Cannot add AF for ext pin '{:s}'".format(self.name()) + ) - def set_is_board_pin(self): - self.is_board = True + # _ + m = re.match("([A-Z][A-Z0-9][A-Z]+)(([0-9]+)(_.*)?)?", af) + af_fn = m.group(1) + af_unit = int(m.group(3)) if m.group(3) is not None else 0 + if af_fn == "PIO": + # Pins can be either PIO unit (unlike, say, I2C where a + # pin can only be I2C0 _or_ I2C1, both sharing the same AF + # index), so each PIO unit has a distinct AF index. + af_fn = "{:s}{:d}".format(af_fn, af_unit) + self._afs.append((af_idx + 1, af_fn, af_unit, af)) - def parse_af(self, af_idx, af_strs_in): - if len(af_strs_in) == 0: - return - # If there is a slash, then the slash separates 2 aliases for the - # same alternate function. - af_strs = af_strs_in.split("/") - for af_str in af_strs: - alt_fn = AlternateFunction(af_idx, af_str) - self.alt_fn.append(alt_fn) - if alt_fn.is_supported(): - self.alt_fn_count += 1 - - def alt_fn_name(self, null_if_0=False): - if null_if_0 and self.alt_fn_count == 0: - return "NULL" - return "pin_{:s}_af".format(self.cpu_pin_name()) - - def print(self, out_source): - if self.is_ext: - print("#if (MICROPY_HW_PIN_EXT_COUNT > {:d})".format(self.pin), file=out_source) - - if self.alt_fn_count == 0: - print("// ", end="", file=out_source) - print("const machine_pin_af_obj_t {:s}[] = {{".format(self.alt_fn_name()), file=out_source) - for alt_fn in self.alt_fn: - alt_fn.print(out_source) - if self.alt_fn_count == 0: - print("// ", end="", file=out_source) - print("};", file=out_source) - print("", file=out_source) - print( - "{:s}machine_pin_obj_t pin_{:s} = PIN({:d}, {:s}, {:d}, {:d}, {:s});".format( - "" if self.is_ext else "const ", - self.cpu_pin_name(), - self.pin, - self.cpu_pin_name(), - self.is_ext, - self.alt_fn_count, - self.alt_fn_name(null_if_0=True), - ), - file=out_source, - ) - if self.is_ext: - print("#endif", file=out_source) - print("", file=out_source) - - def print_header(self, out_header): - n = self.cpu_pin_name() - print( - "extern{:s}machine_pin_obj_t pin_{:s};".format(" " if self.is_ext else " const ", n), - file=out_header, - ) - if self.alt_fn_count > 0: - print("extern const machine_pin_af_obj_t pin_{:s}_af[];".format(n), file=out_header) - - -class NamedPin(object): - def __init__(self, name, pin): - self._name = name - self._pin = pin - - def pin(self): - return self._pin - - def name(self): - return self._name - - -class Pins(object): - def __init__(self): - self.cpu_pins = [] # list of NamedPin objects - self.board_pins = [] # list of NamedPin objects - self.ext_pins = [] # list of NamedPin objects - for i in range(0, 10): - self.ext_pins.append(NamedPin("EXT_GPIO{:d}".format(i), Pin(i, True))) - - def find_pin(self, pin_name): - for pin in self.cpu_pins: - if pin.name() == pin_name: - return pin.pin() - - for pin in self.ext_pins: - if pin.name() == pin_name: - return pin.pin() - - def parse_af_file(self, filename, pinname_col, af_col): - with open(filename, "r") as csvfile: - rows = csv.reader(csvfile) - for row in rows: - try: - pin_num = parse_pin(row[pinname_col]) - except Exception: - # import traceback; traceback.print_exc() - continue - pin = Pin(pin_num) - for af_idx in range(af_col, len(row)): - if af_idx >= af_col: - pin.parse_af(af_idx, row[af_idx]) - self.cpu_pins.append(NamedPin(pin.cpu_pin_name(), pin)) - - def parse_board_file(self, filename): - with open(filename, "r") as csvfile: - rows = csv.reader(csvfile) - for row in rows: - if len(row) == 0 or row[0].startswith("#"): - # Skip empty lines, and lines starting with "#" - continue - if len(row) != 2: - raise ValueError("Expecting two entries in a row") - - cpu_pin_name = row[1] - try: - parse_pin(cpu_pin_name) - except: - # import traceback; traceback.print_exc() - continue - pin = self.find_pin(cpu_pin_name) - if pin: - pin.set_is_board_pin() - if row[0]: # Only add board pins that have a name - self.board_pins.append(NamedPin(row[0], pin)) - - def print_table(self, label, named_pins, out_source): - print("", file=out_source) - print( - "const machine_pin_obj_t *machine_pin_{:s}_pins[] = {{".format(label), file=out_source - ) - for pin in named_pins: - if not pin.pin().is_ext: - print(" &pin_{},".format(pin.name()), file=out_source) - print("};", file=out_source) - print("", file=out_source) - - def print_named(self, label, named_pins, out_source): - print("", file=out_source) - print( - "STATIC const mp_rom_map_elem_t pin_{:s}_pins_locals_dict_table[] = {{".format(label), - file=out_source, - ) - for named_pin in named_pins: - pin = named_pin.pin() - if pin.is_ext: - print(" #if (MICROPY_HW_PIN_EXT_COUNT > {:d})".format(pin.pin), file=out_source) + # This will be called at the start of the output (after the prefix). Use + # it to emit the af objects (via the AF() macro in rp2_prefix.c). + def print_source(self, out_source): + if self._index is not None: print( - " {{ MP_ROM_QSTR(MP_QSTR_{:s}), MP_ROM_PTR(&pin_{:s}) }},".format( - named_pin.name(), pin.cpu_pin_name() - ), + "const machine_pin_af_obj_t pin_GPIO{:d}_af[] = {{".format(self.index()), file=out_source, ) - if pin.is_ext: - print(" #endif", file=out_source) - - print("};", file=out_source) - print( - "MP_DEFINE_CONST_DICT(pin_{:s}_pins_locals_dict, pin_{:s}_pins_locals_dict_table);".format( - label, label - ), - file=out_source, - ) - print("", file=out_source) - - def print(self, out_source): - for pin in self.cpu_pins: - pin.pin().print(out_source) - - for pin in self.ext_pins: - if pin.pin().is_ext: - pin.pin().print(out_source) - - self.print_table("cpu", self.cpu_pins, out_source) - self.print_named("cpu", self.cpu_pins + self.ext_pins, out_source) - self.print_named("board", self.board_pins, out_source) - - def print_header(self, out_header): - for named_pin in self.cpu_pins: - pin = named_pin.pin() - pin.print_header(out_header) - for named_pin in self.board_pins: - pin = named_pin.pin() - if pin.is_ext: - pin.print_header(out_header) - # provide #define's mapping board to cpu name - for named_pin in self.board_pins: - if named_pin.pin().is_board_pin(): + for af_idx, af_fn, af_unit, af in self._afs: print( - "#define pin_{:s} pin_{:s}".format( - named_pin.name(), named_pin.pin().cpu_pin_name() - ), - file=out_header, + " AF({:d}, {:4s}, {:d}), // {:s}".format(af_idx, af_fn, af_unit, af), + file=out_source, ) + print("};", file=out_source) + print(file=out_source) + + # rp2 cpu names must be "GPIOn" or "EXT_GPIOn". + @staticmethod + def validate_cpu_pin_name(cpu_pin_name): + boardgen.Pin.validate_cpu_pin_name(cpu_pin_name) + + if cpu_pin_name.startswith("GPIO") and cpu_pin_name[4:].isnumeric(): + if not (0 <= int(cpu_pin_name[4:]) < NUM_GPIOS): + raise boardgen.PinGeneratorError("Unknown cpu pin '{}'".format(cpu_pin_name)) + elif cpu_pin_name.startswith("EXT_GPIO") and cpu_pin_name[8:].isnumeric(): + if not (0 <= int(cpu_pin_name[8:]) < NUM_EXT_GPIOS): + raise boardgen.PinGeneratorError("Unknown ext pin '{}'".format(cpu_pin_name)) + else: + raise boardgen.PinGeneratorError( + "Invalid cpu pin name '{}', must be 'GPIOn' or 'EXT_GPIOn'".format(cpu_pin_name) + ) -def main(): - parser = argparse.ArgumentParser(description="Generate board specific pin file") - parser.add_argument("--board-csv") - parser.add_argument("--af-csv") - parser.add_argument("--prefix") - parser.add_argument("--output-source") - parser.add_argument("--output-header") - args = parser.parse_args() +class Rp2PinGenerator(boardgen.NumericPinGenerator): + def __init__(self): + # Use custom pin type above, and also enable the --af-csv argument. + super().__init__( + pin_type=Rp2Pin, + enable_af=True, + ) - pins = Pins() + # Pre-define the pins (i.e. don't require them to be listed in pins.csv). + for i in range(NUM_GPIOS): + self.add_cpu_pin("GPIO{}".format(i)) + for i in range(NUM_EXT_GPIOS): + self.add_cpu_pin("EXT_GPIO{}".format(i)) - with open(args.output_source, "w") as out_source: - print("// This file was automatically generated by make-pins.py", file=out_source) - print("//", file=out_source) - if args.af_csv: - print("// --af {:s}".format(args.af_csv), file=out_source) - pins.parse_af_file(args.af_csv, 0, 1) + # Provided by pico-sdk. + def cpu_table_size(self): + return "NUM_BANK0_GPIOS" - if args.board_csv: - print("// --board {:s}".format(args.board_csv), file=out_source) - pins.parse_board_file(args.board_csv) + # Only use pre-defined cpu pins (do not let board.csv create them). + def find_pin_by_cpu_pin_name(self, cpu_pin_name, create=True): + return super().find_pin_by_cpu_pin_name(cpu_pin_name, create=False) - if args.prefix: - print("// --prefix {:s}".format(args.prefix), file=out_source) - print("", file=out_source) - with open(args.prefix, "r") as prefix_file: - print(prefix_file.read(), file=out_source) - pins.print(out_source) - - with open(args.output_header, "w") as out_header: - pins.print_header(out_header) + # NumericPinGenerator doesn't include the cpu dict by default (only the + # board dict), so add that to the output for rp2. + def print_source(self, out_source): + super().print_source(out_source) + self.print_cpu_locals_dict(out_source) if __name__ == "__main__": - main() + Rp2PinGenerator().main() diff --git a/ports/rp2/machine_pin.c b/ports/rp2/machine_pin.c index 77d9e13480..b277a939e5 100644 --- a/ports/rp2/machine_pin.c +++ b/ports/rp2/machine_pin.c @@ -69,14 +69,14 @@ MP_DEFINE_CONST_OBJ_TYPE( pin_cpu_pins_obj_type, MP_QSTR_cpu, MP_TYPE_FLAG_NONE, - locals_dict, &pin_cpu_pins_locals_dict + locals_dict, &machine_pin_cpu_pins_locals_dict ); MP_DEFINE_CONST_OBJ_TYPE( pin_board_pins_obj_type, MP_QSTR_board, MP_TYPE_FLAG_NONE, - locals_dict, &pin_board_pins_locals_dict + locals_dict, &machine_pin_board_pins_locals_dict ); typedef struct _machine_pin_irq_obj_t { @@ -86,7 +86,7 @@ typedef struct _machine_pin_irq_obj_t { } machine_pin_irq_obj_t; STATIC const mp_irq_methods_t machine_pin_irq_methods; -extern const machine_pin_obj_t *machine_pin_cpu_pins[NUM_BANK0_GPIOS]; +extern const machine_pin_obj_t machine_pin_obj_table[NUM_BANK0_GPIOS]; // Mask with "1" indicating that the corresponding pin is in simulated open-drain mode. uint32_t machine_pin_open_drain_mask; @@ -170,18 +170,18 @@ const machine_pin_af_obj_t *machine_pin_find_alt_by_index(const machine_pin_obj_ const machine_pin_obj_t *machine_pin_find(mp_obj_t pin) { // Is already a object of the proper type if (mp_obj_is_type(pin, &machine_pin_type)) { - return pin; + return MP_OBJ_TO_PTR(pin); } if (mp_obj_is_str(pin)) { const char *name = mp_obj_str_get_str(pin); // Try to find the pin in the board pins first. - const machine_pin_obj_t *self = machine_pin_find_named(&pin_board_pins_locals_dict, pin); + const machine_pin_obj_t *self = machine_pin_find_named(&machine_pin_board_pins_locals_dict, pin); if (self != NULL) { return self; } // If not found, try to find the pin in the cpu pins which include // CPU and and externally controlled pins (if any). - self = machine_pin_find_named(&pin_cpu_pins_locals_dict, pin); + self = machine_pin_find_named(&machine_pin_cpu_pins_locals_dict, pin); if (self != NULL) { return self; } @@ -189,8 +189,8 @@ const machine_pin_obj_t *machine_pin_find(mp_obj_t pin) { } else if (mp_obj_is_int(pin)) { // get the wanted pin object int wanted_pin = mp_obj_get_int(pin); - if (0 <= wanted_pin && wanted_pin < MP_ARRAY_SIZE(machine_pin_cpu_pins)) { - return machine_pin_cpu_pins[wanted_pin]; + if (0 <= wanted_pin && wanted_pin < MP_ARRAY_SIZE(machine_pin_obj_table)) { + return &machine_pin_obj_table[wanted_pin]; } } mp_raise_ValueError("invalid pin"); @@ -432,7 +432,7 @@ STATIC machine_pin_irq_obj_t *machine_pin_get_irq(mp_hal_pin_obj_t pin) { irq = m_new_obj(machine_pin_irq_obj_t); irq->base.base.type = &mp_irq_type; irq->base.methods = (mp_irq_methods_t *)&machine_pin_irq_methods; - irq->base.parent = MP_OBJ_FROM_PTR(machine_pin_cpu_pins[pin]); + irq->base.parent = MP_OBJ_FROM_PTR(&machine_pin_obj_table[pin]); irq->base.handler = mp_const_none; irq->base.ishard = false; MP_STATE_PORT(machine_pin_irq_obj[pin]) = irq; diff --git a/ports/rp2/machine_pin.h b/ports/rp2/machine_pin.h index d2a39f8e37..b3349188e8 100644 --- a/ports/rp2/machine_pin.h +++ b/ports/rp2/machine_pin.h @@ -65,10 +65,10 @@ extern const mp_obj_type_t machine_pin_af_type; #include "genhdr/pins.h" extern const mp_obj_type_t pin_cpu_pins_obj_type; -extern const mp_obj_dict_t pin_cpu_pins_locals_dict; +extern const mp_obj_dict_t machine_pin_cpu_pins_locals_dict; extern const mp_obj_type_t pin_board_pins_obj_type; -extern const mp_obj_dict_t pin_board_pins_locals_dict; +extern const mp_obj_dict_t machine_pin_board_pins_locals_dict; void machine_pin_ext_init(void); bool machine_pin_ext_is_adc_channel(const machine_pin_obj_t *self); diff --git a/tools/boardgen.py b/tools/boardgen.py new file mode 100644 index 0000000000..bef8e74727 --- /dev/null +++ b/tools/boardgen.py @@ -0,0 +1,506 @@ +# This file is part of the MicroPython project, http://micropython.org/ +# +# The MIT License (MIT) +# +# Copyright (c) 2023 Jim Mussared +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. + +# This script contains common functionality to assist a port in implementing +# make-pins.py, which is used to emit compile-time definitions of pin, AF, and +# ADC objects based on inputs from the vendor HAL/SDK and the board +# definition's pins.csv. + +# The pins.csv file can contain empty lines, comments (a line beginning with "#") +# or pin definition lines. Pin definition lines must be of the form: +# +# board,cpu +# +# Where "board" is the user-facing name of the pin as specified by the particular +# board layout and markings, and "cpu" is the corresponding name of the CPU/MCU +# pin. +# +# The "board" entry may be absent if the CPU pin has no additional name, and both +# entries may start with "-" to hide them from the corresponding Python dict of +# pins, and hence hide them from the user (but they are still accessible in C). +# +# For example, take the following pins.csv file: +# +# X1,PA0 +# -X2,PA1 +# X3,-PA2 +# -X4,-PA3 +# ,PA4 +# ,-PA5 +# +# The first row here configures: +# - The CPU pin PA0 is labelled X1. +# - The Python user can access both by the names Pin("X1") and Pin("A0"). +# - The Python user can access both by the members Pin.board.X1 and Pin.cpu.A0. +# - In C code they are available as pyb_pin_X1 and pin_A0. +# +# Prefixing the names with "-" hides them from the user. The following table +# summarises the various possibilities: +# +# pins.csv entry | board name | cpu name | C board name | C cpu name +# ---------------+------------+----------+--------------+----------- +# X1,PA0 "X1" "A0" pyb_pin_X1 pin_A0 +# -X2,PA1 - "A1" pyb_pin_X2 pin_A1 +# X3,-PA2 "X3" - pyb_pin_X3 pin_A2 +# -X4,-PA3 - - pyb_pin_X4 pin_A3 +# ,PA4 - "A4" - pin_A4 +# ,-PA5 - - - pin_A5 + +import argparse +import csv +import os +import sys + + +class PinGeneratorError(Exception): + pass + + +# A port should define a subclass of Pin that knows how to validate cpu/board +# names and emits the required structures. +class Pin: + def __init__(self, cpu_pin_name): + self._cpu_pin_name = cpu_pin_name + # Optional aliases for the board from pins.csv. Each entry is a tuple + # of (name, hidden). Hidden board pins are in pins.csv with with a "-" + # prefix and will available to C but not Python. + self._board_pin_names = set() + # An unavailable pin is one that is not explicitly mentioned at all in + # pins.csv (or added explicitly with PinGenerator.add_cpu_pin). + self._available = False + # A hidden pin is one that is in pins.csv with a "-" prefix and will + # be still available to C but not Python. + self._hidden = False + # Reference to the PinGenerator instance. + self._generator = None + + # The name of the pin to use in MP_QSTR_{} or pin_{}. Defaults to the cpu name. + def name(self): + return self._cpu_pin_name + + # Add a board alias (e.g. from pins.csv). + def add_board_pin_name(self, board_pin_name, hidden=False): + self._board_pin_names.add( + ( + board_pin_name, + hidden, + ) + ) + + # Override this to handle an af specified in af.csv. + def add_af(self, af_idx, af_name, af): + raise NotImplementedError + + # Override this to verify that naming matches the MCU standard (e.g. "GPIOn" or "PXn"). + @staticmethod + def validate_cpu_pin_name(cpu_pin_name): + if not cpu_pin_name.strip(): + raise PinGeneratorError("Missing cpu pin name") + + # Override this to provide additional validation of board names. + @staticmethod + def validate_board_pin_name(board_pin_name): + # TODO: ensure this is a valid Python/C identifier and can be used as MP_QSTR_foo. + pass + + # Must be implemented when using NumericPinGenerator. + # Returns the integer index, or None to exclude this pin from the table. + def index(self): + raise NotImplementedError + + # Can be overridden when using NumericPinGenerator. + # Returns a string which is a C expression that evaluates to the index + # e.g. `GPIO_NUM_7`. + # This is used whenever the index is emitted in source code and defaults + # to just returning the pin index as a literal. + # Return None to exclude this pin from the table. + def index_name(self): + i = self.index() + return str(i) if i is not None else None + + # Returns an expression that defines the pin. e.g. `{ .base { ... }, .x }`. + # This is used as the RHS of the `const machine_pin_obj_t + # pin_EXT_GPIO0_obj =` statements for named pins, and elements of + # `machine_pin_obj_table` for numeric pins. + # This will typically might be implemented as an invocation of a macro + # defined in the port-specific prefix. + def definition(self): + raise NotImplementedError + + # Whether the pin object should be declared as "const". This should be True + # for most pins, but some special cases (e.g. external pins on rp2) might + # need mutable pin objects (e.g. to track current pin state). + def is_const(self): + return True + + # Optionally return a preprocessor expression that can be used to suppress + # this pin (e.g. `MICROPY_HW_ENABLE_GPIOn`). + def enable_macro(self): + return None + + # Override this to output any additional per-pin definitions or other + # content that should appear at the start of the source output. + # This could be used to define additional objects such as IRQs or AFs. + def print_source(self, out_source): + pass + + +# A port should define a subclass of PinGenerator (or NumericPinGenerator). +class PinGenerator: + def __init__(self, pin_type, enable_af=False): + self._pins = [] + self._pin_type = pin_type + self._enable_af = enable_af + + # Allows a port to define a known cpu pin (without relying on it being in the + # csv file). + def add_cpu_pin(self, cpu_pin_name, available=True): + pin = self._pin_type(cpu_pin_name) + pin._available = available + self._pins.append(pin) + pin._generator = self + return pin + + # Iterate just the available pins (i.e. ones in pins.csv). + def available_pins(self, exclude_hidden=False): + for pin in self._pins: + if not pin._available: + continue + if exclude_hidden and pin._hidden: + continue + yield pin + + # Allows a port to add additional command-line arguments to be handled. + def extra_args(self, parser): + pass + + # Load board->cpu mapping from csv. + def parse_board_csv(self, filename): + with open(filename, "r") as csvfile: + rows = csv.reader(csvfile) + for linenum, row in enumerate(rows): + try: + # Skip empty lines, and lines starting with "#". + if len(row) == 0 or row[0].startswith("#"): + continue + + # Lines must be pairs of names. + if len(row) != 2: + raise PinGeneratorError("Expecting two entries in each row") + board_pin_name, cpu_pin_name = (x.strip() for x in row) + + # All rows must include a cpu name. + cpu_hidden = False + if cpu_pin_name.startswith("-"): + cpu_hidden = True + cpu_pin_name = cpu_pin_name[1:] + self._pin_type.validate_cpu_pin_name(cpu_pin_name) + pin = self.find_pin_by_cpu_pin_name(cpu_pin_name, create=True) + pin._available = True # It's in pins.csv so must be available. + pin._hidden = cpu_hidden # Optionally don't make available to Python. + + # Rows can optionally alias to a board name. + if board_pin_name: + board_hidden = False + if board_pin_name.startswith("-"): + board_hidden = True + board_pin_name = board_pin_name[1:] + self._pin_type.validate_board_pin_name(board_pin_name) + pin.add_board_pin_name(board_pin_name, board_hidden) + + # Inject "file:line: " into the exception. + except PinGeneratorError as er: + raise PinGeneratorError("{}:{}: {}".format(filename, linenum, er)) + + def parse_af_csv(self, filename, header_rows=1, pin_col=0, af_col=1): + headings = {} + with open(filename, "r") as csvfile: + rows = csv.reader(csvfile) + for linenum, row in enumerate(rows): + try: + # Skip empty lines, and lines starting with "#". + if len(row) == 0 or row[0].startswith("#"): + continue + + # Consume `header_rows` non-blank/comment rows at the start. + if header_rows: + if not headings: + # If this is the first header row then initialise + # the headings dict. + for af_idx, header in enumerate(row[af_col:]): + headings[af_idx] = header.strip() + header_rows -= 1 + continue + + # Lines must be pairs of names. + if len(row) <= max(pin_col, af_col): + raise PinGeneratorError( + "Expecting {} entries in each row".format(max(pin_col, af_col)) + ) + + cpu_pin_name = row[pin_col].strip() + if cpu_pin_name == "-": + continue + self._pin_type.validate_cpu_pin_name(cpu_pin_name) + pin = self.find_pin_by_cpu_pin_name(cpu_pin_name, create=True) + + for af_idx, af in enumerate(row[af_col:]): + af = af.strip() + if not af: + continue + pin.add_af(af_idx, headings.get(af_idx, ""), af) + + # Inject "file:line: " into the exception. + except PinGeneratorError as er: + raise PinGeneratorError("{}:{}: {}".format(filename, linenum, er)) + + # Find an existing pin. + def find_pin_by_cpu_pin_name(self, cpu_pin_name, create=True): + for pin in self._pins: + if pin._cpu_pin_name == cpu_pin_name: + return pin + if create: + return self.add_cpu_pin(cpu_pin_name, available=False) + else: + raise PinGeneratorError("Unknown cpu pin {}".format(cpu_pin_name)) + + # Print the locals dict for Pin.board. + def print_board_locals_dict(self, out_source): + print(file=out_source) + print( + "STATIC const mp_rom_map_elem_t machine_pin_board_pins_locals_dict_table[] = {", + file=out_source, + ) + for pin in self.available_pins(): + for board_pin_name, board_hidden in pin._board_pin_names: + if board_hidden: + # Don't include hidden pins in Pins.board. + continue + + # We don't use the enable macro for board pins, because they + # shouldn't be referenced in pins.csv unless they're + # available. + print( + " {{ MP_ROM_QSTR(MP_QSTR_{:s}), MP_ROM_PTR(pin_{:s}) }},".format( + board_pin_name, + pin.name(), + ), + file=out_source, + ) + print("};", file=out_source) + print( + "MP_DEFINE_CONST_DICT(machine_pin_board_pins_locals_dict, machine_pin_board_pins_locals_dict_table);", + file=out_source, + ) + + # Print the locals dict for Pin.cpu. + def print_cpu_locals_dict(self, out_source): + print(file=out_source) + print( + "STATIC const mp_rom_map_elem_t machine_pin_cpu_pins_locals_dict_table[] = {", + file=out_source, + ) + for pin in self.available_pins(exclude_hidden=True): + m = pin.enable_macro() + if m: + print(" #if {}".format(m), file=out_source) + print( + " {{ MP_ROM_QSTR(MP_QSTR_{:s}), MP_ROM_PTR(pin_{:s}) }},".format( + pin.name(), + pin.name(), + ), + file=out_source, + ) + if m: + print(" #endif", file=out_source) + print("};", file=out_source) + print( + "MP_DEFINE_CONST_DICT(machine_pin_cpu_pins_locals_dict, machine_pin_cpu_pins_locals_dict_table);", + file=out_source, + ) + + # NumericPinGenerator can override this to use an entry in machine_pin_obj_table. + def _cpu_pin_pointer(self, pin): + return "&pin_{:s}_obj".format(pin.name()) + + # Print the pin_CPUNAME and pin_BOARDNAME macros. + def print_defines(self, out_header): + # Provide #defines for each cpu pin. + for pin in self.available_pins(): + print(file=out_header) + m = pin.enable_macro() + if m: + print("#if {}".format(m), file=out_header) + + # #define pin_CPUNAME (...) + print( + "#define pin_{:s} ({:s})".format(pin.name(), self._cpu_pin_pointer(pin)), + file=out_header, + ) + + # #define pin_BOARDNAME (pin_CPUNAME) + for board_pin_name, _board_hidden in pin._board_pin_names: + # Note: Hidden board pins are still available to C via the macro. + print( + "#define pin_{:s} (pin_{:s})".format( + board_pin_name, + pin.name(), + ), + file=out_header, + ) + + if m: + print("#endif", file=out_header) + + def print_source(self, out_source): + raise NotImplementedError + + def print_header(self, out_header): + self.print_defines(out_header) + + # A port can override this if it has extra input files (e.g. af.csv) to load. + def load_inputs(self, out_source): + # Optionally load pins.csv to get cpu->board name mappings. + if self._enable_af and self.args.af_csv: + print("// --af-csv {:s}".format(self.args.af_csv), file=out_source) + self.parse_af_csv(self.args.af_csv) + + # Optionally load pins.csv to get cpu->board name mappings. + if self.args.board_csv: + print("// --board-csv {:s}".format(self.args.board_csv), file=out_source) + self.parse_board_csv(self.args.board_csv) + + # Prepend the prefix file to the start of the output. + if self.args.prefix: + print("// --prefix {:s}".format(self.args.prefix), file=out_source) + print(file=out_source) + with open(self.args.prefix, "r") as prefix_file: + print(prefix_file.read(), end="", file=out_source) + + # A port can override this to do extra work after the main source+header + # have been written, such as generating additional header files. + def generate_extra_files(self): + pass + + def main(self): + parser = argparse.ArgumentParser(description="Generate board specific pin file") + parser.add_argument("--board-csv") + if self._enable_af: + parser.add_argument("--af-csv") + parser.add_argument("--prefix") + parser.add_argument("--output-source") + parser.add_argument("--output-header") + self.extra_args(parser) + self.args = parser.parse_args() + + try: + with open(self.args.output_source, "w") as out_source: + print("// This file was automatically generated by make-pins.py", file=out_source) + print("//", file=out_source) + + # Load additional files (including port-specific ones). + self.load_inputs(out_source) + + # Allow a port to print arbitrary per-pin content. + for pin in self.available_pins(): + pin.print_source(out_source) + + # Print the tables and dictionaries. + self.print_source(out_source) + + with open(self.args.output_header, "w") as out_header: + self.print_header(out_header) + + self.generate_extra_files() + except PinGeneratorError as er: + print(er) + sys.exit(1) + + +# For ports that use numeric pin identifiers (e.g. ESP32, rp2). +# This emits the machine_pin_obj_t instances as an array (machine_pin_obj_table). +class NumericPinGenerator(PinGenerator): + # This should return a const expression that is the number of GPIO pins + # for this board. + def cpu_table_size(self): + raise NotImplementedError + + def print_cpu_table(self, out_source): + # Print machine_pin_obj_table, where each element is `[n] = {obj}`. + print(file=out_source) + print( + "const machine_pin_obj_t machine_pin_obj_table[{}] = {{".format(self.cpu_table_size()), + file=out_source, + ) + for pin in self.available_pins(): + n = pin.index_name() + if n is None: + continue + + m = pin.enable_macro() + if m: + print(" #if {}".format(m), file=out_source) + print( + " [{:s}] = {:s},".format( + pin.index_name(), + pin.definition(), + ), + file=out_source, + ) + if m: + print(" #endif", file=out_source) + print("};", file=out_source) + + # For pins that do not have an index, print them out in the same style as PinGenerator. + print(file=out_source) + for pin in self.available_pins(): + n = pin.index_name() + if n is not None: + continue + + m = pin.enable_macro() + if m: + print("#if {}".format(m), file=out_source) + print( + "{:s}machine_pin_obj_t pin_{:s}_obj = {:s};".format( + "const " if pin.is_const() else "", + pin.name(), + pin.definition(), + ), + file=out_source, + ) + if m: + print("#endif", file=out_source) + + # Replace PinGenerator's implementation to print the numeric table. + def print_source(self, out_source): + self.print_cpu_table(out_source) + self.print_board_locals_dict(out_source) + + def _cpu_pin_pointer(self, pin): + n = pin.index_name() + if n is not None: + return "&machine_pin_obj_table[{:s}]".format(pin.index_name()) + else: + return super()._cpu_pin_pointer(pin)