summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xports/esp32/boards/make-pins.py203
-rw-r--r--ports/rp2/CMakeLists.txt1
-rwxr-xr-xports/rp2/boards/make-pins.py414
-rw-r--r--ports/rp2/machine_pin.c18
-rw-r--r--ports/rp2/machine_pin.h4
-rw-r--r--tools/boardgen.py506
6 files changed, 669 insertions, 477 deletions
diff --git a/ports/esp32/boards/make-pins.py b/ports/esp32/boards/make-pins.py
index 205427469..49b10f0ce 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)
-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:
- def __init__(self):
- self.cpu_pins = [] # list of NamedPin objects
- self.board_pins = [] # list of NamedPin objects
-
- def find_pin(self, pin_name):
- for pin in self.cpu_pins:
- if pin.name() == pin_name:
- return pin.pin()
-
- 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))
-
- 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,
+ 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)
)
- 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,
- )
-
+ if not (0 <= int(cpu_pin_name[4:]) < NUM_GPIOS):
+ raise boardgen.PinGeneratorError("Unknown cpu pin '{}'".format(cpu_pin_name))
-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)
+class Esp32PinGenerator(boardgen.NumericPinGenerator):
+ def __init__(self):
+ # Use custom pin type above.
+ super().__init__(pin_type=Esp32Pin)
- 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)
+ # 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))
- pins.print_tables(out_source)
+ # 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)
- 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 7b715c31f..4f76c5864 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 4cf73d990..cbc342470 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"],
-}
-
-
-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])
-
-
-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
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../../../tools"))
+import boardgen
- self.func = ""
- self.fn_num = None
- self.pin_type = ""
- self.supported = False
+# 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
- 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
+class Rp2Pin(boardgen.Pin):
+ def __init__(self, cpu_pin_name):
+ super().__init__(cpu_pin_name)
+ self._afs = []
- 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,
- )
-
-
-class Pin(object):
- """Holds the information associated with a pin."""
-
- 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
-
- def cpu_pin_name(self):
- return "{:s}GPIO{:d}".format("EXT_" if self.is_ext else "", self.pin)
-
- def is_board_pin(self):
- return self.is_board
-
- def set_is_board_pin(self):
- self.is_board = True
-
- 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)
+ self._index = int(self.name()[4:]) # "GPIOn"
+ self._ext_index = None
+
+ # Required by NumericPinGenerator.
+ def index(self):
+ return self._index
+
+ # 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)
+
+ # External pins need to be mutable (because they track the output state).
+ def is_const(self):
+ return self._index is not None
+
+ # 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 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 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)
+ # <af><unit>_<pin>
+ 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))
+
+ # 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()
-
- pins = Pins()
+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,
+ )
- 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)
+ # 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))
- if args.board_csv:
- print("// --board {:s}".format(args.board_csv), file=out_source)
- pins.parse_board_file(args.board_csv)
+ # Provided by pico-sdk.
+ def cpu_table_size(self):
+ return "NUM_BANK0_GPIOS"
- 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)
+ # 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)
- 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 77d9e1348..b277a939e 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 d2a39f8e3..b3349188e 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 000000000..bef8e7472
--- /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)