speeduino/libs/digitalIOPerformance/generateDigitalIOHeader.py

310 lines
12 KiB
Python
Executable File

#!/usr/bin/env python
"""
Code generation script to build digitalIOPerformance.h from the Arduino
boards.txt & pins_arduino.h files.
You shouldn't need to rerun this unless you have a new version of
Arduino installed, or you're using different boards to the default
installation.
See the README file for more details.
Copyright (c) 2013 Angus Gratton. Script licensed under the GNU Lesser
General Public License, version 2 or above.
"""
# TODO: command line options
ARDUINO_PATH="/Applications/Arduino.app/Contents/Resources/Java/"
import os, re, subprocess
def main():
boards = extract_boards()
for board in boards:
add_variant_macros(board)
find_unambiguous_macros(boards)
identifying_keys = find_unique_macro_keys(boards)
boards = merge_matching_boards(boards, identifying_keys)
for board in boards:
extract_portnames_pins(board)
with open("digitalIOPerformance.h", "w") as output:
generate_header_file(boards, identifying_keys, output)
def extract_boards():
""" Parse the Arduino boards file and return a list of all the boards,
with each board as a dictionary containing all their board-specific
key-value pairs
"""
with open(os.path.join(ARDUINO_PATH, "hardware/arduino/boards.txt")) as board_contents:
boards = dict()
RE_BOARDENTRY = re.compile(r"^([A-Za-z][^=]+)=(.+)$", re.MULTILINE)
for full_key,value in re.findall(RE_BOARDENTRY, board_contents.read()):
board = full_key.split(".")[0]
key = ".".join(full_key.split(".")[1:])
if not board in boards:
boards[board] = { "id" : board }
boards[board][key] = value
return list(boards.values())
def run_preprocessor(board, additional_args=[]):
"""
Run the C preprocessor over a particular board, and return
the contents of the processed file as a string
"""
source_path = board["pin_path"]
args = ["/Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/bin/avr-gcc", "-DARDUINO_MAIN", "-E",
"-mmcu=%s" % board["build.mcu"],
"-DF_CPU=%s" % board["build.f_cpu"] ]
if "build.vid" in board:
args += [ "-DUSB_VID=%s" % board["build.vid" ] ]
if "build.pid" in board:
args += [ "-DUSB_PID=%s" % board["build.pid" ] ]
print args + additional_args + [ source_path ]
proc = subprocess.Popen(args + additional_args + [ source_path ],stdout=subprocess.PIPE)
proc.wait()
if proc.returncode != 0:
raise Error("Failed to run preprocessor")
return proc.stdout.read()
def add_variant_macros(board):
"""
Run the pin_arduinos.h header for this board through the preprocessor,
extract all defined macros and attach them to the board dict under key
'macros'
"""
print os.path.join(ARDUINO_PATH, "hardware/arduino/variants/%s/pins_arduino.h" % (board["build.variant"]))
board["pin_path"] = os.path.join(ARDUINO_PATH, "hardware/arduino/variants/%s/pins_arduino.h" % (board["build.variant"]))
macros = run_preprocessor(board, ["-dM"])
macros = [ re.sub(r"^#define ", "", macro) for macro in macros.split("\n") ]
macros = [ tuple(macro.split(" ", 1)) for macro in macros if " " in macro ]
board["macros"] = dict(macros)
def find_unambiguous_macros(boards):
"""
Trim the macros defined against any of the boards, to leave only those with
only numeric values (anything else is too tricky, especially with
token representation ambiguities.)
Modifies the board dict in-place.
"""
ambiguous_macros = set()
# build list of ambiguous macros
for board in boards:
for key,value in board["macros"].items():
if not re.match(r"^-?0?x?[\dA-Fa-f]+L?$", value.strip()):
ambiguous_macros.add(key)
# trim the ambiguous macros from any of the boards
for board in boards:
for ambiguous in ambiguous_macros:
if ambiguous in board["macros"]:
del board["macros"][ambiguous]
def find_unique_macro_keys(boards):
"""
Go through each board and find a small subset of unique macro
values that distinguish each board from the others.
This allows us to generate a header file that can automatically
determine which board profile it's being compiled against, at compile
time.
Returns a set of macro names.
"""
identifying_keys = set()
for board in boards:
duplicates = list( dup for dup in boards if dup != board )
for dup in duplicates:
# skip anything that's already unique as per existing keys
identified = False
for key in identifying_keys:
identified = identified or board["macros"].get(key,"") != dup["macros"].get(key, "")
if identified:
continue
# find something unique about this duplicate
uniques = set(board["macros"].items()) ^ set(dup["macros"].items())
uniques = [ key for key,value in uniques ]
# find the least selective key in the uniques
def selectiveness(key):
return len([d for d in duplicates if ( d["macros"].get(key, "") == board["macros"].get(key, ""))])
uniques = sorted(uniques, key=selectiveness)
if len(uniques) > 0:
identifying_keys.add(uniques[0])
return identifying_keys
def merge_matching_boards(boards, identifying_keys):
"""
Go through each board and merge together any that we can't unambiguously
identify by using the macros defined in identifying_keys.
We assume any matching boards will have matching 'variants' defined, otherwise
we throw an error.
Returns a new list of boards, with an ambiguously defined ones merged together.
"""
def key(board):
return str([ board["macros"].get(key,"") for key in identifying_keys ])
# Merge together any boards with identical keys (making composite names & ids)
unique_boards = []
for board in boards:
found = False
for unique in unique_boards:
if key(unique) == key(board):
if board["build.variant"] != unique["build.variant"]:
raise RuntimeError("Ambiguous boards %s / %s have matching variant! Can't distinguish reliably between them." % (board["id"], unique["id"]))
unique["id"] += " | " + board["id"]
unique["name"] += " | " + board["name"]
found = True
if not found:
unique_boards += [ board ]
return unique_boards
def extract_portnames_pins(board):
"""
Run the preprocessor over this boards' pin header file to pull
out port names and bitmasks.
"""
def extract_array(array_name):
""" Match a multiline C array with the given name (pretty clumsy.) """
content = re.search(array_name + ".+?\{(.+?)}", output, re.MULTILINE|re.DOTALL).group(1)
return [ e.strip() for e in content.split(",") if len(e.strip()) ]
output = run_preprocessor(board)
board["ports"] = extract_array("digital_pin_to_port_PGM")
# strip P prefix, ie PD becomes D
board["ports"] = [ e[1] if e[0] == "P" else None for e in board["ports"] ]
board["bitmasks"] = extract_array("digital_pin_to_bit_mask_PGM")
pin_to_timer = extract_array("digital_pin_to_timer_PGM")
board["timers"] = [ p if p != "NOT_ON_TIMER" else None for p in pin_to_timer ]
# do some sanity checks on the data we extracted
if len(board["ports"]) != len(board["bitmasks"]):
raise RuntimeError("Number of ports in %s doesn't match bitmask count - %d vs %d" % (board["id"], len(board["ports"]), len(board["bitmasks"])))
if len(board["ports"]) != int(board["macros"]["NUM_DIGITAL_PINS"]):
raise RuntimeError("Number of ports in %s doesn't match number of digital pins reported in header" % (board["id"], len(board["ports"]), board["macros"]["NUM_DIGITAL_PINS"]))
DIGITALWRITE_TEMPLATE = """
else if(pin == %(number)s && value) PORT%(port)s |= %(bitmask)s;
else if(pin == %(number)s && !value) PORT%(port)s &= ~%(bitmask)s;
""".lstrip("\n")
PINMODE_TEMPLATE = """
else if(pin == %(number)s && mode == INPUT) {
DDR%(port)s &= ~%(bitmask)s;
PORT%(port)s &= ~%(bitmask)s;
} else if(pin == %(number)s && mode == INPUT_PULLUP) {
DDR%(port)s &= ~%(bitmask)s;
PORT%(port)s |= %(bitmask)s;
} else if(pin == %(number)s) DDR%(port)s |= %(bitmask)s;
""".lstrip("\n")
DIGITALREAD_TEMPLATE = """
else if(pin == %(number)s) return PIN%(port)s & %(bitmask)s ? HIGH : LOW;
""".lstrip("\n")
TIMER_TEMPLATE = """
else if(pin == %(number)s) %(timer_reg)s &= ~_BV(%(timer_bit)s);
""".lstrip("\n")
ISPWM_TEMPLATE = """
if(pin == %(number)s)
return true;
""".lstrip("\n")
PORT_TEST_TEMPLATE = """
if(pin == %(number)s)
return _SFR_IO_REG_P(%(port)s);
""".lstrip("\n")
# Lookup table from the timer specifications given in pins_arduino.h
# to the actual timer register bits
#
# This is equivalent of the case statement in wiring_digital.c, so if that
# ever changes this needs to change too. :(
TIMER_LOOKUP = {
"TIMER1A": ("TCCR1A", "COM1A1"),
"TIMER1B": ("TCCR1A", "COM1B1"),
"TIMER2": ("TCCR2", "COM21"),
"TIMER0A": ("TCCR0A", "COM0A1"),
"TIMER0B": ("TCCR0A", "COM0B1"),
"TIMER2A": ("TCCR2A", "COM2A1"),
"TIMER2B": ("TCCR2A", "COM2B1"),
"TIMER3A": ("TCCR3A", "COM3A1"),
"TIMER3B": ("TCCR3A", "COM3B1"),
"TIMER3C": ("TCCR3A", "COM3C1"),
"TIMER4A": ("TCCR4A", "COM4A1"),
"TIMER4B": ("TCCR4A", "COM4B1"),
"TIMER4C": ("TCCR4A", "COM4C1"),
"TIMER4D": ("TCCR4C", "COM4D1"),
"TIMER5A": ("TCCR5A", "COM5A1"),
"TIMER5B": ("TCCR5A", "COM5B1"),
"TIMER5C": ("TCCR5A", "COM5C1"),
}
def generate_header_file(boards, identifying_keys, output):
"""
Write a header file with fast inlined methods for all the board types
"""
with open("components/board_template.cpp") as template:
BOARD_TEMPLATE = template.read()
with open("components/header.cpp") as header:
output.write(header.read())
for board in boards:
# Work out the macro conditional
ifdef_clause = []
for key in identifying_keys:
# all the +0 nonsense here is to detect defined-but-empty macros
# (Arduino-mk inserts them)
if key in board["macros"]:
ifdef_clause.append("defined(%(key)s) && (%(key)s+0) == %(value)s" %
{"key": key, "value": board["macros"][key]})
else:
ifdef_clause.append("(!defined(%s) || !(%s+0))" % (key,key))
ifdef_clause = " && ".join(ifdef_clause)
# Build up the macro conditionals
digitalwrite_clause = ""
digitalread_clause = ""
pinmode_clause = ""
timer_clause = ""
ispwm_clause = ""
direction_clause = ""
output_clause = ""
input_clause = ""
for number,port,bitmask,timer in zip(range(len(board["ports"])),
board["ports"],
board["bitmasks"],
board["timers"]):
if port is not None:
digitalwrite_clause += DIGITALWRITE_TEMPLATE % locals()
digitalread_clause += DIGITALREAD_TEMPLATE % locals()
pinmode_clause += PINMODE_TEMPLATE % locals()
if timer is not None:
timer_reg,timer_bit = TIMER_LOOKUP[timer]
timer_clause += TIMER_TEMPLATE % locals()
ispwm_clause += ISPWM_TEMPLATE % locals()
direction_clause += PORT_TEST_TEMPLATE % {"number":number,
"port":"DDR"+port}
output_clause += PORT_TEST_TEMPLATE % {"number":number,
"port":"PORT"+port}
input_clause += PORT_TEST_TEMPLATE % {"number":number,
"port":"PIN"+port}
output.write(BOARD_TEMPLATE % dict(locals(), **board))
with open("components/footer.cpp") as footer:
output.write(footer.read())
if __name__ == "__main__":
main()