From 26e9bf696f07a262d10bdd3d8879198fbf304438 Mon Sep 17 00:00:00 2001 From: mraardvark Date: Mon, 21 Nov 2016 13:43:37 +0100 Subject: [PATCH] Restructure (#2) * Change file structure, add linter, requirements and gitignore * added basic config for logging --- .gitignore | 2 + device/__init__.py | 0 device/device.py | 16 + pylintrc | 407 +++++++++++++++++++++ pyupdi.py | 868 +------------------------------------------- requirements.txt | 3 + updi/__init__.py | 0 updi/application.py | 295 +++++++++++++++ updi/constants.py | 98 +++++ updi/link.py | 205 +++++++++++ updi/nvm.py | 143 ++++++++ updi/physical.py | 116 ++++++ 12 files changed, 1299 insertions(+), 854 deletions(-) create mode 100644 .gitignore create mode 100644 device/__init__.py create mode 100644 device/device.py create mode 100644 pylintrc create mode 100644 requirements.txt create mode 100644 updi/__init__.py create mode 100644 updi/application.py create mode 100644 updi/constants.py create mode 100644 updi/link.py create mode 100644 updi/nvm.py create mode 100644 updi/physical.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4edd750 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.vscode +__pycache__ \ No newline at end of file diff --git a/device/__init__.py b/device/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/device/device.py b/device/device.py new file mode 100644 index 0000000..2cd8e87 --- /dev/null +++ b/device/device.py @@ -0,0 +1,16 @@ + + +class Device(object): # pylint: disable=too-few-public-methods + """ + Contains device specific information needed for programming + """ + def __init__(self, device_name): + if device_name == "tiny817": + self.flash_start = 0x8000 + self.flash_size = 8 * 1024 + self.flash_pagesize = 64 + self.nvmctrl_address = 0x1000 + self.sigrow_address = 0x1100 + self.syscfg_address = 0x0F00 + else: + raise Exception("Unknown device") diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..a7e50a9 --- /dev/null +++ b/pylintrc @@ -0,0 +1,407 @@ +[MASTER] + +# Specify a configuration file. +#rcfile= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Add files or directories to the blacklist. They should be base names, not +# paths. +ignore=CVS + +# Add files or directories matching the regex patterns to the blacklist. The +# regex matches against base names, not paths. +ignore-patterns= + +# Pickle collected data for later comparisons. +persistent=yes + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Use multiple processes to speed up Pylint. +jobs=1 + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist= + +# Allow optimization of some AST trees. This will activate a peephole AST +# optimizer, which will apply various small optimizations. For instance, it can +# be used to obtain the result of joining multiple strings with the addition +# operator. Joining a lot of strings can lead to a maximum recursion error in +# Pylint and this flag can prevent that. It has one side effect, the resulting +# AST will be different than the one from reality. This option is deprecated +# and it will be removed in Pylint 2.0. +optimize-ast=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once).You can also use "--disable=all" to +# disable everything first and then reenable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use"--disable=all --enable=classes +# --disable=W" +disable=coerce-method,cmp-method,range-builtin-not-iterating,old-division,xrange-builtin,hex-method,standarderror-builtin,map-builtin-not-iterating,intern-builtin,raising-string,nonzero-method,dict-view-method,long-suffix,unichr-builtin,backtick,old-octal-literal,round-builtin,old-ne-operator,buffer-builtin,metaclass-assignment,delslice-method,next-method-called,file-builtin,reduce-builtin,setslice-method,print-statement,apply-builtin,indexing-exception,using-cmp-argument,no-absolute-import,basestring-builtin,filter-builtin-not-iterating,reload-builtin,zip-builtin-not-iterating,long-builtin,parameter-unpacking,dict-iter-method,old-raise-syntax,getslice-method,cmp-builtin,unpacking-in-except,raw_input-builtin,suppressed-message,input-builtin,execfile-builtin,oct-method,coerce-builtin,unicode-builtin,useless-suppression,import-star-module-level,C0103,C0301,W1202 + + +[REPORTS] + +# Set the output format. Available formats are text, parseable, colorized, msvs +# (visual studio) and html. You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Put messages in a separate file for each module / package specified on the +# command line instead of printing them on stdout. Reports (if any) will be +# written in a file name "pylint_global.[txt|html]". This option is deprecated +# and it will be removed in Pylint 2.0. +files-output=no + +# Tells whether to display a full report or only the messages +reports=yes + +# Python expression which should return a note less than 10 (10 is the highest +# note). You have access to the variables errors warning, statement which +# respectively contain the number of errors / warnings messages and the total +# number of statements analyzed. This is used by the global evaluation report +# (RP0004). +evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details +#msg-template= + + +[BASIC] + +# Good variable names which should always be accepted, separated by a comma +good-names=i,j,k,ex,Run,_ + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo,bar,baz,toto,tutu,tata + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=no + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +property-classes=abc.abstractproperty + +# Regular expression matching correct class attribute names +class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Naming hint for class attribute names +class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ + +# Regular expression matching correct method names +method-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for method names +method-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct constant names +const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Naming hint for constant names +const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ + +# Regular expression matching correct module names +module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Naming hint for module names +module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ + +# Regular expression matching correct class names +class-rgx=[A-Z_][a-zA-Z0-9]+$ + +# Naming hint for class names +class-name-hint=[A-Z_][a-zA-Z0-9]+$ + +# Regular expression matching correct argument names +argument-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for argument names +argument-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct inline iteration names +inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ + +# Naming hint for inline iteration names +inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ + +# Regular expression matching correct function names +function-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for function names +function-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct attribute names +attr-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for attribute names +attr-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression matching correct variable names +variable-rgx=[a-z_][a-z0-9_]{2,30}$ + +# Naming hint for variable names +variable-name-hint=[a-z_][a-z0-9_]{2,30}$ + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + + +[ELIF] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + + +[FORMAT] + +# Maximum number of characters on a single line. +max-line-length=100 + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + +# List of optional constructs for which whitespace checking is disabled. `dict- +# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. +# `trailing-comma` allows a space between comma and closing bracket: (a, ). +# `empty-line` allows space-only lines. +no-space-check=trailing-comma,dict-separator + +# Maximum number of lines in a module +max-module-lines=1000 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + + +[LOGGING] + +# Logging modules to check that the string format arguments are in logging +# function parameter format +logging-modules=logging + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME,XXX,TODO + + +[SIMILARITIES] + +# Minimum lines number of a similarity. +min-similarity-lines=4 + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + + +[SPELLING] + +# Spelling dictionary name. Available dictionaries: none. To make it working +# install python-enchant package. +spelling-dict= + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to indicated private dictionary in +# --spelling-private-dict-file option instead of raising a message. +spelling-store-unknown-words=no + + +[TYPECHECK] + +# Tells whether missing members accessed in mixin class should be ignored. A +# mixin class is detected if its name ends with "mixin" (case insensitive). +ignore-mixin-members=yes + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules= + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members= + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + + +[VARIABLES] + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=(_+[a-zA-Z0-9]*?$)|dummy + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_,_cb + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,future.builtins + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__,__new__,setUp + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.* + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of statements in function / method body +max-statements=50 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + + +[IMPORTS] + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=optparse + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph= + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of internal dependencies in the given file (report RP0402 must +# not be disabled) +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/pyupdi.py b/pyupdi.py index 2af52f7..92744ad 100644 --- a/pyupdi.py +++ b/pyupdi.py @@ -1,3 +1,9 @@ + +from device.device import Device +from updi.nvm import UpdiNvmProgrammer + +import logging +logging.basicConfig(level=logging.WARNING) """ Copyright (c) 2016 Atmel Corporation, a wholly owned subsidiary of Microchip Technology Inc. @@ -42,845 +48,6 @@ """ -import serial -import time - -# UPDI commands and control definitions -UPDI_BREAK = 0x00 - -UPDI_LDS = 0x00 -UPDI_STS = 0x40 -UPDI_LD = 0x20 -UPDI_ST = 0x60 -UPDI_LDCS = 0x80 -UPDI_STCS = 0xC0 -UPDI_REPEAT = 0xA0 -UPDI_KEY = 0xE0 - -UPDI_PTR = 0x00 -UPDI_PTR_INC = 0x04 -UPDI_PTR_ADDRESS = 0x08 - -UPDI_ADDRESS_8 = 0x00 -UPDI_ADDRESS_16 = 0x04 - -UPDI_DATA_8 = 0x00 -UPDI_DATA_16 = 0x01 - -UPDI_KEY_SIB = 0x04 -UPDI_KEY_KEY = 0x00 - -UPDI_KEY_64 = 0x00 -UPDI_KEY_128 = 0x01 - -UPDI_SIB_8BYTES = UPDI_KEY_64 -UPDI_SIB_16BYTES = UPDI_KEY_128 - -UPDI_REPEAT_BYTE = 0x00 -UPDI_REPEAT_WORD = 0x01 - -UPDI_PHY_SYNC = 0x55 -UPDI_PHY_ACK = 0x40 - -UPDI_MAX_REPEAT_SIZE = 0xFF - -# CS and ASI Register Address map -UPDI_CS_STATUSA = 0x00 -UPDI_CS_STATUSB = 0x01 -UPDI_CS_CTRLA = 0x02 -UPDI_CS_CTRLB = 0x03 -UPDI_ASI_KEY_STATUS = 0x07 -UPDI_ASI_RESET_REQ = 0x08 -UPDI_ASI_CTRLA = 0x09 -UPDI_ASI_SYS_CTRLA = 0x0A -UPDI_ASI_SYS_STATUS = 0x0B -UPDI_ASI_CRC_STATUS = 0x0C - -UPDI_CTRLA_IBDLY_BIT = 7 -UPDI_CTRLB_CCDETDIS_BIT = 3 -UPDI_CTRLB_UPDIDIS_BIT = 2 - -UPDI_KEY_NVM = "NVMProg " -UPDI_KEY_CHIPERASE = "NVMErase" - -UPDI_ASI_STATUSA_REVID = 4 -UPDI_ASI_STATUSB_PESIG = 0 - -UPDI_ASI_KEY_STATUS_CHIPERASE = 3 -UPDI_ASI_KEY_STATUS_NVMPROG = 4 -UPDI_ASI_KEY_STATUS_UROWWRITE = 5 - -UPDI_ASI_SYS_STATUS_RSTSYS = 5 -UPDI_ASI_SYS_STATUS_INSLEEP = 4 -UPDI_ASI_SYS_STATUS_NVMPROG = 3 -UPDI_ASI_SYS_STATUS_UROWPROG = 2 -UPDI_ASI_SYS_STATUS_LOCKSTATUS = 0 - -UPDI_RESET_REQ_VALUE = 0x59 - -# FLASH CONTROLLER -UPDI_NVMCTRL_CTRLA = 0x00 -UPDI_NVMCTRL_CTRLB = 0x01 -UPDI_NVMCTRL_STATUS = 0x02 -UPDI_NVMCTRL_INTCTRL = 0x03 -UPDI_NVMCTRL_INTFLAGS = 0x04 -UPDI_NVMCTRL_DATAL = 0x06 -UPDI_NVMCTRL_DATAH = 0x07 -UPDI_NVMCTRL_ADDRL = 0x08 -UPDI_NVMCTRL_ADDRH = 0x09 - -# CTRLA -UPDI_NVMCTRL_CTRLA_NOP = 0x00 -UPDI_NVMCTRL_CTRLA_WRITE_PAGE = 0x01 -UPDI_NVMCTRL_CTRLA_ERASE_PAGE = 0x02 -UPDI_NVMCTRL_CTRLA_ERASE_WRITE_PAGE = 0x03 -UPDI_NVMCTRL_CTRLA_PAGE_BUFFER_CLR = 0x04 -UPDI_NVMCTRL_CTRLA_CHIP_ERASE = 0x05 -UPDI_NVMCTRL_CTRLA_ERASE_EEPROM = 0x06 -UPDI_NVMCTRL_CTRLA_WRITE_FUSE = 0x07 - -UPDI_NVM_STATUS_WRITE_ERROR = 2 -UPDI_NVM_STATUS_EEPROM_BUSY = 1 -UPDI_NVM_STATUS_FLASH_BUSY = 0 - - -class UpdiPhysical: - """ - PDI physical driver using a given COM port at a given baud - """ - - def __init__(self, port, baud=100000, verbose=0): - """ - Initialise the COM port - """ - self.VERBOSE_THRESHOLD = 4 - self.verbose = verbose - self.notify("Constructor") - - # Inter-byte delay - self.ibdly = 0.0001 - self.port = port - self.baud = baud - self.ser = None - self.initialise_serial(self.port, self.baud) - - def notify(self, message): - if self.verbose >= self.VERBOSE_THRESHOLD: - print " PHY:{}".format(message) - - def initialise_serial(self, port, baud): - # Standard COM port initialisation - self.notify("Opening {} at {} baud".format(port, baud)) - self.ser = serial.Serial(port, baud, parity=serial.PARITY_EVEN, timeout=1) - - def send_double_break(self): - """ - Sends a double break to reset the UPDI port - BREAK is actually just a slower zero frame - A double break is guaranteed to push the UPDI state - machine into a known state, albeit rather brutally - """ - self.notify("Sending double break") - # Re-init at a lower baud - self.ser.close() - s = serial.Serial(self.port, 1000) - # Send a break - s.write([UPDI_BREAK]) - # Wait - time.sleep(0.05) - # Send another - s.write([UPDI_BREAK]) - time.sleep(0.001) - # Re-init at the real baud - s.close() - self.initialise_serial(self.port, self.baud) - - def send(self, command): - """ - Sends a char array to UPDI with inter-byte delay - Note that the byte will echo back - """ - if self.verbose >= self.VERBOSE_THRESHOLD: - self.notify("send: {}".format(command)) - for c in command: - # Send - self.ser.write([c]) - # Echo - self.ser.read() - # Inter-byte delay - time.sleep(self.ibdly) - - def receive(self, size): - """ - Receives a frame of a known number of chars from UPDI - """ - response = [] - timeout = 1 - # For each byte - while size and timeout: - # Read - c = self.ser.read() - # Anything in? - if c != "": - response.append(ord(c)) - size -= 1 - else: - timeout -= 1 - self.notify("receive: {}".format(response)) - return response - - def sib(self): - """ - System information block is just a string coming back from a SIB command - """ - self.send([UPDI_PHY_SYNC, UPDI_KEY | UPDI_KEY_SIB | UPDI_SIB_16BYTES]) - return self.ser.readline() - - def __del__(self): - self.notify("Closing {}".format(self.port)) - self.ser.close() - - -class UpdiDatalink: - """ - UPDI data link class handles the UPDI data protocol within the device - """ - - def __init__(self, comport, baud, verbose=0): - self.VERBOSE_THRESHOLD = 3 - self.verbose = verbose - self.notify("Constructor") - # Create a UPDI physical connection - self.updi_phy = UpdiPhysical(comport, baud, verbose=verbose) - # Initialise - self.init() - # Check - if not self.check(): - # Send double break if all is not well, and re-check - self.updi_phy.send_double_break() - self.init() - if not self.check(): - raise (Exception("UPDI initialisation failed.")) - - def notify(self, message): - if self.verbose >= self.VERBOSE_THRESHOLD: - print " DL:{}".format(message) - - def init(self): - # Set the inter-byte delay bit and disable collision detection - self.stcs(UPDI_CS_CTRLB, 1 << UPDI_CTRLB_CCDETDIS_BIT) - self.stcs(UPDI_CS_CTRLA, 1 << UPDI_CTRLA_IBDLY_BIT) - - def check(self): - """ - Check UPDI by loading CS STATUSA - """ - if self.ldcs(UPDI_CS_STATUSA) != 0: - self.notify("UPDI init OK") - return True - self.notify("UPDI not OK - reinitialisation required") - return False - - def ldcs(self, address): - """ - Load data from Control/Status space - """ - self.notify("LDCS from 0x{0:02X}".format(address)) - self.updi_phy.send([UPDI_PHY_SYNC, UPDI_LDCS | (address & 0x0F)]) - response = self.updi_phy.receive(1) - if len(response) != 1: - # Todo - flag error - return 0x00 - return response[0] - - def stcs(self, address, value): - """ - Store a value to Control/Status space - """ - self.notify("STCS to 0x{0:04X}".format(address)) - self.updi_phy.send([UPDI_PHY_SYNC, UPDI_STCS | (address & 0x0F), value]) - - def ld(self, address): - """ - Load a single byte direct from a 16-bit address - """ - self.notify("LD from 0x{0:04X}".format(address)) - self.updi_phy.send( - [UPDI_PHY_SYNC, UPDI_LDS | UPDI_ADDRESS_16 | UPDI_DATA_8, address & 0xFF, (address >> 8) & 0xFF]) - return self.updi_phy.receive(1)[0] - - def ld16(self, address): - """ - Load a 16-bit word directly from a 16-bit address - """ - self.notify("LD from 0x{0:04X}".format(address)) - self.updi_phy.send( - [UPDI_PHY_SYNC, UPDI_LDS | UPDI_ADDRESS_16 | UPDI_DATA_16, address & 0xFF, (address >> 8) & 0xFF]) - return self.updi_phy.receive(2) - - def st(self, address, value): - """ - Store a single byte value directly to a 16-bit address - """ - self.notify("ST to 0x{0:04X}".format(address)) - self.updi_phy.send( - [UPDI_PHY_SYNC, UPDI_STS | UPDI_ADDRESS_16 | UPDI_DATA_8, address & 0xFF, (address >> 8) & 0xFF]) - response = self.updi_phy.receive(1) - if len(response) != 1 or response[0] != UPDI_PHY_ACK: - raise (Exception("Error with st")) - - self.updi_phy.send([value & 0xFF]) - response = self.updi_phy.receive(1) - if len(response) != 1 or response[0] != UPDI_PHY_ACK: - raise (Exception("Error with st")) - - def st16(self, address, value): - """ - Store a 16-bit word value directly to a 16-bit address - """ - self.notify("ST to 0x{0:04X}".format(address)) - self.updi_phy.send( - [UPDI_PHY_SYNC, UPDI_STS | UPDI_ADDRESS_16 | UPDI_DATA_16, address & 0xFF, (address >> 8) & 0xFF]) - response = self.updi_phy.receive(1) - if len(response) != 1 or response[0] != UPDI_PHY_ACK: - raise (Exception("Error with st")) - - self.updi_phy.send([value & 0xFF, (value >> 8) & 0xFF]) - response = self.updi_phy.receive(1) - if len(response) != 1 or response[0] != UPDI_PHY_ACK: - raise (Exception("Error with st")) - - def ld_ptr_inc(self, size): - """ - Loads a number of bytes from the pointer location with pointer post-increment - """ - self.notify("LD8 from ptr++") - self.updi_phy.send([UPDI_PHY_SYNC, UPDI_LD | UPDI_PTR_INC | UPDI_DATA_8]) - return self.updi_phy.receive(size) - - def ld_ptr_inc16(self, words): - """ - Load a 16-bit word value from the pointer location with pointer post-increment - """ - self.notify("LD16 from ptr++") - self.updi_phy.send([UPDI_PHY_SYNC, UPDI_LD | UPDI_PTR_INC | UPDI_DATA_16]) - return self.updi_phy.receive(words << 1) - - def st_ptr(self, address): - """ - Set the pointer location - """ - self.notify("ST to ptr") - self.updi_phy.send( - [UPDI_PHY_SYNC, UPDI_ST | UPDI_PTR_ADDRESS | UPDI_DATA_16, address & 0xFF, (address >> 8) & 0xFF]) - response = self.updi_phy.receive(1) - if len(response) != 1 or response[0] != UPDI_PHY_ACK: - raise (Exception("Error with st_ptr")) - - def st_ptr_inc(self, data): - """ - Store data to the pointer location with pointer post-increment - """ - self.notify("ST8 to *ptr++") - self.updi_phy.send([UPDI_PHY_SYNC, UPDI_ST | UPDI_PTR_INC | UPDI_DATA_8, data[0]]) - response = self.updi_phy.receive(1) - if len(response) != 1 or response[0] != UPDI_PHY_ACK: - raise (Exception("ACK error with st_ptr_inc")) - n = 1 - while n < len(data): - self.updi_phy.send([data[n]]) - response = self.updi_phy.receive(1) - if len(response) != 1 or response[0] != UPDI_PHY_ACK: - raise (Exception("Error with st_ptr_inc")) - n += 1 - - def st_ptr_inc16(self, data): - """ - Store a 16-bit word value to the pointer location with pointer post-increment - """ - self.notify("ST16 to *ptr++") - self.updi_phy.send([UPDI_PHY_SYNC, UPDI_ST | UPDI_PTR_INC | UPDI_DATA_16, data[0], data[1]]) - response = self.updi_phy.receive(1) - if len(response) != 1 or response[0] != UPDI_PHY_ACK: - raise (Exception("ACK error with st_ptr_inc16")) - n = 2 - while n < len(data): - self.updi_phy.send([data[n], data[n + 1]]) - response = self.updi_phy.receive(1) - if len(response) != 1 or response[0] != UPDI_PHY_ACK: - raise (Exception("Error with st_ptr_inc16")) - n += 2 - - def repeat(self, repeats): - """ - Store a value to the repeat counter - """ - self.notify("Repeat {0:d}".format(repeats)) - repeats -= 1 - self.updi_phy.send([UPDI_PHY_SYNC, UPDI_REPEAT | UPDI_REPEAT_WORD, repeats & 0xFF, (repeats >> 8) & 0xFF]) - - def read_sib(self): - """ - Read the SIB - """ - return self.updi_phy.sib() - - def key(self, size, key): - """ - Write a key - """ - self.notify("Writing key") - if len(key) != 8 << size: - raise (Exception("Invalid KEY length!")) - self.updi_phy.send([UPDI_PHY_SYNC, UPDI_KEY | UPDI_KEY_KEY | size]) - self.updi_phy.send(list(reversed(list(key)))) - - -class UpdiApplication: - """ - Generic application layer for UPDI - """ - - def __init__(self, comport, baud, device=None, verbose=0): - self.VERBOSE_THRESHOLD = 2 - self.verbose = verbose - self.notify("Constructor") - self.datalink = UpdiDatalink(comport, baud, verbose) - self.device = device - - def notify(self, message): - if self.verbose >= self.VERBOSE_THRESHOLD: - print " APP:{}".format(message) - - def device_info(self): - """ - Reads out device information from various sources - """ - sib = self.datalink.read_sib() - print "SIB read out as: {}".format(sib) - print "Family ID = {}".format(sib[0:7]) - print "NVM revision = {}".format(sib[10]) - print "OCD revision = {}".format(sib[13]) - print "PDI OSC = {}MHz".format(sib[15]) - - print "PDI revision = 0x{:X}".format(self.datalink.ldcs(UPDI_CS_STATUSA) >> 4) - if self.in_prog_mode(): - if self.device is not None: - devid = self.read_data(self.device.sigrow_address, 3) - devrev = self.read_data(self.device.syscfg_address + 1, 1) - print "Device ID = {0:X}{1:X}{2:X} rev {3:}".format(devid[0], devid[1], devid[2], - chr(ord('A') + devrev[0])) - - def in_prog_mode(self): - """ - Checks whether the NVM PROG flag is up - """ - if self.datalink.ldcs(UPDI_ASI_SYS_STATUS) & (1 << UPDI_ASI_SYS_STATUS_NVMPROG): - return True - return False - - def wait_unlocked(self, timeout_ms): - """ - Waits for the device to be unlocked. - All devices boot up as locked until proven otherwise - """ - while True: - if not self.datalink.ldcs(UPDI_ASI_SYS_STATUS) & (1 << UPDI_ASI_SYS_STATUS_LOCKSTATUS): - return True - if timeout_ms == 0: - self.notify("Timeout waiting for device to unlock") - return False - time.sleep((timeout_ms / 1000.0) / 10) - timeout_ms -= 1 - - def unlock(self): - """ - Unlock and erase - """ - # Put in the key - self.datalink.key(UPDI_KEY_64, UPDI_KEY_CHIPERASE) - - # Check key status - key_status = self.datalink.ldcs(UPDI_ASI_KEY_STATUS) - self.notify("Key status = 0x{0:02X}".format(key_status)) - if not key_status & (1 << UPDI_ASI_KEY_STATUS_CHIPERASE): - raise (Exception("Key not accepted")) - - # Toggle reset - self.reset(True) - self.reset(False) - - # And wait for unlock - if not self.wait_unlocked(10): - raise (Exception("Failed to chip erase using key.")) - - def enter_progmode(self): - """ - Enters into NVM programming mode - """ - # First check if NVM is already enabled - if self.in_prog_mode(): - self.notify("Already in NVM programming mode") - return True - - self.notify("Entering NVM programming mode") - - # Put in the key - self.datalink.key(UPDI_KEY_64, UPDI_KEY_NVM) - - # Check key status - key_status = self.datalink.ldcs(UPDI_ASI_KEY_STATUS) - self.notify("Key status = 0x{0:02X}".format(key_status)) - if not key_status & (1 << UPDI_ASI_KEY_STATUS_NVMPROG): - raise (Exception("Key not accepted")) - - # Toggle reset - self.reset(True) - self.reset(False) - - # And wait for unlock - if not self.wait_unlocked(10): - raise (Exception("Failed to enter NVM programming mode: device is locked")) - - # Check for NVMPROG flag - if not self.in_prog_mode(): - raise (Exception("Failed to enter NVM programming mode")) - - self.notify("Now in NVM programming mode") - return True - - def leave_progmode(self): - """ - Disables UPDI which releases any keys enabled - """ - self.notify("Leaving NVM programming mode") - self.datalink.stcs(UPDI_CS_CTRLB, (1 << UPDI_CTRLB_UPDIDIS_BIT) | (1 << UPDI_CTRLB_CCDETDIS_BIT)) - - def reset(self, apply_reset): - """ - Applies or releases an UPDI reset condition - """ - if apply_reset: - self.notify("Apply reset") - self.datalink.stcs(UPDI_ASI_RESET_REQ, UPDI_RESET_REQ_VALUE) - else: - self.notify("Release reset") - self.datalink.stcs(UPDI_ASI_RESET_REQ, 0x00) - - def wait_flash_ready(self): - """ - Waits for the NVM controller to be ready - """ - # TODO - add timeout - self.notify("Wait flash ready") - while True: - status = self.datalink.ld(self.device.nvmctrl_address + UPDI_NVMCTRL_STATUS) - if status & (1 << UPDI_NVM_STATUS_WRITE_ERROR): - self.notify("NVM error") - return False - - if not (status & ((1 << UPDI_NVM_STATUS_EEPROM_BUSY) | (1 << UPDI_NVM_STATUS_FLASH_BUSY))): - return True - - def execute_nvm_command(self, command): - """ - Executes an NVM COMMAND on the NVM CTRL - """ - self.notify("NVMCMD {:d} executing".format(command)) - return self.datalink.st(self.device.nvmctrl_address + UPDI_NVMCTRL_CTRLA, command) - - def chip_erase(self): - """ - Does a chip erase using the NVM controller - Note that on locked devices this it not possible and the ERASE KEY has to be used instead - """ - self.notify("Chip erase using NVM CTRL") - - # Wait until NVM CTRL is ready to erase - if not self.wait_flash_ready(): - raise (Exception("Timeout waiting for flash ready before erase ")) - - # Erase - self.execute_nvm_command(UPDI_NVMCTRL_CTRLA_CHIP_ERASE) - - # And wait for it - if not self.wait_flash_ready(): - raise (Exception("Timeout waiting for flash ready after erase")) - - return True - - def write_data_words(self, address, data): - """ - Writes a number of words to memory - """ - # Special-case of 1 word - if len(data) == 2: - value = data[0] + (data[1] << 8) - return self.datalink.st16(address, value) - - # Range check - if len(data) > (UPDI_MAX_REPEAT_SIZE + 1) << 1: - raise (Exception("Invalid length")) - - # Store the address - self.datalink.st_ptr(address) - - # Fire up the repeat - self.datalink.repeat(len(data) >> 1) - return self.datalink.st_ptr_inc16(data) - - def write_data(self, address, data): - """ - Writes a number of bytes to memory - """ - # Special case of 1 byte - if len(data) == 1: - return self.datalink.st(address, data[0]) - # Special case of 2 byte - elif len(data) == 2: - self.datalink.st(address, data[0]) - return self.datalink.st(address + 1, data[1]) - - # Range check - if len(data) > (UPDI_MAX_REPEAT_SIZE + 1): - raise (Exception("Invalid length")) - - # Store the address - self.datalink.st_ptr(address) - - # Fire up the repeat - self.datalink.repeat(len(data)) - return self.datalink.st_ptr_inc(data) - - def write_nvm(self, address, data, nvm_command=UPDI_NVMCTRL_CTRLA_WRITE_PAGE, use_word_access=True): - """ - Writes a page of data to NVM. - By default the PAGE_WRITE command is used, which requires that the page is already erased - By default word access is used (flash) - """ - - # Check that NVM controller is ready - if not self.wait_flash_ready(): - raise (Exception("Timeout waiting for flash ready before page buffer clear ")) - - # Clear the page buffer - self.notify("Clear page buffer") - self.execute_nvm_command(UPDI_NVMCTRL_CTRLA_PAGE_BUFFER_CLR) - - # Waif for NVM controller to be ready - if not self.wait_flash_ready(): - raise (Exception("Timeout waiting for flash ready after page buffer clear ")) - - # Load the page buffer by writing directly to location - if use_word_access: - self.write_data_words(address, data) - else: - self.write_data(address, data) - - # Write the page to NVM, maybe erase first - self.notify("Committing page") - self.execute_nvm_command(nvm_command) - - # Waif for NVM controller to be ready again - if not self.wait_flash_ready(): - raise (Exception("Timeout waiting for flash ready after page write ")) - - def read_data(self, address, size): - """ - Reads a number of bytes of data from UPDI - """ - self.notify("Reading {0:d} bytes from 0x{1:04X}".format(size, address)) - # Range check - if size > UPDI_MAX_REPEAT_SIZE + 1: - raise (Exception("Cant read that many bytes in one go")) - - # Store the address - self.datalink.st_ptr(address) - - # Fire up the repeat - if size > 1: - self.datalink.repeat(size) - - # Do the read(s) - return self.datalink.ld_ptr_inc(size) - - def read_data_words(self, address, words): - """ - Reads a number of words of data from UPDI - """ - self.notify("Reading {0:d} words from 0x{1:04X}".format(words, address)) - # Range check - if words > (UPDI_MAX_REPEAT_SIZE >> 1) + 1: - raise (Exception("Cant read that many words in one go")) - - # Store the address - self.datalink.st_ptr(address) - - # Fire up the repeat - if words > 1: - self.datalink.repeat(words) - - # Do the read - return self.datalink.ld_ptr_inc16(words) - - -class UpdiNvmProgrammer: - """ - NVM programming utility for UPDI - """ - - def __init__(self, comport, baud, device, verbose=0): - self.VERBOSE_THRESHOLD = 1 - self.verbose = verbose - self.notify("Constructor") - self.application = UpdiApplication(comport, baud, device, verbose) - self.device = device - self.progmode = False - - def notify(self, message): - if self.verbose >= self.VERBOSE_THRESHOLD: - print "{}".format(message) - - def get_device_info(self): - """ - Reads device info - """ - self.notify("Reading device info") - return self.application.device_info() - - def enter_progmode(self): - """ - Enter programming mode - """ - self.notify("Entering NVM programming mode") - if self.application.enter_progmode(): - self.progmode = True - - def leave_progmode(self): - """ - Leave programming mode - """ - self.notify("Leaving NVM programming mode") - self.application.leave_progmode() - self.progmode = False - - def unlock_device(self): - """ - Unlock and erase a device - """ - if self.progmode: - self.notify("Device already unlocked") - return - - # Unlock - self.application.unlock() - # Unlock after using the NVM key results in prog mode. - self.progmode = True - - def chip_erase(self): - """ - Erase (unlocked) device - """ - if not self.progmode: - raise (Exception("Enter progmode first!")) - return self.application.chip_erase() - - def read_flash(self, address, size): - """ - Reads from flash - """ - # Must be in prog mode here - if not self.progmode: - raise (Exception("Enter progmode first!")) - # Find the number of pages - pages = size / device.flash_pagesize - if size % device.flash_pagesize: - raise (Exception("Only full page aligned flash supported.")) - data = [] - # Read out page-wise for convenience - for i in range(pages): - self.notify("Reading page at 0x{0:04X}".format(address)) - data += (self.application.read_data_words(address, device.flash_pagesize >> 1)) - address += device.flash_pagesize - return data - - def write_flash(self, address, data): - """ - Writes to flash - """ - # Must be in prog mode - if not self.progmode: - raise (Exception("Enter progmode first!")) - # Pad to full page - data = self.pad_data(data, self.device.flash_pagesize) - # Divide up into pages - pages = self.page_data(data, self.device.flash_pagesize) - # Program each page - for page in pages: - self.notify("Writing page at 0x{0:04X}".format(address)) - self.application.write_nvm(address, page) - address += len(page) - - def pad_data(self, data, blocksize, character=0xFF): - """ - Pads data so that there are full pages - """ - self.notify("Padding to blocksize {0:d} with 0x{1:X}".format(blocksize, character)) - if len(data) % blocksize > 0: - for i in range(len(data) % blocksize, blocksize): - data.append(0) - return data - - def page_data(self, data, size): - """ - Divide data into pages - """ - self.notify("Paging into {} byte blocks".format(size)) - total_length = len(data) - result = [] - while len(result) < total_length / size: - result.append(data[:size].tolist()) - data = data[size:] - return result - - def load_ihex(self, filename): - """ - Load from intel hex format - """ - self.notify("Loading from hexfile '{}'".format(filename)) - from intelhex import IntelHex - - ih = IntelHex() - ih.loadhex(filename) - data = ih.tobinarray() - start_address = ih.minaddr() - self.notify("Loaded {0:d} bytes from ihex starting at address 0x{1:04X}".format(len(data), start_address)) - - # Size check - if len(data) > self.device.flash_size: - raise (Exception("ihex too large for flash")) - - # Offset to actual flash start - if start_address < self.device.flash_start: - self.notify("Adjusting flash offset to address 0x{:04X}".format(self.device.flash_start)) - start_address += self.device.flash_start - - return data, start_address - - -class Device: - def __init__(self, device_name): - if device_name == "tiny817": - self.flash_start = 0x8000 - self.flash_size = 8 * 1024 - self.flash_pagesize = 64 - self.nvmctrl_address = 0x1000 - self.sigrow_address = 0x1100 - self.syscfg_address = 0x0F00 - else: - raise (Exception("Unknown device")) - - """ Simple command line interface for programming flash """ @@ -889,8 +56,8 @@ def __init__(self, device_name): import sys if len(sys.argv) != 4: - print "Python UPDI programmer demo" - print "Usage: pyupdi.py comport device filename" + print("Python UPDI programmer demo") + print("Usage: pyupdi.py comport device filename") sys.exit(1) # Retrieve parameters @@ -898,14 +65,7 @@ def __init__(self, device_name): device = Device(sys.argv[2]) filename = sys.argv[3] - # Initialise the UPDI stack on a COM port - # Verbose 0: quiet - # Verbose 1: NVM logging - # Verbose 2: APP logging - # Verbose 3: DL logging - # Verbose 4: PHY logging - # - nvm = UpdiNvmProgrammer(comport=comport, baud=100000, device=device, verbose=1) + nvm = UpdiNvmProgrammer(comport=comport, baud=100000, device=device) # Retrieve data to write data, start_address = nvm.load_ihex(filename) @@ -913,8 +73,8 @@ def __init__(self, device_name): # Enter programming mode try: nvm.enter_progmode() - except: - print "Device is locked. Performing unlock with chip erase." + except Exception: + print("Device is locked. Performing unlock with chip erase.") nvm.unlock_device() # Read and display device info @@ -930,8 +90,8 @@ def __init__(self, device_name): readback = nvm.read_flash(device.flash_start, len(data)) for i in range(len(data)): if data[i] != readback[i]: - print "Verify error at location 0x{0:04X}: expected 0x{1:02X} read 0x{2:02X} ".format(i, data[i], - readback[i]) + print("Verify error at location 0x{0:04X}: expected 0x{1:02X} read 0x{2:02X} ".format(i, data[i], + readback[i])) # Exit programming mode - nvm.leave_progmode() \ No newline at end of file + nvm.leave_progmode() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2e5888e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +intelhex +pylint +pyserial diff --git a/updi/__init__.py b/updi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/updi/application.py b/updi/application.py new file mode 100644 index 0000000..4d0ce3d --- /dev/null +++ b/updi/application.py @@ -0,0 +1,295 @@ + +import logging +import time + +import updi.constants as constants +from updi.link import UpdiDatalink + +class UpdiApplication(object): + """ + Generic application layer for UPDI + """ + + def __init__(self, comport, baud, device=None): + self.datalink = UpdiDatalink(comport, baud) + self.device = device + + self.logger = logging.getLogger("app") + + def device_info(self): + """ + Reads out device information from various sources + """ + sib = self.datalink.read_sib() + print("SIB read out as: {}".format(sib)) + print("Family ID = {}".format(sib[0:7])) + print("NVM revision = {}".format(sib[10])) + print("OCD revision = {}".format(sib[13])) + print("PDI OSC = {}MHz".format(sib[15])) + + print("PDI revision = 0x{:X}".format(self.datalink.ldcs(constants.UPDI_CS_STATUSA) >> 4)) + if self.in_prog_mode(): + if self.device is not None: + devid = self.read_data(self.device.sigrow_address, 3) + devrev = self.read_data(self.device.syscfg_address + 1, 1) + print("Device ID = {0:X}{1:X}{2:X} rev {3:}".format(devid[0], devid[1], devid[2], + chr(ord('A') + devrev[0]))) + + def in_prog_mode(self): + """ + Checks whether the NVM PROG flag is up + """ + if self.datalink.ldcs(constants.UPDI_ASI_SYS_STATUS) & (1 << constants.UPDI_ASI_SYS_STATUS_NVMPROG): + return True + return False + + def wait_unlocked(self, timeout_ms): + """ + Waits for the device to be unlocked. + All devices boot up as locked until proven otherwise + """ + while True: + if not self.datalink.ldcs(constants.UPDI_ASI_SYS_STATUS) & (1 << constants.UPDI_ASI_SYS_STATUS_LOCKSTATUS): + return True + + if timeout_ms == 0: + self.logger.info("Timeout waiting for device to unlock") + return False + + time.sleep((timeout_ms / 1000.0) / 10) + timeout_ms -= 1 + + def unlock(self): + """ + Unlock and erase + """ + # Put in the key + self.datalink.key(constants.UPDI_KEY_64, constants.UPDI_KEY_CHIPERASE) + + # Check key status + key_status = self.datalink.ldcs(constants.UPDI_ASI_KEY_STATUS) + self.logger.info("Key status = 0x{0:02X}".format(key_status)) + + if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_CHIPERASE): + raise Exception("Key not accepted") + + # Toggle reset + self.reset(True) + self.reset(False) + + # And wait for unlock + if not self.wait_unlocked(10): + raise Exception("Failed to chip erase using key") + + def enter_progmode(self): + """ + Enters into NVM programming mode + """ + # First check if NVM is already enabled + if self.in_prog_mode(): + self.logger.info("Already in NVM programming mode") + return True + + self.logger.info("Entering NVM programming mode") + + # Put in the key + self.datalink.key(constants.UPDI_KEY_64, constants.UPDI_KEY_NVM) + + # Check key status + key_status = self.datalink.ldcs(constants.UPDI_ASI_KEY_STATUS) + self.logger.info("Key status = 0x{0:02X}".format(key_status)) + + if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_NVMPROG): + raise Exception("Key not accepted") + + # Toggle reset + self.reset(True) + self.reset(False) + + # And wait for unlock + if not self.wait_unlocked(10): + raise Exception("Failed to enter NVM programming mode: device is locked") + + # Check for NVMPROG flag + if not self.in_prog_mode(): + raise Exception("Failed to enter NVM programming mode") + + self.logger.info("Now in NVM programming mode") + return True + + def leave_progmode(self): + """ + Disables UPDI which releases any keys enabled + """ + self.logger.info("Leaving NVM programming mode") + self.datalink.stcs(constants.UPDI_CS_CTRLB, (1 << constants.UPDI_CTRLB_UPDIDIS_BIT) | (1 << constants.UPDI_CTRLB_CCDETDIS_BIT)) + + def reset(self, apply_reset): + """ + Applies or releases an UPDI reset condition + """ + if apply_reset: + self.logger.info("Apply reset") + self.datalink.stcs(constants.UPDI_ASI_RESET_REQ, constants.UPDI_RESET_REQ_VALUE) + else: + self.logger.info("Release reset") + self.datalink.stcs(constants.UPDI_ASI_RESET_REQ, 0x00) + + def wait_flash_ready(self): + """ + Waits for the NVM controller to be ready + """ + # TODO - add timeout + self.logger.info("Wait flash ready") + while True: + status = self.datalink.ld(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_STATUS) + if status & (1 << constants.UPDI_NVM_STATUS_WRITE_ERROR): + self.logger.info("NVM error") + return False + + if not status & ((1 < (constants.UPDI_MAX_REPEAT_SIZE + 1) << 1: + raise Exception("Invalid length") + + # Store the address + self.datalink.st_ptr(address) + + # Fire up the repeat + self.datalink.repeat(len(data) >> 1) + return self.datalink.st_ptr_inc16(data) + + def write_data(self, address, data): + """ + Writes a number of bytes to memory + """ + # Special case of 1 byte + if len(data) == 1: + return self.datalink.st(address, data[0]) + # Special case of 2 byte + elif len(data) == 2: + self.datalink.st(address, data[0]) + return self.datalink.st(address + 1, data[1]) + + # Range check + if len(data) > (constants.UPDI_MAX_REPEAT_SIZE + 1): + raise Exception("Invalid length") + + # Store the address + self.datalink.st_ptr(address) + + # Fire up the repeat + self.datalink.repeat(len(data)) + return self.datalink.st_ptr_inc(data) + + def write_nvm(self, address, data, nvm_command=constants.UPDI_NVMCTRL_CTRLA_WRITE_PAGE, use_word_access=True): + """ + Writes a page of data to NVM. + By default the PAGE_WRITE command is used, which + requires that the page is already erased. + By default word access is used (flash) + """ + + # Check that NVM controller is ready + if not self.wait_flash_ready(): + raise Exception("Timeout waiting for flash ready before page buffer clear ") + + # Clear the page buffer + self.logger.info("Clear page buffer") + self.execute_nvm_command(constants.UPDI_NVMCTRL_CTRLA_PAGE_BUFFER_CLR) + + # Waif for NVM controller to be ready + if not self.wait_flash_ready(): + raise Exception("Timeout waiting for flash ready after page buffer clear") + + # Load the page buffer by writing directly to location + if use_word_access: + self.write_data_words(address, data) + else: + self.write_data(address, data) + + # Write the page to NVM, maybe erase first + self.logger.info("Committing page") + self.execute_nvm_command(nvm_command) + + # Waif for NVM controller to be ready again + if not self.wait_flash_ready(): + raise Exception("Timeout waiting for flash ready after page write ") + + def read_data(self, address, size): + """ + Reads a number of bytes of data from UPDI + """ + self.logger.info("Reading {0:d} bytes from 0x{1:04X}".format(size, address)) + # Range check + if size > constants.UPDI_MAX_REPEAT_SIZE + 1: + raise Exception("Cant read that many bytes in one go") + + # Store the address + self.datalink.st_ptr(address) + + # Fire up the repeat + if size > 1: + self.datalink.repeat(size) + + # Do the read(s) + return self.datalink.ld_ptr_inc(size) + + def read_data_words(self, address, words): + """ + Reads a number of words of data from UPDI + """ + self.logger.info("Reading {0:d} words from 0x{1:04X}".format(words, address)) + # Range check + if words > (constants.UPDI_MAX_REPEAT_SIZE >> 1) + 1: + raise Exception("Cant read that many words in one go") + + # Store the address + self.datalink.st_ptr(address) + + # Fire up the repeat + if words > 1: + self.datalink.repeat(words) + + # Do the read + return self.datalink.ld_ptr_inc16(words) + diff --git a/updi/constants.py b/updi/constants.py new file mode 100644 index 0000000..0d7d94e --- /dev/null +++ b/updi/constants.py @@ -0,0 +1,98 @@ + +# UPDI commands and control definitions +UPDI_BREAK = 0x00 + +UPDI_LDS = 0x00 +UPDI_STS = 0x40 +UPDI_LD = 0x20 +UPDI_ST = 0x60 +UPDI_LDCS = 0x80 +UPDI_STCS = 0xC0 +UPDI_REPEAT = 0xA0 +UPDI_KEY = 0xE0 + +UPDI_PTR = 0x00 +UPDI_PTR_INC = 0x04 +UPDI_PTR_ADDRESS = 0x08 + +UPDI_ADDRESS_8 = 0x00 +UPDI_ADDRESS_16 = 0x04 + +UPDI_DATA_8 = 0x00 +UPDI_DATA_16 = 0x01 + +UPDI_KEY_SIB = 0x04 +UPDI_KEY_KEY = 0x00 + +UPDI_KEY_64 = 0x00 +UPDI_KEY_128 = 0x01 + +UPDI_SIB_8BYTES = UPDI_KEY_64 +UPDI_SIB_16BYTES = UPDI_KEY_128 + +UPDI_REPEAT_BYTE = 0x00 +UPDI_REPEAT_WORD = 0x01 + +UPDI_PHY_SYNC = 0x55 +UPDI_PHY_ACK = 0x40 + +UPDI_MAX_REPEAT_SIZE = 0xFF + +# CS and ASI Register Address map +UPDI_CS_STATUSA = 0x00 +UPDI_CS_STATUSB = 0x01 +UPDI_CS_CTRLA = 0x02 +UPDI_CS_CTRLB = 0x03 +UPDI_ASI_KEY_STATUS = 0x07 +UPDI_ASI_RESET_REQ = 0x08 +UPDI_ASI_CTRLA = 0x09 +UPDI_ASI_SYS_CTRLA = 0x0A +UPDI_ASI_SYS_STATUS = 0x0B +UPDI_ASI_CRC_STATUS = 0x0C + +UPDI_CTRLA_IBDLY_BIT = 7 +UPDI_CTRLB_CCDETDIS_BIT = 3 +UPDI_CTRLB_UPDIDIS_BIT = 2 + +UPDI_KEY_NVM = "NVMProg " +UPDI_KEY_CHIPERASE = "NVMErase" + +UPDI_ASI_STATUSA_REVID = 4 +UPDI_ASI_STATUSB_PESIG = 0 + +UPDI_ASI_KEY_STATUS_CHIPERASE = 3 +UPDI_ASI_KEY_STATUS_NVMPROG = 4 +UPDI_ASI_KEY_STATUS_UROWWRITE = 5 + +UPDI_ASI_SYS_STATUS_RSTSYS = 5 +UPDI_ASI_SYS_STATUS_INSLEEP = 4 +UPDI_ASI_SYS_STATUS_NVMPROG = 3 +UPDI_ASI_SYS_STATUS_UROWPROG = 2 +UPDI_ASI_SYS_STATUS_LOCKSTATUS = 0 + +UPDI_RESET_REQ_VALUE = 0x59 + +# FLASH CONTROLLER +UPDI_NVMCTRL_CTRLA = 0x00 +UPDI_NVMCTRL_CTRLB = 0x01 +UPDI_NVMCTRL_STATUS = 0x02 +UPDI_NVMCTRL_INTCTRL = 0x03 +UPDI_NVMCTRL_INTFLAGS = 0x04 +UPDI_NVMCTRL_DATAL = 0x06 +UPDI_NVMCTRL_DATAH = 0x07 +UPDI_NVMCTRL_ADDRL = 0x08 +UPDI_NVMCTRL_ADDRH = 0x09 + +# CTRLA +UPDI_NVMCTRL_CTRLA_NOP = 0x00 +UPDI_NVMCTRL_CTRLA_WRITE_PAGE = 0x01 +UPDI_NVMCTRL_CTRLA_ERASE_PAGE = 0x02 +UPDI_NVMCTRL_CTRLA_ERASE_WRITE_PAGE = 0x03 +UPDI_NVMCTRL_CTRLA_PAGE_BUFFER_CLR = 0x04 +UPDI_NVMCTRL_CTRLA_CHIP_ERASE = 0x05 +UPDI_NVMCTRL_CTRLA_ERASE_EEPROM = 0x06 +UPDI_NVMCTRL_CTRLA_WRITE_FUSE = 0x07 + +UPDI_NVM_STATUS_WRITE_ERROR = 2 +UPDI_NVM_STATUS_EEPROM_BUSY = 1 +UPDI_NVM_STATUS_FLASH_BUSY = 0 diff --git a/updi/link.py b/updi/link.py new file mode 100644 index 0000000..1bb587c --- /dev/null +++ b/updi/link.py @@ -0,0 +1,205 @@ + +import logging + +from updi.physical import UpdiPhysical +import updi.constants as constants + +class UpdiDatalink(object): + """ + UPDI data link class handles the UPDI data protocol within the device + """ + + def __init__(self, comport, baud): + self.logger = logging.getLogger("link") + + # Create a UPDI physical connection + self.updi_phy = UpdiPhysical(comport, baud) + + # Initialise + self.init() + + # Check + if not self.check(): + # Send double break if all is not well, and re-check + self.updi_phy.send_double_break() + self.init() + if not self.check(): + raise Exception("UPDI initialisation failed") + + def init(self): + """ + Set the inter-byte delay bit and disable collision detection + """ + self.stcs(constants.UPDI_CS_CTRLB, 1 << constants.UPDI_CTRLB_CCDETDIS_BIT) + self.stcs(constants.UPDI_CS_CTRLA, 1 << constants.UPDI_CTRLA_IBDLY_BIT) + + def check(self): + """ + Check UPDI by loading CS STATUSA + """ + if self.ldcs(constants.UPDI_CS_STATUSA) != 0: + self.logger.info("UPDI init OK") + return True + self.logger.info("UPDI not OK - reinitialisation required") + return False + + def ldcs(self, address): + """ + Load data from Control/Status space + """ + self.logger.info("LDCS from 0x{0:02X}".format(address)) + self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LDCS | (address & 0x0F)]) + response = self.updi_phy.receive(1) + if len(response) != 1: + # Todo - flag error + return 0x00 + return response[0] + + def stcs(self, address, value): + """ + Store a value to Control/Status space + """ + self.logger.info("STCS to 0x{0:04X}".format(address)) + self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_STCS | (address & 0x0F), value]) + + def ld(self, address): + """ + Load a single byte direct from a 16-bit address + """ + self.logger.info("LD from 0x{0:04X}".format(address)) + self.updi_phy.send( + [constants.UPDI_PHY_SYNC, constants.UPDI_LDS | constants.UPDI_ADDRESS_16 | constants.UPDI_DATA_8, address & 0xFF, (address >> 8) & 0xFF]) + return self.updi_phy.receive(1)[0] + + def ld16(self, address): + """ + Load a 16-bit word directly from a 16-bit address + """ + self.logger.info("LD from 0x{0:04X}".format(address)) + self.updi_phy.send( + [constants.UPDI_PHY_SYNC, constants.UPDI_LDS | constants.constants.UPDI_ADDRESS_16 | constants.UPDI_DATA_16, address & 0xFF, (address >> 8) & 0xFF]) + return self.updi_phy.receive(2) + + def st(self, address, value): + """ + Store a single byte value directly to a 16-bit address + """ + self.logger.info("ST to 0x{0:04X}".format(address)) + self.updi_phy.send( + [constants.UPDI_PHY_SYNC, constants.UPDI_STS | constants.UPDI_ADDRESS_16 | constants.UPDI_DATA_8, address & 0xFF, (address >> 8) & 0xFF]) + response = self.updi_phy.receive(1) + if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: + raise Exception("Error with st") + + self.updi_phy.send([value & 0xFF]) + response = self.updi_phy.receive(1) + if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: + raise Exception("Error with st") + + def st16(self, address, value): + """ + Store a 16-bit word value directly to a 16-bit address + """ + self.logger.info("ST to 0x{0:04X}".format(address)) + self.updi_phy.send( + [constants.UPDI_PHY_SYNC, constants.UPDI_STS | constants.UPDI_ADDRESS_16 | constants.UPDI_DATA_16, address & 0xFF, (address >> 8) & 0xFF]) + response = self.updi_phy.receive(1) + if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: + raise Exception("Error with st") + + self.updi_phy.send([value & 0xFF, (value >> 8) & 0xFF]) + response = self.updi_phy.receive(1) + if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: + raise Exception("Error with st") + + def ld_ptr_inc(self, size): + """ + Loads a number of bytes from the pointer location with pointer post-increment + """ + self.logger.info("LD8 from ptr++") + self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LD | constants.UPDI_PTR_INC | constants.UPDI_DATA_8]) + return self.updi_phy.receive(size) + + def ld_ptr_inc16(self, words): + """ + Load a 16-bit word value from the pointer location with pointer post-increment + """ + self.logger.info("LD16 from ptr++") + self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LD | constants.UPDI_PTR_INC | constants.UPDI_DATA_16]) + return self.updi_phy.receive(words << 1) + + def st_ptr(self, address): + """ + Set the pointer location + """ + self.logger.info("ST to ptr") + self.updi_phy.send( + [constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_ADDRESS | constants.UPDI_DATA_16, address & 0xFF, (address >> 8) & 0xFF]) + response = self.updi_phy.receive(1) + if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: + raise Exception("Error with st_ptr") + + def st_ptr_inc(self, data): + """ + Store data to the pointer location with pointer post-increment + """ + self.logger.info("ST8 to *ptr++") + self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC | constants.UPDI_DATA_8, data[0]]) + response = self.updi_phy.receive(1) + + if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: + raise Exception("ACK error with st_ptr_inc") + + n = 1 + while n < len(data): + self.updi_phy.send([data[n]]) + response = self.updi_phy.receive(1) + + if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: + raise Exception("Error with st_ptr_inc") + n += 1 + + def st_ptr_inc16(self, data): + """ + Store a 16-bit word value to the pointer location with pointer post-increment + """ + self.logger.info("ST16 to *ptr++") + self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC | constants.UPDI_DATA_16, data[0], data[1]]) + response = self.updi_phy.receive(1) + + if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: + raise Exception("ACK error with st_ptr_inc16") + + n = 2 + while n < len(data): + self.updi_phy.send([data[n], data[n + 1]]) + response = self.updi_phy.receive(1) + + if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: + raise Exception("Error with st_ptr_inc16") + n += 2 + + def repeat(self, repeats): + """ + Store a value to the repeat counter + """ + self.logger.info("Repeat {0:d}".format(repeats)) + repeats -= 1 + self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_REPEAT | constants.UPDI_REPEAT_WORD, repeats & 0xFF, (repeats >> 8) & 0xFF]) + + def read_sib(self): + """ + Read the SIB + """ + return self.updi_phy.sib() + + def key(self, size, key): + """ + Write a key + """ + self.logger.info("Writing key") + if len(key) != 8 << size: + raise Exception("Invalid KEY length!") + self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_KEY | constants.UPDI_KEY_KEY | size]) + self.updi_phy.send(list(reversed(list(key)))) + diff --git a/updi/nvm.py b/updi/nvm.py new file mode 100644 index 0000000..c5e6250 --- /dev/null +++ b/updi/nvm.py @@ -0,0 +1,143 @@ + +import logging + +from updi.application import UpdiApplication + +class UpdiNvmProgrammer(object): + """ + NVM programming utility for UPDI + """ + + def __init__(self, comport, baud, device): + + self.application = UpdiApplication(comport, baud, device) + self.device = device + self.progmode = False + self.logger = logging.getLogger("nvm") + + def get_device_info(self): + """ + Reads device info + """ + self.logger.info("Reading device info") + return self.application.device_info() + + def enter_progmode(self): + """ + Enter programming mode + """ + self.logger.info("Entering NVM programming mode") + if self.application.enter_progmode(): + self.progmode = True + + def leave_progmode(self): + """ + Leave programming mode + """ + self.logger.info("Leaving NVM programming mode") + self.application.leave_progmode() + self.progmode = False + + def unlock_device(self): + """ + Unlock and erase a device + """ + if self.progmode: + self.logger.info("Device already unlocked") + return + + # Unlock + self.application.unlock() + # Unlock after using the NVM key results in prog mode. + self.progmode = True + + def chip_erase(self): + """ + Erase (unlocked) device + """ + if not self.progmode: + raise Exception("Enter progmode first!") + return self.application.chip_erase() + + def read_flash(self, address, size): + """ + Reads from flash + """ + # Must be in prog mode here + if not self.progmode: + raise Exception("Enter progmode first!") + # Find the number of pages + pages = size / self.device.flash_pagesize + if size % self.device.flash_pagesize: + raise Exception("Only full page aligned flash supported.") + data = [] + # Read out page-wise for convenience + for i in range(pages): + self.logger.info("Reading page at 0x{0:04X}".format(address)) + data += (self.application.read_data_words(address, self.device.flash_pagesize >> 1)) + address += self.device.flash_pagesize + return data + + def write_flash(self, address, data): + """ + Writes to flash + """ + # Must be in prog mode + if not self.progmode: + raise Exception("Enter progmode first!") + # Pad to full page + data = self.pad_data(data, self.device.flash_pagesize) + # Divide up into pages + pages = self.page_data(data, self.device.flash_pagesize) + # Program each page + for page in pages: + self.logger.info("Writing page at 0x{0:04X}".format(address)) + self.application.write_nvm(address, page) + address += len(page) + + def pad_data(self, data, blocksize, character=0xFF): + """ + Pads data so that there are full pages + """ + self.logger.info("Padding to blocksize {0:d} with 0x{1:X}".format(blocksize, character)) + if len(data) % blocksize > 0: + for i in range(len(data) % blocksize, blocksize): + data.append(0) + return data + + def page_data(self, data, size): + """ + Divide data into pages + """ + self.logger.info("Paging into {} byte blocks".format(size)) + total_length = len(data) + result = [] + while len(result) < total_length / size: + result.append(data[:size].tolist()) + data = data[size:] + return result + + def load_ihex(self, filename): + """ + Load from intel hex format + """ + self.logger.info("Loading from hexfile '{}'".format(filename)) + from intelhex import IntelHex + + ih = IntelHex() + ih.loadhex(filename) + data = ih.tobinarray() + start_address = ih.minaddr() + self.logger.info("Loaded {0:d} bytes from ihex starting at address 0x{1:04X}".format(len(data), start_address)) + + # Size check + if len(data) > self.device.flash_size: + raise Exception("ihex too large for flash") + + # Offset to actual flash start + if start_address < self.device.flash_start: + self.logger.info("Adjusting flash offset to address 0x{:04X}".format(self.device.flash_start)) + start_address += self.device.flash_start + + return data, start_address + diff --git a/updi/physical.py b/updi/physical.py new file mode 100644 index 0000000..763e1be --- /dev/null +++ b/updi/physical.py @@ -0,0 +1,116 @@ + +import logging +import time +import serial + +import updi.constants as constants + +class UpdiPhysical(object): + """ + PDI physical driver using a given COM port at a given baud + """ + + def __init__(self, port, baud=100000): + """ + Initialise the COM port + """ + + self.logger = logging.getLogger("phy") + + # Inter-byte delay + self.ibdly = 0.0001 + self.port = port + self.baud = baud + self.ser = None + self.initialise_serial(self.port, self.baud) + + def initialise_serial(self, port, baud): + """ + Standard COM port initialisation + """ + + self.logger.info("Opening {} at {} baud".format(port, baud)) + self.ser = serial.Serial(port, baud, parity=serial.PARITY_EVEN, timeout=1) + + def send_double_break(self): + """ + Sends a double break to reset the UPDI port + BREAK is actually just a slower zero frame + A double break is guaranteed to push the UPDI state + machine into a known state, albeit rather brutally + """ + + self.logger.info("Sending double break") + + # Re-init at a lower baud + self.ser.close() + temporary_serial = serial.Serial(self.port, 1000) + + # Send a break + temporary_serial.write([constants.UPDI_BREAK]) + + # Wait + time.sleep(0.05) + + # Send another + temporary_serial.write([constants.UPDI_BREAK]) + time.sleep(0.001) + + # Re-init at the real baud + temporary_serial.close() + self.initialise_serial(self.port, self.baud) + + def send(self, command): + """ + Sends a char array to UPDI with inter-byte delay + Note that the byte will echo back + """ + self.logger.info("send: {}".format(command)) + + for character in command: + + # Send + self.ser.write([character]) + + # Echo + self.ser.read() + + # Inter-byte delay + time.sleep(self.ibdly) + + def receive(self, size): + """ + Receives a frame of a known number of chars from UPDI + """ + response = [] + timeout = 1 + + # For each byte + while size and timeout: + + # Read + character = self.ser.read() + + # Anything in? + if character != "": + response.append(ord(character)) + size -= 1 + else: + timeout -= 1 + + self.logger.info("receive: {}".format(response)) + return response + + def sib(self): + """ + System information block is just a string coming back from a SIB command + """ + self.send([ + constants.UPDI_PHY_SYNC, + constants.UPDI_KEY | constants.UPDI_KEY_SIB | constants.UPDI_SIB_16BYTES]) + return self.ser.readline() + + def __del__(self): + if self.ser: + self.logger.info("Closing {}".format(self.port)) + self.ser.close()