From 1930c8c3648c79242c4d5b59dde5a00b9b16426c Mon Sep 17 00:00:00 2001 From: "Joe Doyle (Ginto8)" Date: Fri, 25 Apr 2014 00:41:55 -0400 Subject: [PATCH 1/5] Add build support for Arduino Teensy The Arduino Teensy has a different processor architecture, and therefore a different build chain, than standard Arduinos. Building and uploading are still possible through the Arduino IDE by patching it and installing the teensy build chain. This patch adds parsing and handling of extra options in the teensy's boards.txt, which should allow it to read all necessary configuration to build on all Teensy versions. With this patch, the following command will build for teensy3 with a 96 MHz clock, an en-US keyboard format, and serial USB mode: $ ino build -m teensy3 --menu='keys:en-us,speed:96,usb:serial' ** This patch supports building only. Uploading will come later. *** For some reason, this patch does not work with teensy1. I don't know why right now, but I will investigate. --- ino/commands/build.py | 149 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 136 insertions(+), 13 deletions(-) diff --git a/ino/commands/build.py b/ino/commands/build.py index 65a9f8e..ccaddaa 100644 --- a/ino/commands/build.py +++ b/ino/commands/build.py @@ -57,31 +57,31 @@ def setup_arg_parser(self, parser): self.e.add_arduino_dist_arg(parser) parser.add_argument('--make', metavar='MAKE', - default=self.default_make, + default='', help='Specifies the make tool to use. If ' 'a full path is not given, searches in Arduino ' 'directories before PATH. Default: "%(default)s".') parser.add_argument('--cc', metavar='COMPILER', - default=self.default_cc, + default='', help='Specifies the compiler used for C files. If ' 'a full path is not given, searches in Arduino ' 'directories before PATH. Default: "%(default)s".') parser.add_argument('--cxx', metavar='COMPILER', - default=self.default_cxx, + default='', help='Specifies the compiler used for C++ files. ' 'If a full path is not given, searches in Arduino ' 'directories before PATH. Default: "%(default)s".') parser.add_argument('--ar', metavar='AR', - default=self.default_ar, + default='', help='Specifies the AR tool to use. If a full path ' 'is not given, searches in Arduino directories ' 'before PATH. Default: "%(default)s".') parser.add_argument('--objcopy', metavar='OBJCOPY', - default=self.default_objcopy, + default='', help='Specifies the OBJCOPY to use. If a full path ' 'is not given, searches in Arduino directories ' 'before PATH. Default: "%(default)s".') @@ -114,18 +114,73 @@ def setup_arg_parser(self, parser): 'being invoked directly (i.e. the `-Wl,\' prefix ' 'should be omitted). Default: "%(default)s".') + parser.add_argument('--menu', metavar='OPTIONS', default='', + help='"key:val,key:val" formatted string of ' + 'build menu items and their desired values') + parser.add_argument('-v', '--verbose', default=False, action='store_true', help='Verbose make output') + # Merges one dictionary into another, overwriting non-dict entries and + # recursively merging dictionaries mapped to the same key. + def _mergeDicts(self,dest,src): + for key,val in src.iteritems(): + if not key in dest: + dest[key] = val + elif type(val) == dict and type(dest[key]) == dict: + self._mergeDicts(dest[key],val) + else: + dest[key] = val + + # Attempts to determine selections in boars['menu'] based on args.menu. + # args.menu is assumed to be formatted as "key0:val0,key1:val1,...". + # Selected options are then merged into board as though the settings + # were there all along. + def _parseMenu(self,args,board): + if not 'menu' in board: + return + choices = {} + for option in args.menu.split(","): + pair = option.split(":") + if len(pair) < 2: + continue + choices[pair[0]] = pair[1] + + selectedOptions = {} + + menu = board['menu'] + failed = 0 + for item,options in menu.iteritems(): + if item in choices: + if not choices[item] in menu[item]: + print '\'%s\' is not a valid choice for %s (valid choices are: %s).' \ + % (choices[item],item, + ",".join(["'%s'" % s for s in options.keys()])) + failed += 1 + else: + self._mergeDicts(selectedOptions,options[choices[item]]) + else: + if len(options) == 0: + continue + print 'No option specified for %s. Defaulting to \'%s\'.' % \ + (item,options.keys()[0]) + self._mergeDicts(selectedOptions,options[options.keys()[0]]) + if failed > 0: + raise KeyError(str(failed) + " invalid menu choices") + del selectedOptions['name'] + self._mergeDicts(board,selectedOptions) + + def discover(self, args): board = self.e.board_model(args.board_model) + self._parseMenu(args,board) core_place = os.path.join(board['_coredir'], 'cores', board['build']['core']) core_header = 'Arduino.h' if self.e.arduino_lib_version.major else 'WProgram.h' self.e.find_dir('arduino_core_dir', [core_header], [core_place], human_name='Arduino core library') - if self.e.arduino_lib_version.major: + if not board['name'].lower().startswith('teensy') and self.e.arduino_lib_version.major: variants_place = os.path.join(board['_coredir'], 'variants') self.e.find_dir('arduino_variants_dir', ['.'], [variants_place], human_name='Arduino variants directory') @@ -133,6 +188,32 @@ def discover(self, args): self.e.find_arduino_dir('arduino_libraries_dir', ['libraries'], human_name='Arduino standard libraries') + if args.make == '': + try: + args.make = board['build']['command']['make'] + except KeyError as _: + args.make = self.default_make + if args.cc == '': + try: + args.cc = board['build']['command']['gcc'] + except KeyError as _: + args.cc = self.default_cc + if args.cxx == '': + try: + args.cxx = board['build']['command']['g++'] + except KeyError as _: + args.cxx = self.default_cxx + if args.ar == '': + try: + args.ar = board['build']['command']['ar'] + except KeyError as _: + args.ar = self.default_ar + if args.objcopy == '': + try: + args.objcopy = board['build']['command']['objcopy'] + except KeyError as _: + args.objcopy = self.default_objcopy + toolset = [ ('make', args.make), ('cc', args.cc), @@ -146,37 +227,79 @@ def discover(self, args): tool_key, ['hardware', 'tools', 'avr', 'bin'], items=[tool_binary], human_name=tool_binary) + # Used to parse board options. Finds a sequence of entries in table with the + # keys prefix0, prefix1, prefix2 (or beginning with prefix1 if start = 1), + # and appends them to the list-like structure out which has a constructor wrap. + # For example: + # >>> o = [] + # >>> table = {'squirrel':3,'a1':1,'a2':1,'a3':2,'a4':3,'a5':5,'a7':13} + # >>> _appendNumberedEntries(o,table,'a',start=1,wrap=lambda x:[x]) + # >>> o + # >>> [1,1,2,3,5] + def _appendNumberedEntries(self, out, table, prefix, start=0, wrap=SpaceList): + i = start + while (prefix + str(i)) in table: + out += wrap([table[prefix+str(i)]]) + i += 1 + def setup_flags(self, args): board = self.e.board_model(args.board_model) - mcu = '-mmcu=' + board['build']['mcu'] + cpu,mcu = '','' + if 'cpu' in board['build']: + cpu = '-mcpu=' + board['build']['cpu'] + elif 'mcu' in board['build']: + mcu = '-mmcu=' + board['build']['mcu'] + if 'f_cpu' in board['build']: + f_cpu = board['build']['f_cpu'] + else: + raise KeyError('No valid source of f_cpu option') + # Hard-code the flags that are essential to building the sketch self.e['cppflags'] = SpaceList([ + cpu, mcu, - '-DF_CPU=' + board['build']['f_cpu'], + '-DF_CPU=' + f_cpu, '-DARDUINO=' + str(self.e.arduino_lib_version.as_int()), '-I' + self.e['arduino_core_dir'], - ]) + ]) # Add additional flags as specified self.e['cppflags'] += SpaceList(shlex.split(args.cppflags)) + self._appendNumberedEntries(self.e['cppflags'],board['build'],'option',start=1) + self._appendNumberedEntries(self.e['cppflags'],board['build'],'define') if 'vid' in board['build']: self.e['cppflags'].append('-DUSB_VID=%s' % board['build']['vid']) if 'pid' in board['build']: self.e['cppflags'].append('-DUSB_PID=%s' % board['build']['pid']) - - if self.e.arduino_lib_version.major: - variant_dir = os.path.join(self.e.arduino_variants_dir, + + if board['name'].lower().startswith('teensy'): + pass + elif self.e.arduino_lib_version.major: + variant_dir = os.path.join(self.e.arduino_variants_dir, board['build']['variant']) self.e.cppflags.append('-I' + variant_dir) self.e['cflags'] = SpaceList(shlex.split(args.cflags)) self.e['cxxflags'] = SpaceList(shlex.split(args.cxxflags)) + self._appendNumberedEntries(self.e['cxxflags'],board['build'], + 'cppoption',start=1) # Again, hard-code the flags that are essential to building the sketch - self.e['ldflags'] = SpaceList([mcu]) + self.e['ldflags'] = SpaceList([cpu,mcu]) self.e['ldflags'] += SpaceList([ '-Wl,' + flag for flag in shlex.split(args.ldflags) ]) + self._appendNumberedEntries(self.e['ldflags'],board['build'], + 'linkoption',start=1) + self._appendNumberedEntries(self.e['ldflags'],board['build'], + 'additionalobject',start=1) + + if 'linkscript' in board['build']: + script = self.e.find_arduino_tool(board['build']['linkscript'], + ['hardware','*','cores','*'], + human_name='Link script') + self.e['ldflags'] = SpaceList(['-T' + script]) + \ + self.e['ldflags'] self.e['names'] = { 'obj': '%s.o', From 60e2c4afa7623eb8c10923a82559d067bc355822 Mon Sep 17 00:00:00 2001 From: "Joe Doyle (Ginto8)" Date: Fri, 25 Apr 2014 01:04:19 -0400 Subject: [PATCH 2/5] Add upload support for Teensy This was easier than I expected; it just overrides avrdude and uses teensy-loader-cli. This should work just fine: $ ino upload -m teensy3 --- ino/commands/upload.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/ino/commands/upload.py b/ino/commands/upload.py index 0659e05..bc41f16 100644 --- a/ino/commands/upload.py +++ b/ino/commands/upload.py @@ -35,9 +35,11 @@ def setup_arg_parser(self, parser): self.e.add_board_model_arg(parser) self.e.add_arduino_dist_arg(parser) - def discover(self): + def discover(self,model): self.e.find_tool('stty', ['stty']) - if platform.system() == 'Linux': + if model.startswith('teensy'): + self.e.find_arduino_tool('teensy-loader-cli', []) + elif platform.system() == 'Linux': self.e.find_arduino_tool('avrdude', ['hardware', 'tools']) conf_places = self.e.arduino_dist_places(['hardware', 'tools']) @@ -48,10 +50,21 @@ def discover(self): self.e.find_arduino_file('avrdude.conf', ['hardware', 'tools', 'avr', 'etc']) def run(self, args): - self.discover() + print args.board_model + self.discover(args.board_model) port = args.serial_port or self.e.guess_serial_port() board = self.e.board_model(args.board_model) + if args.board_model.startswith('teensy'): + print 'Ready to upload... Press reboot button on teensy to continue' + teensy_cli = self.e['teensy-loader-cli'] + subprocess.call([ + teensy_cli, + '-mmcu=' + board['build']['mcu'], + '-w', self.e['hex_path'], + ]) + exit(0) + protocol = board['upload']['protocol'] if protocol == 'stk500': # if v1 is not specifid explicitly avrdude will From 1ac4ff2a0cdc7c5d90c2217cfbe8855c29d9afd8 Mon Sep 17 00:00:00 2001 From: Jesse Rosalia Date: Mon, 3 Nov 2014 21:49:32 -0500 Subject: [PATCH 3/5] Modified teensy upload procedure to use teensy_post_compile and teensy_reboot (bundled with Teensyduino), as defined in the boards.txt. --- ino/commands/upload.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/ino/commands/upload.py b/ino/commands/upload.py index bc41f16..db9edc4 100644 --- a/ino/commands/upload.py +++ b/ino/commands/upload.py @@ -36,9 +36,11 @@ def setup_arg_parser(self, parser): self.e.add_arduino_dist_arg(parser) def discover(self,model): + board = self.e.board_model(model) self.e.find_tool('stty', ['stty']) if model.startswith('teensy'): - self.e.find_arduino_tool('teensy-loader-cli', []) + self.e.find_arduino_tool(board['build']['post_compile_script'], ['hardware', 'tools']) + self.e.find_arduino_tool(board['upload']['avrdude_wrapper'], ['hardware','tools']) elif platform.system() == 'Linux': self.e.find_arduino_tool('avrdude', ['hardware', 'tools']) @@ -50,21 +52,33 @@ def discover(self,model): self.e.find_arduino_file('avrdude.conf', ['hardware', 'tools', 'avr', 'etc']) def run(self, args): - print args.board_model self.discover(args.board_model) - port = args.serial_port or self.e.guess_serial_port() board = self.e.board_model(args.board_model) if args.board_model.startswith('teensy'): - print 'Ready to upload... Press reboot button on teensy to continue' - teensy_cli = self.e['teensy-loader-cli'] + post_compile = self.e[board['build']['post_compile_script']] + reboot = self.e[board['upload']['avrdude_wrapper']] + # post_compile script requires: + # .hex filename w/o the extension + filename = os.path.splitext(self.e.hex_filename)[0] + # full path to directory with the compiled .hex file + fullpath = os.path.realpath(self.e.build_dir) + # full path to the tools directory + tooldir = self.e.find_arduino_dir('', ['hardware', 'tools']) + subprocess.call([ + post_compile, + '-file=' + filename, + '-path=' + fullpath, + '-tools=' + tooldir + ]) + # reboot to complete the upload + # NOTE: this will warn the user if they need to press the reset button subprocess.call([ - teensy_cli, - '-mmcu=' + board['build']['mcu'], - '-w', self.e['hex_path'], + reboot ]) exit(0) + port = args.serial_port or self.e.guess_serial_port() protocol = board['upload']['protocol'] if protocol == 'stk500': # if v1 is not specifid explicitly avrdude will From 62d1b92b87640f05bde760cb69a7e9c1a6a36879 Mon Sep 17 00:00:00 2001 From: Julian Kornberger Date: Sun, 29 Jun 2014 12:36:52 +0200 Subject: [PATCH 4/5] Extend search path for tool binaries Otherwise arm-none-eabi-gcc can not be found for Teensy models. --- ino/commands/build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ino/commands/build.py b/ino/commands/build.py index ccaddaa..6dce75b 100644 --- a/ino/commands/build.py +++ b/ino/commands/build.py @@ -224,7 +224,7 @@ def discover(self, args): for tool_key, tool_binary in toolset: self.e.find_arduino_tool( - tool_key, ['hardware', 'tools', 'avr', 'bin'], + tool_key, ['hardware', 'tools', '*', 'bin'], items=[tool_binary], human_name=tool_binary) # Used to parse board options. Finds a sequence of entries in table with the From 958dae4c1b559711bbb228963a65c63de99eb4af Mon Sep 17 00:00:00 2001 From: Jesse Rosalia Date: Mon, 3 Nov 2014 22:28:45 -0500 Subject: [PATCH 5/5] Fixing support for menu option in ino.ini (_parseMenu was not handling an already split list properly) --- ino/commands/build.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ino/commands/build.py b/ino/commands/build.py index 6dce75b..b73ca91 100644 --- a/ino/commands/build.py +++ b/ino/commands/build.py @@ -140,7 +140,9 @@ def _parseMenu(self,args,board): if not 'menu' in board: return choices = {} - for option in args.menu.split(","): + # Menu args specified in ino.ini will already be split into a list + splitargs = args.menu if isinstance(args.menu, list) else args.menu.split(",") + for option in splitargs: pair = option.split(":") if len(pair) < 2: continue