Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 0813561d10
Fetching contributors…

Cannot retrieve contributors at this time

353 lines (298 sloc) 13.253 kb
#!/usr/bin/python
# scons script for the Arduino sketch
# http://code.google.com/p/arscons/
#
# Copyright (C) 2010 by Homin Lee <ff4500@gmail.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
# You'll need the serial module: http://pypi.python.org/pypi/pyserial
# Basic Usage:
# 1. make a folder which have same name of the sketch (ex. Blink/ for Blik.pde)
# 2. put the sketch and SConstruct(this file) under the folder.
# 3. to make the HEX. do following in the folder.
# $ scons
# 4. to upload the binary, do following in the folder.
# $ scons upload
# Thanks to:
# * Ovidiu Predescu <ovidiu@gmail.com> and Lee Pike <leepike@gmail.com>
# for Mac port and bugfix.
#
# This script tries to determine the port to which you have an Arduino
# attached. If multiple USB serial devices are attached to your
# computer, you'll need to explicitly specify the port to use, like
# this:
#
# $ scons ARDUINO_PORT=/dev/ttyUSB0
#
# To add your own directory containing user libraries, pass EXTRA_LIB
# to scons, like this:
#
# $ scons EXTRA_LIB=<my-extra-library-dir>
#
from glob import glob
import sys
import re
import os
pathJoin = os.path.join
env = Environment()
platform = env['PLATFORM']
def getUsbTty(rx):
usb_ttys = glob(rx)
if len(usb_ttys) == 1: return usb_ttys[0]
else: return None
AVR_BIN_PREFIX = None
AVRDUDE_CONF = None
if platform == 'darwin':
# For MacOS X, pick up the AVR tools from within Arduino.app
ARDUINO_HOME_DEFAULT = '/Applications/Arduino.app/Contents/Resources/Java'
ARDUINO_PORT_DEFAULT = getUsbTty('/dev/tty.usbserial*')
SKETCHBOOK_HOME_DEFAULT = ''
elif platform == 'win32':
# For Windows, use environment variables.
ARDUINO_HOME_DEFAULT = os.environ.get('ARDUINO_HOME')
ARDUINO_PORT_DEFAULT = os.environ.get('ARDUINO_PORT')
SKETCHBOOK_HOME_DEFAULT = ''
else:
# For Ubuntu Linux (9.10 or higher)
ARDUINO_HOME_DEFAULT = '/usr/share/arduino/' #'/home/YOU/apps/arduino-00XX/'
ARDUINO_PORT_DEFAULT = getUsbTty('/dev/ttyUSB*')
AVR_BIN_PREFIX = 'avr-'
SKETCHBOOK_HOME_DEFAULT = os.path.realpath('~/share/arduino/sketchbook/')
ARDUINO_BOARD_DEFAULT = os.environ.get('ARDUINO_BOARD', 'atmega328')
ARDUINO_HOME = ARGUMENTS.get('ARDUINO_HOME', ARDUINO_HOME_DEFAULT)
ARDUINO_PORT = ARGUMENTS.get('ARDUINO_PORT', ARDUINO_PORT_DEFAULT)
ARDUINO_BOARD = ARGUMENTS.get('ARDUINO_BOARD', ARDUINO_BOARD_DEFAULT)
ARDUINO_VER = ARGUMENTS.get('ARDUINO_VER', 0) # Default to 0 if nothing is specified
RST_TRIGGER = ARGUMENTS.get('RST_TRIGGER', None) # use built-in pulseDTR() by default
EXTRA_LIB = ARGUMENTS.get('EXTRA_LIB', None) # handy for adding another arduino-lib dir
SKETCHBOOK_HOME = ARGUMENTS.get('SKETCHBOOK_HOME', SKETCHBOOK_HOME_DEFAULT) # If set will add the libraries dir from the sketchbook
if not ARDUINO_HOME:
print 'ARDUINO_HOME must be defined.'
raise KeyError('ARDUINO_HOME')
ARDUINO_CORE = pathJoin(ARDUINO_HOME, 'hardware/arduino/cores/arduino')
ARDUINO_SKEL = pathJoin(ARDUINO_CORE, 'main.cpp')
ARDUINO_CONF = pathJoin(ARDUINO_HOME, 'hardware/arduino/boards.txt')
arduino_file_path = pathJoin(ARDUINO_CORE, 'Arduino.h')
if ARDUINO_VER == 0:
print "No Arduino version specified. Discovered version",
if os.path.exists(arduino_file_path):
print "100 or above"
ARDUINO_VER = 100
else:
print "0023 or below"
ARDUINO_VER = 23
else:
print "Arduino version " + ARDUINO_VER + " specified"
if ARDUINO_VER < 100: FILE_EXTENSION = ".pde"
if ARDUINO_VER >= 100: FILE_EXTENSION = ".ino"
# Some OSs need bundle with IDE tool-chain
if platform == 'darwin' or platform == 'win32':
AVR_BIN_PREFIX = pathJoin(ARDUINO_HOME, 'hardware/tools/avr/bin', 'avr-')
AVRDUDE_CONF = pathJoin(ARDUINO_HOME, 'hardware/tools/avr/etc/avrdude.conf')
ARDUINO_LIBS = [pathJoin(ARDUINO_HOME, 'libraries')]
if EXTRA_LIB:
ARDUINO_LIBS += [EXTRA_LIB]
if SKETCHBOOK_HOME:
ARDUINO_LIBS += [pathJoin(SKETCHBOOK_HOME, 'libraries')]
# check given board name, ARDUINO_BOARD is valid one
ptnBoard = re.compile(r'^(.*)\.name=(.*)')
boards = {}
for line in open(ARDUINO_CONF):
result = ptnBoard.findall(line)
if result:
boards[result[0][0]] = result[0][1]
if not ARDUINO_BOARD in boards.keys():
print ("ERROR! the given board name, %s is not in the supported board list:"%ARDUINO_BOARD)
print ("all available board names are:")
for name in boards.keys():
print ("\t%s for %s"%(name.ljust(14), boards[name]))
print ("however, you may edit %s to add a new board."%ARDUINO_CONF)
sys.exit(-1)
def getBoardConf(strPtn):
ptn = re.compile(strPtn)
for line in open(ARDUINO_CONF):
result = ptn.findall(line)
if result:
return result[0]
assert(False)
MCU = getBoardConf(r'^%s\.build\.mcu=(.*)'%ARDUINO_BOARD)
MCU = ARGUMENTS.get('MCU', MCU)
F_CPU = getBoardConf(r'^%s\.build\.f_cpu=(.*)'%ARDUINO_BOARD)
F_CPU = ARGUMENTS.get('F_CPU', F_CPU)
# There should be a file with the same name as the folder and with the extension .pde
TARGET = os.path.basename(os.path.realpath(os.curdir))
assert(os.path.exists(TARGET+FILE_EXTENSION))
cFlags = ['-ffunction-sections', '-fdata-sections', '-fno-exceptions',
'-funsigned-char', '-funsigned-bitfields', '-fpack-struct', '-fshort-enums',
'-Os', '-mmcu=%s'%MCU]
envArduino = Environment(CC = AVR_BIN_PREFIX+'gcc', CXX = AVR_BIN_PREFIX+'g++', AS=AVR_BIN_PREFIX+'gcc',
CPPPATH = ['build/core'], CPPDEFINES = {'F_CPU':F_CPU, 'ARDUINO':ARDUINO_VER},
CFLAGS = cFlags+['-std=gnu99'], CCFLAGS = cFlags, ASFLAGS=['-assembler-with-cpp','-mmcu=%s'%MCU],
TOOLS = ['gcc','g++', 'as'])
if ARDUINO_VER >= 100:
if ARDUINO_BOARD == 'nano328': envArduino.Append(CPPPATH = pathJoin(ARDUINO_HOME, 'hardware/arduino/variants/eightanaloginputs/'))
elif ARDUINO_BOARD == 'leonardo': envArduino.Append(CPPPATH = pathJoin(ARDUINO_HOME, 'hardware/arduino/variants/leonardo/'))
elif ARDUINO_BOARD == 'mega2560': envArduino.Append(CPPPATH = pathJoin(ARDUINO_HOME, 'hardware/arduino/variants/mega/'))
elif ARDUINO_BOARD == 'micro': envArduino.Append(CPPPATH = pathJoin(ARDUINO_HOME, 'hardware/arduino/variants/micro/'))
else: envArduino.Append(CPPPATH = pathJoin(ARDUINO_HOME, 'hardware/arduino/variants/standard/'))
def run(cmd):
"""Run a command and decipher the return code. Exit by default."""
import SCons.Script
print cmd
res = os.system(cmd)
# Assumes that if a process doesn't call exit, it was successful
if (os.WIFEXITED(res)):
code = os.WEXITSTATUS(res)
if code != 0:
print "Error: return code: " + str(code)
if SCons.Script.keep_going_on_error == 0:
sys.exit(code)
def fnCompressCore(target, source, env):
core_files = filter(lambda x: str(x).startswith('build/core/'), source)
for file in core_files:
run(AVR_BIN_PREFIX+'ar rcs %s %s'%(target[0], file))
def fnProcessing(target, source, env):
wp = open ('%s'%target[0], 'wb')
wp.write(open(ARDUINO_SKEL).read())
types='''void
int char word long
float double byte long
boolean
uint8_t uint16_t uint32_t
int8_t int16_t int32_t
'''
types=' | '.join(types.split())
re_signature=re.compile(r"""^\s* (
(?: (%s) \s+ )?
\w+ \s*
\( \s* ((%s) \s+ \*? \w+ (?:\s*,\s*)? )* \)
) \s* {? \s* $""" % (types,types), re.MULTILINE|re.VERBOSE)
prototypes = {}
for file in glob(os.path.realpath(os.curdir) + "/*" + FILE_EXTENSION):
for line in open(file):
result = re_signature.findall(line)
if result:
prototypes[result[0][0]] = result[0][1]
for name in prototypes.keys():
print ("%s;"%(name))
wp.write("%s;\n"%name)
for file in glob(os.path.realpath(os.curdir) + "/*" + FILE_EXTENSION):
print file, TARGET
if not os.path.samefile(file, TARGET+FILE_EXTENSION):
wp.write('#line 1 "%s"\r\n' % file)
wp.write(open(file).read())
# Add this preprocessor directive to localize the errors.
sourcePath = str(source[0]).replace('\\', '\\\\');
wp.write('#line 1 "%s"\r\n' % sourcePath)
wp.write(open('%s'%source[0]).read())
envArduino.Append(BUILDERS = {'Processing':Builder(action = fnProcessing,
suffix = '.cpp', src_suffix = FILE_EXTENSION)})
envArduino.Append(BUILDERS = {'CompressCore':Builder(action = fnCompressCore) })
envArduino.Append(BUILDERS={'Elf':Builder(action=AVR_BIN_PREFIX+'gcc '+
'-mmcu=%s -Os -Wl,--gc-sections -o $TARGET $SOURCES -lm'%MCU)})
envArduino.Append(BUILDERS={'Hex':Builder(action=AVR_BIN_PREFIX+'objcopy '+
'-O ihex -R .eeprom $SOURCES $TARGET')})
envArduino.Append(BUILDERS={'Bin':Builder(action=AVR_BIN_PREFIX+'objcopy '+
'-O binary -I ihex $SOURCES $TARGET')})
gatherSources = lambda x: glob(pathJoin(x, '*.c'))+\
glob(pathJoin(x, '*.cpp'))+\
glob(pathJoin(x, '*.S'))
# add arduino core sources
VariantDir('build/core', ARDUINO_CORE)
core_sources = gatherSources(ARDUINO_CORE)
core_sources = filter(lambda x: not (os.path.basename(x) == 'main.cpp'), core_sources)
core_sources = map(lambda x: x.replace(ARDUINO_CORE, 'build/core/'), core_sources)
# add libraries
libCandidates = []
ptnLib = re.compile(r'^[ ]*#[ ]*include [<"](.*)\.h[>"]')
for line in open (TARGET+FILE_EXTENSION):
result = ptnLib.findall(line)
if result:
# Look for the library directory that contains the header.
filename=result[0]+'.h'
for libdir in ARDUINO_LIBS:
for root, dirs, files in os.walk(libdir, followlinks=True):
for f in files:
if f == filename:
libCandidates += [os.path.basename(root)]
# Hack. In version 20 of the Arduino IDE, the Ethernet library depends
# implicitly on the SPI library.
if ARDUINO_VER >= 20 and 'Ethernet' in libCandidates:
libCandidates += ['SPI']
all_libs_sources = []
index = 0
for orig_lib_dir in ARDUINO_LIBS:
lib_sources = []
lib_dir = 'build/lib_%02d'%index
VariantDir(lib_dir, orig_lib_dir)
for libPath in filter(os.path.isdir, glob(pathJoin(orig_lib_dir, '*'))):
libName = os.path.basename(libPath)
if not libName in libCandidates:
continue
envArduino.Append(CPPPATH = libPath.replace(orig_lib_dir, lib_dir))
lib_sources = gatherSources(libPath)
utilDir = pathJoin(libPath, 'utility')
if os.path.exists(utilDir) and os.path.isdir(utilDir):
lib_sources += gatherSources(utilDir)
envArduino.Append(CPPPATH = utilDir.replace(orig_lib_dir, lib_dir))
lib_sources = map(lambda x: x.replace(orig_lib_dir, lib_dir), lib_sources)
all_libs_sources += lib_sources
index += 1
# Add raw sources which live in sketch dir.
build_top = os.path.realpath('.')
VariantDir('build/local/', build_top)
local_sources = gatherSources(build_top)
local_sources = map(lambda x: x.replace(build_top, 'build/local/'), local_sources)
if local_sources:
envArduino.Append(CPPPATH = 'build/local')
# Convert sketch(.pde) to cpp
envArduino.Processing('build/'+TARGET+'.cpp', 'build/'+TARGET+FILE_EXTENSION)
VariantDir('build', '.')
sources = ['build/'+TARGET+'.cpp']
#sources += core_sources
sources += local_sources
sources += all_libs_sources
# Finally Build!!
core_objs = envArduino.Object(core_sources)
objs = envArduino.Object(sources) #, LIBS=libs, LIBPATH='.')
objs = objs + envArduino.CompressCore('build/core.a', core_objs)
envArduino.Elf(TARGET+'.elf', objs)
envArduino.Hex(TARGET+'.hex', TARGET+'.elf')
envArduino.Bin(TARGET+'.bin', TARGET+'.hex')
# Print Size
# TODO: check binary size
MAX_SIZE = getBoardConf(r'^%s\.upload.maximum_size=(.*)'%ARDUINO_BOARD)
print ("maximum size for hex file: %s bytes"%MAX_SIZE)
envArduino.Command(None, TARGET+'.hex', AVR_BIN_PREFIX+'size --target=ihex $SOURCE')
# Reset
def pulseDTR(target, source, env):
import serial
import time
ser = serial.Serial(ARDUINO_PORT)
ser.setDTR(1)
time.sleep(0.5)
ser.setDTR(0)
ser.close()
if RST_TRIGGER:
reset_cmd = '%s %s'%(RST_TRIGGER, ARDUINO_PORT)
else:
reset_cmd = pulseDTR
# Upload
UPLOAD_PROTOCOL = getBoardConf(r'^%s\.upload\.protocol=(.*)'%ARDUINO_BOARD)
UPLOAD_SPEED = getBoardConf(r'^%s\.upload\.speed=(.*)'%ARDUINO_BOARD)
if UPLOAD_PROTOCOL == 'stk500':
UPLOAD_PROTOCOL = 'stk500v1'
avrdudeOpts = ['-V', '-F', '-c %s'%UPLOAD_PROTOCOL, '-b %s'%UPLOAD_SPEED,
'-p %s'%MCU, '-P %s'%ARDUINO_PORT, '-U flash:w:$SOURCES']
if AVRDUDE_CONF:
avrdudeOpts += ['-C %s'%AVRDUDE_CONF]
fuse_cmd = '%s %s'%(pathJoin(os.path.dirname(AVR_BIN_PREFIX), 'avrdude'),
' '.join(avrdudeOpts))
upload = envArduino.Alias('upload', TARGET+'.hex', [reset_cmd, fuse_cmd]);
AlwaysBuild(upload)
# Clean build directory
envArduino.Clean('all', 'build/')
# vim: et sw=4 fenc=utf-8:
Jump to Line
Something went wrong with that request. Please try again.