"""Python bindings for `make` and `atprogram`.
"""
from os import path, getenv, getcwd
from subprocess import run, PIPE, STDOUT
import re
[docs]def atprogram(project_path=None, device_name="ATSAML11E16A", verbose=0,
clean=False, build=True, erase=True, program=True, verify=False,
tool="EDBG", interface="SWD", atmel_studio_path=path.join(
getenv("programfiles(x86)"), "Atmel", "Studio", "7.0"),
make_path=None, atprogram_path=None, configuration="Debug",
device_sn=None, jobs=getenv("NUMBER_OF_PROCESSORS"),
make_command=None, atprogram_command=None, return_output=False,
dry_run=False):
r"""This function accesses the ``atprogram.exe`` program from Atmel
Studio's installation directory, to automatically compile and flash
code on Atmel boards.
This function can compile projects and write them to a device. It
determines what to do based on the arguments it gets. Specify at least
one of either `project_path`, `make_command` or `atprogram_command`.
.. warning:: \
**WARNING**: This function works only on projects that have
been **built** at least *once*. The function makes use of
the `makefiles` that are only generated by Atmel Studio.
.. note:: \
**NOTE**: Verifying that the code has been flashed correctly
to the board is known to return ``23``. This also happens
when using ``atprogram.exe`` from the command line. This has
been experienced with the Atmel board model `ATSAML11E16A`.
Parameters
----------
project_path : str
Location where the project resides. If it ends in
``.elf`` the elf file will be used. If it ends in ``Makefile`` the
Makefile will be used. Otherwise it should be a path to a folder
which holds the `Debug` folder. (the default is ``None``,
which will return an error)
device_name : str, optional
Device name. E.g. `"atxmega128a1"` or `"at32uc3a0256"`.
(the default is ``"ATSAML11E16A"``, which is a variant of the
Atmel SAM L11 board)
verbose : int, optional
Specify the verbosity of the `atprogram` function:
- 0: Silent
- 1: Info
- 2: Debug
- 3: List
(the default is `0`, which only prints compiler error and/or
warnings)
clean : bool, optional
Run ``make clean`` in the provided `project_path`, if `True`
(the default is `False`)
build : bool, optional
Run ``make all`` in the provided `project_path`, if `True`
(the default is `True`)
erase : bool, optional
Run ``atprogram chiperase`` if `True` (the default is `True`)
program : bool, optional
Run ``atprogram program`` if `True` (the default is `True`)
verify : bool, optional
Run ``atprogram verify`` if `True` (the default is `False`)
tool : str, optional
Tool's name. Available options are: ``avrdragon``,
``avrispmk2``, ``avrone``, ``jtagice3``, ``jtagicemkii``, ``qt600``,
``stk500``, ``stk600``, ``samice``, ``edbg``, ``medbg``, ``nedbg``,
``atmelice``, ``pickit4``, ``powerdebugger``, ``megadfu`` or ``flip``.
(the default is `"EDBG"`, being the Atmel® Embedded Debugger, which
is what the Atmel board of model `ATSAML11E16A` uses)
interface : str, optional
Physical interface. Available options are: ``aWire``,
``debugWIRE``, ``HVPP``, ``HVSP``, ``ISP``, ``JTAG``, ``PDI``,
``UPDI``, ``TPI`` or ``SWD``. (the default is `"SWD"`, which is
what the Atmel board of model `ATSAML11E16A` uses)
atmel_studio_path : str, optional
Specify the location where Atmel Studio is installed. Note that the
path usually ends in a folder name corresponding to the version name
e.g. 7.0
(the default is `"C:\\Program Files (x86)\\Atmel\\Studio\\7.0"`),
which is the default installation location of Atmel Studio 7.0)
make_path : str, optional
Specify the path to `make.exe`, which should be the location of
a GNU make installation for Windows. (the default is
`"C:\\Program Files (x86)\\Atmel\\Studio\\7.0\\shellutils\\make.exe"`,
as Atmel Studio already provides a GNU make installation for Windows)
atprogram_path : str, optional
Specify the location where `atprogram.exe` is installed. (the default
is `"C:\\Program Files (x86)\\Atmel\\Studio\\7.0\\atbackend\\
atprogram.exe"`, as it is included with the installation of
Atmel Studio)
configuration : str, optional
Specify which configuration of the Atmel Studio solution project should
be used for compilation, flashing, etc. Most common options are:
`Release`, `Debug`. (the default is `"Debug"`)
device_sn : str, optional
The programmer/debugger's serial number. Must be
specified when more than one debugger is connected. (the default is
`None`, as it is common to have a single programmer/debugger connected)
jobs : int, optional
How many jobs `make` should use (the default is
``getenv("NUMBER_OF_PROCESSORS")``,
which takes your number of processor cores available)
make_command : str, optional
Other options to specify to `make`, similar to the command line
``[options] [target] ...``
(the default is `None`, which means no extra commands/options)
atprogram_command : str, optional
Other command(s) to pass to `atprogram`, similar to the command line
arguments. This will be passed as:
``-d {device_name} -i {interface} [options] <command> [arguments]
[<command> [arguments] ...]``.
(the default is `None`, which means no extra commands/options)
return_output : bool, optional
If `True` the return value will be the output, else it will be
the return code (the default is `False`, which means the
return value is the return code of ``atprogram.exe`` or ``make``)
dry_run : bool, optional
Whether to run the commands using the `subprocess`
module or just print the commands. (the default is `False`, which
means the commands are indeed run using the `subprocess` module)
Raises
------
ValueError
Need to specify at least one of `project_path`,
`make_command` or `atprogram_command`.
Returns
-------
int or str
Depending on the `return_output` value, objects of different type
are returned by this function.
If the `return_output` value is `False`, then it returns a non-zero
return value indicates the subprocess call returned an error.
If `return_output` is `True`, then it returns a string holding the
text returned by ``atprogram.exe`` or ``make``.
"""
elf_mode = False
makefile_mode = False
makefile_path = None
if project_path is not None:
elf_mode = path.splitext(project_path)[1] is ".elf"
makefile_path, makefile = path.split(project_path)
makefile_mode = makefile is "Makefile"
if not makefile_mode:
makefile_path = path.join(project_path, configuration)
elif make_command is None and atprogram_command is None:
raise ValueError(
"Need to specify at least one of project_path, make_command or " +
"atprogram_command.")
else:
# This is make_command mode or atprogram_command mode
clean = build = erase = program = verify = False
returncode = 0
output = SavePrint(return_output)
stdout = PIPE if verbose >= 0 else None
stderr = STDOUT if verbose >= 1 else None
if not elf_mode and (clean or build or make_command):
make_path = make_path or path.join(
atmel_studio_path, "shellutils", "make.exe")
def make_caller(command):
args = ([make_path] + command.split())
kwargs = dict(cwd=makefile_path, stdout=stdout, stderr=stderr)
if dry_run:
output.print("".join(kwargs.get("cwd", getcwd())
) + "> " + " ".join(args))
return 0
res = run(args, **kwargs)
if verbose:
output.print(res.stdout.decode())
return res.returncode
if make_command and not returncode:
returncode = make_caller(make_command)
if clean and not returncode:
returncode = make_caller("clean")
if build and not returncode:
returncode = make_caller(f"all --jobs {jobs} --output-sync")
if (not makefile_mode and
(erase or program or verify or atprogram_command) and
not returncode):
atprogram_path = atprogram_path or path.join(
atmel_studio_path, "atbackend", "atprogram.exe")
def atprogram_caller(command):
args = ([atprogram_path] + (verbose-1) * ["-v"] + command.split())
kwargs = dict(stdout=stdout, stderr=stderr)
if dry_run:
output.print(getcwd() + "> " + " ".join(args))
return 0
res = run(args, **kwargs)
if verbose:
output.print(res.stdout.decode())
return res.returncode
elf_file_path = None if not (program or verify) else project_path if \
elf_mode else path.join(project_path, configuration,
path.basename(project_path)) + ".elf"
atprogram_command = \
f"-t {tool} -i {interface} -d {device_name} " + \
(device_sn is not None) * f" -s {device_sn} " + \
(atprogram_command or
erase * " chiperase " +
program * f" program -f {elf_file_path} " +
verify * f" verify -f {elf_file_path} " +
(verbose >= 3) * " info")
returncode = atprogram_caller(atprogram_command)
if returncode:
output.print(returncode)
atprogram_caller("exitcode")
if return_output:
return output.output + f"\n{returncode}"
else:
return returncode
[docs]class SavePrint(object):
"""SavePrint
A helper class to store standard output or print if `return_output`
is false.
Parameters
----------
return_output : bool
If `True`, the output is returned but not printed. If `False`,
the output is printed.
Attributes
----------
output : str
Holds the text that was taken from standard output.
"""
def __init__(self, return_output):
self.return_output = return_output
self.output = ""
[docs] def print(self, s):
"""print
Parameters
----------
s : str
A string to be added to the output.
"""
if self.return_output:
self.output += s
else:
print(s)
[docs]def get_device_info(
device_name="ATSAML11E16A", verbose=0, tool="EDBG", interface="SWD",
atmel_studio_path=path.join(
getenv("programfiles(x86)"), "Atmel", "Studio", "7.0"),
atprogram_path=None, device_sn=None):
r"""Polls the connected board for information, such as:
target voltage, general device information (name, JTAG ID, CPU
architecture,board series, DAL (debug access levels fuses status)
and memory information regarding the address space and fuses).
Parameters
----------
device_name : str, optional
Device name. E.g. `"atxmega128a1"` or `"at32uc3a0256"`.
(the default is ``"ATSAML11E16A"``, which is a variant of the
Atmel SAM L11 board)
verbose : int, optional
Print most of `atprogram` output, if value is `1`, and print
debugging information as well, if value is `2` (the default is `0`,
which is to print only compiler warnings and errors)
tool : str, optional
Tool's name. Available options are: ``avrdragon``,
``avrispmk2``, ``avrone``, ``jtagice3``, ``jtagicemkii``, ``qt600``,
``stk500``, ``stk600``, ``samice``, ``edbg``, ``medbg``, ``nedbg``,
``atmelice``, ``pickit4``, ``powerdebugger``, ``megadfu`` or ``flip``.
(the default is `"EDBG"`, being the Atmel® Embedded Debugger, which
is what the Atmel board of model `ATSAML11E16A` uses)
interface : str, optional
Physical interface. Available options are: ``aWire``,
``debugWIRE``, ``HVPP``, ``HVSP``, ``ISP``, ``JTAG``, ``PDI``,
``UPDI``, ``TPI`` or ``SWD``. (the default is `"SWD"`, which is
what the Atmel board of model `ATSAML11E16A` uses)
atmel_studio_path : str, optional
Specify the location where Atmel Studio is installed. Note that the
path usually ends in a folder name corresponding to the version
name e.g. 7.0 (the default is `"C:\\Program Files (x86)\\Atmel\\
Studio\\7.0"`), which is the default installation location of Atmel
Studio 7.0)
atprogram_path : str, optional
Specify the location where `atprogram.exe` is installed. (the default
is `"C:\\Program Files (x86)\\Atmel\\Studio\\7.0\\atbackend\\
atprogram.exe"`, as it is included with the installation of
Atmel Studio)
device_sn : str, optional
The programmer/debugger's serial number. Must be
specified when more than one debugger is connected. (the default is
`None`, as it is common to have a single programmer/debugger connected)
Returns
-------
str
The device information.
"""
atprogram_info = atprogram(
atprogram_command="info", return_output=True, verbose=1,
device_name=device_name, tool=tool, interface=interface,
atmel_studio_path=atmel_studio_path, atprogram_path=atprogram_path,
device_sn=device_sn, dry_run=False)
device_info = {
"Target voltage": float(re.findall(
r"\nTarget voltage:\s(\d+(\.\d+)?)\sV", atprogram_info)[0][0]),
"Device information": {
"Name": re.findall(r"\nName:\s+(\S+)\s+", atprogram_info)[0],
"JtagId": re.findall(r"\nJtagId:\s+(\S+)\s+", atprogram_info)[0],
"CPU arch.": re.findall(r"\nCPU arch.:\s+(\S+)\s+", atprogram_info)[0],
"Series": re.findall(r"\nSeries:\s+(\S+)\s+", atprogram_info)[0],
"DAL": int(re.findall(r"\nDAL:\s+(\d+)\s+", atprogram_info)[0])},
"Memory Information": {
"base": {
address_space: [int(start_address, 16), int(size, 16)]
for address_space, start_address, size in re.findall(
r"\n (\w+)\s+(0[xX][0-9a-fA-F]+)\s+(0[xX][0-9a-fA-F]+)\s+\n",
atprogram_info)},
"fuses": {
fuse: int(value, 16) for fuse, value in re.findall(
r"\n (\w+)\s+(0[xX][0-9a-fA-F]+)\s+\n", atprogram_info)}
}
}
if verbose >= 2:
print(device_info)
return(device_info)
size_regexp = re.compile(
r"\.elf\"\n\s*text\t\s*data\t\s*bss\t\s*dec\t\s*hex\t\s*filename\r\n\s*" +
r"(\d+)\t\s*(\d+)\t\s*(\d+)\t\s*(\d+)\t\s*([0-9a-fA-F]+)\t\s*(\S+.elf)")
[docs]def get_project_size(
project_path, device_name="ATSAML11E16A", verbose=0, tool="EDBG",
interface="SWD", atmel_studio_path=path.join(
getenv("programfiles(x86)"), "Atmel", "Studio", "7.0"),
make_path=None, configuration="Debug", device_sn=None,
jobs=getenv("NUMBER_OF_PROCESSORS")):
"""Returns a dictionary object with the information about the
sizes of the program segments/sections (text, data, BSS) that
usually make up a computer program, and that are in the ``.elf`` file
compiled from the Atmel Studio project at `project_path`.
The dictionary object returned specifically contains the `text`, `data`
and `bss` sizes of the respective program segments, but also includes
`dec` and `hex` values which are the decimal and, respectively, the
hexadecimal value of the sum of `text`, `data` and `bss`, making up
to the whole program size to be flashed to the Atmel board.
The returned dictionary object also contains the `filename` of the
``.elf`` file.
.. note:: \
All of the information returned by this function is obtained after
a new compilation, of the Atmel project located at ``project_path``,
using the :func:`atprogram` function.
Parameters
----------
project_path : str
Location where the project resides. If it ends in
``.elf`` the elf file will be used. If it ends in ``Makefile`` the
Makefile will be used. Otherwise it should be a path to a folder
which holds the `Debug` folder. (the default is ``None``,
which will return an error)
device_name : str, optional
Device name. E.g. `"atxmega128a1"` or `"at32uc3a0256"`.
(the default is ``"ATSAML11E16A"``, which is a variant of the
Atmel SAM L11 board)
verbose : int, optional
Print most of `atprogram` output, if value is `1`, and print
debugging information as well, if value is `2` (the default is
`0`, which is to print only compiler warnings and errors)
tool : str, optional
Tool's name. Available options are: ``avrdragon``,
``avrispmk2``, ``avrone``, ``jtagice3``, ``jtagicemkii``, ``qt600``,
``stk500``, ``stk600``, ``samice``, ``edbg``, ``medbg``, ``nedbg``,
``atmelice``, ``pickit4``, ``powerdebugger``, ``megadfu`` or ``flip``.
(the default is `"EDBG"`, being the Atmel® Embedded Debugger, which
is what the Atmel board of model `ATSAML11E16A` uses)
interface : str, optional
Physical interface. Available options are: ``aWire``,
``debugWIRE``, ``HVPP``, ``HVSP``, ``ISP``, ``JTAG``, ``PDI``,
``UPDI``, ``TPI`` or ``SWD``. (the default is `"SWD"`, which is
what the Atmel board of model `ATSAML11E16A` uses)
atmel_studio_path : str, optional
Specify the location where Atmel Studio is installed. Note that the
path usually ends in a folder name corresponding to the version
name e.g. 7.0 (the default is `"C:\\Program Files (x86)\\Atmel\\
Studio\\7.0"`), which is the default installation location of Atmel
Studio 7.0)
make_path : str, optional
Specify the path to `make.exe`, which should be the location of
a GNU make installation for Windows. (the default is
`"C:\\Program Files (x86)\\Atmel\\Studio\\7.0\\shellutils\\
make.exe"`, as Atmel Studio already provides a GNU make
installation for Windows)
configuration : str, optional
Specify which configuration of the Atmel Studio solution project
should be used for compilation, flashing, etc. Most common
options are: `Release`, `Debug`. (the default is `"Debug"`)
device_sn : str, optional
The programmer/debugger's serial number. Must be
specified when more than one debugger is connected. (the default is
`None`, as it is common to have a single programmer/debugger
connected)
jobs : int, optional
How many jobs `make` should use (the default is
``getenv("NUMBER_OF_PROCESSORS")``,
which takes your number of processor cores available)
Returns
-------
dict
A dictionary of sizes of the sections, the total size and the
file name. The keys of the dictionary are: ``"text"``, ``"data"``
``"dss"``, ``"hex"`` and ``"filename"``.
"""
(text, data, bss, dec, hex, filename) = size_regexp.findall(atprogram(
project_path, clean=True, build=True, erase=False, program=False,
verify=False, return_output=True, verbose=1, dry_run=False))[0]
result = {"text": int(text), "data": int(data), "bss": int(
bss), "dec": int(dec), "hex": int(hex, 16), "filename": filename}
if verbose >= 2:
print(result)
return result