diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0438f41 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,85 @@ +# Changelog + +## [3.13] - May 2022 + +## Added +- DSG-3936 Fixed AVR ISP implementation and added commands (beta) +- DSG-4172 github-10 Disable ACK response signature on serialUPDI block write (speed-up) +- DSG-3951 github-8 Added --erase argument to erase device before write with single execution +- DSG-3972 CLI help additions +- DSG-3997 Added debugwire_disable() to Avr8Protocol + +## Fixed +- DSG-3945, DSG-3938 Unable to write fuse byte 0 on Curiosity Nano ATtiny kits +- DSG-4488 github-19 Return bytearray (not list) from serialUPDI read +- DSG-4594 SAMD21 performance improvement (SAM-IoT provisioning) +- DSG-4540 Fixed SAMD21 non-word-oriented read failure +- DSG-3941 Improved feedback on verification failure +- DSG-3944 Removed timeout warning for serialUPDI with a locked device +- DSG-4419 Corrected AVR high voltage UPDI device data +- DSG-3993 github-9 Corrected AVR signature sizes to make additional data available + +## [3.10] - October 2021 + +### Added +- DSG-2702 Add serialupdi backend for AVR EA +- DSG-3633 github-3 Add missing AVR-DB devices +- DSG-3635 github-4 Add missing ATtiny devices +- DSG-3662 Add ascii-art for serialUPDI +- DSG-3804 Add py39 metadata to package +- DSG-3943 github-7 Add CLI documentation + +### Fixed +- DSG-2859 github-1 serialUPDI write user_row on locked device fails +- DSG-3538 github-2 Unable to write fuses on ATmega4809 using serialUPDI +- DSG-3817 SAM D21 user row programming fails +- DSG-3952 Incorrect size of FUSES on Dx, Ex devices + +## [3.9] - April 2021 + +### Added +- DSG-2920 Raise exception if device ID does not match +- DSG-2918 SerialUPDI: error recovery if non-ascii characters are read in SIB +- DSG-2861 Valid memory types are listed if an invalid one is specified + +### Fixed +- DSG-3238 PIC16 eeprom displays incorrect address +- DSG-3239 PIC16 eeprom verification does not work +- DSG-2925 UPDI device revision not correctly parsed/displayed +- DSG-2860 SerialUPDI: chip erase does not work on locked device +- DSG-2857 SerialUPDI: crash when writing lockbits +- DSG-2855 Verify action fails if hex file contains eeprom content +- DSG-2854 User row excluded when reading to hex file +- DSG-2850 UPDI device model fix (sram) + +### Changed +- DSG-2862 Improved exception handling +- DSG-3203 Improved exception handling +- DSG-3178 Cosmetic changes for publication + +## [3.7.4] - December 2020 + +### Added +- DSG-1492 Added verify function +- DSG-2039 Added all UPDI devices +- DSG-2279 Added error codes +- DSG-1550 Flash-only erase + +### Fixed +- DSG-2470 No feedback when multiple kits are connected +- DSG-2014 Error when reading using -m and -o but no -b +- DSG-2738 Padding to page size when writing user row on locked device + +### Changed +- DSG-2234 Logging using logging module +- DSG-2034 prevent read using -b with no -m specified +- DSG-2009 prevent writing from hexfile with memory type specified +- DSG-2012 prevent writing from hexfile with offset specified +- DSG-2458 documentation changes +- DSG-2041 documentation changes +- DSG-2042 documentation changes +- DSG-2043 documentation changes +- DSG-2011 documentation changes + +## [3.1.3] - June 2020 +- First public release to PyPi diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..4107caf --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2021 Microchip Technology Inc. and its subsidiaries. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..fc4886a --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +include pymcuprog/logging.yaml +include images/microchip.png +# These files are read in setup.py so they must be included in the source zip for pip to be able to install the zip +# Note however that since the files are not a part of the package (not inside the pymcuprog sub folder) +# they won't be included in the python wheel. +include pypi.md +include requirements.txt \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..7369774 --- /dev/null +++ b/README.md @@ -0,0 +1,135 @@ +[![MCHP](images/microchip.png)](https://www.microchip.com) + +# pymcuprog - Python MCU programmer +pymcuprog is a Python utility for programming various Microchip MCU devices using Microchip CMSIS-DAP based debuggers + +Install using pip from [pypi](https://pypi.org/project/pymcuprog): +```bash +pip install pymcuprog +``` + +Browse source code on [github](https://github.com/microchip-pic-avr-tools/pymcuprog) + +Read API documentation on [github](https://microchip-pic-avr-tools.github.io/pymcuprog) + +Read the changelog on [github](https://github.com/microchip-pic-avr-tools/pymcuprog/blob/main/CHANGELOG.md) + +## Usage +pymcuprog can be used as a command-line interface or a library + +### CLI help +For more help with using pymcuprog CLI see [help](./help.md) + +### CLI examples +When installed using pip, pymcuprog CLI is located in the Python scripts folder. + +Test connectivity by reading the device ID using Curiosity Nano: +```bash +pymcuprog ping +``` + +Erase memories then write contents of an Intel(R) hex file to flash using Curiosity Nano (pymcuprog does NOT automatically erase before writing): +```bash +pymcuprog erase +pymcuprog write -f app.hex +``` + +Erase memories and write an Intel hex file (using the --erase switch): +```bash +pymcuprog write -f app.hex --erase +``` + +Erase memories, write an Intel hex file and verify the content: +```bash +pymcuprog write -f app.hex --erase --verify +``` + + +### Serial port UPDI (pyupdi) +The AVR UPDI interface implements a UART protocol, which means that it can be used by simply connecting TX and RX pins of a serial port together with the UPDI pin; with a series resistor (eg: 1k) between TX and UPDI to handle contention. (This configuration is also known as "pyupdi".) Be sure to connect a common ground, and use a TTL serial adapter running at the same voltage as the AVR device. + +
+                        Vcc                     Vcc
+                        +-+                     +-+
+                         |                       |
+ +---------------------+ |                       | +--------------------+
+ | Serial port         +-+                       +-+  AVR device        |
+ |                     |      +----------+         |                    |
+ |                  TX +------+   1k     +---------+ UPDI               |
+ |                     |      +----------+    |    |                    |
+ |                     |                      |    |                    |
+ |                  RX +----------------------+    |                    |
+ |                     |                           |                    |
+ |                     +--+                     +--+                    |
+ +---------------------+  |                     |  +--------------------+
+                         +-+                   +-+
+                         GND                   GND
+
+ +pymcuprog includes this implementation as an alternative to USB/EDBG-based tools. To connect via a serial port, use the "uart" tool type with the UART switch in addition. + +Example: checks connectivity by reading the device identity +```bash +pymcuprog ping -d avr128da48 -t uart -u com35 +``` + +For more examples see [pymcuprog on pypi.org](https://pypi.org/project/pymcuprog/) + +### Library usage example +pymcuprog can be used as a library using its backend API. For example: +```python +""" +Example usage of pymcuprog as a library to read the device ID +""" +# pymcuprog uses the Python logging module +import logging +logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.WARNING) + +# Configure the session +from pymcuprog.backend import SessionConfig +sessionconfig = SessionConfig("atmega4808") + +# Instantiate USB transport (only 1 tool connected) +from pymcuprog.toolconnection import ToolUsbHidConnection +transport = ToolUsbHidConnection() + +# Instantiate backend +from pymcuprog.backend import Backend +backend = Backend() + +# Connect to tool using transport +backend.connect_to_tool(transport) + +# Start the session +backend.start_session(sessionconfig) + +# Read the target device_id +device_id = backend.read_device_id() +print ("Device ID is {0:06X}".format(int.from_bytes(device_id, byteorder="little"))) +``` + +## Supported devices and tools +pymcuprog is primarily intended for use with PKOB nano (nEDBG) debuggers which are found on Curiosity Nano kits and other development boards. This means that it is continuously tested with a selection of AVR devices with UPDI interface as well as a selection of PIC devices. However since the protocol is compatible between all EDBG-based debuggers (pyedbglib) it is possible to use pymcuprog with a wide range of debuggers and devices, although not all device families/interfaces have been implemented. + +### Debuggers / Tools +pymcuprog supports: +* PKOB nano (nEDBG) - on-board debugger on Curiosity Nano +* MPLAB PICkit 4 In-Circuit Debugger (when in 'AVR mode') +* MPLAB Snap In-Circuit Debugger (when in 'AVR mode') +* Atmel-ICE +* Power Debugger +* EDBG - on-board debugger on Xplained Pro/Ultra +* mEDBG - on-board debugger on Xplained Mini/Nano +* JTAGICE3 (firmware version 3.0 or newer) + +Although not all functionality is provided on all debuggers/boards. See device support section below. + +### Devices +pymcuprog supports: +* All UPDI devices, whether mounted on kits or standalone +* PIC devices mounted on Curiosity Nano kits, or similar board with PKOB nano (nEDBG) debugger + +Other devices (eg ATmega328P, ATsamd21e18a) may be partially supported for experimental purposes + +## Notes for Linux® systems +This package uses pyedbglib and other libraries for USB transport and some udev rules are required. For details see the pyedbglib package: https://pypi.org/project/pyedbglib diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..d0c3cbf --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/make.bat b/doc/make.bat new file mode 100644 index 0000000..9534b01 --- /dev/null +++ b/doc/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 0000000..5ef9b89 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1 @@ +# No requirements - all are mocked. diff --git a/doc/source/_static/.keep b/doc/source/_static/.keep new file mode 100644 index 0000000..e69de29 diff --git a/doc/source/_templates/module.rst_t b/doc/source/_templates/module.rst_t new file mode 100644 index 0000000..d9a50e6 --- /dev/null +++ b/doc/source/_templates/module.rst_t @@ -0,0 +1,9 @@ +{%- if show_headings %} +{{- basename | e | heading }} + +{% endif -%} +.. automodule:: {{ qualname }} +{%- for option in automodule_options %} + :{{ option }}: +{%- endfor %} + diff --git a/doc/source/_templates/package.rst_t b/doc/source/_templates/package.rst_t new file mode 100644 index 0000000..18dc35e --- /dev/null +++ b/doc/source/_templates/package.rst_t @@ -0,0 +1,47 @@ +{%- macro automodule(modname, options) -%} +.. automodule:: {{ modname }} +{%- for option in options %} + :{{ option }}: +{%- endfor %} +{%- endmacro %} + +{%- macro toctree(docnames) -%} +.. toctree:: + :maxdepth: {{ maxdepth }} +{% for docname in docnames %} + {{ docname }} +{%- endfor %} +{%- endmacro %} + +{%- if is_namespace %} +{{- [pkgname, "namespace"] | join(" ") | e | heading }} +{% else %} +{{- pkgname | e | heading }} +{% endif %} + +{%- if modulefirst and not is_namespace %} +{{ automodule(pkgname, automodule_options) }} +{% endif %} + +{%- if subpackages %} + +{{ toctree(subpackages) }} +{% endif %} + +{%- if submodules %} +{% if separatemodules %} +{{ toctree(submodules) }} +{%- else %} +{%- for submodule in submodules %} +{% if show_headings %} +{{- submodule | e | heading(2) }} +{% endif %} +{{ automodule(submodule, automodule_options) }} +{% endfor %} +{%- endif %} +{% endif %} + +{%- if not modulefirst and not is_namespace %} + +{{ automodule(pkgname, automodule_options) }} +{% endif %} diff --git a/doc/source/_templates/toc.rst_t b/doc/source/_templates/toc.rst_t new file mode 100644 index 0000000..f0877ee --- /dev/null +++ b/doc/source/_templates/toc.rst_t @@ -0,0 +1,8 @@ +{{ header | heading }} + +.. toctree:: + :maxdepth: {{ maxdepth }} +{% for docname in docnames %} + {{ docname }} +{%- endfor %} + diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 0000000..770757e --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,54 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +sys.path.insert(0, os.path.abspath('../..')) + +# -- Project information ----------------------------------------------------- + +project = 'pymcuprog' +copyright = '2021, Microchip Technology Inc' +author = 'Microchip Technology Inc' + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = ['sphinx.ext.autodoc'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# Mock out these objects (note: modules, not packages) +autodoc_mock_imports = ['pyedbglib', 'intelhex', 'appdirs', 'yaml', 'serial'] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'Microchip' +html_theme_path = ['../../themes'] +pygments_style = 'default' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..de2ce7b --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,21 @@ +pymcuprog documentation +======================= + +.. automodule:: pymcuprog + :members: + :undoc-members: + :show-inheritance: + :noindex: + +.. toctree:: + :maxdepth: 4 + :caption: Contents: + + pymcuprog + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/doc/source/pymcuprog.deviceinfo.rst b/doc/source/pymcuprog.deviceinfo.rst new file mode 100644 index 0000000..4aae401 --- /dev/null +++ b/doc/source/pymcuprog.deviceinfo.rst @@ -0,0 +1,45 @@ +pymcuprog.deviceinfo package +============================ + +Submodules +---------- + +pymcuprog.deviceinfo.deviceinfo module +-------------------------------------- + +.. automodule:: pymcuprog.deviceinfo.deviceinfo + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.deviceinfo.deviceinfokeys module +------------------------------------------ + +.. automodule:: pymcuprog.deviceinfo.deviceinfokeys + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.deviceinfo.eraseflags module +-------------------------------------- + +.. automodule:: pymcuprog.deviceinfo.eraseflags + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.deviceinfo.memorynames module +--------------------------------------- + +.. automodule:: pymcuprog.deviceinfo.memorynames + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: pymcuprog.deviceinfo + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/pymcuprog.rst b/doc/source/pymcuprog.rst new file mode 100644 index 0000000..0a8d075 --- /dev/null +++ b/doc/source/pymcuprog.rst @@ -0,0 +1,191 @@ +pymcuprog package +================= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + pymcuprog.deviceinfo + pymcuprog.serialupdi + +Submodules +---------- + +AVR8 target +----------- + +.. automodule:: pymcuprog.avr8target + :members: + :undoc-members: + :show-inheritance: + +AVR32 target (alpha) +-------------------- + +.. automodule:: pymcuprog.avr32target + :members: + :undoc-members: + :show-inheritance: + +SAM target (alpha) +------------------ + +.. automodule:: pymcuprog.samtarget + :members: + :undoc-members: + :show-inheritance: + +Backend API (library functions) +------------------------------- + +.. automodule:: pymcuprog.backend + :members: + :undoc-members: + :show-inheritance: + +Programmer API +-------------- + +.. automodule:: pymcuprog.programmer + :members: + :undoc-members: + :show-inheritance: + +AVR debugger API (alpha) +------------------------ + +.. automodule:: pymcuprog.avrdebugger + :members: + :undoc-members: + :show-inheritance: + +NVM Access (implementations) +---------------------------- + +.. automodule:: pymcuprog.nvm + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: UPDI +------------------------ + +.. automodule:: pymcuprog.nvmupdi + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: serialUPDI +------------------------------ + +.. automodule:: pymcuprog.nvmserialupdi + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: PIC +----------------------- + +.. automodule:: pymcuprog.nvmpic + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: SAM M0+ (alpha) +----------------------------------- + +.. automodule:: pymcuprog.nvmmzeroplus + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: debugWIRE (alpha) +------------------------------------- + +.. automodule:: pymcuprog.nvmdebugwire + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: SPI (alpha) +------------------------------- + +.. automodule:: pymcuprog.nvmspi + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: TPI (alpha) +------------------------------- + +.. automodule:: pymcuprog.nvmtpi + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: megaAVR (JTAG) - (alpha) +-------------------------------------------- + +.. automodule:: pymcuprog.nvmmegaavrjtag + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: XMEGA (alpha) +--------------------------------- + +.. automodule:: pymcuprog.nvmxmega + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: AVR32 (alpha) +--------------------------------- + +.. automodule:: pymcuprog.nvmavr32 + :members: + :undoc-members: + :show-inheritance: + +Utilities +--------- + +.. automodule:: pymcuprog.utils + :members: + :undoc-members: + :show-inheritance: + +Hex file utilities +------------------ + +.. automodule:: pymcuprog.hexfileutils + :members: + :undoc-members: + :show-inheritance: + +Tool connections +---------------- + +.. automodule:: pymcuprog.toolconnection + :members: + :undoc-members: + :show-inheritance: + +pymcuprog errors +---------------- + +.. automodule:: pymcuprog.pymcuprog_errors + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: pymcuprog + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/source/pymcuprog.serialupdi.rst b/doc/source/pymcuprog.serialupdi.rst new file mode 100644 index 0000000..0c26d2b --- /dev/null +++ b/doc/source/pymcuprog.serialupdi.rst @@ -0,0 +1,69 @@ +pymcuprog.serialupdi package +============================ + +Submodules +---------- + +pymcuprog.serialupdi.application module +--------------------------------------- + +.. automodule:: pymcuprog.serialupdi.application + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.serialupdi.constants module +------------------------------------- + +.. automodule:: pymcuprog.serialupdi.constants + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.serialupdi.link module +-------------------------------- + +.. automodule:: pymcuprog.serialupdi.link + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.serialupdi.nvm module +------------------------------- + +.. automodule:: pymcuprog.serialupdi.nvm + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.serialupdi.physical module +------------------------------------ + +.. automodule:: pymcuprog.serialupdi.physical + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.serialupdi.readwrite module +------------------------------------- + +.. automodule:: pymcuprog.serialupdi.readwrite + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.serialupdi.timeout module +----------------------------------- + +.. automodule:: pymcuprog.serialupdi.timeout + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: pymcuprog.serialupdi + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/.buildinfo b/docs/.buildinfo new file mode 100644 index 0000000..354e3f8 --- /dev/null +++ b/docs/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 1c400cb5073abd566b4ff083d5e9075f +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt new file mode 100644 index 0000000..de2ce7b --- /dev/null +++ b/docs/_sources/index.rst.txt @@ -0,0 +1,21 @@ +pymcuprog documentation +======================= + +.. automodule:: pymcuprog + :members: + :undoc-members: + :show-inheritance: + :noindex: + +.. toctree:: + :maxdepth: 4 + :caption: Contents: + + pymcuprog + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/_sources/pymcuprog.deviceinfo.rst.txt b/docs/_sources/pymcuprog.deviceinfo.rst.txt new file mode 100644 index 0000000..4aae401 --- /dev/null +++ b/docs/_sources/pymcuprog.deviceinfo.rst.txt @@ -0,0 +1,45 @@ +pymcuprog.deviceinfo package +============================ + +Submodules +---------- + +pymcuprog.deviceinfo.deviceinfo module +-------------------------------------- + +.. automodule:: pymcuprog.deviceinfo.deviceinfo + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.deviceinfo.deviceinfokeys module +------------------------------------------ + +.. automodule:: pymcuprog.deviceinfo.deviceinfokeys + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.deviceinfo.eraseflags module +-------------------------------------- + +.. automodule:: pymcuprog.deviceinfo.eraseflags + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.deviceinfo.memorynames module +--------------------------------------- + +.. automodule:: pymcuprog.deviceinfo.memorynames + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: pymcuprog.deviceinfo + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/pymcuprog.rst.txt b/docs/_sources/pymcuprog.rst.txt new file mode 100644 index 0000000..0a8d075 --- /dev/null +++ b/docs/_sources/pymcuprog.rst.txt @@ -0,0 +1,191 @@ +pymcuprog package +================= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + pymcuprog.deviceinfo + pymcuprog.serialupdi + +Submodules +---------- + +AVR8 target +----------- + +.. automodule:: pymcuprog.avr8target + :members: + :undoc-members: + :show-inheritance: + +AVR32 target (alpha) +-------------------- + +.. automodule:: pymcuprog.avr32target + :members: + :undoc-members: + :show-inheritance: + +SAM target (alpha) +------------------ + +.. automodule:: pymcuprog.samtarget + :members: + :undoc-members: + :show-inheritance: + +Backend API (library functions) +------------------------------- + +.. automodule:: pymcuprog.backend + :members: + :undoc-members: + :show-inheritance: + +Programmer API +-------------- + +.. automodule:: pymcuprog.programmer + :members: + :undoc-members: + :show-inheritance: + +AVR debugger API (alpha) +------------------------ + +.. automodule:: pymcuprog.avrdebugger + :members: + :undoc-members: + :show-inheritance: + +NVM Access (implementations) +---------------------------- + +.. automodule:: pymcuprog.nvm + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: UPDI +------------------------ + +.. automodule:: pymcuprog.nvmupdi + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: serialUPDI +------------------------------ + +.. automodule:: pymcuprog.nvmserialupdi + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: PIC +----------------------- + +.. automodule:: pymcuprog.nvmpic + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: SAM M0+ (alpha) +----------------------------------- + +.. automodule:: pymcuprog.nvmmzeroplus + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: debugWIRE (alpha) +------------------------------------- + +.. automodule:: pymcuprog.nvmdebugwire + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: SPI (alpha) +------------------------------- + +.. automodule:: pymcuprog.nvmspi + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: TPI (alpha) +------------------------------- + +.. automodule:: pymcuprog.nvmtpi + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: megaAVR (JTAG) - (alpha) +-------------------------------------------- + +.. automodule:: pymcuprog.nvmmegaavrjtag + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: XMEGA (alpha) +--------------------------------- + +.. automodule:: pymcuprog.nvmxmega + :members: + :undoc-members: + :show-inheritance: + +NVM implementation: AVR32 (alpha) +--------------------------------- + +.. automodule:: pymcuprog.nvmavr32 + :members: + :undoc-members: + :show-inheritance: + +Utilities +--------- + +.. automodule:: pymcuprog.utils + :members: + :undoc-members: + :show-inheritance: + +Hex file utilities +------------------ + +.. automodule:: pymcuprog.hexfileutils + :members: + :undoc-members: + :show-inheritance: + +Tool connections +---------------- + +.. automodule:: pymcuprog.toolconnection + :members: + :undoc-members: + :show-inheritance: + +pymcuprog errors +---------------- + +.. automodule:: pymcuprog.pymcuprog_errors + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: pymcuprog + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/pymcuprog.serialupdi.rst.txt b/docs/_sources/pymcuprog.serialupdi.rst.txt new file mode 100644 index 0000000..0c26d2b --- /dev/null +++ b/docs/_sources/pymcuprog.serialupdi.rst.txt @@ -0,0 +1,69 @@ +pymcuprog.serialupdi package +============================ + +Submodules +---------- + +pymcuprog.serialupdi.application module +--------------------------------------- + +.. automodule:: pymcuprog.serialupdi.application + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.serialupdi.constants module +------------------------------------- + +.. automodule:: pymcuprog.serialupdi.constants + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.serialupdi.link module +-------------------------------- + +.. automodule:: pymcuprog.serialupdi.link + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.serialupdi.nvm module +------------------------------- + +.. automodule:: pymcuprog.serialupdi.nvm + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.serialupdi.physical module +------------------------------------ + +.. automodule:: pymcuprog.serialupdi.physical + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.serialupdi.readwrite module +------------------------------------- + +.. automodule:: pymcuprog.serialupdi.readwrite + :members: + :undoc-members: + :show-inheritance: + +pymcuprog.serialupdi.timeout module +----------------------------------- + +.. automodule:: pymcuprog.serialupdi.timeout + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: pymcuprog.serialupdi + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_static/basic.css b/docs/_static/basic.css new file mode 100644 index 0000000..bf18350 --- /dev/null +++ b/docs/_static/basic.css @@ -0,0 +1,906 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 450px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +a.brackets:before, +span.brackets > a:before{ + content: "["; +} + +a.brackets:after, +span.brackets > a:after { + content: "]"; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +dl.footnote > dt, +dl.citation > dt { + float: left; + margin-right: 0.5em; +} + +dl.footnote > dd, +dl.citation > dd { + margin-bottom: 0em; +} + +dl.footnote > dd:after, +dl.citation > dd:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dt:after { + content: ":"; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/_static/doctools.js b/docs/_static/doctools.js new file mode 100644 index 0000000..e1bfd70 --- /dev/null +++ b/docs/_static/doctools.js @@ -0,0 +1,358 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for all documentation. + * + * :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", + "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", + "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {}; +} + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initIndexTable(); + this.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated === 'undefined') + return string; + return (typeof translated === 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated === 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + if (!body.length) { + body = $('body'); + } + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlighted'); + }); + }, 10); + $('') + .appendTo($('#searchbox')); + } + }, + + /** + * init the domain index toggle buttons + */ + initIndexTable : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + $('tr.cg-' + idnum).toggle(); + if (src.substr(-9) === 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('#searchbox .highlight-link').fadeOut(300); + $('span.highlighted').removeClass('highlighted'); + var url = new URL(window.location); + url.searchParams.delete('highlight'); + window.history.replaceState({}, '', url); + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar : function() { + $('input[name=q]').first().focus(); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this === '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + }, + + initOnKeyListeners: function() { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) + return; + + $(document).keydown(function(event) { + var activeElementType = document.activeElement.tagName; + // don't navigate when in search box, textarea, dropdown or button + if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' + && activeElementType !== 'BUTTON') { + if (event.altKey || event.ctrlKey || event.metaKey) + return; + + if (!event.shiftKey) { + switch (event.key) { + case 'ArrowLeft': + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) + break; + var prevHref = $('link[rel="prev"]').prop('href'); + if (prevHref) { + window.location.href = prevHref; + return false; + } + break; + case 'ArrowRight': + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) + break; + var nextHref = $('link[rel="next"]').prop('href'); + if (nextHref) { + window.location.href = nextHref; + return false; + } + break; + case 'Escape': + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) + break; + Documentation.hideSearchWords(); + return false; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case '/': + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) + break; + Documentation.focusSearchBar(); + return false; + } + } + }); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/docs/_static/documentation_options.js b/docs/_static/documentation_options.js new file mode 100644 index 0000000..724e382 --- /dev/null +++ b/docs/_static/documentation_options.js @@ -0,0 +1,14 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '', + LANGUAGE: 'None', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/docs/_static/file.png b/docs/_static/file.png new file mode 100644 index 0000000..a858a41 Binary files /dev/null and b/docs/_static/file.png differ diff --git a/docs/_static/jquery-3.5.1.js b/docs/_static/jquery-3.5.1.js new file mode 100644 index 0000000..5093733 --- /dev/null +++ b/docs/_static/jquery-3.5.1.js @@ -0,0 +1,10872 @@ +/*! + * jQuery JavaScript Library v3.5.1 + * https://jquery.com/ + * + * Includes Sizzle.js + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://jquery.org/license + * + * Date: 2020-05-04T22:49Z + */ +( function( global, factory ) { + + "use strict"; + + if ( typeof module === "object" && typeof module.exports === "object" ) { + + // For CommonJS and CommonJS-like environments where a proper `window` + // is present, execute the factory and get jQuery. + // For environments that do not have a `window` with a `document` + // (such as Node.js), expose a factory as module.exports. + // This accentuates the need for the creation of a real `window`. + // e.g. var jQuery = require("jquery")(window); + // See ticket #14549 for more info. + module.exports = global.document ? + factory( global, true ) : + function( w ) { + if ( !w.document ) { + throw new Error( "jQuery requires a window with a document" ); + } + return factory( w ); + }; + } else { + factory( global ); + } + +// Pass this if window is not defined yet +} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { + +// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 +// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode +// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common +// enough that all such attempts are guarded in a try block. +"use strict"; + +var arr = []; + +var getProto = Object.getPrototypeOf; + +var slice = arr.slice; + +var flat = arr.flat ? function( array ) { + return arr.flat.call( array ); +} : function( array ) { + return arr.concat.apply( [], array ); +}; + + +var push = arr.push; + +var indexOf = arr.indexOf; + +var class2type = {}; + +var toString = class2type.toString; + +var hasOwn = class2type.hasOwnProperty; + +var fnToString = hasOwn.toString; + +var ObjectFunctionString = fnToString.call( Object ); + +var support = {}; + +var isFunction = function isFunction( obj ) { + + // Support: Chrome <=57, Firefox <=52 + // In some browsers, typeof returns "function" for HTML elements + // (i.e., `typeof document.createElement( "object" ) === "function"`). + // We don't want to classify *any* DOM node as a function. + return typeof obj === "function" && typeof obj.nodeType !== "number"; + }; + + +var isWindow = function isWindow( obj ) { + return obj != null && obj === obj.window; + }; + + +var document = window.document; + + + + var preservedScriptAttributes = { + type: true, + src: true, + nonce: true, + noModule: true + }; + + function DOMEval( code, node, doc ) { + doc = doc || document; + + var i, val, + script = doc.createElement( "script" ); + + script.text = code; + if ( node ) { + for ( i in preservedScriptAttributes ) { + + // Support: Firefox 64+, Edge 18+ + // Some browsers don't support the "nonce" property on scripts. + // On the other hand, just using `getAttribute` is not enough as + // the `nonce` attribute is reset to an empty string whenever it + // becomes browsing-context connected. + // See https://github.com/whatwg/html/issues/2369 + // See https://html.spec.whatwg.org/#nonce-attributes + // The `node.getAttribute` check was added for the sake of + // `jQuery.globalEval` so that it can fake a nonce-containing node + // via an object. + val = node[ i ] || node.getAttribute && node.getAttribute( i ); + if ( val ) { + script.setAttribute( i, val ); + } + } + } + doc.head.appendChild( script ).parentNode.removeChild( script ); + } + + +function toType( obj ) { + if ( obj == null ) { + return obj + ""; + } + + // Support: Android <=2.3 only (functionish RegExp) + return typeof obj === "object" || typeof obj === "function" ? + class2type[ toString.call( obj ) ] || "object" : + typeof obj; +} +/* global Symbol */ +// Defining this global in .eslintrc.json would create a danger of using the global +// unguarded in another place, it seems safer to define global only for this module + + + +var + version = "3.5.1", + + // Define a local copy of jQuery + jQuery = function( selector, context ) { + + // The jQuery object is actually just the init constructor 'enhanced' + // Need init if jQuery is called (just allow error to be thrown if not included) + return new jQuery.fn.init( selector, context ); + }; + +jQuery.fn = jQuery.prototype = { + + // The current version of jQuery being used + jquery: version, + + constructor: jQuery, + + // The default length of a jQuery object is 0 + length: 0, + + toArray: function() { + return slice.call( this ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + + // Return all the elements in a clean array + if ( num == null ) { + return slice.call( this ); + } + + // Return just the one element from the set + return num < 0 ? this[ num + this.length ] : this[ num ]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems ) { + + // Build a new jQuery matched element set + var ret = jQuery.merge( this.constructor(), elems ); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + each: function( callback ) { + return jQuery.each( this, callback ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map( this, function( elem, i ) { + return callback.call( elem, i, elem ); + } ) ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ) ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + even: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return ( i + 1 ) % 2; + } ) ); + }, + + odd: function() { + return this.pushStack( jQuery.grep( this, function( _elem, i ) { + return i % 2; + } ) ); + }, + + eq: function( i ) { + var len = this.length, + j = +i + ( i < 0 ? len : 0 ); + return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); + }, + + end: function() { + return this.prevObject || this.constructor(); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: arr.sort, + splice: arr.splice +}; + +jQuery.extend = jQuery.fn.extend = function() { + var options, name, src, copy, copyIsArray, clone, + target = arguments[ 0 ] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + + // Skip the boolean and the target + target = arguments[ i ] || {}; + i++; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !isFunction( target ) ) { + target = {}; + } + + // Extend jQuery itself if only one argument is passed + if ( i === length ) { + target = this; + i--; + } + + for ( ; i < length; i++ ) { + + // Only deal with non-null/undefined values + if ( ( options = arguments[ i ] ) != null ) { + + // Extend the base object + for ( name in options ) { + copy = options[ name ]; + + // Prevent Object.prototype pollution + // Prevent never-ending loop + if ( name === "__proto__" || target === copy ) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( deep && copy && ( jQuery.isPlainObject( copy ) || + ( copyIsArray = Array.isArray( copy ) ) ) ) { + src = target[ name ]; + + // Ensure proper type for the source value + if ( copyIsArray && !Array.isArray( src ) ) { + clone = []; + } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { + clone = {}; + } else { + clone = src; + } + copyIsArray = false; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend( { + + // Unique for each copy of jQuery on the page + expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), + + // Assume jQuery is ready without the ready module + isReady: true, + + error: function( msg ) { + throw new Error( msg ); + }, + + noop: function() {}, + + isPlainObject: function( obj ) { + var proto, Ctor; + + // Detect obvious negatives + // Use toString instead of jQuery.type to catch host objects + if ( !obj || toString.call( obj ) !== "[object Object]" ) { + return false; + } + + proto = getProto( obj ); + + // Objects with no prototype (e.g., `Object.create( null )`) are plain + if ( !proto ) { + return true; + } + + // Objects with prototype are plain iff they were constructed by a global Object function + Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; + return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; + }, + + isEmptyObject: function( obj ) { + var name; + + for ( name in obj ) { + return false; + } + return true; + }, + + // Evaluates a script in a provided context; falls back to the global one + // if not specified. + globalEval: function( code, options, doc ) { + DOMEval( code, { nonce: options && options.nonce }, doc ); + }, + + each: function( obj, callback ) { + var length, i = 0; + + if ( isArrayLike( obj ) ) { + length = obj.length; + for ( ; i < length; i++ ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } else { + for ( i in obj ) { + if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { + break; + } + } + } + + return obj; + }, + + // results is for internal usage only + makeArray: function( arr, results ) { + var ret = results || []; + + if ( arr != null ) { + if ( isArrayLike( Object( arr ) ) ) { + jQuery.merge( ret, + typeof arr === "string" ? + [ arr ] : arr + ); + } else { + push.call( ret, arr ); + } + } + + return ret; + }, + + inArray: function( elem, arr, i ) { + return arr == null ? -1 : indexOf.call( arr, elem, i ); + }, + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + merge: function( first, second ) { + var len = +second.length, + j = 0, + i = first.length; + + for ( ; j < len; j++ ) { + first[ i++ ] = second[ j ]; + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var length, value, + i = 0, + ret = []; + + // Go through the array, translating each of the items to their new values + if ( isArrayLike( elems ) ) { + length = elems.length; + for ( ; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + + // Go through every key on the object, + } else { + for ( i in elems ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret.push( value ); + } + } + } + + // Flatten any nested arrays + return flat( ret ); + }, + + // A global GUID counter for objects + guid: 1, + + // jQuery.support is not used in Core but other projects attach their + // properties to it so it needs to exist. + support: support +} ); + +if ( typeof Symbol === "function" ) { + jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; +} + +// Populate the class2type map +jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), +function( _i, name ) { + class2type[ "[object " + name + "]" ] = name.toLowerCase(); +} ); + +function isArrayLike( obj ) { + + // Support: real iOS 8.2 only (not reproducible in simulator) + // `in` check used to prevent JIT error (gh-2145) + // hasOwn isn't used here due to false negatives + // regarding Nodelist length in IE + var length = !!obj && "length" in obj && obj.length, + type = toType( obj ); + + if ( isFunction( obj ) || isWindow( obj ) ) { + return false; + } + + return type === "array" || length === 0 || + typeof length === "number" && length > 0 && ( length - 1 ) in obj; +} +var Sizzle = +/*! + * Sizzle CSS Selector Engine v2.3.5 + * https://sizzlejs.com/ + * + * Copyright JS Foundation and other contributors + * Released under the MIT license + * https://js.foundation/ + * + * Date: 2020-03-14 + */ +( function( window ) { +var i, + support, + Expr, + getText, + isXML, + tokenize, + compile, + select, + outermostContext, + sortInput, + hasDuplicate, + + // Local document vars + setDocument, + document, + docElem, + documentIsHTML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + + // Instance-specific data + expando = "sizzle" + 1 * new Date(), + preferredDoc = window.document, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + nonnativeSelectorCache = createCache(), + sortOrder = function( a, b ) { + if ( a === b ) { + hasDuplicate = true; + } + return 0; + }, + + // Instance methods + hasOwn = ( {} ).hasOwnProperty, + arr = [], + pop = arr.pop, + pushNative = arr.push, + push = arr.push, + slice = arr.slice, + + // Use a stripped-down indexOf as it's faster than native + // https://jsperf.com/thor-indexof-vs-for/5 + indexOf = function( list, elem ) { + var i = 0, + len = list.length; + for ( ; i < len; i++ ) { + if ( list[ i ] === elem ) { + return i; + } + } + return -1; + }, + + booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|" + + "ismap|loop|multiple|open|readonly|required|scoped", + + // Regular expressions + + // http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = "[\\x20\\t\\r\\n\\f]", + + // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram + identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + + "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", + + // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors + attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + + + // Operator (capture 2) + "*([*^$|!~]?=)" + whitespace + + + // "Attribute values must be CSS identifiers [capture 5] + // or strings [capture 3 or capture 4]" + "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + + whitespace + "*\\]", + + pseudos = ":(" + identifier + ")(?:\\((" + + + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: + // 1. quoted (capture 3; capture 4 or capture 5) + "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + + + // 2. simple (capture 6) + "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + + + // 3. anything else (capture 2) + ".*" + + ")\\)|)", + + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rwhitespace = new RegExp( whitespace + "+", "g" ), + rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + + whitespace + "+$", "g" ), + + rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), + rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + + "*" ), + rdescend = new RegExp( whitespace + "|>" ), + + rpseudo = new RegExp( pseudos ), + ridentifier = new RegExp( "^" + identifier + "$" ), + + matchExpr = { + "ID": new RegExp( "^#(" + identifier + ")" ), + "CLASS": new RegExp( "^\\.(" + identifier + ")" ), + "TAG": new RegExp( "^(" + identifier + "|[*])" ), + "ATTR": new RegExp( "^" + attributes ), + "PSEUDO": new RegExp( "^" + pseudos ), + "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), + "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), + + // For use in libraries implementing .is() + // We use this for POS matching in `select` + "needsContext": new RegExp( "^" + whitespace + + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) + }, + + rhtml = /HTML$/i, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + + rnative = /^[^{]+\{\s*\[native \w/, + + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + + rsibling = /[+~]/, + + // CSS escapes + // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + "?|\\\\([^\\r\\n\\f])", "g" ), + funescape = function( escape, nonHex ) { + var high = "0x" + escape.slice( 1 ) - 0x10000; + + return nonHex ? + + // Strip the backslash prefix from a non-hex escape sequence + nonHex : + + // Replace a hexadecimal escape sequence with the encoded Unicode code point + // Support: IE <=11+ + // For values outside the Basic Multilingual Plane (BMP), manually construct a + // surrogate pair + high < 0 ? + String.fromCharCode( high + 0x10000 ) : + String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); + }, + + // CSS string/identifier serialization + // https://drafts.csswg.org/cssom/#common-serializing-idioms + rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, + fcssescape = function( ch, asCodePoint ) { + if ( asCodePoint ) { + + // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER + if ( ch === "\0" ) { + return "\uFFFD"; + } + + // Control characters and (dependent upon position) numbers get escaped as code points + return ch.slice( 0, -1 ) + "\\" + + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; + } + + // Other potentially-special ASCII characters get backslash-escaped + return "\\" + ch; + }, + + // Used for iframes + // See setDocument() + // Removing the function wrapper causes a "Permission Denied" + // error in IE + unloadHandler = function() { + setDocument(); + }, + + inDisabledFieldset = addCombinator( + function( elem ) { + return elem.disabled === true && elem.nodeName.toLowerCase() === "fieldset"; + }, + { dir: "parentNode", next: "legend" } + ); + +// Optimize for push.apply( _, NodeList ) +try { + push.apply( + ( arr = slice.call( preferredDoc.childNodes ) ), + preferredDoc.childNodes + ); + + // Support: Android<4.0 + // Detect silently failing push.apply + // eslint-disable-next-line no-unused-expressions + arr[ preferredDoc.childNodes.length ].nodeType; +} catch ( e ) { + push = { apply: arr.length ? + + // Leverage slice if possible + function( target, els ) { + pushNative.apply( target, slice.call( els ) ); + } : + + // Support: IE<9 + // Otherwise append directly + function( target, els ) { + var j = target.length, + i = 0; + + // Can't trust NodeList.length + while ( ( target[ j++ ] = els[ i++ ] ) ) {} + target.length = j - 1; + } + }; +} + +function Sizzle( selector, context, results, seed ) { + var m, i, elem, nid, match, groups, newSelector, + newContext = context && context.ownerDocument, + + // nodeType defaults to 9, since context defaults to document + nodeType = context ? context.nodeType : 9; + + results = results || []; + + // Return early from calls with invalid selector or context + if ( typeof selector !== "string" || !selector || + nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { + + return results; + } + + // Try to shortcut find operations (as opposed to filters) in HTML documents + if ( !seed ) { + setDocument( context ); + context = context || document; + + if ( documentIsHTML ) { + + // If the selector is sufficiently simple, try using a "get*By*" DOM method + // (excepting DocumentFragment context, where the methods don't exist) + if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { + + // ID selector + if ( ( m = match[ 1 ] ) ) { + + // Document context + if ( nodeType === 9 ) { + if ( ( elem = context.getElementById( m ) ) ) { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( elem.id === m ) { + results.push( elem ); + return results; + } + } else { + return results; + } + + // Element context + } else { + + // Support: IE, Opera, Webkit + // TODO: identify versions + // getElementById can match elements by name instead of ID + if ( newContext && ( elem = newContext.getElementById( m ) ) && + contains( context, elem ) && + elem.id === m ) { + + results.push( elem ); + return results; + } + } + + // Type selector + } else if ( match[ 2 ] ) { + push.apply( results, context.getElementsByTagName( selector ) ); + return results; + + // Class selector + } else if ( ( m = match[ 3 ] ) && support.getElementsByClassName && + context.getElementsByClassName ) { + + push.apply( results, context.getElementsByClassName( m ) ); + return results; + } + } + + // Take advantage of querySelectorAll + if ( support.qsa && + !nonnativeSelectorCache[ selector + " " ] && + ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) && + + // Support: IE 8 only + // Exclude object elements + ( nodeType !== 1 || context.nodeName.toLowerCase() !== "object" ) ) { + + newSelector = selector; + newContext = context; + + // qSA considers elements outside a scoping root when evaluating child or + // descendant combinators, which is not what we want. + // In such cases, we work around the behavior by prefixing every selector in the + // list with an ID selector referencing the scope context. + // The technique has to be used as well when a leading combinator is used + // as such selectors are not recognized by querySelectorAll. + // Thanks to Andrew Dupont for this technique. + if ( nodeType === 1 && + ( rdescend.test( selector ) || rcombinators.test( selector ) ) ) { + + // Expand context for sibling selectors + newContext = rsibling.test( selector ) && testContext( context.parentNode ) || + context; + + // We can use :scope instead of the ID hack if the browser + // supports it & if we're not changing the context. + if ( newContext !== context || !support.scope ) { + + // Capture the context ID, setting it first if necessary + if ( ( nid = context.getAttribute( "id" ) ) ) { + nid = nid.replace( rcssescape, fcssescape ); + } else { + context.setAttribute( "id", ( nid = expando ) ); + } + } + + // Prefix every selector in the list + groups = tokenize( selector ); + i = groups.length; + while ( i-- ) { + groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + + toSelector( groups[ i ] ); + } + newSelector = groups.join( "," ); + } + + try { + push.apply( results, + newContext.querySelectorAll( newSelector ) + ); + return results; + } catch ( qsaError ) { + nonnativeSelectorCache( selector, true ); + } finally { + if ( nid === expando ) { + context.removeAttribute( "id" ); + } + } + } + } + } + + // All others + return select( selector.replace( rtrim, "$1" ), context, results, seed ); +} + +/** + * Create key-value caches of limited size + * @returns {function(string, object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ +function createCache() { + var keys = []; + + function cache( key, value ) { + + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if ( keys.push( key + " " ) > Expr.cacheLength ) { + + // Only keep the most recent entries + delete cache[ keys.shift() ]; + } + return ( cache[ key + " " ] = value ); + } + return cache; +} + +/** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ +function markFunction( fn ) { + fn[ expando ] = true; + return fn; +} + +/** + * Support testing using an element + * @param {Function} fn Passed the created element and returns a boolean result + */ +function assert( fn ) { + var el = document.createElement( "fieldset" ); + + try { + return !!fn( el ); + } catch ( e ) { + return false; + } finally { + + // Remove from its parent by default + if ( el.parentNode ) { + el.parentNode.removeChild( el ); + } + + // release memory in IE + el = null; + } +} + +/** + * Adds the same handler for all of the specified attrs + * @param {String} attrs Pipe-separated list of attributes + * @param {Function} handler The method that will be applied + */ +function addHandle( attrs, handler ) { + var arr = attrs.split( "|" ), + i = arr.length; + + while ( i-- ) { + Expr.attrHandle[ arr[ i ] ] = handler; + } +} + +/** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b + */ +function siblingCheck( a, b ) { + var cur = b && a, + diff = cur && a.nodeType === 1 && b.nodeType === 1 && + a.sourceIndex - b.sourceIndex; + + // Use IE sourceIndex if available on both nodes + if ( diff ) { + return diff; + } + + // Check if b follows a + if ( cur ) { + while ( ( cur = cur.nextSibling ) ) { + if ( cur === b ) { + return -1; + } + } + } + + return a ? 1 : -1; +} + +/** + * Returns a function to use in pseudos for input types + * @param {String} type + */ +function createInputPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for buttons + * @param {String} type + */ +function createButtonPseudo( type ) { + return function( elem ) { + var name = elem.nodeName.toLowerCase(); + return ( name === "input" || name === "button" ) && elem.type === type; + }; +} + +/** + * Returns a function to use in pseudos for :enabled/:disabled + * @param {Boolean} disabled true for :disabled; false for :enabled + */ +function createDisabledPseudo( disabled ) { + + // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable + return function( elem ) { + + // Only certain elements can match :enabled or :disabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled + // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled + if ( "form" in elem ) { + + // Check for inherited disabledness on relevant non-disabled elements: + // * listed form-associated elements in a disabled fieldset + // https://html.spec.whatwg.org/multipage/forms.html#category-listed + // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled + // * option elements in a disabled optgroup + // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled + // All such elements have a "form" property. + if ( elem.parentNode && elem.disabled === false ) { + + // Option elements defer to a parent optgroup if present + if ( "label" in elem ) { + if ( "label" in elem.parentNode ) { + return elem.parentNode.disabled === disabled; + } else { + return elem.disabled === disabled; + } + } + + // Support: IE 6 - 11 + // Use the isDisabled shortcut property to check for disabled fieldset ancestors + return elem.isDisabled === disabled || + + // Where there is no isDisabled, check manually + /* jshint -W018 */ + elem.isDisabled !== !disabled && + inDisabledFieldset( elem ) === disabled; + } + + return elem.disabled === disabled; + + // Try to winnow out elements that can't be disabled before trusting the disabled property. + // Some victims get caught in our net (label, legend, menu, track), but it shouldn't + // even exist on them, let alone have a boolean value. + } else if ( "label" in elem ) { + return elem.disabled === disabled; + } + + // Remaining elements are neither :enabled nor :disabled + return false; + }; +} + +/** + * Returns a function to use in pseudos for positionals + * @param {Function} fn + */ +function createPositionalPseudo( fn ) { + return markFunction( function( argument ) { + argument = +argument; + return markFunction( function( seed, matches ) { + var j, + matchIndexes = fn( [], seed.length, argument ), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while ( i-- ) { + if ( seed[ ( j = matchIndexes[ i ] ) ] ) { + seed[ j ] = !( matches[ j ] = seed[ j ] ); + } + } + } ); + } ); +} + +/** + * Checks a node for validity as a Sizzle context + * @param {Element|Object=} context + * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value + */ +function testContext( context ) { + return context && typeof context.getElementsByTagName !== "undefined" && context; +} + +// Expose support vars for convenience +support = Sizzle.support = {}; + +/** + * Detects XML nodes + * @param {Element|Object} elem An element or a document + * @returns {Boolean} True iff elem is a non-HTML XML node + */ +isXML = Sizzle.isXML = function( elem ) { + var namespace = elem.namespaceURI, + docElem = ( elem.ownerDocument || elem ).documentElement; + + // Support: IE <=8 + // Assume HTML when documentElement doesn't yet exist, such as inside loading iframes + // https://bugs.jquery.com/ticket/4833 + return !rhtml.test( namespace || docElem && docElem.nodeName || "HTML" ); +}; + +/** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ +setDocument = Sizzle.setDocument = function( node ) { + var hasCompare, subWindow, + doc = node ? node.ownerDocument || node : preferredDoc; + + // Return early if doc is invalid or already selected + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { + return document; + } + + // Update global variables + document = doc; + docElem = document.documentElement; + documentIsHTML = !isXML( document ); + + // Support: IE 9 - 11+, Edge 12 - 18+ + // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( preferredDoc != document && + ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { + + // Support: IE 11, Edge + if ( subWindow.addEventListener ) { + subWindow.addEventListener( "unload", unloadHandler, false ); + + // Support: IE 9 - 10 only + } else if ( subWindow.attachEvent ) { + subWindow.attachEvent( "onunload", unloadHandler ); + } + } + + // Support: IE 8 - 11+, Edge 12 - 18+, Chrome <=16 - 25 only, Firefox <=3.6 - 31 only, + // Safari 4 - 5 only, Opera <=11.6 - 12.x only + // IE/Edge & older browsers don't support the :scope pseudo-class. + // Support: Safari 6.0 only + // Safari 6.0 supports :scope but it's an alias of :root there. + support.scope = assert( function( el ) { + docElem.appendChild( el ).appendChild( document.createElement( "div" ) ); + return typeof el.querySelectorAll !== "undefined" && + !el.querySelectorAll( ":scope fieldset div" ).length; + } ); + + /* Attributes + ---------------------------------------------------------------------- */ + + // Support: IE<8 + // Verify that getAttribute really returns attributes and not properties + // (excepting IE8 booleans) + support.attributes = assert( function( el ) { + el.className = "i"; + return !el.getAttribute( "className" ); + } ); + + /* getElement(s)By* + ---------------------------------------------------------------------- */ + + // Check if getElementsByTagName("*") returns only elements + support.getElementsByTagName = assert( function( el ) { + el.appendChild( document.createComment( "" ) ); + return !el.getElementsByTagName( "*" ).length; + } ); + + // Support: IE<9 + support.getElementsByClassName = rnative.test( document.getElementsByClassName ); + + // Support: IE<10 + // Check if getElementById returns elements by name + // The broken getElementById methods don't pick up programmatically-set names, + // so use a roundabout getElementsByName test + support.getById = assert( function( el ) { + docElem.appendChild( el ).id = expando; + return !document.getElementsByName || !document.getElementsByName( expando ).length; + } ); + + // ID filter and find + if ( support.getById ) { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + return elem.getAttribute( "id" ) === attrId; + }; + }; + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var elem = context.getElementById( id ); + return elem ? [ elem ] : []; + } + }; + } else { + Expr.filter[ "ID" ] = function( id ) { + var attrId = id.replace( runescape, funescape ); + return function( elem ) { + var node = typeof elem.getAttributeNode !== "undefined" && + elem.getAttributeNode( "id" ); + return node && node.value === attrId; + }; + }; + + // Support: IE 6 - 7 only + // getElementById is not reliable as a find shortcut + Expr.find[ "ID" ] = function( id, context ) { + if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { + var node, i, elems, + elem = context.getElementById( id ); + + if ( elem ) { + + // Verify the id attribute + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + + // Fall back on getElementsByName + elems = context.getElementsByName( id ); + i = 0; + while ( ( elem = elems[ i++ ] ) ) { + node = elem.getAttributeNode( "id" ); + if ( node && node.value === id ) { + return [ elem ]; + } + } + } + + return []; + } + }; + } + + // Tag + Expr.find[ "TAG" ] = support.getElementsByTagName ? + function( tag, context ) { + if ( typeof context.getElementsByTagName !== "undefined" ) { + return context.getElementsByTagName( tag ); + + // DocumentFragment nodes don't have gEBTN + } else if ( support.qsa ) { + return context.querySelectorAll( tag ); + } + } : + + function( tag, context ) { + var elem, + tmp = [], + i = 0, + + // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too + results = context.getElementsByTagName( tag ); + + // Filter out possible comments + if ( tag === "*" ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem.nodeType === 1 ) { + tmp.push( elem ); + } + } + + return tmp; + } + return results; + }; + + // Class + Expr.find[ "CLASS" ] = support.getElementsByClassName && function( className, context ) { + if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { + return context.getElementsByClassName( className ); + } + }; + + /* QSA/matchesSelector + ---------------------------------------------------------------------- */ + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21) + // We allow this because of a bug in IE8/9 that throws an error + // whenever `document.activeElement` is accessed on an iframe + // So, we allow :focus to pass through QSA all the time to avoid the IE error + // See https://bugs.jquery.com/ticket/13378 + rbuggyQSA = []; + + if ( ( support.qsa = rnative.test( document.querySelectorAll ) ) ) { + + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert( function( el ) { + + var input; + + // Select is set to empty string on purpose + // This is to test IE's treatment of not explicitly + // setting a boolean content attribute, + // since its presence should be enough + // https://bugs.jquery.com/ticket/12359 + docElem.appendChild( el ).innerHTML = "" + + ""; + + // Support: IE8, Opera 11-12.16 + // Nothing should be selected when empty strings follow ^= or $= or *= + // The test attribute must be unknown in Opera but "safe" for WinRT + // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section + if ( el.querySelectorAll( "[msallowcapture^='']" ).length ) { + rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); + } + + // Support: IE8 + // Boolean attributes and "value" are not treated correctly + if ( !el.querySelectorAll( "[selected]" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); + } + + // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ + if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { + rbuggyQSA.push( "~=" ); + } + + // Support: IE 11+, Edge 15 - 18+ + // IE 11/Edge don't find elements on a `[name='']` query in some cases. + // Adding a temporary attribute to the document before the selection works + // around the issue. + // Interestingly, IE 10 & older don't seem to have the issue. + input = document.createElement( "input" ); + input.setAttribute( "name", "" ); + el.appendChild( input ); + if ( !el.querySelectorAll( "[name='']" ).length ) { + rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + + whitespace + "*(?:''|\"\")" ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if ( !el.querySelectorAll( ":checked" ).length ) { + rbuggyQSA.push( ":checked" ); + } + + // Support: Safari 8+, iOS 8+ + // https://bugs.webkit.org/show_bug.cgi?id=136851 + // In-page `selector#id sibling-combinator selector` fails + if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { + rbuggyQSA.push( ".#.+[+~]" ); + } + + // Support: Firefox <=3.6 - 5 only + // Old Firefox doesn't throw on a badly-escaped identifier. + el.querySelectorAll( "\\\f" ); + rbuggyQSA.push( "[\\r\\n\\f]" ); + } ); + + assert( function( el ) { + el.innerHTML = "" + + ""; + + // Support: Windows 8 Native Apps + // The type and name attributes are restricted during .innerHTML assignment + var input = document.createElement( "input" ); + input.setAttribute( "type", "hidden" ); + el.appendChild( input ).setAttribute( "name", "D" ); + + // Support: IE8 + // Enforce case-sensitivity of name attribute + if ( el.querySelectorAll( "[name=d]" ).length ) { + rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if ( el.querySelectorAll( ":enabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: IE9-11+ + // IE's :disabled selector does not pick up the children of disabled fieldsets + docElem.appendChild( el ).disabled = true; + if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { + rbuggyQSA.push( ":enabled", ":disabled" ); + } + + // Support: Opera 10 - 11 only + // Opera 10-11 does not throw on post-comma invalid pseudos + el.querySelectorAll( "*,:x" ); + rbuggyQSA.push( ",.*:" ); + } ); + } + + if ( ( support.matchesSelector = rnative.test( ( matches = docElem.matches || + docElem.webkitMatchesSelector || + docElem.mozMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector ) ) ) ) { + + assert( function( el ) { + + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call( el, "*" ); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call( el, "[s!='']:x" ); + rbuggyMatches.push( "!=", pseudos ); + } ); + } + + rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); + rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join( "|" ) ); + + /* Contains + ---------------------------------------------------------------------- */ + hasCompare = rnative.test( docElem.compareDocumentPosition ); + + // Element contains another + // Purposefully self-exclusive + // As in, an element does not contain itself + contains = hasCompare || rnative.test( docElem.contains ) ? + function( a, b ) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return a === bup || !!( bup && bup.nodeType === 1 && ( + adown.contains ? + adown.contains( bup ) : + a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 + ) ); + } : + function( a, b ) { + if ( b ) { + while ( ( b = b.parentNode ) ) { + if ( b === a ) { + return true; + } + } + } + return false; + }; + + /* Sorting + ---------------------------------------------------------------------- */ + + // Document order sorting + sortOrder = hasCompare ? + function( a, b ) { + + // Flag for duplicate removal + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + // Sort on method existence if only one input has compareDocumentPosition + var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; + if ( compare ) { + return compare; + } + + // Calculate position if both inputs belong to the same document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? + a.compareDocumentPosition( b ) : + + // Otherwise we know they are disconnected + 1; + + // Disconnected nodes + if ( compare & 1 || + ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { + + // Choose the first element that is related to our preferred document + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( a == document || a.ownerDocument == preferredDoc && + contains( preferredDoc, a ) ) { + return -1; + } + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( b == document || b.ownerDocument == preferredDoc && + contains( preferredDoc, b ) ) { + return 1; + } + + // Maintain original order + return sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + } + + return compare & 4 ? -1 : 1; + } : + function( a, b ) { + + // Exit early if the nodes are identical + if ( a === b ) { + hasDuplicate = true; + return 0; + } + + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [ a ], + bp = [ b ]; + + // Parentless nodes are either documents or disconnected + if ( !aup || !bup ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + return a == document ? -1 : + b == document ? 1 : + /* eslint-enable eqeqeq */ + aup ? -1 : + bup ? 1 : + sortInput ? + ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : + 0; + + // If the nodes are siblings, we can do a quick check + } else if ( aup === bup ) { + return siblingCheck( a, b ); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ( ( cur = cur.parentNode ) ) { + ap.unshift( cur ); + } + cur = b; + while ( ( cur = cur.parentNode ) ) { + bp.unshift( cur ); + } + + // Walk down the tree looking for a discrepancy + while ( ap[ i ] === bp[ i ] ) { + i++; + } + + return i ? + + // Do a sibling check if the nodes have a common ancestor + siblingCheck( ap[ i ], bp[ i ] ) : + + // Otherwise nodes in our document sort first + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + /* eslint-disable eqeqeq */ + ap[ i ] == preferredDoc ? -1 : + bp[ i ] == preferredDoc ? 1 : + /* eslint-enable eqeqeq */ + 0; + }; + + return document; +}; + +Sizzle.matches = function( expr, elements ) { + return Sizzle( expr, null, null, elements ); +}; + +Sizzle.matchesSelector = function( elem, expr ) { + setDocument( elem ); + + if ( support.matchesSelector && documentIsHTML && + !nonnativeSelectorCache[ expr + " " ] && + ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && + ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { + + try { + var ret = matches.call( elem, expr ); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( ret || support.disconnectedMatch || + + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + elem.document && elem.document.nodeType !== 11 ) { + return ret; + } + } catch ( e ) { + nonnativeSelectorCache( expr, true ); + } + } + + return Sizzle( expr, document, null, [ elem ] ).length > 0; +}; + +Sizzle.contains = function( context, elem ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( context.ownerDocument || context ) != document ) { + setDocument( context ); + } + return contains( context, elem ); +}; + +Sizzle.attr = function( elem, name ) { + + // Set document vars if needed + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( ( elem.ownerDocument || elem ) != document ) { + setDocument( elem ); + } + + var fn = Expr.attrHandle[ name.toLowerCase() ], + + // Don't get fooled by Object.prototype properties (jQuery #13807) + val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? + fn( elem, name, !documentIsHTML ) : + undefined; + + return val !== undefined ? + val : + support.attributes || !documentIsHTML ? + elem.getAttribute( name ) : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; +}; + +Sizzle.escape = function( sel ) { + return ( sel + "" ).replace( rcssescape, fcssescape ); +}; + +Sizzle.error = function( msg ) { + throw new Error( "Syntax error, unrecognized expression: " + msg ); +}; + +/** + * Document sorting and removing duplicates + * @param {ArrayLike} results + */ +Sizzle.uniqueSort = function( results ) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + sortInput = !support.sortStable && results.slice( 0 ); + results.sort( sortOrder ); + + if ( hasDuplicate ) { + while ( ( elem = results[ i++ ] ) ) { + if ( elem === results[ i ] ) { + j = duplicates.push( i ); + } + } + while ( j-- ) { + results.splice( duplicates[ j ], 1 ); + } + } + + // Clear input after sorting to release objects + // See https://github.com/jquery/sizzle/pull/225 + sortInput = null; + + return results; +}; + +/** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ +getText = Sizzle.getText = function( elem ) { + var node, + ret = "", + i = 0, + nodeType = elem.nodeType; + + if ( !nodeType ) { + + // If no nodeType, this is expected to be an array + while ( ( node = elem[ i++ ] ) ) { + + // Do not traverse comment nodes + ret += getText( node ); + } + } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { + + // Use textContent for elements + // innerText usage removed for consistency of new lines (jQuery #11153) + if ( typeof elem.textContent === "string" ) { + return elem.textContent; + } else { + + // Traverse its children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + ret += getText( elem ); + } + } + } else if ( nodeType === 3 || nodeType === 4 ) { + return elem.nodeValue; + } + + // Do not include comment or processing instruction nodes + + return ret; +}; + +Expr = Sizzle.selectors = { + + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + attrHandle: {}, + + find: {}, + + relative: { + ">": { dir: "parentNode", first: true }, + " ": { dir: "parentNode" }, + "+": { dir: "previousSibling", first: true }, + "~": { dir: "previousSibling" } + }, + + preFilter: { + "ATTR": function( match ) { + match[ 1 ] = match[ 1 ].replace( runescape, funescape ); + + // Move the given value to match[3] whether quoted or unquoted + match[ 3 ] = ( match[ 3 ] || match[ 4 ] || + match[ 5 ] || "" ).replace( runescape, funescape ); + + if ( match[ 2 ] === "~=" ) { + match[ 3 ] = " " + match[ 3 ] + " "; + } + + return match.slice( 0, 4 ); + }, + + "CHILD": function( match ) { + + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[ 1 ] = match[ 1 ].toLowerCase(); + + if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { + + // nth-* requires argument + if ( !match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[ 4 ] = +( match[ 4 ] ? + match[ 5 ] + ( match[ 6 ] || 1 ) : + 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) ); + match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); + + // other types prohibit arguments + } else if ( match[ 3 ] ) { + Sizzle.error( match[ 0 ] ); + } + + return match; + }, + + "PSEUDO": function( match ) { + var excess, + unquoted = !match[ 6 ] && match[ 2 ]; + + if ( matchExpr[ "CHILD" ].test( match[ 0 ] ) ) { + return null; + } + + // Accept quoted arguments as-is + if ( match[ 3 ] ) { + match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; + + // Strip excess characters from unquoted arguments + } else if ( unquoted && rpseudo.test( unquoted ) && + + // Get excess from tokenize (recursively) + ( excess = tokenize( unquoted, true ) ) && + + // advance to the next closing parenthesis + ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { + + // excess is a negative index + match[ 0 ] = match[ 0 ].slice( 0, excess ); + match[ 2 ] = unquoted.slice( 0, excess ); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice( 0, 3 ); + } + }, + + filter: { + + "TAG": function( nodeNameSelector ) { + var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); + return nodeNameSelector === "*" ? + function() { + return true; + } : + function( elem ) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + "CLASS": function( className ) { + var pattern = classCache[ className + " " ]; + + return pattern || + ( pattern = new RegExp( "(^|" + whitespace + + ")" + className + "(" + whitespace + "|$)" ) ) && classCache( + className, function( elem ) { + return pattern.test( + typeof elem.className === "string" && elem.className || + typeof elem.getAttribute !== "undefined" && + elem.getAttribute( "class" ) || + "" + ); + } ); + }, + + "ATTR": function( name, operator, check ) { + return function( elem ) { + var result = Sizzle.attr( elem, name ); + + if ( result == null ) { + return operator === "!="; + } + if ( !operator ) { + return true; + } + + result += ""; + + /* eslint-disable max-len */ + + return operator === "=" ? result === check : + operator === "!=" ? result !== check : + operator === "^=" ? check && result.indexOf( check ) === 0 : + operator === "*=" ? check && result.indexOf( check ) > -1 : + operator === "$=" ? check && result.slice( -check.length ) === check : + operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : + operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : + false; + /* eslint-enable max-len */ + + }; + }, + + "CHILD": function( type, what, _argument, first, last ) { + var simple = type.slice( 0, 3 ) !== "nth", + forward = type.slice( -4 ) !== "last", + ofType = what === "of-type"; + + return first === 1 && last === 0 ? + + // Shortcut for :nth-*(n) + function( elem ) { + return !!elem.parentNode; + } : + + function( elem, _context, xml ) { + var cache, uniqueCache, outerCache, node, nodeIndex, start, + dir = simple !== forward ? "nextSibling" : "previousSibling", + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType, + diff = false; + + if ( parent ) { + + // :(first|last|only)-(child|of-type) + if ( simple ) { + while ( dir ) { + node = elem; + while ( ( node = node[ dir ] ) ) { + if ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) { + + return false; + } + } + + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === "only" && !start && "nextSibling"; + } + return true; + } + + start = [ forward ? parent.firstChild : parent.lastChild ]; + + // non-xml :nth-child(...) stores cache data on `parent` + if ( forward && useCache ) { + + // Seek `elem` from a previously-cached index + + // ...in a gzip-friendly way + node = parent; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex && cache[ 2 ]; + node = nodeIndex && parent.childNodes[ nodeIndex ]; + + while ( ( node = ++nodeIndex && node && node[ dir ] || + + // Fallback to seeking `elem` from the start + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + // When found, cache indexes on `parent` and break + if ( node.nodeType === 1 && ++diff && node === elem ) { + uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; + break; + } + } + + } else { + + // Use previously-cached element index if available + if ( useCache ) { + + // ...in a gzip-friendly way + node = elem; + outerCache = node[ expando ] || ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + cache = uniqueCache[ type ] || []; + nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; + diff = nodeIndex; + } + + // xml :nth-child(...) + // or :nth-last-child(...) or :nth(-last)?-of-type(...) + if ( diff === false ) { + + // Use the same loop as above to seek `elem` from the start + while ( ( node = ++nodeIndex && node && node[ dir ] || + ( diff = nodeIndex = 0 ) || start.pop() ) ) { + + if ( ( ofType ? + node.nodeName.toLowerCase() === name : + node.nodeType === 1 ) && + ++diff ) { + + // Cache the index of each encountered element + if ( useCache ) { + outerCache = node[ expando ] || + ( node[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ node.uniqueID ] || + ( outerCache[ node.uniqueID ] = {} ); + + uniqueCache[ type ] = [ dirruns, diff ]; + } + + if ( node === elem ) { + break; + } + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return diff === first || ( diff % first === 0 && diff / first >= 0 ); + } + }; + }, + + "PSEUDO": function( pseudo, argument ) { + + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || + Sizzle.error( "unsupported pseudo: " + pseudo ); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if ( fn[ expando ] ) { + return fn( argument ); + } + + // But maintain support for old signatures + if ( fn.length > 1 ) { + args = [ pseudo, pseudo, "", argument ]; + return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? + markFunction( function( seed, matches ) { + var idx, + matched = fn( seed, argument ), + i = matched.length; + while ( i-- ) { + idx = indexOf( seed, matched[ i ] ); + seed[ idx ] = !( matches[ idx ] = matched[ i ] ); + } + } ) : + function( elem ) { + return fn( elem, 0, args ); + }; + } + + return fn; + } + }, + + pseudos: { + + // Potentially complex pseudos + "not": markFunction( function( selector ) { + + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile( selector.replace( rtrim, "$1" ) ); + + return matcher[ expando ] ? + markFunction( function( seed, matches, _context, xml ) { + var elem, + unmatched = matcher( seed, null, xml, [] ), + i = seed.length; + + // Match elements unmatched by `matcher` + while ( i-- ) { + if ( ( elem = unmatched[ i ] ) ) { + seed[ i ] = !( matches[ i ] = elem ); + } + } + } ) : + function( elem, _context, xml ) { + input[ 0 ] = elem; + matcher( input, null, xml, results ); + + // Don't keep the element (issue #299) + input[ 0 ] = null; + return !results.pop(); + }; + } ), + + "has": markFunction( function( selector ) { + return function( elem ) { + return Sizzle( selector, elem ).length > 0; + }; + } ), + + "contains": markFunction( function( text ) { + text = text.replace( runescape, funescape ); + return function( elem ) { + return ( elem.textContent || getText( elem ) ).indexOf( text ) > -1; + }; + } ), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + "lang": markFunction( function( lang ) { + + // lang value must be a valid identifier + if ( !ridentifier.test( lang || "" ) ) { + Sizzle.error( "unsupported lang: " + lang ); + } + lang = lang.replace( runescape, funescape ).toLowerCase(); + return function( elem ) { + var elemLang; + do { + if ( ( elemLang = documentIsHTML ? + elem.lang : + elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { + + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; + } + } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); + return false; + }; + } ), + + // Miscellaneous + "target": function( elem ) { + var hash = window.location && window.location.hash; + return hash && hash.slice( 1 ) === elem.id; + }, + + "root": function( elem ) { + return elem === docElem; + }, + + "focus": function( elem ) { + return elem === document.activeElement && + ( !document.hasFocus || document.hasFocus() ) && + !!( elem.type || elem.href || ~elem.tabIndex ); + }, + + // Boolean properties + "enabled": createDisabledPseudo( false ), + "disabled": createDisabledPseudo( true ), + + "checked": function( elem ) { + + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( nodeName === "input" && !!elem.checked ) || + ( nodeName === "option" && !!elem.selected ); + }, + + "selected": function( elem ) { + + // Accessing this property makes selected-by-default + // options in Safari work properly + if ( elem.parentNode ) { + // eslint-disable-next-line no-unused-expressions + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + "empty": function( elem ) { + + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), + // but not by others (comment: 8; processing instruction: 7; etc.) + // nodeType < 6 works because attributes (2) do not appear as children + for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { + if ( elem.nodeType < 6 ) { + return false; + } + } + return true; + }, + + "parent": function( elem ) { + return !Expr.pseudos[ "empty" ]( elem ); + }, + + // Element/input types + "header": function( elem ) { + return rheader.test( elem.nodeName ); + }, + + "input": function( elem ) { + return rinputs.test( elem.nodeName ); + }, + + "button": function( elem ) { + var name = elem.nodeName.toLowerCase(); + return name === "input" && elem.type === "button" || name === "button"; + }, + + "text": function( elem ) { + var attr; + return elem.nodeName.toLowerCase() === "input" && + elem.type === "text" && + + // Support: IE<8 + // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" + ( ( attr = elem.getAttribute( "type" ) ) == null || + attr.toLowerCase() === "text" ); + }, + + // Position-in-collection + "first": createPositionalPseudo( function() { + return [ 0 ]; + } ), + + "last": createPositionalPseudo( function( _matchIndexes, length ) { + return [ length - 1 ]; + } ), + + "eq": createPositionalPseudo( function( _matchIndexes, length, argument ) { + return [ argument < 0 ? argument + length : argument ]; + } ), + + "even": createPositionalPseudo( function( matchIndexes, length ) { + var i = 0; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "odd": createPositionalPseudo( function( matchIndexes, length ) { + var i = 1; + for ( ; i < length; i += 2 ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "lt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? + argument + length : + argument > length ? + length : + argument; + for ( ; --i >= 0; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ), + + "gt": createPositionalPseudo( function( matchIndexes, length, argument ) { + var i = argument < 0 ? argument + length : argument; + for ( ; ++i < length; ) { + matchIndexes.push( i ); + } + return matchIndexes; + } ) + } +}; + +Expr.pseudos[ "nth" ] = Expr.pseudos[ "eq" ]; + +// Add button/input type pseudos +for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { + Expr.pseudos[ i ] = createInputPseudo( i ); +} +for ( i in { submit: true, reset: true } ) { + Expr.pseudos[ i ] = createButtonPseudo( i ); +} + +// Easy API for creating new setFilters +function setFilters() {} +setFilters.prototype = Expr.filters = Expr.pseudos; +Expr.setFilters = new setFilters(); + +tokenize = Sizzle.tokenize = function( selector, parseOnly ) { + var matched, match, tokens, type, + soFar, groups, preFilters, + cached = tokenCache[ selector + " " ]; + + if ( cached ) { + return parseOnly ? 0 : cached.slice( 0 ); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while ( soFar ) { + + // Comma and first run + if ( !matched || ( match = rcomma.exec( soFar ) ) ) { + if ( match ) { + + // Don't consume trailing commas as valid + soFar = soFar.slice( match[ 0 ].length ) || soFar; + } + groups.push( ( tokens = [] ) ); + } + + matched = false; + + // Combinators + if ( ( match = rcombinators.exec( soFar ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + + // Cast descendant combinators to space + type: match[ 0 ].replace( rtrim, " " ) + } ); + soFar = soFar.slice( matched.length ); + } + + // Filters + for ( type in Expr.filter ) { + if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || + ( match = preFilters[ type ]( match ) ) ) ) { + matched = match.shift(); + tokens.push( { + value: matched, + type: type, + matches: match + } ); + soFar = soFar.slice( matched.length ); + } + } + + if ( !matched ) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly ? + soFar.length : + soFar ? + Sizzle.error( selector ) : + + // Cache the tokens + tokenCache( selector, groups ).slice( 0 ); +}; + +function toSelector( tokens ) { + var i = 0, + len = tokens.length, + selector = ""; + for ( ; i < len; i++ ) { + selector += tokens[ i ].value; + } + return selector; +} + +function addCombinator( matcher, combinator, base ) { + var dir = combinator.dir, + skip = combinator.next, + key = skip || dir, + checkNonElements = base && key === "parentNode", + doneName = done++; + + return combinator.first ? + + // Check against closest ancestor/preceding element + function( elem, context, xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + return matcher( elem, context, xml ); + } + } + return false; + } : + + // Check against all ancestor/preceding elements + function( elem, context, xml ) { + var oldCache, uniqueCache, outerCache, + newCache = [ dirruns, doneName ]; + + // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching + if ( xml ) { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + if ( matcher( elem, context, xml ) ) { + return true; + } + } + } + } else { + while ( ( elem = elem[ dir ] ) ) { + if ( elem.nodeType === 1 || checkNonElements ) { + outerCache = elem[ expando ] || ( elem[ expando ] = {} ); + + // Support: IE <9 only + // Defend against cloned attroperties (jQuery gh-1709) + uniqueCache = outerCache[ elem.uniqueID ] || + ( outerCache[ elem.uniqueID ] = {} ); + + if ( skip && skip === elem.nodeName.toLowerCase() ) { + elem = elem[ dir ] || elem; + } else if ( ( oldCache = uniqueCache[ key ] ) && + oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { + + // Assign to newCache so results back-propagate to previous elements + return ( newCache[ 2 ] = oldCache[ 2 ] ); + } else { + + // Reuse newcache so results back-propagate to previous elements + uniqueCache[ key ] = newCache; + + // A match means we're done; a fail means we have to keep checking + if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { + return true; + } + } + } + } + } + return false; + }; +} + +function elementMatcher( matchers ) { + return matchers.length > 1 ? + function( elem, context, xml ) { + var i = matchers.length; + while ( i-- ) { + if ( !matchers[ i ]( elem, context, xml ) ) { + return false; + } + } + return true; + } : + matchers[ 0 ]; +} + +function multipleContexts( selector, contexts, results ) { + var i = 0, + len = contexts.length; + for ( ; i < len; i++ ) { + Sizzle( selector, contexts[ i ], results ); + } + return results; +} + +function condense( unmatched, map, filter, context, xml ) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for ( ; i < len; i++ ) { + if ( ( elem = unmatched[ i ] ) ) { + if ( !filter || filter( elem, context, xml ) ) { + newUnmatched.push( elem ); + if ( mapped ) { + map.push( i ); + } + } + } + } + + return newUnmatched; +} + +function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { + if ( postFilter && !postFilter[ expando ] ) { + postFilter = setMatcher( postFilter ); + } + if ( postFinder && !postFinder[ expando ] ) { + postFinder = setMatcher( postFinder, postSelector ); + } + return markFunction( function( seed, results, context, xml ) { + var temp, i, elem, + preMap = [], + postMap = [], + preexisting = results.length, + + // Get initial elements from seed or context + elems = seed || multipleContexts( + selector || "*", + context.nodeType ? [ context ] : context, + [] + ), + + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = preFilter && ( seed || !selector ) ? + condense( elems, preMap, preFilter, context, xml ) : + elems, + + matcherOut = matcher ? + + // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || ( seed ? preFilter : preexisting || postFilter ) ? + + // ...intermediate processing is necessary + [] : + + // ...otherwise use results directly + results : + matcherIn; + + // Find primary matches + if ( matcher ) { + matcher( matcherIn, matcherOut, context, xml ); + } + + // Apply postFilter + if ( postFilter ) { + temp = condense( matcherOut, postMap ); + postFilter( temp, [], context, xml ); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while ( i-- ) { + if ( ( elem = temp[ i ] ) ) { + matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); + } + } + } + + if ( seed ) { + if ( postFinder || preFilter ) { + if ( postFinder ) { + + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) ) { + + // Restore matcherIn since elem is not yet a final match + temp.push( ( matcherIn[ i ] = elem ) ); + } + } + postFinder( null, ( matcherOut = [] ), temp, xml ); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while ( i-- ) { + if ( ( elem = matcherOut[ i ] ) && + ( temp = postFinder ? indexOf( seed, elem ) : preMap[ i ] ) > -1 ) { + + seed[ temp ] = !( results[ temp ] = elem ); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results ? + matcherOut.splice( preexisting, matcherOut.length ) : + matcherOut + ); + if ( postFinder ) { + postFinder( null, results, matcherOut, xml ); + } else { + push.apply( results, matcherOut ); + } + } + } ); +} + +function matcherFromTokens( tokens ) { + var checkContext, matcher, j, + len = tokens.length, + leadingRelative = Expr.relative[ tokens[ 0 ].type ], + implicitRelative = leadingRelative || Expr.relative[ " " ], + i = leadingRelative ? 1 : 0, + + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( function( elem ) { + return elem === checkContext; + }, implicitRelative, true ), + matchAnyContext = addCombinator( function( elem ) { + return indexOf( checkContext, elem ) > -1; + }, implicitRelative, true ), + matchers = [ function( elem, context, xml ) { + var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( + ( checkContext = context ).nodeType ? + matchContext( elem, context, xml ) : + matchAnyContext( elem, context, xml ) ); + + // Avoid hanging onto element (issue #299) + checkContext = null; + return ret; + } ]; + + for ( ; i < len; i++ ) { + if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { + matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; + } else { + matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); + + // Return special upon seeing a positional matcher + if ( matcher[ expando ] ) { + + // Find the next relative operator (if any) for proper handling + j = ++i; + for ( ; j < len; j++ ) { + if ( Expr.relative[ tokens[ j ].type ] ) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher( matchers ), + i > 1 && toSelector( + + // If the preceding token was a descendant combinator, insert an implicit any-element `*` + tokens + .slice( 0, i - 1 ) + .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) + ).replace( rtrim, "$1" ), + matcher, + i < j && matcherFromTokens( tokens.slice( i, j ) ), + j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), + j < len && toSelector( tokens ) + ); + } + matchers.push( matcher ); + } + } + + return elementMatcher( matchers ); +} + +function matcherFromGroupMatchers( elementMatchers, setMatchers ) { + var bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function( seed, context, xml, results, outermost ) { + var elem, j, matcher, + matchedCount = 0, + i = "0", + unmatched = seed && [], + setMatched = [], + contextBackup = outermostContext, + + // We must always have either seed elements or outermost context + elems = seed || byElement && Expr.find[ "TAG" ]( "*", outermost ), + + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), + len = elems.length; + + if ( outermost ) { + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + outermostContext = context == document || context || outermost; + } + + // Add elements passing elementMatchers directly to results + // Support: IE<9, Safari + // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id + for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { + if ( byElement && elem ) { + j = 0; + + // Support: IE 11+, Edge 17 - 18+ + // IE/Edge sometimes throw a "Permission denied" error when strict-comparing + // two documents; shallow comparisons work. + // eslint-disable-next-line eqeqeq + if ( !context && elem.ownerDocument != document ) { + setDocument( elem ); + xml = !documentIsHTML; + } + while ( ( matcher = elementMatchers[ j++ ] ) ) { + if ( matcher( elem, context || document, xml ) ) { + results.push( elem ); + break; + } + } + if ( outermost ) { + dirruns = dirrunsUnique; + } + } + + // Track unmatched elements for set filters + if ( bySet ) { + + // They will have gone through all possible matchers + if ( ( elem = !matcher && elem ) ) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if ( seed ) { + unmatched.push( elem ); + } + } + } + + // `i` is now the count of elements visited above, and adding it to `matchedCount` + // makes the latter nonnegative. + matchedCount += i; + + // Apply set filters to unmatched elements + // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` + // equals `i`), unless we didn't visit _any_ elements in the above loop because we have + // no element matchers and no seed. + // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that + // case, which will result in a "00" `matchedCount` that differs from `i` but is also + // numerically zero. + if ( bySet && i !== matchedCount ) { + j = 0; + while ( ( matcher = setMatchers[ j++ ] ) ) { + matcher( unmatched, setMatched, context, xml ); + } + + if ( seed ) { + + // Reintegrate element matches to eliminate the need for sorting + if ( matchedCount > 0 ) { + while ( i-- ) { + if ( !( unmatched[ i ] || setMatched[ i ] ) ) { + setMatched[ i ] = pop.call( results ); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense( setMatched ); + } + + // Add matches to results + push.apply( results, setMatched ); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( outermost && !seed && setMatched.length > 0 && + ( matchedCount + setMatchers.length ) > 1 ) { + + Sizzle.uniqueSort( results ); + } + } + + // Override manipulation of globals by nested matchers + if ( outermost ) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? + markFunction( superMatcher ) : + superMatcher; +} + +compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[ selector + " " ]; + + if ( !cached ) { + + // Generate a function of recursive functions that can be used to check each element + if ( !match ) { + match = tokenize( selector ); + } + i = match.length; + while ( i-- ) { + cached = matcherFromTokens( match[ i ] ); + if ( cached[ expando ] ) { + setMatchers.push( cached ); + } else { + elementMatchers.push( cached ); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers( elementMatchers, setMatchers ) + ); + + // Save selector and tokenization + cached.selector = selector; + } + return cached; +}; + +/** + * A low-level selection function that works with Sizzle's compiled + * selector functions + * @param {String|Function} selector A selector or a pre-compiled + * selector function built with Sizzle.compile + * @param {Element} context + * @param {Array} [results] + * @param {Array} [seed] A set of elements to match against + */ +select = Sizzle.select = function( selector, context, results, seed ) { + var i, tokens, token, type, find, + compiled = typeof selector === "function" && selector, + match = !seed && tokenize( ( selector = compiled.selector || selector ) ); + + results = results || []; + + // Try to minimize operations if there is only one selector in the list and no seed + // (the latter of which guarantees us context) + if ( match.length === 1 ) { + + // Reduce context if the leading compound selector is an ID + tokens = match[ 0 ] = match[ 0 ].slice( 0 ); + if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && + context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { + + context = ( Expr.find[ "ID" ]( token.matches[ 0 ] + .replace( runescape, funescape ), context ) || [] )[ 0 ]; + if ( !context ) { + return results; + + // Precompiled matchers will still verify ancestry, so step up a level + } else if ( compiled ) { + context = context.parentNode; + } + + selector = selector.slice( tokens.shift().value.length ); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr[ "needsContext" ].test( selector ) ? 0 : tokens.length; + while ( i-- ) { + token = tokens[ i ]; + + // Abort if we hit a combinator + if ( Expr.relative[ ( type = token.type ) ] ) { + break; + } + if ( ( find = Expr.find[ type ] ) ) { + + // Search, expanding context for leading sibling combinators + if ( ( seed = find( + token.matches[ 0 ].replace( runescape, funescape ), + rsibling.test( tokens[ 0 ].type ) && testContext( context.parentNode ) || + context + ) ) ) { + + // If seed is empty or no tokens remain, we can return early + tokens.splice( i, 1 ); + selector = seed.length && toSelector( tokens ); + if ( !selector ) { + push.apply( results, seed ); + return results; + } + + break; + } + } + } + } + + // Compile and execute a filtering function if one is not provided + // Provide `match` to avoid retokenization if we modified the selector above + ( compiled || compile( selector, match ) )( + seed, + context, + !documentIsHTML, + results, + !context || rsibling.test( selector ) && testContext( context.parentNode ) || context + ); + return results; +}; + +// One-time assignments + +// Sort stability +support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; + +// Support: Chrome 14-35+ +// Always assume duplicates if they aren't passed to the comparison function +support.detectDuplicates = !!hasDuplicate; + +// Initialize against the default document +setDocument(); + +// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) +// Detached nodes confoundingly follow *each other* +support.sortDetached = assert( function( el ) { + + // Should return 1, but returns 4 (following) + return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; +} ); + +// Support: IE<8 +// Prevent attribute/property "interpolation" +// https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx +if ( !assert( function( el ) { + el.innerHTML = ""; + return el.firstChild.getAttribute( "href" ) === "#"; +} ) ) { + addHandle( "type|href|height|width", function( elem, name, isXML ) { + if ( !isXML ) { + return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); + } + } ); +} + +// Support: IE<9 +// Use defaultValue in place of getAttribute("value") +if ( !support.attributes || !assert( function( el ) { + el.innerHTML = ""; + el.firstChild.setAttribute( "value", "" ); + return el.firstChild.getAttribute( "value" ) === ""; +} ) ) { + addHandle( "value", function( elem, _name, isXML ) { + if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { + return elem.defaultValue; + } + } ); +} + +// Support: IE<9 +// Use getAttributeNode to fetch booleans when getAttribute lies +if ( !assert( function( el ) { + return el.getAttribute( "disabled" ) == null; +} ) ) { + addHandle( booleans, function( elem, name, isXML ) { + var val; + if ( !isXML ) { + return elem[ name ] === true ? name.toLowerCase() : + ( val = elem.getAttributeNode( name ) ) && val.specified ? + val.value : + null; + } + } ); +} + +return Sizzle; + +} )( window ); + + + +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; + +// Deprecated +jQuery.expr[ ":" ] = jQuery.expr.pseudos; +jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; +jQuery.text = Sizzle.getText; +jQuery.isXMLDoc = Sizzle.isXML; +jQuery.contains = Sizzle.contains; +jQuery.escapeSelector = Sizzle.escape; + + + + +var dir = function( elem, dir, until ) { + var matched = [], + truncate = until !== undefined; + + while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { + if ( elem.nodeType === 1 ) { + if ( truncate && jQuery( elem ).is( until ) ) { + break; + } + matched.push( elem ); + } + } + return matched; +}; + + +var siblings = function( n, elem ) { + var matched = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + matched.push( n ); + } + } + + return matched; +}; + + +var rneedsContext = jQuery.expr.match.needsContext; + + + +function nodeName( elem, name ) { + + return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); + +}; +var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); + + + +// Implement the identical functionality for filter and not +function winnow( elements, qualifier, not ) { + if ( isFunction( qualifier ) ) { + return jQuery.grep( elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) !== not; + } ); + } + + // Single element + if ( qualifier.nodeType ) { + return jQuery.grep( elements, function( elem ) { + return ( elem === qualifier ) !== not; + } ); + } + + // Arraylike of elements (jQuery, arguments, Array) + if ( typeof qualifier !== "string" ) { + return jQuery.grep( elements, function( elem ) { + return ( indexOf.call( qualifier, elem ) > -1 ) !== not; + } ); + } + + // Filtered directly for both simple and complex selectors + return jQuery.filter( qualifier, elements, not ); +} + +jQuery.filter = function( expr, elems, not ) { + var elem = elems[ 0 ]; + + if ( not ) { + expr = ":not(" + expr + ")"; + } + + if ( elems.length === 1 && elem.nodeType === 1 ) { + return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; + } + + return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { + return elem.nodeType === 1; + } ) ); +}; + +jQuery.fn.extend( { + find: function( selector ) { + var i, ret, + len = this.length, + self = this; + + if ( typeof selector !== "string" ) { + return this.pushStack( jQuery( selector ).filter( function() { + for ( i = 0; i < len; i++ ) { + if ( jQuery.contains( self[ i ], this ) ) { + return true; + } + } + } ) ); + } + + ret = this.pushStack( [] ); + + for ( i = 0; i < len; i++ ) { + jQuery.find( selector, self[ i ], ret ); + } + + return len > 1 ? jQuery.uniqueSort( ret ) : ret; + }, + filter: function( selector ) { + return this.pushStack( winnow( this, selector || [], false ) ); + }, + not: function( selector ) { + return this.pushStack( winnow( this, selector || [], true ) ); + }, + is: function( selector ) { + return !!winnow( + this, + + // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + typeof selector === "string" && rneedsContext.test( selector ) ? + jQuery( selector ) : + selector || [], + false + ).length; + } +} ); + + +// Initialize a jQuery object + + +// A central reference to the root jQuery(document) +var rootjQuery, + + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + // Shortcut simple #id case for speed + rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, + + init = jQuery.fn.init = function( selector, context, root ) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if ( !selector ) { + return this; + } + + // Method init() accepts an alternate rootjQuery + // so migrate can support jQuery.sub (gh-2101) + root = root || rootjQuery; + + // Handle HTML strings + if ( typeof selector === "string" ) { + if ( selector[ 0 ] === "<" && + selector[ selector.length - 1 ] === ">" && + selector.length >= 3 ) { + + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [ null, selector, null ]; + + } else { + match = rquickExpr.exec( selector ); + } + + // Match html or make sure no context is specified for #id + if ( match && ( match[ 1 ] || !context ) ) { + + // HANDLE: $(html) -> $(array) + if ( match[ 1 ] ) { + context = context instanceof jQuery ? context[ 0 ] : context; + + // Option to run scripts is true for back-compat + // Intentionally let the error be thrown if parseHTML is not present + jQuery.merge( this, jQuery.parseHTML( + match[ 1 ], + context && context.nodeType ? context.ownerDocument || context : document, + true + ) ); + + // HANDLE: $(html, props) + if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { + for ( match in context ) { + + // Properties of context are called as methods if possible + if ( isFunction( this[ match ] ) ) { + this[ match ]( context[ match ] ); + + // ...and otherwise set as attributes + } else { + this.attr( match, context[ match ] ); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById( match[ 2 ] ); + + if ( elem ) { + + // Inject the element directly into the jQuery object + this[ 0 ] = elem; + this.length = 1; + } + return this; + } + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return ( context || root ).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor( context ).find( selector ); + } + + // HANDLE: $(DOMElement) + } else if ( selector.nodeType ) { + this[ 0 ] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( isFunction( selector ) ) { + return root.ready !== undefined ? + root.ready( selector ) : + + // Execute immediately if ready is not present + selector( jQuery ); + } + + return jQuery.makeArray( selector, this ); + }; + +// Give the init function the jQuery prototype for later instantiation +init.prototype = jQuery.fn; + +// Initialize central reference +rootjQuery = jQuery( document ); + + +var rparentsprev = /^(?:parents|prev(?:Until|All))/, + + // Methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true + }; + +jQuery.fn.extend( { + has: function( target ) { + var targets = jQuery( target, this ), + l = targets.length; + + return this.filter( function() { + var i = 0; + for ( ; i < l; i++ ) { + if ( jQuery.contains( this, targets[ i ] ) ) { + return true; + } + } + } ); + }, + + closest: function( selectors, context ) { + var cur, + i = 0, + l = this.length, + matched = [], + targets = typeof selectors !== "string" && jQuery( selectors ); + + // Positional selectors never match, since there's no _selection_ context + if ( !rneedsContext.test( selectors ) ) { + for ( ; i < l; i++ ) { + for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { + + // Always skip document fragments + if ( cur.nodeType < 11 && ( targets ? + targets.index( cur ) > -1 : + + // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector( cur, selectors ) ) ) { + + matched.push( cur ); + break; + } + } + } + } + + return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); + }, + + // Determine the position of an element within the set + index: function( elem ) { + + // No argument, return index in parent + if ( !elem ) { + return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; + } + + // Index in selector + if ( typeof elem === "string" ) { + return indexOf.call( jQuery( elem ), this[ 0 ] ); + } + + // Locate the position of the desired element + return indexOf.call( this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[ 0 ] : elem + ); + }, + + add: function( selector, context ) { + return this.pushStack( + jQuery.uniqueSort( + jQuery.merge( this.get(), jQuery( selector, context ) ) + ) + ); + }, + + addBack: function( selector ) { + return this.add( selector == null ? + this.prevObject : this.prevObject.filter( selector ) + ); + } +} ); + +function sibling( cur, dir ) { + while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} + return cur; +} + +jQuery.each( { + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, _i, until ) { + return dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return sibling( elem, "nextSibling" ); + }, + prev: function( elem ) { + return sibling( elem, "previousSibling" ); + }, + nextAll: function( elem ) { + return dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, _i, until ) { + return dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, _i, until ) { + return dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return siblings( ( elem.parentNode || {} ).firstChild, elem ); + }, + children: function( elem ) { + return siblings( elem.firstChild ); + }, + contents: function( elem ) { + if ( elem.contentDocument != null && + + // Support: IE 11+ + // elements with no `data` attribute has an object + // `contentDocument` with a `null` prototype. + getProto( elem.contentDocument ) ) { + + return elem.contentDocument; + } + + // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only + // Treat the template element as a regular one in browsers that + // don't support it. + if ( nodeName( elem, "template" ) ) { + elem = elem.content || elem; + } + + return jQuery.merge( [], elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var matched = jQuery.map( this, fn, until ); + + if ( name.slice( -5 ) !== "Until" ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + matched = jQuery.filter( selector, matched ); + } + + if ( this.length > 1 ) { + + // Remove duplicates + if ( !guaranteedUnique[ name ] ) { + jQuery.uniqueSort( matched ); + } + + // Reverse order for parents* and prev-derivatives + if ( rparentsprev.test( name ) ) { + matched.reverse(); + } + } + + return this.pushStack( matched ); + }; +} ); +var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); + + + +// Convert String-formatted options into Object-formatted ones +function createOptions( options ) { + var object = {}; + jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { + object[ flag ] = true; + } ); + return object; +} + +/* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ +jQuery.Callbacks = function( options ) { + + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = typeof options === "string" ? + createOptions( options ) : + jQuery.extend( {}, options ); + + var // Flag to know if list is currently firing + firing, + + // Last fire value for non-forgettable lists + memory, + + // Flag to know if list was already fired + fired, + + // Flag to prevent firing + locked, + + // Actual callback list + list = [], + + // Queue of execution data for repeatable lists + queue = [], + + // Index of currently firing callback (modified by add/remove as needed) + firingIndex = -1, + + // Fire callbacks + fire = function() { + + // Enforce single-firing + locked = locked || options.once; + + // Execute callbacks for all pending executions, + // respecting firingIndex overrides and runtime changes + fired = firing = true; + for ( ; queue.length; firingIndex = -1 ) { + memory = queue.shift(); + while ( ++firingIndex < list.length ) { + + // Run callback and check for early termination + if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && + options.stopOnFalse ) { + + // Jump to end and forget the data so .add doesn't re-fire + firingIndex = list.length; + memory = false; + } + } + } + + // Forget the data if we're done with it + if ( !options.memory ) { + memory = false; + } + + firing = false; + + // Clean up if we're done firing for good + if ( locked ) { + + // Keep an empty list if we have data for future add calls + if ( memory ) { + list = []; + + // Otherwise, this object is spent + } else { + list = ""; + } + } + }, + + // Actual Callbacks object + self = { + + // Add a callback or a collection of callbacks to the list + add: function() { + if ( list ) { + + // If we have memory from a past run, we should fire after adding + if ( memory && !firing ) { + firingIndex = list.length - 1; + queue.push( memory ); + } + + ( function add( args ) { + jQuery.each( args, function( _, arg ) { + if ( isFunction( arg ) ) { + if ( !options.unique || !self.has( arg ) ) { + list.push( arg ); + } + } else if ( arg && arg.length && toType( arg ) !== "string" ) { + + // Inspect recursively + add( arg ); + } + } ); + } )( arguments ); + + if ( memory && !firing ) { + fire(); + } + } + return this; + }, + + // Remove a callback from the list + remove: function() { + jQuery.each( arguments, function( _, arg ) { + var index; + while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { + list.splice( index, 1 ); + + // Handle firing indexes + if ( index <= firingIndex ) { + firingIndex--; + } + } + } ); + return this; + }, + + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function( fn ) { + return fn ? + jQuery.inArray( fn, list ) > -1 : + list.length > 0; + }, + + // Remove all callbacks from the list + empty: function() { + if ( list ) { + list = []; + } + return this; + }, + + // Disable .fire and .add + // Abort any current/pending executions + // Clear all callbacks and values + disable: function() { + locked = queue = []; + list = memory = ""; + return this; + }, + disabled: function() { + return !list; + }, + + // Disable .fire + // Also disable .add unless we have memory (since it would have no effect) + // Abort any pending executions + lock: function() { + locked = queue = []; + if ( !memory && !firing ) { + list = memory = ""; + } + return this; + }, + locked: function() { + return !!locked; + }, + + // Call all callbacks with the given context and arguments + fireWith: function( context, args ) { + if ( !locked ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + queue.push( args ); + if ( !firing ) { + fire(); + } + } + return this; + }, + + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith( this, arguments ); + return this; + }, + + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + } + }; + + return self; +}; + + +function Identity( v ) { + return v; +} +function Thrower( ex ) { + throw ex; +} + +function adoptValue( value, resolve, reject, noValue ) { + var method; + + try { + + // Check for promise aspect first to privilege synchronous behavior + if ( value && isFunction( ( method = value.promise ) ) ) { + method.call( value ).done( resolve ).fail( reject ); + + // Other thenables + } else if ( value && isFunction( ( method = value.then ) ) ) { + method.call( value, resolve, reject ); + + // Other non-thenables + } else { + + // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: + // * false: [ value ].slice( 0 ) => resolve( value ) + // * true: [ value ].slice( 1 ) => resolve() + resolve.apply( undefined, [ value ].slice( noValue ) ); + } + + // For Promises/A+, convert exceptions into rejections + // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in + // Deferred#then to conditionally suppress rejection. + } catch ( value ) { + + // Support: Android 4.0 only + // Strict mode functions invoked without .call/.apply get global-object context + reject.apply( undefined, [ value ] ); + } +} + +jQuery.extend( { + + Deferred: function( func ) { + var tuples = [ + + // action, add listener, callbacks, + // ... .then handlers, argument index, [final state] + [ "notify", "progress", jQuery.Callbacks( "memory" ), + jQuery.Callbacks( "memory" ), 2 ], + [ "resolve", "done", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 0, "resolved" ], + [ "reject", "fail", jQuery.Callbacks( "once memory" ), + jQuery.Callbacks( "once memory" ), 1, "rejected" ] + ], + state = "pending", + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + "catch": function( fn ) { + return promise.then( null, fn ); + }, + + // Keep pipe for back-compat + pipe: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + + return jQuery.Deferred( function( newDefer ) { + jQuery.each( tuples, function( _i, tuple ) { + + // Map tuples (progress, done, fail) to arguments (done, fail, progress) + var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; + + // deferred.progress(function() { bind to newDefer or newDefer.notify }) + // deferred.done(function() { bind to newDefer or newDefer.resolve }) + // deferred.fail(function() { bind to newDefer or newDefer.reject }) + deferred[ tuple[ 1 ] ]( function() { + var returned = fn && fn.apply( this, arguments ); + if ( returned && isFunction( returned.promise ) ) { + returned.promise() + .progress( newDefer.notify ) + .done( newDefer.resolve ) + .fail( newDefer.reject ); + } else { + newDefer[ tuple[ 0 ] + "With" ]( + this, + fn ? [ returned ] : arguments + ); + } + } ); + } ); + fns = null; + } ).promise(); + }, + then: function( onFulfilled, onRejected, onProgress ) { + var maxDepth = 0; + function resolve( depth, deferred, handler, special ) { + return function() { + var that = this, + args = arguments, + mightThrow = function() { + var returned, then; + + // Support: Promises/A+ section 2.3.3.3.3 + // https://promisesaplus.com/#point-59 + // Ignore double-resolution attempts + if ( depth < maxDepth ) { + return; + } + + returned = handler.apply( that, args ); + + // Support: Promises/A+ section 2.3.1 + // https://promisesaplus.com/#point-48 + if ( returned === deferred.promise() ) { + throw new TypeError( "Thenable self-resolution" ); + } + + // Support: Promises/A+ sections 2.3.3.1, 3.5 + // https://promisesaplus.com/#point-54 + // https://promisesaplus.com/#point-75 + // Retrieve `then` only once + then = returned && + + // Support: Promises/A+ section 2.3.4 + // https://promisesaplus.com/#point-64 + // Only check objects and functions for thenability + ( typeof returned === "object" || + typeof returned === "function" ) && + returned.then; + + // Handle a returned thenable + if ( isFunction( then ) ) { + + // Special processors (notify) just wait for resolution + if ( special ) { + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ) + ); + + // Normal processors (resolve) also hook into progress + } else { + + // ...and disregard older resolution values + maxDepth++; + + then.call( + returned, + resolve( maxDepth, deferred, Identity, special ), + resolve( maxDepth, deferred, Thrower, special ), + resolve( maxDepth, deferred, Identity, + deferred.notifyWith ) + ); + } + + // Handle all other returned values + } else { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Identity ) { + that = undefined; + args = [ returned ]; + } + + // Process the value(s) + // Default process is resolve + ( special || deferred.resolveWith )( that, args ); + } + }, + + // Only normal processors (resolve) catch and reject exceptions + process = special ? + mightThrow : + function() { + try { + mightThrow(); + } catch ( e ) { + + if ( jQuery.Deferred.exceptionHook ) { + jQuery.Deferred.exceptionHook( e, + process.stackTrace ); + } + + // Support: Promises/A+ section 2.3.3.3.4.1 + // https://promisesaplus.com/#point-61 + // Ignore post-resolution exceptions + if ( depth + 1 >= maxDepth ) { + + // Only substitute handlers pass on context + // and multiple values (non-spec behavior) + if ( handler !== Thrower ) { + that = undefined; + args = [ e ]; + } + + deferred.rejectWith( that, args ); + } + } + }; + + // Support: Promises/A+ section 2.3.3.3.1 + // https://promisesaplus.com/#point-57 + // Re-resolve promises immediately to dodge false rejection from + // subsequent errors + if ( depth ) { + process(); + } else { + + // Call an optional hook to record the stack, in case of exception + // since it's otherwise lost when execution goes async + if ( jQuery.Deferred.getStackHook ) { + process.stackTrace = jQuery.Deferred.getStackHook(); + } + window.setTimeout( process ); + } + }; + } + + return jQuery.Deferred( function( newDefer ) { + + // progress_handlers.add( ... ) + tuples[ 0 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onProgress ) ? + onProgress : + Identity, + newDefer.notifyWith + ) + ); + + // fulfilled_handlers.add( ... ) + tuples[ 1 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onFulfilled ) ? + onFulfilled : + Identity + ) + ); + + // rejected_handlers.add( ... ) + tuples[ 2 ][ 3 ].add( + resolve( + 0, + newDefer, + isFunction( onRejected ) ? + onRejected : + Thrower + ) + ); + } ).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + return obj != null ? jQuery.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Add list-specific methods + jQuery.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 5 ]; + + // promise.progress = list.add + // promise.done = list.add + // promise.fail = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add( + function() { + + // state = "resolved" (i.e., fulfilled) + // state = "rejected" + state = stateString; + }, + + // rejected_callbacks.disable + // fulfilled_callbacks.disable + tuples[ 3 - i ][ 2 ].disable, + + // rejected_handlers.disable + // fulfilled_handlers.disable + tuples[ 3 - i ][ 3 ].disable, + + // progress_callbacks.lock + tuples[ 0 ][ 2 ].lock, + + // progress_handlers.lock + tuples[ 0 ][ 3 ].lock + ); + } + + // progress_handlers.fire + // fulfilled_handlers.fire + // rejected_handlers.fire + list.add( tuple[ 3 ].fire ); + + // deferred.notify = function() { deferred.notifyWith(...) } + // deferred.resolve = function() { deferred.resolveWith(...) } + // deferred.reject = function() { deferred.rejectWith(...) } + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); + return this; + }; + + // deferred.notifyWith = list.fireWith + // deferred.resolveWith = list.fireWith + // deferred.rejectWith = list.fireWith + deferred[ tuple[ 0 ] + "With" ] = list.fireWith; + } ); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function( singleValue ) { + var + + // count of uncompleted subordinates + remaining = arguments.length, + + // count of unprocessed arguments + i = remaining, + + // subordinate fulfillment data + resolveContexts = Array( i ), + resolveValues = slice.call( arguments ), + + // the master Deferred + master = jQuery.Deferred(), + + // subordinate callback factory + updateFunc = function( i ) { + return function( value ) { + resolveContexts[ i ] = this; + resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; + if ( !( --remaining ) ) { + master.resolveWith( resolveContexts, resolveValues ); + } + }; + }; + + // Single- and empty arguments are adopted like Promise.resolve + if ( remaining <= 1 ) { + adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, + !remaining ); + + // Use .then() to unwrap secondary thenables (cf. gh-3000) + if ( master.state() === "pending" || + isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { + + return master.then(); + } + } + + // Multiple arguments are aggregated like Promise.all array elements + while ( i-- ) { + adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); + } + + return master.promise(); + } +} ); + + +// These usually indicate a programmer mistake during development, +// warn about them ASAP rather than swallowing them by default. +var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; + +jQuery.Deferred.exceptionHook = function( error, stack ) { + + // Support: IE 8 - 9 only + // Console exists when dev tools are open, which can happen at any time + if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { + window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); + } +}; + + + + +jQuery.readyException = function( error ) { + window.setTimeout( function() { + throw error; + } ); +}; + + + + +// The deferred used on DOM ready +var readyList = jQuery.Deferred(); + +jQuery.fn.ready = function( fn ) { + + readyList + .then( fn ) + + // Wrap jQuery.readyException in a function so that the lookup + // happens at the time of error handling instead of callback + // registration. + .catch( function( error ) { + jQuery.readyException( error ); + } ); + + return this; +}; + +jQuery.extend( { + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Handle when the DOM is ready + ready: function( wait ) { + + // Abort if there are pending holds or we're already ready + if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if ( wait !== true && --jQuery.readyWait > 0 ) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith( document, [ jQuery ] ); + } +} ); + +jQuery.ready.then = readyList.then; + +// The ready event handler and self cleanup method +function completed() { + document.removeEventListener( "DOMContentLoaded", completed ); + window.removeEventListener( "load", completed ); + jQuery.ready(); +} + +// Catch cases where $(document).ready() is called +// after the browser event has already occurred. +// Support: IE <=9 - 10 only +// Older IE sometimes signals "interactive" too soon +if ( document.readyState === "complete" || + ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { + + // Handle it asynchronously to allow scripts the opportunity to delay ready + window.setTimeout( jQuery.ready ); + +} else { + + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", completed ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", completed ); +} + + + + +// Multifunctional method to get and set values of a collection +// The value/s can optionally be executed if it's a function +var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { + var i = 0, + len = elems.length, + bulk = key == null; + + // Sets many values + if ( toType( key ) === "object" ) { + chainable = true; + for ( i in key ) { + access( elems, fn, i, key[ i ], true, emptyGet, raw ); + } + + // Sets one value + } else if ( value !== undefined ) { + chainable = true; + + if ( !isFunction( value ) ) { + raw = true; + } + + if ( bulk ) { + + // Bulk operations run against the entire set + if ( raw ) { + fn.call( elems, value ); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function( elem, _key, value ) { + return bulk.call( jQuery( elem ), value ); + }; + } + } + + if ( fn ) { + for ( ; i < len; i++ ) { + fn( + elems[ i ], key, raw ? + value : + value.call( elems[ i ], i, fn( elems[ i ], key ) ) + ); + } + } + } + + if ( chainable ) { + return elems; + } + + // Gets + if ( bulk ) { + return fn.call( elems ); + } + + return len ? fn( elems[ 0 ], key ) : emptyGet; +}; + + +// Matches dashed string for camelizing +var rmsPrefix = /^-ms-/, + rdashAlpha = /-([a-z])/g; + +// Used by camelCase as callback to replace() +function fcamelCase( _all, letter ) { + return letter.toUpperCase(); +} + +// Convert dashed to camelCase; used by the css and data modules +// Support: IE <=9 - 11, Edge 12 - 15 +// Microsoft forgot to hump their vendor prefix (#9572) +function camelCase( string ) { + return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); +} +var acceptData = function( owner ) { + + // Accepts only: + // - Node + // - Node.ELEMENT_NODE + // - Node.DOCUMENT_NODE + // - Object + // - Any + return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); +}; + + + + +function Data() { + this.expando = jQuery.expando + Data.uid++; +} + +Data.uid = 1; + +Data.prototype = { + + cache: function( owner ) { + + // Check if the owner object already has a cache + var value = owner[ this.expando ]; + + // If not, create one + if ( !value ) { + value = {}; + + // We can accept data for non-element nodes in modern browsers, + // but we should not, see #8335. + // Always return an empty object. + if ( acceptData( owner ) ) { + + // If it is a node unlikely to be stringify-ed or looped over + // use plain assignment + if ( owner.nodeType ) { + owner[ this.expando ] = value; + + // Otherwise secure it in a non-enumerable property + // configurable must be true to allow the property to be + // deleted when data is removed + } else { + Object.defineProperty( owner, this.expando, { + value: value, + configurable: true + } ); + } + } + } + + return value; + }, + set: function( owner, data, value ) { + var prop, + cache = this.cache( owner ); + + // Handle: [ owner, key, value ] args + // Always use camelCase key (gh-2257) + if ( typeof data === "string" ) { + cache[ camelCase( data ) ] = value; + + // Handle: [ owner, { properties } ] args + } else { + + // Copy the properties one-by-one to the cache object + for ( prop in data ) { + cache[ camelCase( prop ) ] = data[ prop ]; + } + } + return cache; + }, + get: function( owner, key ) { + return key === undefined ? + this.cache( owner ) : + + // Always use camelCase key (gh-2257) + owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; + }, + access: function( owner, key, value ) { + + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( key === undefined || + ( ( key && typeof key === "string" ) && value === undefined ) ) { + + return this.get( owner, key ); + } + + // When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set( owner, key, value ); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function( owner, key ) { + var i, + cache = owner[ this.expando ]; + + if ( cache === undefined ) { + return; + } + + if ( key !== undefined ) { + + // Support array or space separated string of keys + if ( Array.isArray( key ) ) { + + // If key is an array of keys... + // We always set camelCase keys, so remove that. + key = key.map( camelCase ); + } else { + key = camelCase( key ); + + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + key = key in cache ? + [ key ] : + ( key.match( rnothtmlwhite ) || [] ); + } + + i = key.length; + + while ( i-- ) { + delete cache[ key[ i ] ]; + } + } + + // Remove the expando if there's no more data + if ( key === undefined || jQuery.isEmptyObject( cache ) ) { + + // Support: Chrome <=35 - 45 + // Webkit & Blink performance suffers when deleting properties + // from DOM nodes, so set to undefined instead + // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) + if ( owner.nodeType ) { + owner[ this.expando ] = undefined; + } else { + delete owner[ this.expando ]; + } + } + }, + hasData: function( owner ) { + var cache = owner[ this.expando ]; + return cache !== undefined && !jQuery.isEmptyObject( cache ); + } +}; +var dataPriv = new Data(); + +var dataUser = new Data(); + + + +// Implementation Summary +// +// 1. Enforce API surface and semantic compatibility with 1.9.x branch +// 2. Improve the module's maintainability by reducing the storage +// paths to a single mechanism. +// 3. Use the same single mechanism to support "private" and "user" data. +// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) +// 5. Avoid exposing implementation details on user objects (eg. expando properties) +// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 + +var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + rmultiDash = /[A-Z]/g; + +function getData( data ) { + if ( data === "true" ) { + return true; + } + + if ( data === "false" ) { + return false; + } + + if ( data === "null" ) { + return null; + } + + // Only convert to a number if it doesn't change the string + if ( data === +data + "" ) { + return +data; + } + + if ( rbrace.test( data ) ) { + return JSON.parse( data ); + } + + return data; +} + +function dataAttr( elem, key, data ) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if ( data === undefined && elem.nodeType === 1 ) { + name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); + data = elem.getAttribute( name ); + + if ( typeof data === "string" ) { + try { + data = getData( data ); + } catch ( e ) {} + + // Make sure we set the data so it isn't changed later + dataUser.set( elem, key, data ); + } else { + data = undefined; + } + } + return data; +} + +jQuery.extend( { + hasData: function( elem ) { + return dataUser.hasData( elem ) || dataPriv.hasData( elem ); + }, + + data: function( elem, name, data ) { + return dataUser.access( elem, name, data ); + }, + + removeData: function( elem, name ) { + dataUser.remove( elem, name ); + }, + + // TODO: Now that all calls to _data and _removeData have been replaced + // with direct calls to dataPriv methods, these can be deprecated. + _data: function( elem, name, data ) { + return dataPriv.access( elem, name, data ); + }, + + _removeData: function( elem, name ) { + dataPriv.remove( elem, name ); + } +} ); + +jQuery.fn.extend( { + data: function( key, value ) { + var i, name, data, + elem = this[ 0 ], + attrs = elem && elem.attributes; + + // Gets all values + if ( key === undefined ) { + if ( this.length ) { + data = dataUser.get( elem ); + + if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { + i = attrs.length; + while ( i-- ) { + + // Support: IE 11 only + // The attrs elements can be null (#14894) + if ( attrs[ i ] ) { + name = attrs[ i ].name; + if ( name.indexOf( "data-" ) === 0 ) { + name = camelCase( name.slice( 5 ) ); + dataAttr( elem, name, data[ name ] ); + } + } + } + dataPriv.set( elem, "hasDataAttrs", true ); + } + } + + return data; + } + + // Sets multiple values + if ( typeof key === "object" ) { + return this.each( function() { + dataUser.set( this, key ); + } ); + } + + return access( this, function( value ) { + var data; + + // The calling jQuery object (element matches) is not empty + // (and therefore has an element appears at this[ 0 ]) and the + // `value` parameter was not undefined. An empty jQuery object + // will result in `undefined` for elem = this[ 0 ] which will + // throw an exception if an attempt to read a data cache is made. + if ( elem && value === undefined ) { + + // Attempt to get data from the cache + // The key will always be camelCased in Data + data = dataUser.get( elem, key ); + if ( data !== undefined ) { + return data; + } + + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr( elem, key ); + if ( data !== undefined ) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return; + } + + // Set the data... + this.each( function() { + + // We always store the camelCased key + dataUser.set( this, key, value ); + } ); + }, null, value, arguments.length > 1, null, true ); + }, + + removeData: function( key ) { + return this.each( function() { + dataUser.remove( this, key ); + } ); + } +} ); + + +jQuery.extend( { + queue: function( elem, type, data ) { + var queue; + + if ( elem ) { + type = ( type || "fx" ) + "queue"; + queue = dataPriv.get( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( data ) { + if ( !queue || Array.isArray( data ) ) { + queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); + } else { + queue.push( data ); + } + } + return queue || []; + } + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks( elem, type ), + next = function() { + jQuery.dequeue( elem, type ); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + startLength--; + } + + if ( fn ) { + + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift( "inprogress" ); + } + + // Clear up the last queue stop function + delete hooks.stop; + fn.call( elem, next, hooks ); + } + + if ( !startLength && hooks ) { + hooks.empty.fire(); + } + }, + + // Not public - generate a queueHooks object, or return the current one + _queueHooks: function( elem, type ) { + var key = type + "queueHooks"; + return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { + empty: jQuery.Callbacks( "once memory" ).add( function() { + dataPriv.remove( elem, [ type + "queue", key ] ); + } ) + } ); + } +} ); + +jQuery.fn.extend( { + queue: function( type, data ) { + var setter = 2; + + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + setter--; + } + + if ( arguments.length < setter ) { + return jQuery.queue( this[ 0 ], type ); + } + + return data === undefined ? + this : + this.each( function() { + var queue = jQuery.queue( this, type, data ); + + // Ensure a hooks for this queue + jQuery._queueHooks( this, type ); + + if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + } ); + }, + dequeue: function( type ) { + return this.each( function() { + jQuery.dequeue( this, type ); + } ); + }, + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + }, + + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function( type, obj ) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if ( !( --count ) ) { + defer.resolveWith( elements, [ elements ] ); + } + }; + + if ( typeof type !== "string" ) { + obj = type; + type = undefined; + } + type = type || "fx"; + + while ( i-- ) { + tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); + if ( tmp && tmp.empty ) { + count++; + tmp.empty.add( resolve ); + } + } + resolve(); + return defer.promise( obj ); + } +} ); +var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; + +var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); + + +var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; + +var documentElement = document.documentElement; + + + + var isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ); + }, + composed = { composed: true }; + + // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only + // Check attachment across shadow DOM boundaries when possible (gh-3504) + // Support: iOS 10.0-10.2 only + // Early iOS 10 versions support `attachShadow` but not `getRootNode`, + // leading to errors. We need to check for `getRootNode`. + if ( documentElement.getRootNode ) { + isAttached = function( elem ) { + return jQuery.contains( elem.ownerDocument, elem ) || + elem.getRootNode( composed ) === elem.ownerDocument; + }; + } +var isHiddenWithinTree = function( elem, el ) { + + // isHiddenWithinTree might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + + // Inline style trumps all + return elem.style.display === "none" || + elem.style.display === "" && + + // Otherwise, check computed style + // Support: Firefox <=43 - 45 + // Disconnected elements can have computed display: none, so first confirm that elem is + // in the document. + isAttached( elem ) && + + jQuery.css( elem, "display" ) === "none"; + }; + + + +function adjustCSS( elem, prop, valueParts, tween ) { + var adjusted, scale, + maxIterations = 20, + currentValue = tween ? + function() { + return tween.cur(); + } : + function() { + return jQuery.css( elem, prop, "" ); + }, + initial = currentValue(), + unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), + + // Starting value computation is required for potential unit mismatches + initialInUnit = elem.nodeType && + ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && + rcssNum.exec( jQuery.css( elem, prop ) ); + + if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { + + // Support: Firefox <=54 + // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) + initial = initial / 2; + + // Trust units reported by jQuery.css + unit = unit || initialInUnit[ 3 ]; + + // Iteratively approximate from a nonzero starting point + initialInUnit = +initial || 1; + + while ( maxIterations-- ) { + + // Evaluate and update our best guess (doubling guesses that zero out). + // Finish if the scale equals or crosses 1 (making the old*new product non-positive). + jQuery.style( elem, prop, initialInUnit + unit ); + if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { + maxIterations = 0; + } + initialInUnit = initialInUnit / scale; + + } + + initialInUnit = initialInUnit * 2; + jQuery.style( elem, prop, initialInUnit + unit ); + + // Make sure we update the tween properties later on + valueParts = valueParts || []; + } + + if ( valueParts ) { + initialInUnit = +initialInUnit || +initial || 0; + + // Apply relative offset (+=/-=) if specified + adjusted = valueParts[ 1 ] ? + initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : + +valueParts[ 2 ]; + if ( tween ) { + tween.unit = unit; + tween.start = initialInUnit; + tween.end = adjusted; + } + } + return adjusted; +} + + +var defaultDisplayMap = {}; + +function getDefaultDisplay( elem ) { + var temp, + doc = elem.ownerDocument, + nodeName = elem.nodeName, + display = defaultDisplayMap[ nodeName ]; + + if ( display ) { + return display; + } + + temp = doc.body.appendChild( doc.createElement( nodeName ) ); + display = jQuery.css( temp, "display" ); + + temp.parentNode.removeChild( temp ); + + if ( display === "none" ) { + display = "block"; + } + defaultDisplayMap[ nodeName ] = display; + + return display; +} + +function showHide( elements, show ) { + var display, elem, + values = [], + index = 0, + length = elements.length; + + // Determine new display value for elements that need to change + for ( ; index < length; index++ ) { + elem = elements[ index ]; + if ( !elem.style ) { + continue; + } + + display = elem.style.display; + if ( show ) { + + // Since we force visibility upon cascade-hidden elements, an immediate (and slow) + // check is required in this first loop unless we have a nonempty display value (either + // inline or about-to-be-restored) + if ( display === "none" ) { + values[ index ] = dataPriv.get( elem, "display" ) || null; + if ( !values[ index ] ) { + elem.style.display = ""; + } + } + if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { + values[ index ] = getDefaultDisplay( elem ); + } + } else { + if ( display !== "none" ) { + values[ index ] = "none"; + + // Remember what we're overwriting + dataPriv.set( elem, "display", display ); + } + } + } + + // Set the display of the elements in a second loop to avoid constant reflow + for ( index = 0; index < length; index++ ) { + if ( values[ index ] != null ) { + elements[ index ].style.display = values[ index ]; + } + } + + return elements; +} + +jQuery.fn.extend( { + show: function() { + return showHide( this, true ); + }, + hide: function() { + return showHide( this ); + }, + toggle: function( state ) { + if ( typeof state === "boolean" ) { + return state ? this.show() : this.hide(); + } + + return this.each( function() { + if ( isHiddenWithinTree( this ) ) { + jQuery( this ).show(); + } else { + jQuery( this ).hide(); + } + } ); + } +} ); +var rcheckableType = ( /^(?:checkbox|radio)$/i ); + +var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); + +var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); + + + +( function() { + var fragment = document.createDocumentFragment(), + div = fragment.appendChild( document.createElement( "div" ) ), + input = document.createElement( "input" ); + + // Support: Android 4.0 - 4.3 only + // Check state lost if the name is set (#11217) + // Support: Windows Web Apps (WWA) + // `name` and `type` must use .setAttribute for WWA (#14901) + input.setAttribute( "type", "radio" ); + input.setAttribute( "checked", "checked" ); + input.setAttribute( "name", "t" ); + + div.appendChild( input ); + + // Support: Android <=4.1 only + // Older WebKit doesn't clone checked state correctly in fragments + support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; + + // Support: IE <=11 only + // Make sure textarea (and checkbox) defaultValue is properly cloned + div.innerHTML = ""; + support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; + + // Support: IE <=9 only + // IE <=9 replaces "; + support.option = !!div.lastChild; +} )(); + + +// We have to close these tags to support XHTML (#13200) +var wrapMap = { + + // XHTML parsers do not magically insert elements in the + // same way that tag soup parsers do. So we cannot shorten + // this by omitting or other required elements. + thead: [ 1, "", "
" ], + col: [ 2, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + + _default: [ 0, "", "" ] +}; + +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// Support: IE <=9 only +if ( !support.option ) { + wrapMap.optgroup = wrapMap.option = [ 1, "" ]; +} + + +function getAll( context, tag ) { + + // Support: IE <=9 - 11 only + // Use typeof to avoid zero-argument method invocation on host objects (#15151) + var ret; + + if ( typeof context.getElementsByTagName !== "undefined" ) { + ret = context.getElementsByTagName( tag || "*" ); + + } else if ( typeof context.querySelectorAll !== "undefined" ) { + ret = context.querySelectorAll( tag || "*" ); + + } else { + ret = []; + } + + if ( tag === undefined || tag && nodeName( context, tag ) ) { + return jQuery.merge( [ context ], ret ); + } + + return ret; +} + + +// Mark scripts as having already been evaluated +function setGlobalEval( elems, refElements ) { + var i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + dataPriv.set( + elems[ i ], + "globalEval", + !refElements || dataPriv.get( refElements[ i ], "globalEval" ) + ); + } +} + + +var rhtml = /<|&#?\w+;/; + +function buildFragment( elems, context, scripts, selection, ignored ) { + var elem, tmp, tag, wrap, attached, j, + fragment = context.createDocumentFragment(), + nodes = [], + i = 0, + l = elems.length; + + for ( ; i < l; i++ ) { + elem = elems[ i ]; + + if ( elem || elem === 0 ) { + + // Add nodes directly + if ( toType( elem ) === "object" ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); + + // Convert non-html into a text node + } else if ( !rhtml.test( elem ) ) { + nodes.push( context.createTextNode( elem ) ); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); + + // Deserialize a standard representation + tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); + wrap = wrapMap[ tag ] || wrapMap._default; + tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; + + // Descend through wrappers to the right content + j = wrap[ 0 ]; + while ( j-- ) { + tmp = tmp.lastChild; + } + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( nodes, tmp.childNodes ); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Ensure the created nodes are orphaned (#12392) + tmp.textContent = ""; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ""; + + i = 0; + while ( ( elem = nodes[ i++ ] ) ) { + + // Skip elements already in the context collection (trac-4087) + if ( selection && jQuery.inArray( elem, selection ) > -1 ) { + if ( ignored ) { + ignored.push( elem ); + } + continue; + } + + attached = isAttached( elem ); + + // Append to fragment + tmp = getAll( fragment.appendChild( elem ), "script" ); + + // Preserve script evaluation history + if ( attached ) { + setGlobalEval( tmp ); + } + + // Capture executables + if ( scripts ) { + j = 0; + while ( ( elem = tmp[ j++ ] ) ) { + if ( rscriptType.test( elem.type || "" ) ) { + scripts.push( elem ); + } + } + } + } + + return fragment; +} + + +var + rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)/; + +function returnTrue() { + return true; +} + +function returnFalse() { + return false; +} + +// Support: IE <=9 - 11+ +// focus() and blur() are asynchronous, except when they are no-op. +// So expect focus to be synchronous when the element is already active, +// and blur to be synchronous when the element is not already active. +// (focus and blur are always synchronous in other supported browsers, +// this just defines when we can count on it). +function expectSync( elem, type ) { + return ( elem === safeActiveElement() ) === ( type === "focus" ); +} + +// Support: IE <=9 only +// Accessing document.activeElement can throw unexpectedly +// https://bugs.jquery.com/ticket/13393 +function safeActiveElement() { + try { + return document.activeElement; + } catch ( err ) { } +} + +function on( elem, types, selector, data, fn, one ) { + var origFn, type; + + // Types can be a map of types/handlers + if ( typeof types === "object" ) { + + // ( types-Object, selector, data ) + if ( typeof selector !== "string" ) { + + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for ( type in types ) { + on( elem, type, selector, data, types[ type ], one ); + } + return elem; + } + + if ( data == null && fn == null ) { + + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if ( fn == null ) { + if ( typeof selector === "string" ) { + + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if ( fn === false ) { + fn = returnFalse; + } else if ( !fn ) { + return elem; + } + + if ( one === 1 ) { + origFn = fn; + fn = function( event ) { + + // Can use an empty set, since event contains the info + jQuery().off( event ); + return origFn.apply( this, arguments ); + }; + + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); + } + return elem.each( function() { + jQuery.event.add( this, types, fn, data, selector ); + } ); +} + +/* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ +jQuery.event = { + + global: {}, + + add: function( elem, types, handler, data, selector ) { + + var handleObjIn, eventHandle, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.get( elem ); + + // Only attach events to objects that accept data + if ( !acceptData( elem ) ) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Ensure that invalid selectors throw exceptions at attach time + // Evaluate against documentElement in case elem is a non-element node (e.g., document) + if ( selector ) { + jQuery.find.matchesSelector( documentElement, selector ); + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if ( !( events = elemData.events ) ) { + events = elemData.events = Object.create( null ); + } + if ( !( eventHandle = elemData.handle ) ) { + eventHandle = elemData.handle = function( e ) { + + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? + jQuery.event.dispatch.apply( elem, arguments ) : undefined; + }; + } + + // Handle multiple events separated by a space + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // There *must* be a type, no attaching namespace-only handlers + if ( !type ) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[ type ] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = ( selector ? special.delegateType : special.bindType ) || type; + + // Update special based on newly reset type + special = jQuery.event.special[ type ] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: selector && jQuery.expr.match.needsContext.test( selector ), + namespace: namespaces.join( "." ) + }, handleObjIn ); + + // Init the event handler queue if we're the first + if ( !( handlers = events[ type ] ) ) { + handlers = events[ type ] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( !special.setup || + special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if ( selector ) { + handlers.splice( handlers.delegateCount++, 0, handleObj ); + } else { + handlers.push( handleObj ); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[ type ] = true; + } + + }, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, selector, mappedTypes ) { + + var j, origCount, tmp, + events, t, handleObj, + special, handlers, type, namespaces, origType, + elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); + + if ( !elemData || !( events = elemData.events ) ) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; + t = types.length; + while ( t-- ) { + tmp = rtypenamespace.exec( types[ t ] ) || []; + type = origType = tmp[ 1 ]; + namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); + + // Unbind all events (on this namespace, if provided) for the element + if ( !type ) { + for ( type in events ) { + jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); + } + continue; + } + + special = jQuery.event.special[ type ] || {}; + type = ( selector ? special.delegateType : special.bindType ) || type; + handlers = events[ type ] || []; + tmp = tmp[ 2 ] && + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); + + // Remove matching events + origCount = j = handlers.length; + while ( j-- ) { + handleObj = handlers[ j ]; + + if ( ( mappedTypes || origType === handleObj.origType ) && + ( !handler || handler.guid === handleObj.guid ) && + ( !tmp || tmp.test( handleObj.namespace ) ) && + ( !selector || selector === handleObj.selector || + selector === "**" && handleObj.selector ) ) { + handlers.splice( j, 1 ); + + if ( handleObj.selector ) { + handlers.delegateCount--; + } + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if ( origCount && !handlers.length ) { + if ( !special.teardown || + special.teardown.call( elem, namespaces, elemData.handle ) === false ) { + + jQuery.removeEvent( elem, type, elemData.handle ); + } + + delete events[ type ]; + } + } + + // Remove data and the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + dataPriv.remove( elem, "handle events" ); + } + }, + + dispatch: function( nativeEvent ) { + + var i, j, ret, matched, handleObj, handlerQueue, + args = new Array( arguments.length ), + + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix( nativeEvent ), + + handlers = ( + dataPriv.get( this, "events" ) || Object.create( null ) + )[ event.type ] || [], + special = jQuery.event.special[ event.type ] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[ 0 ] = event; + + for ( i = 1; i < arguments.length; i++ ) { + args[ i ] = arguments[ i ]; + } + + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call( this, event, handlers ); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { + event.currentTarget = matched.elem; + + j = 0; + while ( ( handleObj = matched.handlers[ j++ ] ) && + !event.isImmediatePropagationStopped() ) { + + // If the event is namespaced, then each handler is only invoked if it is + // specially universal or its namespaces are a superset of the event's. + if ( !event.rnamespace || handleObj.namespace === false || + event.rnamespace.test( handleObj.namespace ) ) { + + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || + handleObj.handler ).apply( matched.elem, args ); + + if ( ret !== undefined ) { + if ( ( event.result = ret ) === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if ( special.postDispatch ) { + special.postDispatch.call( this, event ); + } + + return event.result; + }, + + handlers: function( event, handlers ) { + var i, handleObj, sel, matchedHandlers, matchedSelectors, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + if ( delegateCount && + + // Support: IE <=9 + // Black-hole SVG instance trees (trac-13180) + cur.nodeType && + + // Support: Firefox <=42 + // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) + // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click + // Support: IE 11 only + // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) + !( event.type === "click" && event.button >= 1 ) ) { + + for ( ; cur !== this; cur = cur.parentNode || this ) { + + // Don't check non-elements (#13208) + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { + matchedHandlers = []; + matchedSelectors = {}; + for ( i = 0; i < delegateCount; i++ ) { + handleObj = handlers[ i ]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + " "; + + if ( matchedSelectors[ sel ] === undefined ) { + matchedSelectors[ sel ] = handleObj.needsContext ? + jQuery( sel, this ).index( cur ) > -1 : + jQuery.find( sel, this, null, [ cur ] ).length; + } + if ( matchedSelectors[ sel ] ) { + matchedHandlers.push( handleObj ); + } + } + if ( matchedHandlers.length ) { + handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); + } + } + } + } + + // Add the remaining (directly-bound) handlers + cur = this; + if ( delegateCount < handlers.length ) { + handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); + } + + return handlerQueue; + }, + + addProp: function( name, hook ) { + Object.defineProperty( jQuery.Event.prototype, name, { + enumerable: true, + configurable: true, + + get: isFunction( hook ) ? + function() { + if ( this.originalEvent ) { + return hook( this.originalEvent ); + } + } : + function() { + if ( this.originalEvent ) { + return this.originalEvent[ name ]; + } + }, + + set: function( value ) { + Object.defineProperty( this, name, { + enumerable: true, + configurable: true, + writable: true, + value: value + } ); + } + } ); + }, + + fix: function( originalEvent ) { + return originalEvent[ jQuery.expando ] ? + originalEvent : + new jQuery.Event( originalEvent ); + }, + + special: { + load: { + + // Prevent triggered image.load events from bubbling to window.load + noBubble: true + }, + click: { + + // Utilize native event to ensure correct state for checkable inputs + setup: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Claim the first handler + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + // dataPriv.set( el, "click", ... ) + leverageNative( el, "click", returnTrue ); + } + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function( data ) { + + // For mutual compressibility with _default, replace `this` access with a local var. + // `|| data` is dead code meant only to preserve the variable through minification. + var el = this || data; + + // Force setup before triggering a click + if ( rcheckableType.test( el.type ) && + el.click && nodeName( el, "input" ) ) { + + leverageNative( el, "click" ); + } + + // Return non-false to allow normal event-path propagation + return true; + }, + + // For cross-browser consistency, suppress native .click() on links + // Also prevent it if we're currently inside a leveraged native-event stack + _default: function( event ) { + var target = event.target; + return rcheckableType.test( target.type ) && + target.click && nodeName( target, "input" ) && + dataPriv.get( target, "click" ) || + nodeName( target, "a" ); + } + }, + + beforeunload: { + postDispatch: function( event ) { + + // Support: Firefox 20+ + // Firefox doesn't alert if the returnValue field is not set. + if ( event.result !== undefined && event.originalEvent ) { + event.originalEvent.returnValue = event.result; + } + } + } + } +}; + +// Ensure the presence of an event listener that handles manually-triggered +// synthetic events by interrupting progress until reinvoked in response to +// *native* events that it fires directly, ensuring that state changes have +// already occurred before other listeners are invoked. +function leverageNative( el, type, expectSync ) { + + // Missing expectSync indicates a trigger call, which must force setup through jQuery.event.add + if ( !expectSync ) { + if ( dataPriv.get( el, type ) === undefined ) { + jQuery.event.add( el, type, returnTrue ); + } + return; + } + + // Register the controller as a special universal handler for all event namespaces + dataPriv.set( el, type, false ); + jQuery.event.add( el, type, { + namespace: false, + handler: function( event ) { + var notAsync, result, + saved = dataPriv.get( this, type ); + + if ( ( event.isTrigger & 1 ) && this[ type ] ) { + + // Interrupt processing of the outer synthetic .trigger()ed event + // Saved data should be false in such cases, but might be a leftover capture object + // from an async native handler (gh-4350) + if ( !saved.length ) { + + // Store arguments for use when handling the inner native event + // There will always be at least one argument (an event object), so this array + // will not be confused with a leftover capture object. + saved = slice.call( arguments ); + dataPriv.set( this, type, saved ); + + // Trigger the native event and capture its result + // Support: IE <=9 - 11+ + // focus() and blur() are asynchronous + notAsync = expectSync( this, type ); + this[ type ](); + result = dataPriv.get( this, type ); + if ( saved !== result || notAsync ) { + dataPriv.set( this, type, false ); + } else { + result = {}; + } + if ( saved !== result ) { + + // Cancel the outer synthetic event + event.stopImmediatePropagation(); + event.preventDefault(); + return result.value; + } + + // If this is an inner synthetic event for an event with a bubbling surrogate + // (focus or blur), assume that the surrogate already propagated from triggering the + // native event and prevent that from happening again here. + // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the + // bubbling surrogate propagates *after* the non-bubbling base), but that seems + // less bad than duplication. + } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { + event.stopPropagation(); + } + + // If this is a native event triggered above, everything is now in order + // Fire an inner synthetic event with the original arguments + } else if ( saved.length ) { + + // ...and capture the result + dataPriv.set( this, type, { + value: jQuery.event.trigger( + + // Support: IE <=9 - 11+ + // Extend with the prototype to reset the above stopImmediatePropagation() + jQuery.extend( saved[ 0 ], jQuery.Event.prototype ), + saved.slice( 1 ), + this + ) + } ); + + // Abort handling of the native event + event.stopImmediatePropagation(); + } + } + } ); +} + +jQuery.removeEvent = function( elem, type, handle ) { + + // This "if" is needed for plain objects + if ( elem.removeEventListener ) { + elem.removeEventListener( type, handle ); + } +}; + +jQuery.Event = function( src, props ) { + + // Allow instantiation without the 'new' keyword + if ( !( this instanceof jQuery.Event ) ) { + return new jQuery.Event( src, props ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = src.defaultPrevented || + src.defaultPrevented === undefined && + + // Support: Android <=2.3 only + src.returnValue === false ? + returnTrue : + returnFalse; + + // Create target properties + // Support: Safari <=6 - 7 only + // Target should not be a text node (#504, #13143) + this.target = ( src.target && src.target.nodeType === 3 ) ? + src.target.parentNode : + src.target; + + this.currentTarget = src.currentTarget; + this.relatedTarget = src.relatedTarget; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if ( props ) { + jQuery.extend( this, props ); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = src && src.timeStamp || Date.now(); + + // Mark it as fixed + this[ jQuery.expando ] = true; +}; + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + constructor: jQuery.Event, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + isSimulated: false, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if ( e && !this.isSimulated ) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + var e = this.originalEvent; + + this.isImmediatePropagationStopped = returnTrue; + + if ( e && !this.isSimulated ) { + e.stopImmediatePropagation(); + } + + this.stopPropagation(); + } +}; + +// Includes all common event props including KeyEvent and MouseEvent specific props +jQuery.each( { + altKey: true, + bubbles: true, + cancelable: true, + changedTouches: true, + ctrlKey: true, + detail: true, + eventPhase: true, + metaKey: true, + pageX: true, + pageY: true, + shiftKey: true, + view: true, + "char": true, + code: true, + charCode: true, + key: true, + keyCode: true, + button: true, + buttons: true, + clientX: true, + clientY: true, + offsetX: true, + offsetY: true, + pointerId: true, + pointerType: true, + screenX: true, + screenY: true, + targetTouches: true, + toElement: true, + touches: true, + + which: function( event ) { + var button = event.button; + + // Add which for key events + if ( event.which == null && rkeyEvent.test( event.type ) ) { + return event.charCode != null ? event.charCode : event.keyCode; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { + if ( button & 1 ) { + return 1; + } + + if ( button & 2 ) { + return 3; + } + + if ( button & 4 ) { + return 2; + } + + return 0; + } + + return event.which; + } +}, jQuery.event.addProp ); + +jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { + jQuery.event.special[ type ] = { + + // Utilize native event if possible so blur/focus sequence is correct + setup: function() { + + // Claim the first handler + // dataPriv.set( this, "focus", ... ) + // dataPriv.set( this, "blur", ... ) + leverageNative( this, type, expectSync ); + + // Return false to allow normal processing in the caller + return false; + }, + trigger: function() { + + // Force setup before trigger + leverageNative( this, type ); + + // Return non-false to allow normal event-path propagation + return true; + }, + + delegateType: delegateType + }; +} ); + +// Create mouseenter/leave events using mouseover/out and event-time checks +// so that event delegation works in jQuery. +// Do the same for pointerenter/pointerleave and pointerover/pointerout +// +// Support: Safari 7 only +// Safari sends mouseenter too often; see: +// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 +// for the description of the bug (it existed in older Chrome versions as well). +jQuery.each( { + mouseenter: "mouseover", + mouseleave: "mouseout", + pointerenter: "pointerover", + pointerleave: "pointerout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + delegateType: fix, + bindType: fix, + + handle: function( event ) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mouseenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply( this, arguments ); + event.type = fix; + } + return ret; + } + }; +} ); + +jQuery.fn.extend( { + + on: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn ); + }, + one: function( types, selector, data, fn ) { + return on( this, types, selector, data, fn, 1 ); + }, + off: function( types, selector, fn ) { + var handleObj, type; + if ( types && types.preventDefault && types.handleObj ) { + + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery( types.delegateTarget ).off( + handleObj.namespace ? + handleObj.origType + "." + handleObj.namespace : + handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if ( typeof types === "object" ) { + + // ( types-object [, selector] ) + for ( type in types ) { + this.off( type, selector, types[ type ] ); + } + return this; + } + if ( selector === false || typeof selector === "function" ) { + + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if ( fn === false ) { + fn = returnFalse; + } + return this.each( function() { + jQuery.event.remove( this, types, fn, selector ); + } ); + } +} ); + + +var + + // Support: IE <=10 - 11, Edge 12 - 13 only + // In IE/Edge using regex groups here causes severe slowdowns. + // See https://connect.microsoft.com/IE/feedback/details/1736512/ + rnoInnerhtml = /\s*$/g; + +// Prefer a tbody over its parent table for containing new rows +function manipulationTarget( elem, content ) { + if ( nodeName( elem, "table" ) && + nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { + + return jQuery( elem ).children( "tbody" )[ 0 ] || elem; + } + + return elem; +} + +// Replace/restore the type attribute of script elements for safe DOM manipulation +function disableScript( elem ) { + elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; + return elem; +} +function restoreScript( elem ) { + if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { + elem.type = elem.type.slice( 5 ); + } else { + elem.removeAttribute( "type" ); + } + + return elem; +} + +function cloneCopyEvent( src, dest ) { + var i, l, type, pdataOld, udataOld, udataCur, events; + + if ( dest.nodeType !== 1 ) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if ( dataPriv.hasData( src ) ) { + pdataOld = dataPriv.get( src ); + events = pdataOld.events; + + if ( events ) { + dataPriv.remove( dest, "handle events" ); + + for ( type in events ) { + for ( i = 0, l = events[ type ].length; i < l; i++ ) { + jQuery.event.add( dest, type, events[ type ][ i ] ); + } + } + } + } + + // 2. Copy user data + if ( dataUser.hasData( src ) ) { + udataOld = dataUser.access( src ); + udataCur = jQuery.extend( {}, udataOld ); + + dataUser.set( dest, udataCur ); + } +} + +// Fix IE bugs, see support tests +function fixInput( src, dest ) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if ( nodeName === "input" && rcheckableType.test( src.type ) ) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if ( nodeName === "input" || nodeName === "textarea" ) { + dest.defaultValue = src.defaultValue; + } +} + +function domManip( collection, args, callback, ignored ) { + + // Flatten any nested arrays + args = flat( args ); + + var fragment, first, scripts, hasScripts, node, doc, + i = 0, + l = collection.length, + iNoClone = l - 1, + value = args[ 0 ], + valueIsFunction = isFunction( value ); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( valueIsFunction || + ( l > 1 && typeof value === "string" && + !support.checkClone && rchecked.test( value ) ) ) { + return collection.each( function( index ) { + var self = collection.eq( index ); + if ( valueIsFunction ) { + args[ 0 ] = value.call( this, index, self.html() ); + } + domManip( self, args, callback, ignored ); + } ); + } + + if ( l ) { + fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); + first = fragment.firstChild; + + if ( fragment.childNodes.length === 1 ) { + fragment = first; + } + + // Require either new content or an interest in ignored elements to invoke the callback + if ( first || ignored ) { + scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); + hasScripts = scripts.length; + + // Use the original fragment for the last item + // instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for ( ; i < l; i++ ) { + node = fragment; + + if ( i !== iNoClone ) { + node = jQuery.clone( node, true, true ); + + // Keep references to cloned scripts for later restoration + if ( hasScripts ) { + + // Support: Android <=4.0 only, PhantomJS 1 only + // push.apply(_, arraylike) throws on ancient WebKit + jQuery.merge( scripts, getAll( node, "script" ) ); + } + } + + callback.call( collection[ i ], node, i ); + } + + if ( hasScripts ) { + doc = scripts[ scripts.length - 1 ].ownerDocument; + + // Reenable scripts + jQuery.map( scripts, restoreScript ); + + // Evaluate executable scripts on first document insertion + for ( i = 0; i < hasScripts; i++ ) { + node = scripts[ i ]; + if ( rscriptType.test( node.type || "" ) && + !dataPriv.access( node, "globalEval" ) && + jQuery.contains( doc, node ) ) { + + if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { + + // Optional AJAX dependency, but won't run scripts if not present + if ( jQuery._evalUrl && !node.noModule ) { + jQuery._evalUrl( node.src, { + nonce: node.nonce || node.getAttribute( "nonce" ) + }, doc ); + } + } else { + DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); + } + } + } + } + } + } + + return collection; +} + +function remove( elem, selector, keepData ) { + var node, + nodes = selector ? jQuery.filter( selector, elem ) : elem, + i = 0; + + for ( ; ( node = nodes[ i ] ) != null; i++ ) { + if ( !keepData && node.nodeType === 1 ) { + jQuery.cleanData( getAll( node ) ); + } + + if ( node.parentNode ) { + if ( keepData && isAttached( node ) ) { + setGlobalEval( getAll( node, "script" ) ); + } + node.parentNode.removeChild( node ); + } + } + + return elem; +} + +jQuery.extend( { + htmlPrefilter: function( html ) { + return html; + }, + + clone: function( elem, dataAndEvents, deepDataAndEvents ) { + var i, l, srcElements, destElements, + clone = elem.cloneNode( true ), + inPage = isAttached( elem ); + + // Fix IE cloning issues + if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && + !jQuery.isXMLDoc( elem ) ) { + + // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 + destElements = getAll( clone ); + srcElements = getAll( elem ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + fixInput( srcElements[ i ], destElements[ i ] ); + } + } + + // Copy the events from the original to the clone + if ( dataAndEvents ) { + if ( deepDataAndEvents ) { + srcElements = srcElements || getAll( elem ); + destElements = destElements || getAll( clone ); + + for ( i = 0, l = srcElements.length; i < l; i++ ) { + cloneCopyEvent( srcElements[ i ], destElements[ i ] ); + } + } else { + cloneCopyEvent( elem, clone ); + } + } + + // Preserve script evaluation history + destElements = getAll( clone, "script" ); + if ( destElements.length > 0 ) { + setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); + } + + // Return the cloned set + return clone; + }, + + cleanData: function( elems ) { + var data, elem, type, + special = jQuery.event.special, + i = 0; + + for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { + if ( acceptData( elem ) ) { + if ( ( data = elem[ dataPriv.expando ] ) ) { + if ( data.events ) { + for ( type in data.events ) { + if ( special[ type ] ) { + jQuery.event.remove( elem, type ); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent( elem, type, data.handle ); + } + } + } + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataPriv.expando ] = undefined; + } + if ( elem[ dataUser.expando ] ) { + + // Support: Chrome <=35 - 45+ + // Assign undefined instead of using delete, see Data#remove + elem[ dataUser.expando ] = undefined; + } + } + } + } +} ); + +jQuery.fn.extend( { + detach: function( selector ) { + return remove( this, selector, true ); + }, + + remove: function( selector ) { + return remove( this, selector ); + }, + + text: function( value ) { + return access( this, function( value ) { + return value === undefined ? + jQuery.text( this ) : + this.empty().each( function() { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + this.textContent = value; + } + } ); + }, null, value, arguments.length ); + }, + + append: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.appendChild( elem ); + } + } ); + }, + + prepend: function() { + return domManip( this, arguments, function( elem ) { + if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { + var target = manipulationTarget( this, elem ); + target.insertBefore( elem, target.firstChild ); + } + } ); + }, + + before: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this ); + } + } ); + }, + + after: function() { + return domManip( this, arguments, function( elem ) { + if ( this.parentNode ) { + this.parentNode.insertBefore( elem, this.nextSibling ); + } + } ); + }, + + empty: function() { + var elem, + i = 0; + + for ( ; ( elem = this[ i ] ) != null; i++ ) { + if ( elem.nodeType === 1 ) { + + // Prevent memory leaks + jQuery.cleanData( getAll( elem, false ) ); + + // Remove any remaining nodes + elem.textContent = ""; + } + } + + return this; + }, + + clone: function( dataAndEvents, deepDataAndEvents ) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map( function() { + return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); + } ); + }, + + html: function( value ) { + return access( this, function( value ) { + var elem = this[ 0 ] || {}, + i = 0, + l = this.length; + + if ( value === undefined && elem.nodeType === 1 ) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( typeof value === "string" && !rnoInnerhtml.test( value ) && + !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { + + value = jQuery.htmlPrefilter( value ); + + try { + for ( ; i < l; i++ ) { + elem = this[ i ] || {}; + + // Remove element nodes and prevent memory leaks + if ( elem.nodeType === 1 ) { + jQuery.cleanData( getAll( elem, false ) ); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch ( e ) {} + } + + if ( elem ) { + this.empty().append( value ); + } + }, null, value, arguments.length ); + }, + + replaceWith: function() { + var ignored = []; + + // Make the changes, replacing each non-ignored context element with the new content + return domManip( this, arguments, function( elem ) { + var parent = this.parentNode; + + if ( jQuery.inArray( this, ignored ) < 0 ) { + jQuery.cleanData( getAll( this ) ); + if ( parent ) { + parent.replaceChild( elem, this ); + } + } + + // Force callback invocation + }, ignored ); + } +} ); + +jQuery.each( { + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" +}, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var elems, + ret = [], + insert = jQuery( selector ), + last = insert.length - 1, + i = 0; + + for ( ; i <= last; i++ ) { + elems = i === last ? this : this.clone( true ); + jQuery( insert[ i ] )[ original ]( elems ); + + // Support: Android <=4.0 only, PhantomJS 1 only + // .get() because push.apply(_, arraylike) throws on ancient WebKit + push.apply( ret, elems.get() ); + } + + return this.pushStack( ret ); + }; +} ); +var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); + +var getStyles = function( elem ) { + + // Support: IE <=11 only, Firefox <=30 (#15098, #14150) + // IE throws on elements created in popups + // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" + var view = elem.ownerDocument.defaultView; + + if ( !view || !view.opener ) { + view = window; + } + + return view.getComputedStyle( elem ); + }; + +var swap = function( elem, options, callback ) { + var ret, name, + old = {}; + + // Remember the old values, and insert the new ones + for ( name in options ) { + old[ name ] = elem.style[ name ]; + elem.style[ name ] = options[ name ]; + } + + ret = callback.call( elem ); + + // Revert the old values + for ( name in options ) { + elem.style[ name ] = old[ name ]; + } + + return ret; +}; + + +var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); + + + +( function() { + + // Executing both pixelPosition & boxSizingReliable tests require only one layout + // so they're executed at the same time to save the second computation. + function computeStyleTests() { + + // This is a singleton, we need to execute it only once + if ( !div ) { + return; + } + + container.style.cssText = "position:absolute;left:-11111px;width:60px;" + + "margin-top:1px;padding:0;border:0"; + div.style.cssText = + "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + + "margin:auto;border:1px;padding:1px;" + + "width:60%;top:1%"; + documentElement.appendChild( container ).appendChild( div ); + + var divStyle = window.getComputedStyle( div ); + pixelPositionVal = divStyle.top !== "1%"; + + // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 + reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; + + // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 + // Some styles come back with percentage values, even though they shouldn't + div.style.right = "60%"; + pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; + + // Support: IE 9 - 11 only + // Detect misreporting of content dimensions for box-sizing:border-box elements + boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; + + // Support: IE 9 only + // Detect overflow:scroll screwiness (gh-3699) + // Support: Chrome <=64 + // Don't get tricked when zoom affects offsetWidth (gh-4029) + div.style.position = "absolute"; + scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; + + documentElement.removeChild( container ); + + // Nullify the div so it wouldn't be stored in the memory and + // it will also be a sign that checks already performed + div = null; + } + + function roundPixelMeasures( measure ) { + return Math.round( parseFloat( measure ) ); + } + + var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, + reliableTrDimensionsVal, reliableMarginLeftVal, + container = document.createElement( "div" ), + div = document.createElement( "div" ); + + // Finish early in limited (non-browser) environments + if ( !div.style ) { + return; + } + + // Support: IE <=9 - 11 only + // Style of cloned element affects source element cloned (#8908) + div.style.backgroundClip = "content-box"; + div.cloneNode( true ).style.backgroundClip = ""; + support.clearCloneStyle = div.style.backgroundClip === "content-box"; + + jQuery.extend( support, { + boxSizingReliable: function() { + computeStyleTests(); + return boxSizingReliableVal; + }, + pixelBoxStyles: function() { + computeStyleTests(); + return pixelBoxStylesVal; + }, + pixelPosition: function() { + computeStyleTests(); + return pixelPositionVal; + }, + reliableMarginLeft: function() { + computeStyleTests(); + return reliableMarginLeftVal; + }, + scrollboxSize: function() { + computeStyleTests(); + return scrollboxSizeVal; + }, + + // Support: IE 9 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Behavior in IE 9 is more subtle than in newer versions & it passes + // some versions of this test; make sure not to make it pass there! + reliableTrDimensions: function() { + var table, tr, trChild, trStyle; + if ( reliableTrDimensionsVal == null ) { + table = document.createElement( "table" ); + tr = document.createElement( "tr" ); + trChild = document.createElement( "div" ); + + table.style.cssText = "position:absolute;left:-11111px"; + tr.style.height = "1px"; + trChild.style.height = "9px"; + + documentElement + .appendChild( table ) + .appendChild( tr ) + .appendChild( trChild ); + + trStyle = window.getComputedStyle( tr ); + reliableTrDimensionsVal = parseInt( trStyle.height ) > 3; + + documentElement.removeChild( table ); + } + return reliableTrDimensionsVal; + } + } ); +} )(); + + +function curCSS( elem, name, computed ) { + var width, minWidth, maxWidth, ret, + + // Support: Firefox 51+ + // Retrieving style before computed somehow + // fixes an issue with getting wrong values + // on detached elements + style = elem.style; + + computed = computed || getStyles( elem ); + + // getPropertyValue is needed for: + // .css('filter') (IE 9 only, #12537) + // .css('--customProperty) (#3144) + if ( computed ) { + ret = computed.getPropertyValue( name ) || computed[ name ]; + + if ( ret === "" && !isAttached( elem ) ) { + ret = jQuery.style( elem, name ); + } + + // A tribute to the "awesome hack by Dean Edwards" + // Android Browser returns percentage for some values, + // but width seems to be reliably pixels. + // This is against the CSSOM draft spec: + // https://drafts.csswg.org/cssom/#resolved-values + if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { + + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret !== undefined ? + + // Support: IE <=9 - 11 only + // IE returns zIndex value as an integer. + ret + "" : + ret; +} + + +function addGetHookIf( conditionFn, hookFn ) { + + // Define the hook, we'll check on the first run if it's really needed. + return { + get: function() { + if ( conditionFn() ) { + + // Hook not needed (or it's not possible to use it due + // to missing dependency), remove it. + delete this.get; + return; + } + + // Hook needed; redefine it so that the support test is not executed again. + return ( this.get = hookFn ).apply( this, arguments ); + } + }; +} + + +var cssPrefixes = [ "Webkit", "Moz", "ms" ], + emptyStyle = document.createElement( "div" ).style, + vendorProps = {}; + +// Return a vendor-prefixed property or undefined +function vendorPropName( name ) { + + // Check for vendor prefixed names + var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), + i = cssPrefixes.length; + + while ( i-- ) { + name = cssPrefixes[ i ] + capName; + if ( name in emptyStyle ) { + return name; + } + } +} + +// Return a potentially-mapped jQuery.cssProps or vendor prefixed property +function finalPropName( name ) { + var final = jQuery.cssProps[ name ] || vendorProps[ name ]; + + if ( final ) { + return final; + } + if ( name in emptyStyle ) { + return name; + } + return vendorProps[ name ] = vendorPropName( name ) || name; +} + + +var + + // Swappable if display is none or starts with table + // except "table", "table-cell", or "table-caption" + // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rcustomProp = /^--/, + cssShow = { position: "absolute", visibility: "hidden", display: "block" }, + cssNormalTransform = { + letterSpacing: "0", + fontWeight: "400" + }; + +function setPositiveNumber( _elem, value, subtract ) { + + // Any relative (+/-) values have already been + // normalized at this point + var matches = rcssNum.exec( value ); + return matches ? + + // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : + value; +} + +function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { + var i = dimension === "width" ? 1 : 0, + extra = 0, + delta = 0; + + // Adjustment may not be necessary + if ( box === ( isBorderBox ? "border" : "content" ) ) { + return 0; + } + + for ( ; i < 4; i += 2 ) { + + // Both box models exclude margin + if ( box === "margin" ) { + delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); + } + + // If we get here with a content-box, we're seeking "padding" or "border" or "margin" + if ( !isBorderBox ) { + + // Add padding + delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + + // For "border" or "margin", add border + if ( box !== "padding" ) { + delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + + // But still keep track of it otherwise + } else { + extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + + // If we get here with a border-box (content + padding + border), we're seeking "content" or + // "padding" or "margin" + } else { + + // For "content", subtract padding + if ( box === "content" ) { + delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); + } + + // For "content" or "padding", subtract border + if ( box !== "margin" ) { + delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); + } + } + } + + // Account for positive content-box scroll gutter when requested by providing computedVal + if ( !isBorderBox && computedVal >= 0 ) { + + // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border + // Assuming integer scroll gutter, subtract the rest and round down + delta += Math.max( 0, Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + computedVal - + delta - + extra - + 0.5 + + // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter + // Use an explicit zero to avoid NaN (gh-3964) + ) ) || 0; + } + + return delta; +} + +function getWidthOrHeight( elem, dimension, extra ) { + + // Start with computed style + var styles = getStyles( elem ), + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). + // Fake content-box until we know it's needed to know the true value. + boxSizingNeeded = !support.boxSizingReliable() || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + valueIsBorderBox = isBorderBox, + + val = curCSS( elem, dimension, styles ), + offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); + + // Support: Firefox <=54 + // Return a confounding non-pixel value or feign ignorance, as appropriate. + if ( rnumnonpx.test( val ) ) { + if ( !extra ) { + return val; + } + val = "auto"; + } + + + // Support: IE 9 - 11 only + // Use offsetWidth/offsetHeight for when box sizing is unreliable. + // In those cases, the computed value can be trusted to be border-box. + if ( ( !support.boxSizingReliable() && isBorderBox || + + // Support: IE 10 - 11+, Edge 15 - 18+ + // IE/Edge misreport `getComputedStyle` of table rows with width/height + // set in CSS while `offset*` properties report correct values. + // Interestingly, in some cases IE 9 doesn't suffer from this issue. + !support.reliableTrDimensions() && nodeName( elem, "tr" ) || + + // Fall back to offsetWidth/offsetHeight when value is "auto" + // This happens for inline elements with no explicit setting (gh-3571) + val === "auto" || + + // Support: Android <=4.1 - 4.3 only + // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) + !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && + + // Make sure the element is visible & connected + elem.getClientRects().length ) { + + isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; + + // Where available, offsetWidth/offsetHeight approximate border box dimensions. + // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the + // retrieved value as a content box dimension. + valueIsBorderBox = offsetProp in elem; + if ( valueIsBorderBox ) { + val = elem[ offsetProp ]; + } + } + + // Normalize "" and auto + val = parseFloat( val ) || 0; + + // Adjust for the element's box model + return ( val + + boxModelAdjustment( + elem, + dimension, + extra || ( isBorderBox ? "border" : "content" ), + valueIsBorderBox, + styles, + + // Provide the current computed size to request scroll gutter calculation (gh-3589) + val + ) + ) + "px"; +} + +jQuery.extend( { + + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function( elem, computed ) { + if ( computed ) { + + // We should always get a number back from opacity + var ret = curCSS( elem, "opacity" ); + return ret === "" ? "1" : ret; + } + } + } + }, + + // Don't automatically add "px" to these possibly-unitless properties + cssNumber: { + "animationIterationCount": true, + "columnCount": true, + "fillOpacity": true, + "flexGrow": true, + "flexShrink": true, + "fontWeight": true, + "gridArea": true, + "gridColumn": true, + "gridColumnEnd": true, + "gridColumnStart": true, + "gridRow": true, + "gridRowEnd": true, + "gridRowStart": true, + "lineHeight": true, + "opacity": true, + "order": true, + "orphans": true, + "widows": true, + "zIndex": true, + "zoom": true + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: {}, + + // Get and set the style property on a DOM Node + style: function( elem, name, value, extra ) { + + // Don't set styles on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { + return; + } + + // Make sure that we're working with the right name + var ret, type, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ), + style = elem.style; + + // Make sure that we're working with the right name. We don't + // want to query the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Gets hook for the prefixed version, then unprefixed version + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // Check if we're setting a value + if ( value !== undefined ) { + type = typeof value; + + // Convert "+=" or "-=" to relative numbers (#7345) + if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { + value = adjustCSS( elem, name, ret ); + + // Fixes bug #9237 + type = "number"; + } + + // Make sure that null and NaN values aren't set (#7116) + if ( value == null || value !== value ) { + return; + } + + // If a number was passed in, add the unit (except for certain CSS properties) + // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append + // "px" to a few hardcoded values. + if ( type === "number" && !isCustomProp ) { + value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); + } + + // background-* props affect original clone's values + if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { + style[ name ] = "inherit"; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( !hooks || !( "set" in hooks ) || + ( value = hooks.set( elem, value, extra ) ) !== undefined ) { + + if ( isCustomProp ) { + style.setProperty( name, value ); + } else { + style[ name ] = value; + } + } + + } else { + + // If a hook was provided get the non-computed value from there + if ( hooks && "get" in hooks && + ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { + + return ret; + } + + // Otherwise just get the value from the style object + return style[ name ]; + } + }, + + css: function( elem, name, extra, styles ) { + var val, num, hooks, + origName = camelCase( name ), + isCustomProp = rcustomProp.test( name ); + + // Make sure that we're working with the right name. We don't + // want to modify the value if it is a CSS custom property + // since they are user-defined. + if ( !isCustomProp ) { + name = finalPropName( origName ); + } + + // Try prefixed name followed by the unprefixed name + hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; + + // If a hook was provided get the computed value from there + if ( hooks && "get" in hooks ) { + val = hooks.get( elem, true, extra ); + } + + // Otherwise, if a way to get the computed value exists, use that + if ( val === undefined ) { + val = curCSS( elem, name, styles ); + } + + // Convert "normal" to computed value + if ( val === "normal" && name in cssNormalTransform ) { + val = cssNormalTransform[ name ]; + } + + // Make numeric if forced or a qualifier was provided and val looks numeric + if ( extra === "" || extra ) { + num = parseFloat( val ); + return extra === true || isFinite( num ) ? num || 0 : val; + } + + return val; + } +} ); + +jQuery.each( [ "height", "width" ], function( _i, dimension ) { + jQuery.cssHooks[ dimension ] = { + get: function( elem, computed, extra ) { + if ( computed ) { + + // Certain elements can have dimension info if we invisibly show them + // but it must have a current display style that would benefit + return rdisplayswap.test( jQuery.css( elem, "display" ) ) && + + // Support: Safari 8+ + // Table columns in Safari have non-zero offsetWidth & zero + // getBoundingClientRect().width unless display is changed. + // Support: IE <=11 only + // Running getBoundingClientRect on a disconnected node + // in IE throws an error. + ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? + swap( elem, cssShow, function() { + return getWidthOrHeight( elem, dimension, extra ); + } ) : + getWidthOrHeight( elem, dimension, extra ); + } + }, + + set: function( elem, value, extra ) { + var matches, + styles = getStyles( elem ), + + // Only read styles.position if the test has a chance to fail + // to avoid forcing a reflow. + scrollboxSizeBuggy = !support.scrollboxSize() && + styles.position === "absolute", + + // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) + boxSizingNeeded = scrollboxSizeBuggy || extra, + isBorderBox = boxSizingNeeded && + jQuery.css( elem, "boxSizing", false, styles ) === "border-box", + subtract = extra ? + boxModelAdjustment( + elem, + dimension, + extra, + isBorderBox, + styles + ) : + 0; + + // Account for unreliable border-box dimensions by comparing offset* to computed and + // faking a content-box to get border and padding (gh-3699) + if ( isBorderBox && scrollboxSizeBuggy ) { + subtract -= Math.ceil( + elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - + parseFloat( styles[ dimension ] ) - + boxModelAdjustment( elem, dimension, "border", false, styles ) - + 0.5 + ); + } + + // Convert to pixels if value adjustment is needed + if ( subtract && ( matches = rcssNum.exec( value ) ) && + ( matches[ 3 ] || "px" ) !== "px" ) { + + elem.style[ dimension ] = value; + value = jQuery.css( elem, dimension ); + } + + return setPositiveNumber( elem, value, subtract ); + } + }; +} ); + +jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, + function( elem, computed ) { + if ( computed ) { + return ( parseFloat( curCSS( elem, "marginLeft" ) ) || + elem.getBoundingClientRect().left - + swap( elem, { marginLeft: 0 }, function() { + return elem.getBoundingClientRect().left; + } ) + ) + "px"; + } + } +); + +// These hooks are used by animate to expand properties +jQuery.each( { + margin: "", + padding: "", + border: "Width" +}, function( prefix, suffix ) { + jQuery.cssHooks[ prefix + suffix ] = { + expand: function( value ) { + var i = 0, + expanded = {}, + + // Assumes a single number if not a string + parts = typeof value === "string" ? value.split( " " ) : [ value ]; + + for ( ; i < 4; i++ ) { + expanded[ prefix + cssExpand[ i ] + suffix ] = + parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; + } + + return expanded; + } + }; + + if ( prefix !== "margin" ) { + jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; + } +} ); + +jQuery.fn.extend( { + css: function( name, value ) { + return access( this, function( elem, name, value ) { + var styles, len, + map = {}, + i = 0; + + if ( Array.isArray( name ) ) { + styles = getStyles( elem ); + len = name.length; + + for ( ; i < len; i++ ) { + map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); + } + + return map; + } + + return value !== undefined ? + jQuery.style( elem, name, value ) : + jQuery.css( elem, name ); + }, name, value, arguments.length > 1 ); + } +} ); + + +function Tween( elem, options, prop, end, easing ) { + return new Tween.prototype.init( elem, options, prop, end, easing ); +} +jQuery.Tween = Tween; + +Tween.prototype = { + constructor: Tween, + init: function( elem, options, prop, end, easing, unit ) { + this.elem = elem; + this.prop = prop; + this.easing = easing || jQuery.easing._default; + this.options = options; + this.start = this.now = this.cur(); + this.end = end; + this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); + }, + cur: function() { + var hooks = Tween.propHooks[ this.prop ]; + + return hooks && hooks.get ? + hooks.get( this ) : + Tween.propHooks._default.get( this ); + }, + run: function( percent ) { + var eased, + hooks = Tween.propHooks[ this.prop ]; + + if ( this.options.duration ) { + this.pos = eased = jQuery.easing[ this.easing ]( + percent, this.options.duration * percent, 0, 1, this.options.duration + ); + } else { + this.pos = eased = percent; + } + this.now = ( this.end - this.start ) * eased + this.start; + + if ( this.options.step ) { + this.options.step.call( this.elem, this.now, this ); + } + + if ( hooks && hooks.set ) { + hooks.set( this ); + } else { + Tween.propHooks._default.set( this ); + } + return this; + } +}; + +Tween.prototype.init.prototype = Tween.prototype; + +Tween.propHooks = { + _default: { + get: function( tween ) { + var result; + + // Use a property on the element directly when it is not a DOM element, + // or when there is no matching style property that exists. + if ( tween.elem.nodeType !== 1 || + tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { + return tween.elem[ tween.prop ]; + } + + // Passing an empty string as a 3rd parameter to .css will automatically + // attempt a parseFloat and fallback to a string if the parse fails. + // Simple values such as "10px" are parsed to Float; + // complex values such as "rotate(1rad)" are returned as-is. + result = jQuery.css( tween.elem, tween.prop, "" ); + + // Empty strings, null, undefined and "auto" are converted to 0. + return !result || result === "auto" ? 0 : result; + }, + set: function( tween ) { + + // Use step hook for back compat. + // Use cssHook if its there. + // Use .style if available and use plain properties where available. + if ( jQuery.fx.step[ tween.prop ] ) { + jQuery.fx.step[ tween.prop ]( tween ); + } else if ( tween.elem.nodeType === 1 && ( + jQuery.cssHooks[ tween.prop ] || + tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { + jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); + } else { + tween.elem[ tween.prop ] = tween.now; + } + } + } +}; + +// Support: IE <=9 only +// Panic based approach to setting things on disconnected nodes +Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { + set: function( tween ) { + if ( tween.elem.nodeType && tween.elem.parentNode ) { + tween.elem[ tween.prop ] = tween.now; + } + } +}; + +jQuery.easing = { + linear: function( p ) { + return p; + }, + swing: function( p ) { + return 0.5 - Math.cos( p * Math.PI ) / 2; + }, + _default: "swing" +}; + +jQuery.fx = Tween.prototype.init; + +// Back compat <1.8 extension point +jQuery.fx.step = {}; + + + + +var + fxNow, inProgress, + rfxtypes = /^(?:toggle|show|hide)$/, + rrun = /queueHooks$/; + +function schedule() { + if ( inProgress ) { + if ( document.hidden === false && window.requestAnimationFrame ) { + window.requestAnimationFrame( schedule ); + } else { + window.setTimeout( schedule, jQuery.fx.interval ); + } + + jQuery.fx.tick(); + } +} + +// Animations created synchronously will run synchronously +function createFxNow() { + window.setTimeout( function() { + fxNow = undefined; + } ); + return ( fxNow = Date.now() ); +} + +// Generate parameters to create a standard animation +function genFx( type, includeWidth ) { + var which, + i = 0, + attrs = { height: type }; + + // If we include width, step value is 1 to do all cssExpand values, + // otherwise step value is 2 to skip over Left and Right + includeWidth = includeWidth ? 1 : 0; + for ( ; i < 4; i += 2 - includeWidth ) { + which = cssExpand[ i ]; + attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; + } + + if ( includeWidth ) { + attrs.opacity = attrs.width = type; + } + + return attrs; +} + +function createTween( value, prop, animation ) { + var tween, + collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), + index = 0, + length = collection.length; + for ( ; index < length; index++ ) { + if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { + + // We're done with this property + return tween; + } + } +} + +function defaultPrefilter( elem, props, opts ) { + var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, + isBox = "width" in props || "height" in props, + anim = this, + orig = {}, + style = elem.style, + hidden = elem.nodeType && isHiddenWithinTree( elem ), + dataShow = dataPriv.get( elem, "fxshow" ); + + // Queue-skipping animations hijack the fx hooks + if ( !opts.queue ) { + hooks = jQuery._queueHooks( elem, "fx" ); + if ( hooks.unqueued == null ) { + hooks.unqueued = 0; + oldfire = hooks.empty.fire; + hooks.empty.fire = function() { + if ( !hooks.unqueued ) { + oldfire(); + } + }; + } + hooks.unqueued++; + + anim.always( function() { + + // Ensure the complete handler is called before this completes + anim.always( function() { + hooks.unqueued--; + if ( !jQuery.queue( elem, "fx" ).length ) { + hooks.empty.fire(); + } + } ); + } ); + } + + // Detect show/hide animations + for ( prop in props ) { + value = props[ prop ]; + if ( rfxtypes.test( value ) ) { + delete props[ prop ]; + toggle = toggle || value === "toggle"; + if ( value === ( hidden ? "hide" : "show" ) ) { + + // Pretend to be hidden if this is a "show" and + // there is still data from a stopped show/hide + if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { + hidden = true; + + // Ignore all other no-op show/hide data + } else { + continue; + } + } + orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); + } + } + + // Bail out if this is a no-op like .hide().hide() + propTween = !jQuery.isEmptyObject( props ); + if ( !propTween && jQuery.isEmptyObject( orig ) ) { + return; + } + + // Restrict "overflow" and "display" styles during box animations + if ( isBox && elem.nodeType === 1 ) { + + // Support: IE <=9 - 11, Edge 12 - 15 + // Record all 3 overflow attributes because IE does not infer the shorthand + // from identically-valued overflowX and overflowY and Edge just mirrors + // the overflowX value there. + opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; + + // Identify a display type, preferring old show/hide data over the CSS cascade + restoreDisplay = dataShow && dataShow.display; + if ( restoreDisplay == null ) { + restoreDisplay = dataPriv.get( elem, "display" ); + } + display = jQuery.css( elem, "display" ); + if ( display === "none" ) { + if ( restoreDisplay ) { + display = restoreDisplay; + } else { + + // Get nonempty value(s) by temporarily forcing visibility + showHide( [ elem ], true ); + restoreDisplay = elem.style.display || restoreDisplay; + display = jQuery.css( elem, "display" ); + showHide( [ elem ] ); + } + } + + // Animate inline elements as inline-block + if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { + if ( jQuery.css( elem, "float" ) === "none" ) { + + // Restore the original display value at the end of pure show/hide animations + if ( !propTween ) { + anim.done( function() { + style.display = restoreDisplay; + } ); + if ( restoreDisplay == null ) { + display = style.display; + restoreDisplay = display === "none" ? "" : display; + } + } + style.display = "inline-block"; + } + } + } + + if ( opts.overflow ) { + style.overflow = "hidden"; + anim.always( function() { + style.overflow = opts.overflow[ 0 ]; + style.overflowX = opts.overflow[ 1 ]; + style.overflowY = opts.overflow[ 2 ]; + } ); + } + + // Implement show/hide animations + propTween = false; + for ( prop in orig ) { + + // General show/hide setup for this element animation + if ( !propTween ) { + if ( dataShow ) { + if ( "hidden" in dataShow ) { + hidden = dataShow.hidden; + } + } else { + dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); + } + + // Store hidden/visible for toggle so `.stop().toggle()` "reverses" + if ( toggle ) { + dataShow.hidden = !hidden; + } + + // Show elements before animating them + if ( hidden ) { + showHide( [ elem ], true ); + } + + /* eslint-disable no-loop-func */ + + anim.done( function() { + + /* eslint-enable no-loop-func */ + + // The final step of a "hide" animation is actually hiding the element + if ( !hidden ) { + showHide( [ elem ] ); + } + dataPriv.remove( elem, "fxshow" ); + for ( prop in orig ) { + jQuery.style( elem, prop, orig[ prop ] ); + } + } ); + } + + // Per-property setup + propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); + if ( !( prop in dataShow ) ) { + dataShow[ prop ] = propTween.start; + if ( hidden ) { + propTween.end = propTween.start; + propTween.start = 0; + } + } + } +} + +function propFilter( props, specialEasing ) { + var index, name, easing, value, hooks; + + // camelCase, specialEasing and expand cssHook pass + for ( index in props ) { + name = camelCase( index ); + easing = specialEasing[ name ]; + value = props[ index ]; + if ( Array.isArray( value ) ) { + easing = value[ 1 ]; + value = props[ index ] = value[ 0 ]; + } + + if ( index !== name ) { + props[ name ] = value; + delete props[ index ]; + } + + hooks = jQuery.cssHooks[ name ]; + if ( hooks && "expand" in hooks ) { + value = hooks.expand( value ); + delete props[ name ]; + + // Not quite $.extend, this won't overwrite existing keys. + // Reusing 'index' because we have the correct "name" + for ( index in value ) { + if ( !( index in props ) ) { + props[ index ] = value[ index ]; + specialEasing[ index ] = easing; + } + } + } else { + specialEasing[ name ] = easing; + } + } +} + +function Animation( elem, properties, options ) { + var result, + stopped, + index = 0, + length = Animation.prefilters.length, + deferred = jQuery.Deferred().always( function() { + + // Don't match elem in the :animated selector + delete tick.elem; + } ), + tick = function() { + if ( stopped ) { + return false; + } + var currentTime = fxNow || createFxNow(), + remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), + + // Support: Android 2.3 only + // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) + temp = remaining / animation.duration || 0, + percent = 1 - temp, + index = 0, + length = animation.tweens.length; + + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( percent ); + } + + deferred.notifyWith( elem, [ animation, percent, remaining ] ); + + // If there's more to do, yield + if ( percent < 1 && length ) { + return remaining; + } + + // If this was an empty animation, synthesize a final progress notification + if ( !length ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + } + + // Resolve the animation and report its conclusion + deferred.resolveWith( elem, [ animation ] ); + return false; + }, + animation = deferred.promise( { + elem: elem, + props: jQuery.extend( {}, properties ), + opts: jQuery.extend( true, { + specialEasing: {}, + easing: jQuery.easing._default + }, options ), + originalProperties: properties, + originalOptions: options, + startTime: fxNow || createFxNow(), + duration: options.duration, + tweens: [], + createTween: function( prop, end ) { + var tween = jQuery.Tween( elem, animation.opts, prop, end, + animation.opts.specialEasing[ prop ] || animation.opts.easing ); + animation.tweens.push( tween ); + return tween; + }, + stop: function( gotoEnd ) { + var index = 0, + + // If we are going to the end, we want to run all the tweens + // otherwise we skip this part + length = gotoEnd ? animation.tweens.length : 0; + if ( stopped ) { + return this; + } + stopped = true; + for ( ; index < length; index++ ) { + animation.tweens[ index ].run( 1 ); + } + + // Resolve when we played the last frame; otherwise, reject + if ( gotoEnd ) { + deferred.notifyWith( elem, [ animation, 1, 0 ] ); + deferred.resolveWith( elem, [ animation, gotoEnd ] ); + } else { + deferred.rejectWith( elem, [ animation, gotoEnd ] ); + } + return this; + } + } ), + props = animation.props; + + propFilter( props, animation.opts.specialEasing ); + + for ( ; index < length; index++ ) { + result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); + if ( result ) { + if ( isFunction( result.stop ) ) { + jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = + result.stop.bind( result ); + } + return result; + } + } + + jQuery.map( props, createTween, animation ); + + if ( isFunction( animation.opts.start ) ) { + animation.opts.start.call( elem, animation ); + } + + // Attach callbacks from options + animation + .progress( animation.opts.progress ) + .done( animation.opts.done, animation.opts.complete ) + .fail( animation.opts.fail ) + .always( animation.opts.always ); + + jQuery.fx.timer( + jQuery.extend( tick, { + elem: elem, + anim: animation, + queue: animation.opts.queue + } ) + ); + + return animation; +} + +jQuery.Animation = jQuery.extend( Animation, { + + tweeners: { + "*": [ function( prop, value ) { + var tween = this.createTween( prop, value ); + adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); + return tween; + } ] + }, + + tweener: function( props, callback ) { + if ( isFunction( props ) ) { + callback = props; + props = [ "*" ]; + } else { + props = props.match( rnothtmlwhite ); + } + + var prop, + index = 0, + length = props.length; + + for ( ; index < length; index++ ) { + prop = props[ index ]; + Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; + Animation.tweeners[ prop ].unshift( callback ); + } + }, + + prefilters: [ defaultPrefilter ], + + prefilter: function( callback, prepend ) { + if ( prepend ) { + Animation.prefilters.unshift( callback ); + } else { + Animation.prefilters.push( callback ); + } + } +} ); + +jQuery.speed = function( speed, easing, fn ) { + var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { + complete: fn || !fn && easing || + isFunction( speed ) && speed, + duration: speed, + easing: fn && easing || easing && !isFunction( easing ) && easing + }; + + // Go to the end state if fx are off + if ( jQuery.fx.off ) { + opt.duration = 0; + + } else { + if ( typeof opt.duration !== "number" ) { + if ( opt.duration in jQuery.fx.speeds ) { + opt.duration = jQuery.fx.speeds[ opt.duration ]; + + } else { + opt.duration = jQuery.fx.speeds._default; + } + } + } + + // Normalize opt.queue - true/undefined/null -> "fx" + if ( opt.queue == null || opt.queue === true ) { + opt.queue = "fx"; + } + + // Queueing + opt.old = opt.complete; + + opt.complete = function() { + if ( isFunction( opt.old ) ) { + opt.old.call( this ); + } + + if ( opt.queue ) { + jQuery.dequeue( this, opt.queue ); + } + }; + + return opt; +}; + +jQuery.fn.extend( { + fadeTo: function( speed, to, easing, callback ) { + + // Show any hidden elements after setting opacity to 0 + return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() + + // Animate to the value specified + .end().animate( { opacity: to }, speed, easing, callback ); + }, + animate: function( prop, speed, easing, callback ) { + var empty = jQuery.isEmptyObject( prop ), + optall = jQuery.speed( speed, easing, callback ), + doAnimation = function() { + + // Operate on a copy of prop so per-property easing won't be lost + var anim = Animation( this, jQuery.extend( {}, prop ), optall ); + + // Empty animations, or finishing resolves immediately + if ( empty || dataPriv.get( this, "finish" ) ) { + anim.stop( true ); + } + }; + doAnimation.finish = doAnimation; + + return empty || optall.queue === false ? + this.each( doAnimation ) : + this.queue( optall.queue, doAnimation ); + }, + stop: function( type, clearQueue, gotoEnd ) { + var stopQueue = function( hooks ) { + var stop = hooks.stop; + delete hooks.stop; + stop( gotoEnd ); + }; + + if ( typeof type !== "string" ) { + gotoEnd = clearQueue; + clearQueue = type; + type = undefined; + } + if ( clearQueue ) { + this.queue( type || "fx", [] ); + } + + return this.each( function() { + var dequeue = true, + index = type != null && type + "queueHooks", + timers = jQuery.timers, + data = dataPriv.get( this ); + + if ( index ) { + if ( data[ index ] && data[ index ].stop ) { + stopQueue( data[ index ] ); + } + } else { + for ( index in data ) { + if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { + stopQueue( data[ index ] ); + } + } + } + + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && + ( type == null || timers[ index ].queue === type ) ) { + + timers[ index ].anim.stop( gotoEnd ); + dequeue = false; + timers.splice( index, 1 ); + } + } + + // Start the next in the queue if the last step wasn't forced. + // Timers currently will call their complete callbacks, which + // will dequeue but only if they were gotoEnd. + if ( dequeue || !gotoEnd ) { + jQuery.dequeue( this, type ); + } + } ); + }, + finish: function( type ) { + if ( type !== false ) { + type = type || "fx"; + } + return this.each( function() { + var index, + data = dataPriv.get( this ), + queue = data[ type + "queue" ], + hooks = data[ type + "queueHooks" ], + timers = jQuery.timers, + length = queue ? queue.length : 0; + + // Enable finishing flag on private data + data.finish = true; + + // Empty the queue first + jQuery.queue( this, type, [] ); + + if ( hooks && hooks.stop ) { + hooks.stop.call( this, true ); + } + + // Look for any active animations, and finish them + for ( index = timers.length; index--; ) { + if ( timers[ index ].elem === this && timers[ index ].queue === type ) { + timers[ index ].anim.stop( true ); + timers.splice( index, 1 ); + } + } + + // Look for any animations in the old queue and finish them + for ( index = 0; index < length; index++ ) { + if ( queue[ index ] && queue[ index ].finish ) { + queue[ index ].finish.call( this ); + } + } + + // Turn off finishing flag + delete data.finish; + } ); + } +} ); + +jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { + var cssFn = jQuery.fn[ name ]; + jQuery.fn[ name ] = function( speed, easing, callback ) { + return speed == null || typeof speed === "boolean" ? + cssFn.apply( this, arguments ) : + this.animate( genFx( name, true ), speed, easing, callback ); + }; +} ); + +// Generate shortcuts for custom animations +jQuery.each( { + slideDown: genFx( "show" ), + slideUp: genFx( "hide" ), + slideToggle: genFx( "toggle" ), + fadeIn: { opacity: "show" }, + fadeOut: { opacity: "hide" }, + fadeToggle: { opacity: "toggle" } +}, function( name, props ) { + jQuery.fn[ name ] = function( speed, easing, callback ) { + return this.animate( props, speed, easing, callback ); + }; +} ); + +jQuery.timers = []; +jQuery.fx.tick = function() { + var timer, + i = 0, + timers = jQuery.timers; + + fxNow = Date.now(); + + for ( ; i < timers.length; i++ ) { + timer = timers[ i ]; + + // Run the timer and safely remove it when done (allowing for external removal) + if ( !timer() && timers[ i ] === timer ) { + timers.splice( i--, 1 ); + } + } + + if ( !timers.length ) { + jQuery.fx.stop(); + } + fxNow = undefined; +}; + +jQuery.fx.timer = function( timer ) { + jQuery.timers.push( timer ); + jQuery.fx.start(); +}; + +jQuery.fx.interval = 13; +jQuery.fx.start = function() { + if ( inProgress ) { + return; + } + + inProgress = true; + schedule(); +}; + +jQuery.fx.stop = function() { + inProgress = null; +}; + +jQuery.fx.speeds = { + slow: 600, + fast: 200, + + // Default speed + _default: 400 +}; + + +// Based off of the plugin by Clint Helfers, with permission. +// https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ +jQuery.fn.delay = function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; + type = type || "fx"; + + return this.queue( type, function( next, hooks ) { + var timeout = window.setTimeout( next, time ); + hooks.stop = function() { + window.clearTimeout( timeout ); + }; + } ); +}; + + +( function() { + var input = document.createElement( "input" ), + select = document.createElement( "select" ), + opt = select.appendChild( document.createElement( "option" ) ); + + input.type = "checkbox"; + + // Support: Android <=4.3 only + // Default value for a checkbox should be "on" + support.checkOn = input.value !== ""; + + // Support: IE <=11 only + // Must access selectedIndex to make default options select + support.optSelected = opt.selected; + + // Support: IE <=11 only + // An input loses its value after becoming a radio + input = document.createElement( "input" ); + input.value = "t"; + input.type = "radio"; + support.radioValue = input.value === "t"; +} )(); + + +var boolHook, + attrHandle = jQuery.expr.attrHandle; + +jQuery.fn.extend( { + attr: function( name, value ) { + return access( this, jQuery.attr, name, value, arguments.length > 1 ); + }, + + removeAttr: function( name ) { + return this.each( function() { + jQuery.removeAttr( this, name ); + } ); + } +} ); + +jQuery.extend( { + attr: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set attributes on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + // Fallback to prop when attributes are not supported + if ( typeof elem.getAttribute === "undefined" ) { + return jQuery.prop( elem, name, value ); + } + + // Attribute hooks are determined by the lowercase version + // Grab necessary hook if one is defined + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + hooks = jQuery.attrHooks[ name.toLowerCase() ] || + ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); + } + + if ( value !== undefined ) { + if ( value === null ) { + jQuery.removeAttr( elem, name ); + return; + } + + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + elem.setAttribute( name, value + "" ); + return value; + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + ret = jQuery.find.attr( elem, name ); + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + }, + + attrHooks: { + type: { + set: function( elem, value ) { + if ( !support.radioValue && value === "radio" && + nodeName( elem, "input" ) ) { + var val = elem.value; + elem.setAttribute( "type", value ); + if ( val ) { + elem.value = val; + } + return value; + } + } + } + }, + + removeAttr: function( elem, value ) { + var name, + i = 0, + + // Attribute names can contain non-HTML whitespace characters + // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 + attrNames = value && value.match( rnothtmlwhite ); + + if ( attrNames && elem.nodeType === 1 ) { + while ( ( name = attrNames[ i++ ] ) ) { + elem.removeAttribute( name ); + } + } + } +} ); + +// Hooks for boolean attributes +boolHook = { + set: function( elem, value, name ) { + if ( value === false ) { + + // Remove boolean attributes when set to false + jQuery.removeAttr( elem, name ); + } else { + elem.setAttribute( name, name ); + } + return name; + } +}; + +jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { + var getter = attrHandle[ name ] || jQuery.find.attr; + + attrHandle[ name ] = function( elem, name, isXML ) { + var ret, handle, + lowercaseName = name.toLowerCase(); + + if ( !isXML ) { + + // Avoid an infinite loop by temporarily removing this function from the getter + handle = attrHandle[ lowercaseName ]; + attrHandle[ lowercaseName ] = ret; + ret = getter( elem, name, isXML ) != null ? + lowercaseName : + null; + attrHandle[ lowercaseName ] = handle; + } + return ret; + }; +} ); + + + + +var rfocusable = /^(?:input|select|textarea|button)$/i, + rclickable = /^(?:a|area)$/i; + +jQuery.fn.extend( { + prop: function( name, value ) { + return access( this, jQuery.prop, name, value, arguments.length > 1 ); + }, + + removeProp: function( name ) { + return this.each( function() { + delete this[ jQuery.propFix[ name ] || name ]; + } ); + } +} ); + +jQuery.extend( { + prop: function( elem, name, value ) { + var ret, hooks, + nType = elem.nodeType; + + // Don't get/set properties on text, comment and attribute nodes + if ( nType === 3 || nType === 8 || nType === 2 ) { + return; + } + + if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { + + // Fix name and attach hooks + name = jQuery.propFix[ name ] || name; + hooks = jQuery.propHooks[ name ]; + } + + if ( value !== undefined ) { + if ( hooks && "set" in hooks && + ( ret = hooks.set( elem, value, name ) ) !== undefined ) { + return ret; + } + + return ( elem[ name ] = value ); + } + + if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { + return ret; + } + + return elem[ name ]; + }, + + propHooks: { + tabIndex: { + get: function( elem ) { + + // Support: IE <=9 - 11 only + // elem.tabIndex doesn't always return the + // correct value when it hasn't been explicitly set + // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + // Use proper attribute retrieval(#12072) + var tabindex = jQuery.find.attr( elem, "tabindex" ); + + if ( tabindex ) { + return parseInt( tabindex, 10 ); + } + + if ( + rfocusable.test( elem.nodeName ) || + rclickable.test( elem.nodeName ) && + elem.href + ) { + return 0; + } + + return -1; + } + } + }, + + propFix: { + "for": "htmlFor", + "class": "className" + } +} ); + +// Support: IE <=11 only +// Accessing the selectedIndex property +// forces the browser to respect setting selected +// on the option +// The getter ensures a default option is selected +// when in an optgroup +// eslint rule "no-unused-expressions" is disabled for this code +// since it considers such accessions noop +if ( !support.optSelected ) { + jQuery.propHooks.selected = { + get: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent && parent.parentNode ) { + parent.parentNode.selectedIndex; + } + return null; + }, + set: function( elem ) { + + /* eslint no-unused-expressions: "off" */ + + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + }; +} + +jQuery.each( [ + "tabIndex", + "readOnly", + "maxLength", + "cellSpacing", + "cellPadding", + "rowSpan", + "colSpan", + "useMap", + "frameBorder", + "contentEditable" +], function() { + jQuery.propFix[ this.toLowerCase() ] = this; +} ); + + + + + // Strip and collapse whitespace according to HTML spec + // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace + function stripAndCollapse( value ) { + var tokens = value.match( rnothtmlwhite ) || []; + return tokens.join( " " ); + } + + +function getClass( elem ) { + return elem.getAttribute && elem.getAttribute( "class" ) || ""; +} + +function classesToArray( value ) { + if ( Array.isArray( value ) ) { + return value; + } + if ( typeof value === "string" ) { + return value.match( rnothtmlwhite ) || []; + } + return []; +} + +jQuery.fn.extend( { + addClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + if ( cur.indexOf( " " + clazz + " " ) < 0 ) { + cur += clazz + " "; + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + var classes, elem, cur, curValue, clazz, j, finalValue, + i = 0; + + if ( isFunction( value ) ) { + return this.each( function( j ) { + jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); + } ); + } + + if ( !arguments.length ) { + return this.attr( "class", "" ); + } + + classes = classesToArray( value ); + + if ( classes.length ) { + while ( ( elem = this[ i++ ] ) ) { + curValue = getClass( elem ); + + // This expression is here for better compressibility (see addClass) + cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + + if ( cur ) { + j = 0; + while ( ( clazz = classes[ j++ ] ) ) { + + // Remove *all* instances + while ( cur.indexOf( " " + clazz + " " ) > -1 ) { + cur = cur.replace( " " + clazz + " ", " " ); + } + } + + // Only assign if different to avoid unneeded rendering. + finalValue = stripAndCollapse( cur ); + if ( curValue !== finalValue ) { + elem.setAttribute( "class", finalValue ); + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, + isValidValue = type === "string" || Array.isArray( value ); + + if ( typeof stateVal === "boolean" && isValidValue ) { + return stateVal ? this.addClass( value ) : this.removeClass( value ); + } + + if ( isFunction( value ) ) { + return this.each( function( i ) { + jQuery( this ).toggleClass( + value.call( this, i, getClass( this ), stateVal ), + stateVal + ); + } ); + } + + return this.each( function() { + var className, i, self, classNames; + + if ( isValidValue ) { + + // Toggle individual class names + i = 0; + self = jQuery( this ); + classNames = classesToArray( value ); + + while ( ( className = classNames[ i++ ] ) ) { + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } + } + + // Toggle whole class name + } else if ( value === undefined || type === "boolean" ) { + className = getClass( this ); + if ( className ) { + + // Store className if set + dataPriv.set( this, "__className__", className ); + } + + // If the element has a class name or if we're passed `false`, + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + if ( this.setAttribute ) { + this.setAttribute( "class", + className || value === false ? + "" : + dataPriv.get( this, "__className__" ) || "" + ); + } + } + } ); + }, + + hasClass: function( selector ) { + var className, elem, + i = 0; + + className = " " + selector + " "; + while ( ( elem = this[ i++ ] ) ) { + if ( elem.nodeType === 1 && + ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { + return true; + } + } + + return false; + } +} ); + + + + +var rreturn = /\r/g; + +jQuery.fn.extend( { + val: function( value ) { + var hooks, ret, valueIsFunction, + elem = this[ 0 ]; + + if ( !arguments.length ) { + if ( elem ) { + hooks = jQuery.valHooks[ elem.type ] || + jQuery.valHooks[ elem.nodeName.toLowerCase() ]; + + if ( hooks && + "get" in hooks && + ( ret = hooks.get( elem, "value" ) ) !== undefined + ) { + return ret; + } + + ret = elem.value; + + // Handle most common string cases + if ( typeof ret === "string" ) { + return ret.replace( rreturn, "" ); + } + + // Handle cases where value is null/undef or number + return ret == null ? "" : ret; + } + + return; + } + + valueIsFunction = isFunction( value ); + + return this.each( function( i ) { + var val; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( valueIsFunction ) { + val = value.call( this, i, jQuery( this ).val() ); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if ( val == null ) { + val = ""; + + } else if ( typeof val === "number" ) { + val += ""; + + } else if ( Array.isArray( val ) ) { + val = jQuery.map( val, function( value ) { + return value == null ? "" : value + ""; + } ); + } + + hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; + + // If set returns undefined, fall back to normal setting + if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { + this.value = val; + } + } ); + } +} ); + +jQuery.extend( { + valHooks: { + option: { + get: function( elem ) { + + var val = jQuery.find.attr( elem, "value" ); + return val != null ? + val : + + // Support: IE <=10 - 11 only + // option.text throws exceptions (#14686, #14858) + // Strip and collapse whitespace + // https://html.spec.whatwg.org/#strip-and-collapse-whitespace + stripAndCollapse( jQuery.text( elem ) ); + } + }, + select: { + get: function( elem ) { + var value, option, i, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === "select-one", + values = one ? null : [], + max = one ? index + 1 : options.length; + + if ( index < 0 ) { + i = max; + + } else { + i = one ? index : 0; + } + + // Loop through all the selected options + for ( ; i < max; i++ ) { + option = options[ i ]; + + // Support: IE <=9 only + // IE8-9 doesn't update selected after form reset (#2551) + if ( ( option.selected || i === index ) && + + // Don't return options that are disabled or in a disabled optgroup + !option.disabled && + ( !option.parentNode.disabled || + !nodeName( option.parentNode, "optgroup" ) ) ) { + + // Get the specific value for the option + value = jQuery( option ).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + }, + + set: function( elem, value ) { + var optionSet, option, + options = elem.options, + values = jQuery.makeArray( value ), + i = options.length; + + while ( i-- ) { + option = options[ i ]; + + /* eslint-disable no-cond-assign */ + + if ( option.selected = + jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 + ) { + optionSet = true; + } + + /* eslint-enable no-cond-assign */ + } + + // Force browsers to behave consistently when non-matching value is set + if ( !optionSet ) { + elem.selectedIndex = -1; + } + return values; + } + } + } +} ); + +// Radios and checkboxes getter/setter +jQuery.each( [ "radio", "checkbox" ], function() { + jQuery.valHooks[ this ] = { + set: function( elem, value ) { + if ( Array.isArray( value ) ) { + return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); + } + } + }; + if ( !support.checkOn ) { + jQuery.valHooks[ this ].get = function( elem ) { + return elem.getAttribute( "value" ) === null ? "on" : elem.value; + }; + } +} ); + + + + +// Return jQuery for attributes-only inclusion + + +support.focusin = "onfocusin" in window; + + +var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + stopPropagationCallback = function( e ) { + e.stopPropagation(); + }; + +jQuery.extend( jQuery.event, { + + trigger: function( event, data, elem, onlyHandlers ) { + + var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, + eventPath = [ elem || document ], + type = hasOwn.call( event, "type" ) ? event.type : event, + namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; + + cur = lastElement = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { + return; + } + + if ( type.indexOf( "." ) > -1 ) { + + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split( "." ); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf( ":" ) < 0 && "on" + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[ jQuery.expando ] ? + event : + new jQuery.Event( type, typeof event === "object" && event ); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join( "." ); + event.rnamespace = event.namespace ? + new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : + null; + + // Clean up the event in case it is being reused + event.result = undefined; + if ( !event.target ) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? + [ event ] : + jQuery.makeArray( data, [ event ] ); + + // Allow special events to draw outside the lines + special = jQuery.event.special[ type ] || {}; + if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { + + bubbleType = special.delegateType || type; + if ( !rfocusMorph.test( bubbleType + type ) ) { + cur = cur.parentNode; + } + for ( ; cur; cur = cur.parentNode ) { + eventPath.push( cur ); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if ( tmp === ( elem.ownerDocument || document ) ) { + eventPath.push( tmp.defaultView || tmp.parentWindow || window ); + } + } + + // Fire handlers on the event path + i = 0; + while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { + lastElement = cur; + event.type = i > 1 ? + bubbleType : + special.bindType || type; + + // jQuery handler + handle = ( + dataPriv.get( cur, "events" ) || Object.create( null ) + )[ event.type ] && + dataPriv.get( cur, "handle" ); + if ( handle ) { + handle.apply( cur, data ); + } + + // Native handler + handle = ontype && cur[ ontype ]; + if ( handle && handle.apply && acceptData( cur ) ) { + event.result = handle.apply( cur, data ); + if ( event.result === false ) { + event.preventDefault(); + } + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if ( !onlyHandlers && !event.isDefaultPrevented() ) { + + if ( ( !special._default || + special._default.apply( eventPath.pop(), data ) === false ) && + acceptData( elem ) ) { + + // Call a native DOM method on the target with the same name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { + + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ ontype ]; + + if ( tmp ) { + elem[ ontype ] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + + if ( event.isPropagationStopped() ) { + lastElement.addEventListener( type, stopPropagationCallback ); + } + + elem[ type ](); + + if ( event.isPropagationStopped() ) { + lastElement.removeEventListener( type, stopPropagationCallback ); + } + + jQuery.event.triggered = undefined; + + if ( tmp ) { + elem[ ontype ] = tmp; + } + } + } + } + + return event.result; + }, + + // Piggyback on a donor event to simulate a different one + // Used only for `focus(in | out)` events + simulate: function( type, elem, event ) { + var e = jQuery.extend( + new jQuery.Event(), + event, + { + type: type, + isSimulated: true + } + ); + + jQuery.event.trigger( e, null, elem ); + } + +} ); + +jQuery.fn.extend( { + + trigger: function( type, data ) { + return this.each( function() { + jQuery.event.trigger( type, data, this ); + } ); + }, + triggerHandler: function( type, data ) { + var elem = this[ 0 ]; + if ( elem ) { + return jQuery.event.trigger( type, data, elem, true ); + } + } +} ); + + +// Support: Firefox <=44 +// Firefox doesn't have focus(in | out) events +// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 +// +// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 +// focus(in | out) events fire after focus & blur events, +// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order +// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 +if ( !support.focusin ) { + jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { + + // Attach a single capturing handler on the document while someone wants focusin/focusout + var handler = function( event ) { + jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); + }; + + jQuery.event.special[ fix ] = { + setup: function() { + + // Handle: regular nodes (via `this.ownerDocument`), window + // (via `this.document`) & document (via `this`). + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ); + + if ( !attaches ) { + doc.addEventListener( orig, handler, true ); + } + dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); + }, + teardown: function() { + var doc = this.ownerDocument || this.document || this, + attaches = dataPriv.access( doc, fix ) - 1; + + if ( !attaches ) { + doc.removeEventListener( orig, handler, true ); + dataPriv.remove( doc, fix ); + + } else { + dataPriv.access( doc, fix, attaches ); + } + } + }; + } ); +} +var location = window.location; + +var nonce = { guid: Date.now() }; + +var rquery = ( /\?/ ); + + + +// Cross-browser xml parsing +jQuery.parseXML = function( data ) { + var xml; + if ( !data || typeof data !== "string" ) { + return null; + } + + // Support: IE 9 - 11 only + // IE throws on parseFromString with invalid input. + try { + xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); + } catch ( e ) { + xml = undefined; + } + + if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { + jQuery.error( "Invalid XML: " + data ); + } + return xml; +}; + + +var + rbracket = /\[\]$/, + rCRLF = /\r?\n/g, + rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, + rsubmittable = /^(?:input|select|textarea|keygen)/i; + +function buildParams( prefix, obj, traditional, add ) { + var name; + + if ( Array.isArray( obj ) ) { + + // Serialize array item. + jQuery.each( obj, function( i, v ) { + if ( traditional || rbracket.test( prefix ) ) { + + // Treat each array item as a scalar. + add( prefix, v ); + + } else { + + // Item is non-scalar (array or object), encode its numeric index. + buildParams( + prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", + v, + traditional, + add + ); + } + } ); + + } else if ( !traditional && toType( obj ) === "object" ) { + + // Serialize object item. + for ( name in obj ) { + buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); + } + + } else { + + // Serialize scalar item. + add( prefix, obj ); + } +} + +// Serialize an array of form elements or a set of +// key/values into a query string +jQuery.param = function( a, traditional ) { + var prefix, + s = [], + add = function( key, valueOrFunction ) { + + // If value is a function, invoke it and use its return value + var value = isFunction( valueOrFunction ) ? + valueOrFunction() : + valueOrFunction; + + s[ s.length ] = encodeURIComponent( key ) + "=" + + encodeURIComponent( value == null ? "" : value ); + }; + + if ( a == null ) { + return ""; + } + + // If an array was passed in, assume that it is an array of form elements. + if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { + + // Serialize the form elements + jQuery.each( a, function() { + add( this.name, this.value ); + } ); + + } else { + + // If traditional, encode the "old" way (the way 1.3.2 or older + // did it), otherwise encode params recursively. + for ( prefix in a ) { + buildParams( prefix, a[ prefix ], traditional, add ); + } + } + + // Return the resulting serialization + return s.join( "&" ); +}; + +jQuery.fn.extend( { + serialize: function() { + return jQuery.param( this.serializeArray() ); + }, + serializeArray: function() { + return this.map( function() { + + // Can add propHook for "elements" to filter or add form elements + var elements = jQuery.prop( this, "elements" ); + return elements ? jQuery.makeArray( elements ) : this; + } ) + .filter( function() { + var type = this.type; + + // Use .is( ":disabled" ) so that fieldset[disabled] works + return this.name && !jQuery( this ).is( ":disabled" ) && + rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && + ( this.checked || !rcheckableType.test( type ) ); + } ) + .map( function( _i, elem ) { + var val = jQuery( this ).val(); + + if ( val == null ) { + return null; + } + + if ( Array.isArray( val ) ) { + return jQuery.map( val, function( val ) { + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ); + } + + return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; + } ).get(); + } +} ); + + +var + r20 = /%20/g, + rhash = /#.*$/, + rantiCache = /([?&])_=[^&]*/, + rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, + + // #7653, #8125, #8152: local protocol detection + rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, + rnoContent = /^(?:GET|HEAD)$/, + rprotocol = /^\/\//, + + /* Prefilters + * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) + * 2) These are called: + * - BEFORE asking for a transport + * - AFTER param serialization (s.data is a string if s.processData is true) + * 3) key is the dataType + * 4) the catchall symbol "*" can be used + * 5) execution will start with transport dataType and THEN continue down to "*" if needed + */ + prefilters = {}, + + /* Transports bindings + * 1) key is the dataType + * 2) the catchall symbol "*" can be used + * 3) selection will start with transport dataType and THEN go to "*" if needed + */ + transports = {}, + + // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression + allTypes = "*/".concat( "*" ), + + // Anchor tag for parsing the document origin + originAnchor = document.createElement( "a" ); + originAnchor.href = location.href; + +// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport +function addToPrefiltersOrTransports( structure ) { + + // dataTypeExpression is optional and defaults to "*" + return function( dataTypeExpression, func ) { + + if ( typeof dataTypeExpression !== "string" ) { + func = dataTypeExpression; + dataTypeExpression = "*"; + } + + var dataType, + i = 0, + dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; + + if ( isFunction( func ) ) { + + // For each dataType in the dataTypeExpression + while ( ( dataType = dataTypes[ i++ ] ) ) { + + // Prepend if requested + if ( dataType[ 0 ] === "+" ) { + dataType = dataType.slice( 1 ) || "*"; + ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); + + // Otherwise append + } else { + ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); + } + } + } + }; +} + +// Base inspection function for prefilters and transports +function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { + + var inspected = {}, + seekingTransport = ( structure === transports ); + + function inspect( dataType ) { + var selected; + inspected[ dataType ] = true; + jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { + var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); + if ( typeof dataTypeOrTransport === "string" && + !seekingTransport && !inspected[ dataTypeOrTransport ] ) { + + options.dataTypes.unshift( dataTypeOrTransport ); + inspect( dataTypeOrTransport ); + return false; + } else if ( seekingTransport ) { + return !( selected = dataTypeOrTransport ); + } + } ); + return selected; + } + + return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); +} + +// A special extend for ajax options +// that takes "flat" options (not to be deep extended) +// Fixes #9887 +function ajaxExtend( target, src ) { + var key, deep, + flatOptions = jQuery.ajaxSettings.flatOptions || {}; + + for ( key in src ) { + if ( src[ key ] !== undefined ) { + ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; + } + } + if ( deep ) { + jQuery.extend( true, target, deep ); + } + + return target; +} + +/* Handles responses to an ajax request: + * - finds the right dataType (mediates between content-type and expected dataType) + * - returns the corresponding response + */ +function ajaxHandleResponses( s, jqXHR, responses ) { + + var ct, type, finalDataType, firstDataType, + contents = s.contents, + dataTypes = s.dataTypes; + + // Remove auto dataType and get content-type in the process + while ( dataTypes[ 0 ] === "*" ) { + dataTypes.shift(); + if ( ct === undefined ) { + ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); + } + } + + // Check if we're dealing with a known content-type + if ( ct ) { + for ( type in contents ) { + if ( contents[ type ] && contents[ type ].test( ct ) ) { + dataTypes.unshift( type ); + break; + } + } + } + + // Check to see if we have a response for the expected dataType + if ( dataTypes[ 0 ] in responses ) { + finalDataType = dataTypes[ 0 ]; + } else { + + // Try convertible dataTypes + for ( type in responses ) { + if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { + finalDataType = type; + break; + } + if ( !firstDataType ) { + firstDataType = type; + } + } + + // Or just use first one + finalDataType = finalDataType || firstDataType; + } + + // If we found a dataType + // We add the dataType to the list if needed + // and return the corresponding response + if ( finalDataType ) { + if ( finalDataType !== dataTypes[ 0 ] ) { + dataTypes.unshift( finalDataType ); + } + return responses[ finalDataType ]; + } +} + +/* Chain conversions given the request and the original response + * Also sets the responseXXX fields on the jqXHR instance + */ +function ajaxConvert( s, response, jqXHR, isSuccess ) { + var conv2, current, conv, tmp, prev, + converters = {}, + + // Work with a copy of dataTypes in case we need to modify it for conversion + dataTypes = s.dataTypes.slice(); + + // Create converters map with lowercased keys + if ( dataTypes[ 1 ] ) { + for ( conv in s.converters ) { + converters[ conv.toLowerCase() ] = s.converters[ conv ]; + } + } + + current = dataTypes.shift(); + + // Convert to each sequential dataType + while ( current ) { + + if ( s.responseFields[ current ] ) { + jqXHR[ s.responseFields[ current ] ] = response; + } + + // Apply the dataFilter if provided + if ( !prev && isSuccess && s.dataFilter ) { + response = s.dataFilter( response, s.dataType ); + } + + prev = current; + current = dataTypes.shift(); + + if ( current ) { + + // There's only work to do if current dataType is non-auto + if ( current === "*" ) { + + current = prev; + + // Convert response if prev dataType is non-auto and differs from current + } else if ( prev !== "*" && prev !== current ) { + + // Seek a direct converter + conv = converters[ prev + " " + current ] || converters[ "* " + current ]; + + // If none found, seek a pair + if ( !conv ) { + for ( conv2 in converters ) { + + // If conv2 outputs current + tmp = conv2.split( " " ); + if ( tmp[ 1 ] === current ) { + + // If prev can be converted to accepted input + conv = converters[ prev + " " + tmp[ 0 ] ] || + converters[ "* " + tmp[ 0 ] ]; + if ( conv ) { + + // Condense equivalence converters + if ( conv === true ) { + conv = converters[ conv2 ]; + + // Otherwise, insert the intermediate dataType + } else if ( converters[ conv2 ] !== true ) { + current = tmp[ 0 ]; + dataTypes.unshift( tmp[ 1 ] ); + } + break; + } + } + } + } + + // Apply converter (if not an equivalence) + if ( conv !== true ) { + + // Unless errors are allowed to bubble, catch and return them + if ( conv && s.throws ) { + response = conv( response ); + } else { + try { + response = conv( response ); + } catch ( e ) { + return { + state: "parsererror", + error: conv ? e : "No conversion from " + prev + " to " + current + }; + } + } + } + } + } + } + + return { state: "success", data: response }; +} + +jQuery.extend( { + + // Counter for holding the number of active queries + active: 0, + + // Last-Modified header cache for next request + lastModified: {}, + etag: {}, + + ajaxSettings: { + url: location.href, + type: "GET", + isLocal: rlocalProtocol.test( location.protocol ), + global: true, + processData: true, + async: true, + contentType: "application/x-www-form-urlencoded; charset=UTF-8", + + /* + timeout: 0, + data: null, + dataType: null, + username: null, + password: null, + cache: null, + throws: false, + traditional: false, + headers: {}, + */ + + accepts: { + "*": allTypes, + text: "text/plain", + html: "text/html", + xml: "application/xml, text/xml", + json: "application/json, text/javascript" + }, + + contents: { + xml: /\bxml\b/, + html: /\bhtml/, + json: /\bjson\b/ + }, + + responseFields: { + xml: "responseXML", + text: "responseText", + json: "responseJSON" + }, + + // Data converters + // Keys separate source (or catchall "*") and destination types with a single space + converters: { + + // Convert anything to text + "* text": String, + + // Text to html (true = no transformation) + "text html": true, + + // Evaluate text as a json expression + "text json": JSON.parse, + + // Parse text as xml + "text xml": jQuery.parseXML + }, + + // For options that shouldn't be deep extended: + // you can add your own custom options here if + // and when you create one that shouldn't be + // deep extended (see ajaxExtend) + flatOptions: { + url: true, + context: true + } + }, + + // Creates a full fledged settings object into target + // with both ajaxSettings and settings fields. + // If target is omitted, writes into ajaxSettings. + ajaxSetup: function( target, settings ) { + return settings ? + + // Building a settings object + ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : + + // Extending ajaxSettings + ajaxExtend( jQuery.ajaxSettings, target ); + }, + + ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), + ajaxTransport: addToPrefiltersOrTransports( transports ), + + // Main method + ajax: function( url, options ) { + + // If url is an object, simulate pre-1.5 signature + if ( typeof url === "object" ) { + options = url; + url = undefined; + } + + // Force options to be an object + options = options || {}; + + var transport, + + // URL without anti-cache param + cacheURL, + + // Response headers + responseHeadersString, + responseHeaders, + + // timeout handle + timeoutTimer, + + // Url cleanup var + urlAnchor, + + // Request state (becomes false upon send and true upon completion) + completed, + + // To know if global events are to be dispatched + fireGlobals, + + // Loop variable + i, + + // uncached part of the url + uncached, + + // Create the final options object + s = jQuery.ajaxSetup( {}, options ), + + // Callbacks context + callbackContext = s.context || s, + + // Context for global events is callbackContext if it is a DOM node or jQuery collection + globalEventContext = s.context && + ( callbackContext.nodeType || callbackContext.jquery ) ? + jQuery( callbackContext ) : + jQuery.event, + + // Deferreds + deferred = jQuery.Deferred(), + completeDeferred = jQuery.Callbacks( "once memory" ), + + // Status-dependent callbacks + statusCode = s.statusCode || {}, + + // Headers (they are sent all at once) + requestHeaders = {}, + requestHeadersNames = {}, + + // Default abort message + strAbort = "canceled", + + // Fake xhr + jqXHR = { + readyState: 0, + + // Builds headers hashtable if needed + getResponseHeader: function( key ) { + var match; + if ( completed ) { + if ( !responseHeaders ) { + responseHeaders = {}; + while ( ( match = rheaders.exec( responseHeadersString ) ) ) { + responseHeaders[ match[ 1 ].toLowerCase() + " " ] = + ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) + .concat( match[ 2 ] ); + } + } + match = responseHeaders[ key.toLowerCase() + " " ]; + } + return match == null ? null : match.join( ", " ); + }, + + // Raw string + getAllResponseHeaders: function() { + return completed ? responseHeadersString : null; + }, + + // Caches the header + setRequestHeader: function( name, value ) { + if ( completed == null ) { + name = requestHeadersNames[ name.toLowerCase() ] = + requestHeadersNames[ name.toLowerCase() ] || name; + requestHeaders[ name ] = value; + } + return this; + }, + + // Overrides response content-type header + overrideMimeType: function( type ) { + if ( completed == null ) { + s.mimeType = type; + } + return this; + }, + + // Status-dependent callbacks + statusCode: function( map ) { + var code; + if ( map ) { + if ( completed ) { + + // Execute the appropriate callbacks + jqXHR.always( map[ jqXHR.status ] ); + } else { + + // Lazy-add the new callbacks in a way that preserves old ones + for ( code in map ) { + statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; + } + } + } + return this; + }, + + // Cancel the request + abort: function( statusText ) { + var finalText = statusText || strAbort; + if ( transport ) { + transport.abort( finalText ); + } + done( 0, finalText ); + return this; + } + }; + + // Attach deferreds + deferred.promise( jqXHR ); + + // Add protocol if not provided (prefilters might expect it) + // Handle falsy url in the settings object (#10093: consistency with old signature) + // We also use the url parameter if available + s.url = ( ( url || s.url || location.href ) + "" ) + .replace( rprotocol, location.protocol + "//" ); + + // Alias method option to type as per ticket #12004 + s.type = options.method || options.type || s.method || s.type; + + // Extract dataTypes list + s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; + + // A cross-domain request is in order when the origin doesn't match the current origin. + if ( s.crossDomain == null ) { + urlAnchor = document.createElement( "a" ); + + // Support: IE <=8 - 11, Edge 12 - 15 + // IE throws exception on accessing the href property if url is malformed, + // e.g. http://example.com:80x/ + try { + urlAnchor.href = s.url; + + // Support: IE <=8 - 11 only + // Anchor's host property isn't correctly set when s.url is relative + urlAnchor.href = urlAnchor.href; + s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== + urlAnchor.protocol + "//" + urlAnchor.host; + } catch ( e ) { + + // If there is an error parsing the URL, assume it is crossDomain, + // it can be rejected by the transport if it is invalid + s.crossDomain = true; + } + } + + // Convert data if not already a string + if ( s.data && s.processData && typeof s.data !== "string" ) { + s.data = jQuery.param( s.data, s.traditional ); + } + + // Apply prefilters + inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); + + // If request was aborted inside a prefilter, stop there + if ( completed ) { + return jqXHR; + } + + // We can fire global events as of now if asked to + // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) + fireGlobals = jQuery.event && s.global; + + // Watch for a new set of requests + if ( fireGlobals && jQuery.active++ === 0 ) { + jQuery.event.trigger( "ajaxStart" ); + } + + // Uppercase the type + s.type = s.type.toUpperCase(); + + // Determine if request has content + s.hasContent = !rnoContent.test( s.type ); + + // Save the URL in case we're toying with the If-Modified-Since + // and/or If-None-Match header later on + // Remove hash to simplify url manipulation + cacheURL = s.url.replace( rhash, "" ); + + // More options handling for requests with no content + if ( !s.hasContent ) { + + // Remember the hash so we can put it back + uncached = s.url.slice( cacheURL.length ); + + // If data is available and should be processed, append data to url + if ( s.data && ( s.processData || typeof s.data === "string" ) ) { + cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; + + // #9682: remove data so that it's not used in an eventual retry + delete s.data; + } + + // Add or update anti-cache param if needed + if ( s.cache === false ) { + cacheURL = cacheURL.replace( rantiCache, "$1" ); + uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + + uncached; + } + + // Put hash and anti-cache on the URL that will be requested (gh-1732) + s.url = cacheURL + uncached; + + // Change '%20' to '+' if this is encoded form body content (gh-2658) + } else if ( s.data && s.processData && + ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { + s.data = s.data.replace( r20, "+" ); + } + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + if ( jQuery.lastModified[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); + } + if ( jQuery.etag[ cacheURL ] ) { + jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); + } + } + + // Set the correct header, if data is being sent + if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { + jqXHR.setRequestHeader( "Content-Type", s.contentType ); + } + + // Set the Accepts header for the server, depending on the dataType + jqXHR.setRequestHeader( + "Accept", + s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? + s.accepts[ s.dataTypes[ 0 ] ] + + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : + s.accepts[ "*" ] + ); + + // Check for headers option + for ( i in s.headers ) { + jqXHR.setRequestHeader( i, s.headers[ i ] ); + } + + // Allow custom headers/mimetypes and early abort + if ( s.beforeSend && + ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { + + // Abort if not done already and return + return jqXHR.abort(); + } + + // Aborting is no longer a cancellation + strAbort = "abort"; + + // Install callbacks on deferreds + completeDeferred.add( s.complete ); + jqXHR.done( s.success ); + jqXHR.fail( s.error ); + + // Get transport + transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); + + // If no transport, we auto-abort + if ( !transport ) { + done( -1, "No Transport" ); + } else { + jqXHR.readyState = 1; + + // Send global event + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); + } + + // If request was aborted inside ajaxSend, stop there + if ( completed ) { + return jqXHR; + } + + // Timeout + if ( s.async && s.timeout > 0 ) { + timeoutTimer = window.setTimeout( function() { + jqXHR.abort( "timeout" ); + }, s.timeout ); + } + + try { + completed = false; + transport.send( requestHeaders, done ); + } catch ( e ) { + + // Rethrow post-completion exceptions + if ( completed ) { + throw e; + } + + // Propagate others as results + done( -1, e ); + } + } + + // Callback for when everything is done + function done( status, nativeStatusText, responses, headers ) { + var isSuccess, success, error, response, modified, + statusText = nativeStatusText; + + // Ignore repeat invocations + if ( completed ) { + return; + } + + completed = true; + + // Clear timeout if it exists + if ( timeoutTimer ) { + window.clearTimeout( timeoutTimer ); + } + + // Dereference transport for early garbage collection + // (no matter how long the jqXHR object will be used) + transport = undefined; + + // Cache response headers + responseHeadersString = headers || ""; + + // Set readyState + jqXHR.readyState = status > 0 ? 4 : 0; + + // Determine if successful + isSuccess = status >= 200 && status < 300 || status === 304; + + // Get response data + if ( responses ) { + response = ajaxHandleResponses( s, jqXHR, responses ); + } + + // Use a noop converter for missing script + if ( !isSuccess && jQuery.inArray( "script", s.dataTypes ) > -1 ) { + s.converters[ "text script" ] = function() {}; + } + + // Convert no matter what (that way responseXXX fields are always set) + response = ajaxConvert( s, response, jqXHR, isSuccess ); + + // If successful, handle type chaining + if ( isSuccess ) { + + // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. + if ( s.ifModified ) { + modified = jqXHR.getResponseHeader( "Last-Modified" ); + if ( modified ) { + jQuery.lastModified[ cacheURL ] = modified; + } + modified = jqXHR.getResponseHeader( "etag" ); + if ( modified ) { + jQuery.etag[ cacheURL ] = modified; + } + } + + // if no content + if ( status === 204 || s.type === "HEAD" ) { + statusText = "nocontent"; + + // if not modified + } else if ( status === 304 ) { + statusText = "notmodified"; + + // If we have data, let's convert it + } else { + statusText = response.state; + success = response.data; + error = response.error; + isSuccess = !error; + } + } else { + + // Extract error from statusText and normalize for non-aborts + error = statusText; + if ( status || !statusText ) { + statusText = "error"; + if ( status < 0 ) { + status = 0; + } + } + } + + // Set data for the fake xhr object + jqXHR.status = status; + jqXHR.statusText = ( nativeStatusText || statusText ) + ""; + + // Success/Error + if ( isSuccess ) { + deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); + } else { + deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); + } + + // Status-dependent callbacks + jqXHR.statusCode( statusCode ); + statusCode = undefined; + + if ( fireGlobals ) { + globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", + [ jqXHR, s, isSuccess ? success : error ] ); + } + + // Complete + completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); + + if ( fireGlobals ) { + globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); + + // Handle the global AJAX counter + if ( !( --jQuery.active ) ) { + jQuery.event.trigger( "ajaxStop" ); + } + } + } + + return jqXHR; + }, + + getJSON: function( url, data, callback ) { + return jQuery.get( url, data, callback, "json" ); + }, + + getScript: function( url, callback ) { + return jQuery.get( url, undefined, callback, "script" ); + } +} ); + +jQuery.each( [ "get", "post" ], function( _i, method ) { + jQuery[ method ] = function( url, data, callback, type ) { + + // Shift arguments if data argument was omitted + if ( isFunction( data ) ) { + type = type || callback; + callback = data; + data = undefined; + } + + // The url can be an options object (which then must have .url) + return jQuery.ajax( jQuery.extend( { + url: url, + type: method, + dataType: type, + data: data, + success: callback + }, jQuery.isPlainObject( url ) && url ) ); + }; +} ); + +jQuery.ajaxPrefilter( function( s ) { + var i; + for ( i in s.headers ) { + if ( i.toLowerCase() === "content-type" ) { + s.contentType = s.headers[ i ] || ""; + } + } +} ); + + +jQuery._evalUrl = function( url, options, doc ) { + return jQuery.ajax( { + url: url, + + // Make this explicit, since user can override this through ajaxSetup (#11264) + type: "GET", + dataType: "script", + cache: true, + async: false, + global: false, + + // Only evaluate the response if it is successful (gh-4126) + // dataFilter is not invoked for failure responses, so using it instead + // of the default converter is kludgy but it works. + converters: { + "text script": function() {} + }, + dataFilter: function( response ) { + jQuery.globalEval( response, options, doc ); + } + } ); +}; + + +jQuery.fn.extend( { + wrapAll: function( html ) { + var wrap; + + if ( this[ 0 ] ) { + if ( isFunction( html ) ) { + html = html.call( this[ 0 ] ); + } + + // The elements to wrap the target around + wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); + + if ( this[ 0 ].parentNode ) { + wrap.insertBefore( this[ 0 ] ); + } + + wrap.map( function() { + var elem = this; + + while ( elem.firstElementChild ) { + elem = elem.firstElementChild; + } + + return elem; + } ).append( this ); + } + + return this; + }, + + wrapInner: function( html ) { + if ( isFunction( html ) ) { + return this.each( function( i ) { + jQuery( this ).wrapInner( html.call( this, i ) ); + } ); + } + + return this.each( function() { + var self = jQuery( this ), + contents = self.contents(); + + if ( contents.length ) { + contents.wrapAll( html ); + + } else { + self.append( html ); + } + } ); + }, + + wrap: function( html ) { + var htmlIsFunction = isFunction( html ); + + return this.each( function( i ) { + jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); + } ); + }, + + unwrap: function( selector ) { + this.parent( selector ).not( "body" ).each( function() { + jQuery( this ).replaceWith( this.childNodes ); + } ); + return this; + } +} ); + + +jQuery.expr.pseudos.hidden = function( elem ) { + return !jQuery.expr.pseudos.visible( elem ); +}; +jQuery.expr.pseudos.visible = function( elem ) { + return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); +}; + + + + +jQuery.ajaxSettings.xhr = function() { + try { + return new window.XMLHttpRequest(); + } catch ( e ) {} +}; + +var xhrSuccessStatus = { + + // File protocol always yields status code 0, assume 200 + 0: 200, + + // Support: IE <=9 only + // #1450: sometimes IE returns 1223 when it should be 204 + 1223: 204 + }, + xhrSupported = jQuery.ajaxSettings.xhr(); + +support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); +support.ajax = xhrSupported = !!xhrSupported; + +jQuery.ajaxTransport( function( options ) { + var callback, errorCallback; + + // Cross domain only allowed if supported through XMLHttpRequest + if ( support.cors || xhrSupported && !options.crossDomain ) { + return { + send: function( headers, complete ) { + var i, + xhr = options.xhr(); + + xhr.open( + options.type, + options.url, + options.async, + options.username, + options.password + ); + + // Apply custom fields if provided + if ( options.xhrFields ) { + for ( i in options.xhrFields ) { + xhr[ i ] = options.xhrFields[ i ]; + } + } + + // Override mime type if needed + if ( options.mimeType && xhr.overrideMimeType ) { + xhr.overrideMimeType( options.mimeType ); + } + + // X-Requested-With header + // For cross-domain requests, seeing as conditions for a preflight are + // akin to a jigsaw puzzle, we simply never set it to be sure. + // (it can always be set on a per-request basis or even using ajaxSetup) + // For same-domain requests, won't change header if already provided. + if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { + headers[ "X-Requested-With" ] = "XMLHttpRequest"; + } + + // Set headers + for ( i in headers ) { + xhr.setRequestHeader( i, headers[ i ] ); + } + + // Callback + callback = function( type ) { + return function() { + if ( callback ) { + callback = errorCallback = xhr.onload = + xhr.onerror = xhr.onabort = xhr.ontimeout = + xhr.onreadystatechange = null; + + if ( type === "abort" ) { + xhr.abort(); + } else if ( type === "error" ) { + + // Support: IE <=9 only + // On a manual native abort, IE9 throws + // errors on any property access that is not readyState + if ( typeof xhr.status !== "number" ) { + complete( 0, "error" ); + } else { + complete( + + // File: protocol always yields status 0; see #8605, #14207 + xhr.status, + xhr.statusText + ); + } + } else { + complete( + xhrSuccessStatus[ xhr.status ] || xhr.status, + xhr.statusText, + + // Support: IE <=9 only + // IE9 has no XHR2 but throws on binary (trac-11426) + // For XHR2 non-text, let the caller handle it (gh-2498) + ( xhr.responseType || "text" ) !== "text" || + typeof xhr.responseText !== "string" ? + { binary: xhr.response } : + { text: xhr.responseText }, + xhr.getAllResponseHeaders() + ); + } + } + }; + }; + + // Listen to events + xhr.onload = callback(); + errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); + + // Support: IE 9 only + // Use onreadystatechange to replace onabort + // to handle uncaught aborts + if ( xhr.onabort !== undefined ) { + xhr.onabort = errorCallback; + } else { + xhr.onreadystatechange = function() { + + // Check readyState before timeout as it changes + if ( xhr.readyState === 4 ) { + + // Allow onerror to be called first, + // but that will not handle a native abort + // Also, save errorCallback to a variable + // as xhr.onerror cannot be accessed + window.setTimeout( function() { + if ( callback ) { + errorCallback(); + } + } ); + } + }; + } + + // Create the abort callback + callback = callback( "abort" ); + + try { + + // Do send the request (this may raise an exception) + xhr.send( options.hasContent && options.data || null ); + } catch ( e ) { + + // #14683: Only rethrow if this hasn't been notified as an error yet + if ( callback ) { + throw e; + } + } + }, + + abort: function() { + if ( callback ) { + callback(); + } + } + }; + } +} ); + + + + +// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) +jQuery.ajaxPrefilter( function( s ) { + if ( s.crossDomain ) { + s.contents.script = false; + } +} ); + +// Install script dataType +jQuery.ajaxSetup( { + accepts: { + script: "text/javascript, application/javascript, " + + "application/ecmascript, application/x-ecmascript" + }, + contents: { + script: /\b(?:java|ecma)script\b/ + }, + converters: { + "text script": function( text ) { + jQuery.globalEval( text ); + return text; + } + } +} ); + +// Handle cache's special case and crossDomain +jQuery.ajaxPrefilter( "script", function( s ) { + if ( s.cache === undefined ) { + s.cache = false; + } + if ( s.crossDomain ) { + s.type = "GET"; + } +} ); + +// Bind script tag hack transport +jQuery.ajaxTransport( "script", function( s ) { + + // This transport only deals with cross domain or forced-by-attrs requests + if ( s.crossDomain || s.scriptAttrs ) { + var script, callback; + return { + send: function( _, complete ) { + script = jQuery( " + + + + + + +
+ +
+ + + +
+ + +
+
+
+ + +

Index

+ +
+ A + | B + | C + | D + | E + | F + | G + | H + | I + | K + | L + | M + | N + | O + | P + | R + | S + | T + | U + | V + | W + | X + +
+

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + + +
+ +

I

+ + + +
+ +

K

+ + +
+ +

L

+ + + +
+ +

M

+ + +
+ +

N

+ + + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ +

W

+ + + +
+ +

X

+ + +
+ + + +
+
+
+
+ + + \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..03b65e1 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,285 @@ + + + + + + + + + pymcuprog documentation — pymcuprog documentation + + + + + + + + + + +
+ +
+ + + +
+ + +
+
+
+ +
+

pymcuprog documentation

+
+

Python MCU programmer utility

+

pymcuprog is a utility for programming various Microchip MCU devices using Microchip CMSIS-DAP based debuggers

+
+
+

Overview

+
+
pymcuprog is available:
+
+
+
+
+

Command-line interface usage

+

For using pymcuprog as a CLI, see help.md (https://github.com/microchip-pic-avr-tools/pymcuprog/blob/main/help.md)

+
+
+

Library usage

+

pymcuprog can be used as a library using its backend API. For example:

+
# Setup logging - pymcuprog uses the Python logging module
+import logging
+logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.WARNING)
+
+# Configure the session:
+from pymcuprog.backend import SessionConfig
+sessionconfig = SessionConfig("atmega4808")
+
+# Instantiate USB transport (only 1 tool connected)
+from pymcuprog.toolconnection import ToolUsbHidConnection
+transport = ToolUsbHidConnection()
+
+# Instantiate backend
+from pymcuprog.backend import Backend
+backend = Backend()
+
+# Connect to tool using transport
+backend.connect_to_tool(transport)
+
+# Start the session
+backend.start_session(sessionconfig)
+
+# Read the target device_id
+device_id = backend.read_device_id()
+print ("Device ID is {0:06X}".format(int.from_bytes(d, byteorder="little")))
+
+# Print the pymcuprog package version:
+from pymcuprog.version import VERSION as pymcuprog_version
+print("pymcuprog version {}".format(pymcuprog_version))
+
+# In addition, the CLI-backend API is versioned for convenience:
+print("pymcuprog backend API version: {}".format(backend.get_api_version()))
+
+
+
+
+

Logging

+

This package uses the Python logging module for publishing log messages to library users. +A basic configuration can be used (see example), but for best results a more thorough configuration is +recommended in order to control the verbosity of output from dependencies in the stack which also use logging. +See logging.yaml which is included in the package (although only used for CLI)

+
+
+

Dependencies

+

pymcuprog depends on pyedbglib for its transport protocol. +pyedbglib requires a USB transport library like libusb. See pyedbglib package for more information.

+
+
+

Supported devices and tools

+

Note: pymcuprog is primarily intended for use with PKOB nano (nEDBG) debuggers which +are found on Curiosity Nano kits and other development boards. This means that it is +continuously tested with a selection of AVR devices with UPDI interface as well as a +selection of PIC devices. However since the protocol is compatible between all +EDBG-based debuggers (pyedbglib) it is possible to use pymcuprog with a wide range of +debuggers and devices, although not all device families/interfaces have been implemented.

+
+
The following Atmel/Microchip debuggers are supported:
    +
  • PKOB nano (nEDBG)

  • +
  • MPLAB PICkit 4 ICD (only when in ‘AVR mode’)

  • +
  • MPLAB Snap ICD (only when in ‘AVR mode’)

  • +
  • Atmel-ICE

  • +
  • Power Debugger

  • +
  • EDBG

  • +
  • mEDBG

  • +
  • JTAGICE3 (only firmware version 3.x)

  • +
+
+
+

Although not all functionality is provided on all debuggers/boards. See device support section below.

+
+
The following device-types are supported:
    +
  • All AVR UPDI devices, whether mounted on kits or standalone

  • +
  • PIC devices mounted on Curiosity Nano kits, or similar board with PKOB nano (nEDBG) debugger

  • +
  • Other devices (eg ATmega328P, ATsamd21e18a) may be partially supported for experimental purposes

  • +
+
+
+
+ +
+
+

Indices and tables

+ +
+ + +
+
+
+
+ + + \ No newline at end of file diff --git a/docs/objects.inv b/docs/objects.inv new file mode 100644 index 0000000..652a6c3 Binary files /dev/null and b/docs/objects.inv differ diff --git a/docs/py-modindex.html b/docs/py-modindex.html new file mode 100644 index 0000000..404accc --- /dev/null +++ b/docs/py-modindex.html @@ -0,0 +1,266 @@ + + + + + + + + Python Module Index — pymcuprog documentation + + + + + + + + + + + + +
+ +
+ + + +
+ + +
+
+
+ + +

Python Module Index

+ +
+ p +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 
+ p
+ pymcuprog +
    + pymcuprog.avr32target +
    + pymcuprog.avr8target +
    + pymcuprog.avrdebugger +
    + pymcuprog.backend +
    + pymcuprog.deviceinfo +
    + pymcuprog.deviceinfo.deviceinfo +
    + pymcuprog.deviceinfo.deviceinfokeys +
    + pymcuprog.deviceinfo.eraseflags +
    + pymcuprog.deviceinfo.memorynames +
    + pymcuprog.hexfileutils +
    + pymcuprog.nvm +
    + pymcuprog.nvmavr32 +
    + pymcuprog.nvmdebugwire +
    + pymcuprog.nvmmegaavrjtag +
    + pymcuprog.nvmmzeroplus +
    + pymcuprog.nvmpic +
    + pymcuprog.nvmserialupdi +
    + pymcuprog.nvmspi +
    + pymcuprog.nvmtpi +
    + pymcuprog.nvmupdi +
    + pymcuprog.nvmxmega +
    + pymcuprog.programmer +
    + pymcuprog.pymcuprog_errors +
    + pymcuprog.samtarget +
    + pymcuprog.serialupdi +
    + pymcuprog.serialupdi.application +
    + pymcuprog.serialupdi.constants +
    + pymcuprog.serialupdi.link +
    + pymcuprog.serialupdi.nvm +
    + pymcuprog.serialupdi.physical +
    + pymcuprog.serialupdi.readwrite +
    + pymcuprog.serialupdi.timeout +
    + pymcuprog.toolconnection +
    + pymcuprog.utils +
+ + +
+
+
+
+ + + \ No newline at end of file diff --git a/docs/pymcuprog.deviceinfo.html b/docs/pymcuprog.deviceinfo.html new file mode 100644 index 0000000..52a327c --- /dev/null +++ b/docs/pymcuprog.deviceinfo.html @@ -0,0 +1,499 @@ + + + + + + + + + pymcuprog.deviceinfo package — pymcuprog documentation + + + + + + + + + + + +
+ +
+ + + +
+ + +
+
+
+ +
+

pymcuprog.deviceinfo package

+
+

Submodules

+
+
+

pymcuprog.deviceinfo.deviceinfo module

+

deviceinfo.py +A simple Device Information service

+

Device information is stored in files named <devicename>.py in the devices sub-folder +Each device file contains a dict of values +These device files are [ideally] generated from DFP information by [running generate_device_info.py | hand]

+
+
+class pymcuprog.deviceinfo.deviceinfo.DeviceMemoryInfo(device_info)
+

Bases: object

+

API to fetch information about device memory segments

+
+
+static bytes_or_words(address_param)
+

Return multiplier for address parameter

+

The returned multiplier can be used to convert the address parameter to byte address

+
+
Parameters
+

address_param – Address parameter (used as key in device info dict)

+
+
Returns
+

Multiplier to convert the address to byte address

+
+
+
+ +
+
+memory_info_by_address(byte_address, address_type='address', size_type='size')
+

Returns information about the memory type for a given byte address

+
+
Parameters
+
    +
  • byte_address – Memory address to check

  • +
  • address_type – Selects between normal addresses and addresses used in hex files +(ADDRESS vs HEXFILE_ADDRESS)

  • +
  • size_type – Selects between normal size and size used in hexfiles (size vs hexfile_size)

  • +
+
+
+
+ +
+
+memory_info_by_address_range(start, stop, address_type='address', size_type='size')
+

Returns a list of all memories applicable for the address range(start, stop)

+
+
Parameters
+
    +
  • start – Start address (byte)

  • +
  • stop – End address (byte)

  • +
  • address_type – Selects between normal addresses and addresses used in hex files +(address vs hexfile_address)

  • +
  • size_type – Selects between normal size and size used in hexfiles (size vs hexfile_size)

  • +
+
+
+
+ +
+
+memory_info_by_name(name)
+

Returns information about the requested memory

+
+ +
+ +
+
+pymcuprog.deviceinfo.deviceinfo.get_supported_devices()
+

Return a list of all supported devices

+

A device is supported if it has a device model file in the devices folder

+
+
Returns
+

list of devices

+
+
+
+ +
+
+pymcuprog.deviceinfo.deviceinfo.getdeviceinfo(devicename)
+

Looks up device info for a given part

+
+
Parameters
+

devicename – device to look up

+
+
Returns
+

device information dict

+
+
+
+ +
+
+

pymcuprog.deviceinfo.deviceinfokeys module

+

Definitions of keys for device info dictionaries

+
+
+class pymcuprog.deviceinfo.deviceinfokeys.DeviceInfoKeys
+

Bases: object

+

Base class with common device info keys

+
+
+ARCHITECTURE = 'architecture'
+
+ +
+
+DEVICE_ID = 'device_id'
+
+ +
+
+INTERFACE = 'interface'
+
+ +
+
+NAME = 'name'
+
+ +
+
+classmethod get_all()
+

Get a list of all keys

+

:return List of all valid keys (baseclass and any subclass keys if run on a subclass)

+
+ +
+ +
+
+class pymcuprog.deviceinfo.deviceinfokeys.DeviceInfoKeysAvr
+

Bases: pymcuprog.deviceinfo.deviceinfokeys.DeviceInfoKeys

+

Keys specific to AVR device info files

+
+
+ADDRESS_SIZE = 'address_size'
+
+ +
+
+DATA_ADDRESS_SPACE = 'data_space_base'
+
+ +
+
+HV_IMPLEMENTATION = 'hv_implementation'
+
+ +
+
+NVMCTRL_BASE = 'nvmctrl_base'
+
+ +
+
+OCD_BASE = 'ocd_base'
+
+ +
+
+PROG_CLOCK_KHZ = 'prog_clock_khz'
+
+ +
+
+SYSCFG_BASE = 'syscfg_base'
+
+ +
+ +
+
+class pymcuprog.deviceinfo.deviceinfokeys.DeviceInfoKeysAvr32
+

Bases: pymcuprog.deviceinfo.deviceinfokeys.DeviceInfoKeys

+

Keys specific to 32-bit AVR device info files

+
+
+RESET_DOMAINS = 'reset_domains'
+
+ +
+ +
+
+class pymcuprog.deviceinfo.deviceinfokeys.DeviceInfoKeysPic
+

Bases: pymcuprog.deviceinfo.deviceinfokeys.DeviceInfoKeys

+

Keys specific to PIC device info files

+
+
+DEFAULT_BULK_ERASE_ADDRESS = 'default_bulk_erase_address'
+
+ +
+ +
+
+class pymcuprog.deviceinfo.deviceinfokeys.DeviceMemoryInfoKeys
+

Bases: object

+

Keys for device memory info dictionary

+

These keys are found in the dictionaries returned by DeviceMemoryInfo for each memory type

+
+
+ADDRESS = 'address'
+
+ +
+
+CHIPERASE_EFFECT = 'chiperase_effect'
+
+ +
+
+ERASE_ADDRESS = 'erase_address'
+
+ +
+
+HEXFILE_ADDRESS = 'hexfile_address'
+
+ +
+
+HEXFILE_SIZE = 'hexfile_size'
+
+ +
+
+ISOLATED_ERASE = 'isolated_erase'
+
+ +
+
+NAME = 'name'
+
+ +
+
+PAGE_SIZE = 'page_size'
+
+ +
+
+READ_SIZE = 'read_size'
+
+ +
+
+SIZE = 'size'
+
+ +
+
+VERIFY_MASK = 'verify_mask'
+
+ +
+
+WRITE_SIZE = 'write_size'
+
+ +
+
+classmethod get_all()
+

Get a list of all keys

+
+
Returns
+

List of all valid keys (baseclass and any subclass keys if run on a subclass)

+
+
+
+ +
+ +
+
+

pymcuprog.deviceinfo.eraseflags module

+

Definitions of erase related flags for the device models

+
+
+pymcuprog.deviceinfo.eraseflags.get_list_of_chiperase_effects()
+

Return a list of all ChiperaseEffect values

+
+ +
+
+

pymcuprog.deviceinfo.memorynames module

+

Memory name definitions

+
+
+class pymcuprog.deviceinfo.memorynames.MemoryNameAliases
+

Bases: object

+

Memory names that are actually not real memories but an alias for several memories

+
+
+ALL = 'all'
+
+ +
+ +
+
+class pymcuprog.deviceinfo.memorynames.MemoryNames
+

Bases: object

+

Memory names corresponding to target device memories

+
+
+CALIBRATION_ROW = 'calibration_row'
+
+ +
+
+CONFIG_WORD = 'config_words'
+
+ +
+
+EEPROM = 'eeprom'
+
+ +
+
+FLASH = 'flash'
+
+ +
+
+FUSES = 'fuses'
+
+ +
+
+ICD = 'icd'
+
+ +
+
+INTERNAL_SRAM = 'internal_sram'
+
+ +
+
+LOCKBITS = 'lockbits'
+
+ +
+
+SIGNATURES = 'signatures'
+
+ +
+
+USER_ID = 'user_id'
+
+ +
+
+USER_ROW = 'user_row'
+
+ +
+
+classmethod get_all()
+

Get a list of all memories representing actual device memories

+

:return List of all memory names representing actual device memories

+
+ +
+ +
+
+

Module contents

+
+
+ + +
+
+
+
+ + + \ No newline at end of file diff --git a/docs/pymcuprog.html b/docs/pymcuprog.html new file mode 100644 index 0000000..c84438c --- /dev/null +++ b/docs/pymcuprog.html @@ -0,0 +1,3444 @@ + + + + + + + + + pymcuprog package — pymcuprog documentation + + + + + + + + + + + +
+ +
+ + + +
+ + +
+
+
+ +
+

pymcuprog package

+
+

Subpackages

+ +
+
+

Submodules

+
+
+

AVR8 target

+

Device Specific Classes which use AVR8Protocol implementation

+
+
+class pymcuprog.avr8target.AvrDevice(transport)
+

Bases: object

+

Generic AVR device wrapper (maps to avr8 protocol)

+
+
+activate_physical()
+

Activate the physical interface

+
+ +
+
+deactivate_physical()
+

Deactivate the physical interface

+
+ +
+
+enter_progmode()
+

Enter programming mode

+
+ +
+
+erase(mode=0, address=0)
+

Erase NVM

+
+
Parameters
+
    +
  • mode (int) – Flash erase mode to use

  • +
  • address (int) – Start address to erase from (not used by some modes)

  • +
+
+
+
+ +
+
+static is_blank(data)
+

Checks if a buffer represents “blank” flash

+
+
Parameters
+

data (bytearray) – Contents to check

+
+
Returns
+

True if data represents blank flash

+
+
Return type
+

boolean

+
+
+
+ +
+
+leave_progmode()
+

Leave programming mode

+
+ +
+
+memory_read(memory_name, start_address, numbytes)
+

Read device memory

+
+
Parameters
+
    +
  • memory_name (int) – Memory type identifier as defined in the protocol

  • +
  • start_address (int) – First address to read

  • +
  • numbytes (int) – Number of bytes to read

  • +
+
+
Returns
+

Data read out

+
+
Return type
+

bytearray

+
+
+
+ +
+
+memory_write(memory_name, start_address, data)
+

Write device memory

+
+
Parameters
+
    +
  • memory_name (int) – Memory type identifier as defined in the protocol

  • +
  • start_address (int) – First address to write

  • +
  • data (bytearray) – Data to write

  • +
+
+
+
+ +
+
+static memtype_read_from_string(memtype_string)
+

Maps from a string to an avr8 memtype for reads

+
+
Parameters
+

memtype_string (str) – Friendly name of memory

+
+
Returns
+

Memory type identifier as defined in the protocol

+
+
Return type
+

int

+
+
+
+ +
+
+read_memory_section(memory_type, start_address, bytes_to_read, read_chunk_size)
+

Reads a chunked section of memory

+
+
Parameters
+
    +
  • memory_type (int) – Memory type identifier as defined in the protocol

  • +
  • start_address (int) – First address to read

  • +
  • bytes_to_read (int) – Number of bytes to read

  • +
  • read_chunk_size (int) – Number of bytes in each separate read command to the debugger

  • +
+
+
+
+ +
+
+write_memory_section(memory_type, start_address, data_to_write, write_chunk_size, allow_blank_skip=False)
+

Writes a chunked section of memory

+
+
Parameters
+
    +
  • memory_type (int) – Memory type identifier as defined in the protocol

  • +
  • start_address (int) – First address to write to

  • +
  • data_to_write (bytearray) – Raw data values to write

  • +
  • write_chunk_size (int) – Number of bytes in each separate write command to the debugger

  • +
  • allow_blank_skip (boolean) – Allow skipping write of locations with value 0xFF

  • +
+
+
+
+ +
+ +
+
+class pymcuprog.avr8target.MegaAvrJtagTarget(transport)
+

Bases: pymcuprog.avr8target.AvrDevice

+

Implements Mega AVR (JTAG) functionality of the AVR8 protocol

+
+
+setup_config(device_info)
+

Sets up the device config for a mega AVR device

+
+
Parameters
+

device_info (dict) – Target device information as returned by deviceinfo.deviceinfo.getdeviceinfo

+
+
+
+ +
+
+setup_prog_session()
+

Sets up a programming session on an Mega AVR (JTAG)

+
+ +
+ +
+
+class pymcuprog.avr8target.TinyAvrTarget(transport)
+

Bases: pymcuprog.avr8target.AvrDevice

+

Implements Tiny AVR (debugWIRE) functionality of the AVR8 protocol

+
+
+setup_debug_session()
+

Sets up a debugging session on an Tiny AVR (debugwire)

+
+ +
+ +
+
+class pymcuprog.avr8target.TinyXAvrTarget(transport)
+

Bases: pymcuprog.avr8target.AvrDevice

+

Class handling sessions with TinyX AVR targets using the AVR8 generic protocol

+
+
+activate_physical(use_reset=False, user_interaction_callback=None)
+

Override function for high-voltage activation for UPDI

+
+
Parameters
+
    +
  • use_reset (boolean) – Use external reset line during activation (only used for Mega JTAG interface)

  • +
  • user_interaction_callback (function) – Callback to be called when user interaction is required, +for example when doing UPDI high-voltage activation with user target power toggle. +This function could ask the user to toggle power and halt execution waiting for the user +to respond (this is default behavior if the callback is None), or if the user is another +script it could toggle power automatically and then return.

  • +
+
+
+
+ +
+
+breakpoint_clear()
+

Clears the hardware breakpoint

+
+ +
+
+breakpoint_set(address)
+

Sets the hardware breakpoint

+
+
Parameters
+

address (int) – Address to break at

+
+
+
+ +
+
+execute_instruction(instruction)
+

Execute an AVR instruction on the core

+
+
Parameters
+

instruction (int) – Instruction to execute

+
+
+
+ +
+
+execute_patch(instructions, flags=0)
+

Executes an instruction in the AVR core

+
+
Parameters
+
    +
  • instructions (bytearray) – Instructions to execute

  • +
  • flags (int) – Execution flags

  • +
+
+
+
+ +
+
+static memtype_write_from_string(memtype_string)
+

Maps from a string to an avr8 memtype for writes

+
+
Parameters
+

memtype_string (str) – Friendly name of memory

+
+
Returns
+

Memory type identifier as defined in the protocol

+
+
Return type
+

int

+
+
+
+ +
+
+ocdregfile_read()
+

Read OCD registers

+
+
Returns
+

OCD register file

+
+
Return type
+

bytearray

+
+
+
+ +
+
+read_device_id()
+

Reads the device ID from the part

+
+
Returns
+

Device ID raw bytes (little endian)

+
+
Return type
+

bytearray

+
+
+
+ +
+
+run_with_power_nap()
+

Execute run with power-nap enabled. This command is deprecated.

+
+ +
+
+setup_config(device_info)
+

Sets up the device config for a tinyX AVR device

+
+
Parameters
+

device_info (dict) – Target device information as returned by deviceinfo.deviceinfo.getdeviceinfo

+
+
+
+ +
+
+setup_debug_session(interface=pyedbglib.protocols.avr8protocol.Avr8Protocol.AVR8_PHY_INTF_PDI_1W, khz=100, use_hv=pyedbglib.protocols.avr8protocol.Avr8Protocol.UPDI_HV_NONE)
+

Sets up a debug session for a tinyX AVR device

+
+
Parameters
+
    +
  • interface (int) – Physical interface to use

  • +
  • khz (int) – Clock speed in kiloHertz / baud in kbps

  • +
  • use_hv (int) – Use high-voltage pulse to activate UPDI

  • +
+
+
+
+ +
+
+setup_prog_session(interface=pyedbglib.protocols.avr8protocol.Avr8Protocol.AVR8_PHY_INTF_PDI_1W, khz=900, use_hv=pyedbglib.protocols.avr8protocol.Avr8Protocol.UPDI_HV_NONE)
+

Sets up a programming session for a tinyX AVR device

+
+
Parameters
+
    +
  • interface (int) – Physical interface to use

  • +
  • khz (int) – Clock speed in kiloHertz / baud in kbps

  • +
  • use_hv (int) – Use high-voltage pulse to activate UPDI

  • +
+
+
+
+ +
+
+sib_read()
+

Reads the System Information Block

+
+
Returns
+

SIB bytes

+
+
+
+ +
+
+stack_pointer_read()
+

Reads the stack pointer

+
+
Returns
+

Stack pointer

+
+
Return type
+

bytearray

+
+
+
+ +
+ +
+
+class pymcuprog.avr8target.XmegaAvrTarget(transport)
+

Bases: pymcuprog.avr8target.AvrDevice

+

Implements XMEGA (PDI) functionality of the AVR8 protocol

+
+
+setup_config(device_info)
+

Sets up the device config for a XMEGA AVR device

+
+
Parameters
+

device_info – target device information as returned by deviceinfo.deviceinfo.getdeviceinfo

+
+
+
+ +
+
+setup_debug_session()
+

Sets up a debugging session on an XMEGA AVR

+
+ +
+
+setup_prog_session(interface=pyedbglib.protocols.avr8protocol.Avr8Protocol.AVR8_PHY_INTF_PDI, khz=4000)
+

Sets up a programming session for a XMEGA AVR device

+
+
Parameters
+
    +
  • interface – physical interface to use

  • +
  • khz – clock speed in kiloHertz

  • +
+
+
+
+ +
+ +
+
+

AVR32 target (alpha)

+

Device Specific Classes which use AVR32Protocol implementation

+
+
+class pymcuprog.avr32target.Avr32Device(transport, reset_domains=5)
+

Bases: object

+

Generic AVR32 device wrapper (maps to avr32 protocol)

+
+
+activate_physical()
+

Activate the physical interface

+
+ +
+
+deactivate_physical()
+

Deactivate the physical interface

+
+ +
+
+setup_prog_session(interface='jtag')
+

Setup a programming session on AVR32

+
+
Parameters
+

interface – jtag or awire

+
+
+
+ +
+ +
+
+

SAM target (alpha)

+

Access to SAM devices via CMSIS-DAP

+

Transport provided by HID inside pyedbglib

+
+
+class pymcuprog.samtarget.SamD2xTarget(driver)
+

Bases: pymcuprog.samtarget.SamTarget

+

Programmer for SAMD21 and friends

+
+
+CMD_EAR = 5
+
+ +
+
+CMD_LR = 64
+
+ +
+
+CMD_PBC = 68
+
+ +
+
+CMD_UR = 65
+
+ +
+
+CMD_WAP = 6
+
+ +
+
+CMD_WP = 4
+
+ +
+
+DAP_TRANSFER_IDLE_CYCLES = 0
+
+ +
+
+DAP_TRANSFER_MATCH_RETRY = 0
+
+ +
+
+DAP_TRANSFER_RETRY_COUNT = 250
+
+ +
+
+DSU_ADDRESS = 1090527232
+
+ +
+
+DSU_CHIP_ERASE_TIMEOUT_MS = 3000
+
+ +
+
+DSU_CTRL_OFFSET = 0
+
+ +
+
+DSU_CTRL_STATUS_CE_COMMAND_MASK = 16
+
+ +
+
+DSU_CTRL_STATUS_DONE_MASK = 256
+
+ +
+
+DSU_CTRL_STATUS_PROT_MASK = 65536
+
+ +
+
+DSU_DID_OFFSET = 24
+
+ +
+
+DSU_EXTERNAL_OFFSET = 256
+
+ +
+
+FLASH_LOCK_REGIONS = 16
+
+ +
+
+FLASH_LOCK_REGION_SIZE = 16384
+
+ +
+
+NVM_CTRLB_MANW_BIT = 7
+
+ +
+
+NVM_CTRL_ADDRESS = 1090535424
+
+ +
+
+NVM_CTRL_ADDR_OFFSET = 28
+
+ +
+
+NVM_CTRL_CTRLB_OFFSET = 4
+
+ +
+
+NVM_CTRL_CTRL_OFFSET = 0
+
+ +
+
+NVM_CTRL_INTFLAG_OFFSET = 20
+
+ +
+
+NVM_CTRL_LOCK_OFFSET = 32
+
+ +
+
+NVM_CTRL_STATUS_OFFSET = 24
+
+ +
+
+NVM_INT_ERROR_BIT = 1
+
+ +
+
+NVM_INT_READY_BIT = 0
+
+ +
+
+NVM_STATUS_LOCKE = 3
+
+ +
+
+NVM_STATUS_NVME = 4
+
+ +
+
+NVM_STATUS_PROGE = 2
+
+ +
+
+NVM_STATUS_SB = 8
+
+ +
+
+chip_erase_dsu()
+

Perform a chip erase using the DSU

+
+ +
+
+connect(clk_hz)
+

Connect to the DAP and initialise basics

+
+ +
+
+disconnect()
+

Disconnect from the DAP

+
+ +
+
+erase_user_row(address)
+

Erase the user row

+
+
Parameters
+

address – address to erase

+
+
+
+ +
+
+is_device_locked()
+

Checks if the device is locked

+
+ +
+
+is_flash_ready()
+

Checks if Flash Controller is ready

+
+
Returns
+

boolean ready state

+
+
+
+ +
+
+nvm_command(command)
+

Write command in NVM controller

+
+
Parameters
+

command – command to write

+
+
+
+ +
+
+post_flash_write()
+

Wrap up flash write (done once)

+
+ +
+
+pre_flash_write()
+

Prepare flash for write (done once)

+
+ +
+
+read_device_id()
+

Read the device ID register

+
+
Returns
+

Device ID raw bytes (little endian)

+
+
+
+ +
+
+read_flash(address, numbytes)
+

Read flash content

+
+
Parameters
+
    +
  • address – start byte address to read from

  • +
  • numbytes – number of bytes to read

  • +
+
+
Returns
+

array of bytes read

+
+
+
+ +
+
+read_user_row(address, numbytes)
+

Read user row values

+
+
Parameters
+
    +
  • address – address to read from

  • +
  • numbytes – number of bytes to read

  • +
+
+
Returns
+

bytes read

+
+
+
+ +
+
+reinitialise()
+

Re-initialise

+

Required after certain operations which reset the DAP on the target

+
+ +
+
+set_nvmctrl_address(address)
+

Set address in NVM controller

+
+
Parameters
+

address – address to write

+
+
+
+ +
+
+unlock_all_regions()
+

Unlock all regions of flash. +This only issues the NVM UR command, and has no effect on for example BOOTPROT locks.

+
+ +
+
+wait_flash_ready(timeout_ms=1000)
+

Wait for flash to be ready, or timeout

+
+
Parameters
+

timeout_ms

+
+
Returns
+

boolean True if flash is ready; False if timeout waiting

+
+
+
+ +
+
+write_flash_page(data_buffer, address)
+

Write a page of flash memory

+
+
Parameters
+
    +
  • data_buffer – data to write

  • +
  • address – address to write to

  • +
+
+
+
+ +
+
+write_user_row_word(address, value)
+

Write a word the user row

+
+
Parameters
+
    +
  • address – address of the user row

  • +
  • value – value to write

  • +
+
+
+
+ +
+ +
+
+class pymcuprog.samtarget.SamM4Target(driver)
+

Bases: pymcuprog.samtarget.SamTarget

+

SAM M4 targets.

+

SAM M4 targets are not supported

+
+ +
+
+class pymcuprog.samtarget.SamTarget(driver)
+

Bases: object

+

General SAM target interface

+
+
+ap_read(reg)
+

Access port read

+
+
Parameters
+

reg – register to read

+
+
Returns
+

value read

+
+
+
+ +
+
+ap_write(reg, value)
+

Access Port write

+
+
Parameters
+
    +
  • reg – register to write

  • +
  • value – value to write

  • +
+
+
+
+ +
+
+dp_read(reg)
+

Debug Port read

+
+
Parameters
+

reg – register to read

+
+
Returns
+

value read

+
+
+
+ +
+
+dp_write(reg, value)
+

Debug Port write

+
+
Parameters
+
    +
  • reg – register to write

  • +
  • value – value to write

  • +
+
+
+
+ +
+
+read_idcode()
+

Reads the ID code from the DAP (not device ID)

+
+ +
+ +
+
+

Backend API (library functions)

+

Backend interface for the pymcuprog utility.

+

This module is the boundary between the Command Line Interface (CLI) part and +the backend part that does the actual job. Any external utility or script that +needs access to the functionality provided by pymcuprog should connect to the +interface provided by this backend module

+
+
+class pymcuprog.backend.Backend
+

Bases: object

+

Backend interface of the pymcuprog utility. +This class provides access to all the functionality provided by pymcuprog

+
+
+API_VERSION = '2.0'
+
+ +
+
+connect_to_tool(toolconnection)
+

Connect to a tool

+

The tool can either be a USB HID tool or a serial port.

+
+
Parameters
+

toolconnection (object) –

This is an instance of one of the ToolConnection sub-classes. This object wraps +parameters needed to identify which tool to connect to like tool name and USB serial or serial port +name (e.g. ‘COM1’).

+

For USB HID tools there are some special handling:

+
    +
  • If both tool name and usb_serial are None any tool will be picked.

  • +
  • If usb_serial is None any tool matching the tool name will be picked

  • +
  • If tool name is None any tool matching the usb_serial will be picked

  • +
  • +
    If more than one tool is connected that matches the tool name and usb_serial parameters a

    PymcuprogToolConnectionError exception will be raised.

    +
    +
    +
  • +
+

+
+
Raises
+
+
+
+
+ +
+
+disconnect_from_tool()
+

Disconnect the connected tool

+

If no tool is connected nothing is done (i.e. no exception raised when not connected)

+
+ +
+
+end_session()
+

End a programming session

+

This will take down the device model stack and stop the programming session on the tool. However the tool will +not be disconnected and it will be possible to do another start_session without another connect_to_tool call. +If no session has been started this function will do nothing (i.e. it won’t fail even if a session has +not been started)

+
+ +
+
+erase(memory_name='all', address=None)
+

Erase target device memory

+

If a single memory is specified it will only be erased if it won’t affect other memories

+
+
Parameters
+
    +
  • memory_name (object (MemoryNameAliases)) –

    Name of memory to erase. To unlock a device use the MemoryNameAliases.ALL +MemoryNameAliases.ALL run the widest erase:

    +
      +
    • For PIC the widest bulk erase will be run.

    • +
    • For AVR a chip erase will be run

    • +
    • The following memories will not be erased:

      +
      +
        +
      • AVR fuses

      • +
      • EEPROM if EESAVE fuse is set for AVR

      • +
      • EEPROM if the target device does not support EEPROM erase

      • +
      • EEPROM if Data Code Protection (CPD_n) is not enabled for PIC

      • +
      • PIC ICD memory (special memory used for Debug Executives)

      • +
      +
      +
    • +
    +

  • +
  • address (int) – Optional address for erase command. If address is None the complete memory +segment will be erased. Note that the address parameter will just propagate through the stack down to the +device dependent implementation (devicesupportscripts for PIC and firmware for AVR). Normal use is to +leave the address as None.

  • +
+
+
Raises
+
    +
  • PymcuprogToolConnectionError – if not connected to any tool (connect_to_tool not run)

  • +
  • PymcuprogSessionError – if a session has not been started (session_start not run)

  • +
  • ValueError – if the specified memory is not defined for the target device

  • +
  • PymcuprogEraseError – if the memory can’t be erased or if the memory can’t be erased without affecting +other memories

  • +
+
+
+
+ +
+
+get_api_version()
+

Returns the current pymcuprog API version

+
+ +
+
+static get_available_hid_tools(serialnumber_substring='', tool_name=None)
+

Return a list of Microchip USB HID tools (debuggers) connected to the host

+
+
Parameters
+
    +
  • serialnumber_substring (str) – Can be an empty string or a subset of a serial number. Not case sensitive +This function will do matching of the last part of the devices serial numbers to +the serialnumber_substring. Examples: +‘123’ will match “MCHP3252000000043123” but not “MCP32520001230000000” +‘’ will match any serial number

  • +
  • tool_name (str) – Tool type to connect to. If None any tool matching the serialnumber_substring +will be returned

  • +
+
+
Returns
+

List of pyedbglib.hidtransport.hidtransportbase.HidTool objects

+
+
+
+ +
+
+get_chiperase_effect(memory_name)
+

Get the effect of a chip erase (widest bulk erase) on the given memory

+
+
Parameters
+

memory_name (str) – Name of memory

+
+
Returns
+

One of the values defined by deviceinfo.eraseflags.ChiperaseEffect depending upon the settings in the +device model for the configured device. If the chiperase_effect flag is missing in the device model +ChiperaseEffect.NOT_ERASED will be returned.

+
+
Return type
+

object (ChiperaseEffect)

+
+
Raises
+

ValueError – if memory is not defined for the configured device

+
+
+
+ +
+
+static get_device_info(device)
+

Get info about a device

+
+
Parameters
+

device (str) – Name of the device

+
+
Returns
+

Dictionary with device info as defined in the device files in pymcuprog.deviceinfo.devices

+
+
Return type
+

dict

+
+
Raises
+

PymcuprogNotSupportedError – if device is not supported

+
+
+
+ +
+
+static get_supported_devices()
+

Return a list of devices supported by pymcuprog.

+

This will be the list of devices with a corresponding device file

+
+
Returns
+

List of device names

+
+
Return type
+

list

+
+
+
+ +
+
+hold_in_reset()
+

Hold target device in reset

+
+
Raises
+
+
+
+
+ +
+
+is_isolated_erase_possible(memory_name)
+

Can the memory be erased without affecting other memories?

+
+
Parameters
+

memory_name (str) – Name of memory

+
+
Returns
+

True only if the memory can be erased without side effects, False if memory can’t be erased at all or +if erasing it will erase other memories too.

+
+
Return type
+

boolean

+
+
Raises
+

ValueError – if memory is not defined for the configured device

+
+
+
+ +
+
+read_device_id()
+

Read out the device id

+
+
Returns
+

Byte array with device ID as raw byte values. Number of bytes will depend upon target type

+
+
Return type
+

bytearray

+
+
Raises
+
+
+
+
+ +
+
+read_kit_device()
+

Read out the device name from kit configuration.

+

If the connected tool does not have any kit configuration +(i.e. the tool is not an onboard debugger) None will be returned. +connect_to_tool must have been called before calling read_kit_device, but start_session is not necessary. +Typically read_kit_device is used to get the device name required to configure a session before calling +start_session.

+
+
Returns
+

Name of target device as given by the kit, None if the tool does not have any device configured.

+
+
Return type
+

str

+
+
Raises
+

PymcuprogToolConnectionError – if not connected to any USB HID tool (connect_to_tool not run)

+
+
+
+ +
+
+read_memory(memory_name='all', offset_byte=0, numbytes=0)
+

Read target device memory

+
+
Parameters
+
    +
  • memory_name (object (MemoryNameAliases)) – Name of memory as defined in memorynames.py. MemoryNameAliases.ALL reads all memories +defined in the device model (numbytes and offset_byte will be ignored).

  • +
  • offset_byte (int) – Byte offset within memory to start reading at.

  • +
  • numbytes (int) – Number of bytes to read. 0 means read all memory locations from offset_byte and until end +of memory

  • +
+
+
Returns
+

List of namedtuples with two fields: data and memory_info. data contains a byte array of +raw data bytes and memory_info is a dictionary with memory information (as defined in +deviceinfo.deviceinfo.DeviceMemoryInfo). Normally the list will contain one item, but when +memory_name parameter is MemoryNameAliases.ALL there will be one namedtuple item per memory +type read.

+
+
Return type
+

list of namedtuple

+
+
Raises
+
    +
  • PymcuprogToolConnectionError – if not connected to any tool (connect_to_tool not run)

  • +
  • PymcuprogSessionError – if a session has not been started (session_start not run)

  • +
  • ValueError – if trying to read outside the specified memory

  • +
  • ValueError – if the specified memory is not defined for the target device

  • +
+
+
+
+ +
+
+read_supply_voltage_setpoint()
+

Read tool power supply voltage setpoint

+
+
Returns
+

Tool power supply voltage setpoint

+
+
Return type
+

float

+
+
Raises
+
+
+
+
+ +
+
+read_target_voltage()
+

Read target voltage

+
+
Returns
+

Measured target voltage

+
+
Return type
+

float

+
+
Raises
+
+
+
+
+ +
+
+read_tool_info()
+

Interrogates tool (debugger) for useful info

+
+
Returns
+

Dictionary with various info about the connected debugger

+
+
Return type
+

dict

+
+
Raises
+

PymcuprogToolConnectionError – if not connected to any USB HID tool (connect_to_tool not run)

+
+
+
+ +
+
+read_usb_voltage()
+

Read USB voltage

+
+
Returns
+

Measured USB voltage

+
+
Return type
+

float

+
+
Raises
+
+
+
+
+ +
+
+reboot_tool()
+

Trigger a reboot of the tool (debugger)

+
+
Raises
+

PymcuprogToolConnectionError – if not connected to any tool (connect_to_tool not run)

+
+
+
+ +
+
+release_from_reset()
+

Release target device from reset

+
+
Raises
+
+
+
+
+ +
+
+set_supply_voltage_setpoint(setpoint)
+

Set tool power supply voltage setpoint

+
+
Parameters
+

setpoint (float or int) – Power supply setpoint

+
+
Raises
+
+
+
+
+ +
+
+start_session(sessionconfig, user_interaction_callback=None)
+

Start a programming session.

+

This function will build the device model stack and initialize the tool for a +programming session. If a session is already started calling start_session will do an end_session and start +a new session from scratch.

+

Note connect_to_tool must have been called before start_session is called. If not an exception will be thrown.

+
+
Parameters
+
    +
  • sessionconfig (object) – SessionConfig object wrapping the parameters configuring the session

  • +
  • user_interaction_callback (function) – Callback to be called when user interaction is required, +for example when doing UPDI high-voltage activation with user target power toggle. +This function could ask the user to toggle power and halt execution waiting for the user +to respond (this is default behavior if the callback is None), or if the user is another +script it could toggle power automatically and then return.

  • +
+
+
Raises
+
+
+
+
+ +
+
+verify_hex(hexfile)
+

Verify target memory content against hexfile

+
+
Parameters
+

hexfile (str) – Name of file to verify against

+
+
Returns
+

True if contents match

+
+
Return type
+

boolean

+
+
Raises
+
+
+
+
+ +
+
+verify_memory(data, memory_name='flash', offset_byte=0)
+

Verify target device memory

+
+
Parameters
+
    +
  • memory_name (str) – Name of memory as defined in DeviceMemoryInfo (deviceinfo.py)

  • +
  • offset_byte (int) – Byte offset within memory to start verifying at.

  • +
  • data (bytearray) – Raw data bytes to verify against

  • +
+
+
Returns
+

True if contents match

+
+
Return type
+

boolean

+
+
Raises
+
    +
  • PymcuprogToolConnectionError – if not connected to any tool (connect_to_tool not run)

  • +
  • PymcuprogSessionError – if a session has not been started (session_start not run)

  • +
  • ValueError – if trying to verify outside the specified memory

  • +
  • ValueError – if the specified memory is not defined for the target device

  • +
+
+
+
+ +
+
+write_hex_to_target(hexfile)
+

Write hexfile to target device

+

Note no erase will be run (i.e. memory is assumed to already be erased)

+
+
Parameters
+

hexfile (str) – Name of file to write

+
+
+
+ +
+
+write_memory(data, memory_name='flash', offset_byte=0)
+

Write target device memory

+
+
Parameters
+
    +
  • memory_name (str) – Name of memory as defined in memorynames.py

  • +
  • offset_byte (int) – Byte offset within memory to start writing to.

  • +
  • data (bytearray) – Raw data bytes to write

  • +
+
+
Raises
+
    +
  • PymcuprogToolConnectionError – if not connected to any tool (connect_to_tool not run)

  • +
  • PymcuprogSessionError – if a session has not been started (session_start not run)

  • +
  • ValueError – if trying to write outside the specified memory

  • +
  • ValueError – if the specified memory is not defined for the target device

  • +
+
+
+
+ +
+ +
+
+class pymcuprog.backend.SessionConfig(device)
+

Bases: object

+

Collection of all parameters needed when configuring a programming session

+

Used as input parameter for the start_session function

+
+
+device = None
+
+ +
+
+interface = None
+
+ +
+
+interface_speed = None
+
+ +
+
+packpath = None
+
+ +
+
+special_options = None
+
+ +
+ +
+
+

Programmer API

+

Python MCU programmer

+
+
+class pymcuprog.programmer.Programmer(transport)
+

Bases: object

+

Main programmer class.

+
+
+erase(memory_name, address)
+

Erase the device

+
+
Parameters
+
    +
  • memory_name (object (MemoryNameAliases)) – Memory region to erase as defined in deviceinfo.memorynames +MemoryNameAliases.ALL will run the widest erase (e.g. chip erase on AVR or the widest bulk erase on PIC)

  • +
  • address (int) – Address to erase

  • +
+
+
+
+ +
+
+get_device_memory_info()
+

Exposes the device memory model to clients

+
+ +
+
+get_device_model()
+

Exposes the device model in use to clients

+
+ +
+
+hold_in_reset()
+

Hold the device in reset

+
+ +
+
+load_device(device_name)
+

Loads the device from the device folder

+
+
Parameters
+

device_name (str) – Name of device to be used

+
+
Raises
+

PymcuprogNotSupportedError – if device is not supported

+
+
+
+ +
+
+read_device_id()
+

Read the device ID

+
+
Returns
+

Device ID raw bytes (little endian)

+
+
Return type
+

bytearray

+
+
+
+ +
+
+read_memory(memory_name, offset, numbytes=0)
+

Read device memory

+
+
Parameters
+
    +
  • memory_name (object (MemoryNameAliases)) – Memory type to read as defined in deviceinfo.memorynames +MemoryNameAliases.ALL will read all memories defined in the device model for the configured +device (numbytes and offset will be ignored)

  • +
  • offset (int) – Offset/start address within the memory to start reading from

  • +
  • numbytes (int) – Number of bytes to read. 0 means read all memory locations for given memory +type (offset still applies)

  • +
+
+
Returns
+

List of namedtuples with two fields: data and memory_info. data contains a byte array +of raw data bytes and memory_info is a dictionary with memory information as defined in +deviceinfo.deviceinfo.DeviceMemoryInfo. Normally the list will contain one item, +but when memory_name parameter is MemoryNameAliases.ALL there will be one namedtuple +item per memory type read.

+
+
Return type
+

list of namedtuple

+
+
Raises
+
    +
  • ValueError – if trying to read outside the specified memory

  • +
  • ValueError – if the specified memory is not defined for the target device

  • +
+
+
+
+ +
+
+release_from_reset()
+

Release the device from reset (i.e. let the device run)

+
+ +
+
+set_options(options)
+

Stores options

+
+
Parameters
+

options (dict) – Options to store

+
+
+
+ +
+
+setup_device(interface=None, packpath=None, clk=None)
+

Sets up a programming session with a given device

+
+
Parameters
+
    +
  • interface (str) – Physical interface to use

  • +
  • packpath (str) – Path to packs to use (for PIC)

  • +
  • clk (int) – Clock frequency or baud rate to use

  • +
+
+
Raises
+

SerialException – if unable to connect to serial port (if using serial port instead of physical debugger)

+
+
+
+ +
+
+start(user_interaction_callback=None)
+

Starts the programming session with the device model

+
+
Parameters
+

user_interaction_callback (function) – Callback to be called when user interaction is required, +for example when doing UPDI high-voltage activation with user target power toggle. +This function could ask the user to toggle power and halt execution waiting for the user +to respond (this is default behavior if the callback is None), or if the user is another +script it could toggle power automatically and then return.

+
+
+
+ +
+
+stop()
+

Stops the programming session with the device model

+
+ +
+
+verify_memory(data, memory_name, offset=0)
+

Verify memory content

+
+
Parameters
+
    +
  • data (bytearray) – Data to verify against

  • +
  • memory_name (str) – Memory type

  • +
  • offset (int) – Offset/address within that memory region

  • +
+
+
Returns
+

True if contents match

+
+
Return type
+

boolean

+
+
+
+ +
+
+write_memory(data, memory_name, offset=0)
+

Write memory on the device

+
+
Parameters
+
    +
  • data (bytearray) – Data to write

  • +
  • memory_name (str) – Memory type to write

  • +
  • offset (int) – Offset/address within that region to write

  • +
+
+
Returns
+

Boolean status

+
+
Return type
+

boolean

+
+
Raises
+
    +
  • ValueError – if trying to write outside the specified memory

  • +
  • ValueError – if the specified memory is not defined for the target device

  • +
  • PymcuprogNotSupportedError – if memory can’t be written

  • +
+
+
+
+ +
+ +
+
+

AVR debugger API (alpha)

+

Python AVR MCU debugger

+
+
+class pymcuprog.avrdebugger.AvrDebugger(transport, use_events_for_run_stop_state=True)
+

Bases: object

+

AVR debugger wrapper

+
+
Parameters
+
    +
  • transport (object(hid_transport)) – transport object to communicate through

  • +
  • use_events_for_run_stop_state (boolean) – True to use HID event channel, False to polling

  • +
+
+
+
+
+attach(do_break=False)
+

Attach to the AVR core

+
+
Parameters
+

do_break (bool) – set to True to force the core to stop during attach

+
+
+
+ +
+
+detach()
+

Detach from the AVR core

+
+ +
+
+eeprom_read(address, numbytes)
+

Read EEPROM content from the AVR

+
+
Parameters
+
    +
  • address – absolute address to start reading from

  • +
  • numbytes – number of bytes to read

  • +
+
+
+
+ +
+
+eeprom_write(address, data)
+

Write EEPROM content to the AVR

+
+
Parameters
+
    +
  • address – absolute address in EEPROM to start writing

  • +
  • data – content to store to EEPROM

  • +
+
+
+
+ +
+
+flash_read(address, numbytes)
+

Read flash content from the AVR

+
+
Parameters
+
    +
  • address – absolute address to start reading from

  • +
  • numbytes – number of bytes to read

  • +
+
+
+
+ +
+
+flush_events()
+

Flushes all incoming events before or after a session

+
+ +
+
+hardware_breakpoint_clear()
+

Clears the hardware breakpoint in the AVR OCD

+
+ +
+
+hardware_breakpoint_set(address)
+

Sets a hardware breakpoint in the AVR OCD

+
+
Parameters
+

address – byte address to set hardware breakpoint

+
+
+
+ +
+
+poll_event()
+

Poll for events from the debugger +Events are used to signal AVR core transitions from RUN mode to STOPPED mode

+
+ +
+
+program_counter_read()
+

Reads the program counter register from the AVR

+
+
Returns
+

PC value as word address

+
+
+
+ +
+
+program_counter_write(program_counter)
+

Write a new program counter value (word) to the AVR

+
+
Parameters
+

program_counter – new PC value to write (word address)

+
+
+
+ +
+
+register_file_read()
+

Reads out the AVR register file (R0::R31)

+
+
Returns
+

32 bytes of register file content as bytearray

+
+
+
+ +
+
+register_file_write(regs)
+

Writes the AVR register file (R0::R31)

+
+
Parameters
+

data – 32 byte register file content as bytearray

+
+
Raises
+

ValueError – if 32 bytes are not given

+
+
+
+ +
+
+reset()
+

Reset the AVR core. +The PC will point to the first instruction to be executed.

+
+ +
+
+run()
+

Put the AVR core into run mode

+
+ +
+
+run_to(address)
+

Insert a breakpoint at the given address and put the core into run mode +Does not wait for the address to be reached

+
+
Parameters
+

address – byte address of the instruction to break at

+
+
+
+ +
+
+setup_session(device, frequency=900000, options='')
+

Sets up the device for a debug session

+
+
Parameters
+
    +
  • device – name of the device to debug

  • +
  • frequency (int) – UPDI clock frequency in Hz

  • +
  • options (dict) – dictionary of options for starting the session

  • +
+
+
+
+ +
+
+software_breakpoint_clear(address)
+

Clears a software breakpoint in the AVR flash and restores the original instruction

+
+
Parameters
+

address – byte address to remove software breakpoint

+
+
+
+ +
+
+software_breakpoint_clear_all()
+

Removes all software breakpoints immediately and restores flash to its original content

+
+ +
+
+software_breakpoint_set(address)
+

Sets a software breakpoint in the AVR flash

+
+
Parameters
+

address – byte address to set software breakpoint

+
+
+
+ +
+
+sram_read(address, numbytes)
+

Read SRAM content from the AVR

+
+
Parameters
+
    +
  • address – absolute address to start reading from

  • +
  • numbytes – number of bytes to read

  • +
+
+
+
+ +
+
+sram_write(address, data)
+

Write SRAM content to the AVR

+
+
Parameters
+
    +
  • address – absolute address in SRAM to start writing

  • +
  • data – content to store to SRAM

  • +
+
+
+
+ +
+
+stack_pointer_read()
+

Reads the stack pointer

+
+
Returns
+

Stack pointer

+
+
Return type
+

bytearray

+
+
+
+ +
+
+start_debugging(flash_data=None)
+

Start the debug session

+
+
Parameters
+

flash_data – flash data content to program in before debugging

+
+
+
+ +
+
+status_register_read()
+

Reads the status register from the AVR

+
+
Returns
+

8-bit SREG value

+
+
+
+ +
+
+step()
+

Single-step on protocol level +Executes a single AVR instruction, regardless of number of cycles

+
+ +
+
+stop()
+

Request the AVR core to halt and wait for it to enter stopped state

+
+ +
+
+stop_debugging()
+

Stop the debug session and clean up

+
+ +
+ +
+
+

NVM Access (implementations)

+

NVM layer protocols

+
+
+class pymcuprog.nvm.NvmAccessProvider(device_info)
+

Bases: object

+

Wrapper for device info

+
+
+hold_in_reset()
+

Hold target in reset

+
+ +
+
+release_from_reset()
+

Release target from reset

+
+ +
+
+start(user_interaction_callback=None)
+

Start (activate) session

+
+
Parameters
+

user_interaction_callback – Callback to be called when user interaction is required, +for example when doing UPDI high-voltage activation with user target power toggle. +This function could ask the user to toggle power and halt execution waiting for the user +to respond (this is default behavior if the callback is None), or if the user is another +script it could toggle power automatically and then return.

+
+
+
+ +
+
+stop()
+

Stop (deactivate) session

+
+ +
+ +
+
+class pymcuprog.nvm.NvmAccessProviderCmsisDapAvr(device_info)
+

Bases: pymcuprog.nvm.NvmAccessProviderCmsisDapTool

+

AVR CMSIS DAP Tool

+
+ +
+
+class pymcuprog.nvm.NvmAccessProviderCmsisDapTool(device_info)
+

Bases: pymcuprog.nvm.NvmAccessProvider

+

General CMSIS-DAP Tool

+
+ +
+
+pymcuprog.nvm.get_nvm_access_provider(transport, device_info, interface='', packpath=None, frequency=None, options='')
+

Returns an NVM provider with the requested properties

+
+
Parameters
+
    +
  • transport – Transport layer object

  • +
  • device_info – Device info dict

  • +
  • interface – Physical interface for NVM

  • +
  • packpath – Path to pack

  • +
  • frequency – Interface clock

  • +
  • options – Special options

  • +
+
+
Returns
+

NVM access object

+
+
+
+ +
+
+

NVM implementation: UPDI

+

UPDI NVM implementation

+
+
+class pymcuprog.nvmupdi.NvmAccessProviderCmsisDapUpdi(transport, device_info, frequency=None, options='')
+

Bases: pymcuprog.nvm.NvmAccessProviderCmsisDapAvr

+

NVM Access for AVR UPDI devices

+
+
+erase(memory_info=None, address=None)
+

Erase device, or parts of it

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class. +If memory_info is None a chip_erase will be run

  • +
  • address – address info for erase (optional)

  • +
+
+
+
+ +
+
+hold_in_reset()
+

Hold device in reset

+
+ +
+
+read(memory_info, offset, numbytes)
+

Read the memory in chunks

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset in the memory type

  • +
  • numbytes – number of bytes to read

  • +
+
+
Returns
+

array of bytes read

+
+
+
+ +
+
+read_device_id()
+

Read the device info

+
+
Returns
+

Device ID raw bytes (little endian)

+
+
Raises
+

PymcuprogSessionError – if device ID does not match

+
+
+
+ +
+
+release_from_reset()
+

Release device from reset

+
+ +
+
+start(user_interaction_callback=None)
+

Start (activate) session for UPDI targets

+
+
Parameters
+

user_interaction_callback – Callback to be called when user interaction is required, +for example when doing UPDI high-voltage activation with user target power toggle. +This function could ask the user to toggle power and halt execution waiting for the user +to respond (this is default behavior if the callback is None), or if the user is another +script it could toggle power automatically and then return.

+
+
+
+ +
+
+stop()
+

Stop (deactivate) session for UPDI targets

+
+ +
+
+write(memory_info, offset, data)
+

Write the memory with data

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset within the memory type

  • +
  • data – the data to program

  • +
+
+
+
+ +
+ +
+
+

NVM implementation: serialUPDI

+

pyupdi-esque NVM implementation

+
+
+class pymcuprog.nvmserialupdi.Dut(dev_info)
+

Bases: object

+

Create a device object for UpdiApplication

+
+ +
+
+class pymcuprog.nvmserialupdi.NvmAccessProviderSerial(port, device_info, baud, options='')
+

Bases: pymcuprog.nvm.NvmAccessProvider

+

NVM Access the Python AVR way

+
+
+erase(memory_info=None, address=None)
+

Do a chip erase of the device

+
+ +
+
+hold_in_reset()
+

Hold device in reset

+
+ +
+
+read(memory_info, offset, numbytes)
+

Read the memory in chunks

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset in the memory type

  • +
  • numbytes – number of bytes to read

  • +
+
+
Returns
+

array of bytes read

+
+
+
+ +
+
+read_device_id()
+

Read and display (log) the device info

+
+
Returns
+

Device ID raw bytes (little endian)

+
+
Raises
+

PymcuprogSessionError – if device ID does not match

+
+
+
+ +
+
+release_from_reset()
+

Release device from reset

+
+ +
+
+start(user_interaction_callback=None)
+

Start (activate) session for UPDI serial targets

+
+ +
+
+stop()
+

Stop the debugging session

+
+ +
+
+write(memory_info, offset, data)
+

Write the memory with data

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset within the memory type

  • +
  • data – the data to program

  • +
+
+
+
+ +
+ +
+
+

NVM implementation: PIC

+

PIC NVM implementation

+
+
+class pymcuprog.nvmpic.NvmAccessProviderCmsisDapPic(transport, device_info, packpath, options='')
+

Bases: pymcuprog.nvm.NvmAccessProviderCmsisDapTool

+

NVM access the PIC way

+
+
+erase(memory_info=None, address=None)
+

Erase the device or parts of it.

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class +If memory_info is None the default bulk erase will be run

  • +
  • address – address info for erase (optional)

  • +
+
+
+
+ +
+
+hold_in_reset()
+

Hold the device in reset

+
+ +
+
+read(memory_info, offset, numbytes)
+

Read the memory

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset in the memory type

  • +
  • numbytes – number of bytes to read

  • +
+
+
Returns
+

array of bytes read

+
+
+
+ +
+
+read_device_id()
+

Get the device info from the device

+
+
Returns
+

Device ID raw bytes (little endian)

+
+
+
+ +
+
+release_from_reset()
+

Release the device from reset

+
+ +
+
+stop()
+

Stop programming session

+
+ +
+
+write(memory_info, offset, data)
+

Write the memory with data

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset within the memory type

  • +
  • data – the data to program

  • +
+
+
+
+ +
+ +
+
+

NVM implementation: SAM M0+ (alpha)

+

M0+ NVM implementation

+
+
+class pymcuprog.nvmmzeroplus.NvmAccessProviderCmsisDapMZeroPlus(transport, device_info, frequency=None)
+

Bases: pymcuprog.nvm.NvmAccessProviderCmsisDapTool

+

SAMD programmer

+
+
+erase(memory_info=None, address=None)
+

Do an erase of the device

+
+ +
+
+read(memory_info, offset, numbytes)
+

Read the memory in chunks

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset in the memory type

  • +
  • numbytes – number of bytes to read

  • +
+
+
Returns
+

array of bytes read

+
+
+
+ +
+
+read_device_id()
+

Read out the DAP ID and Device ID

+
+
Returns
+

Device ID raw bytes (little endian)

+
+
+
+ +
+
+stop()
+

Stop programming session

+
+ +
+
+write(memory_info, offset, data)
+

Write the memory with data

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset within the memory type

  • +
  • data – the data to program

  • +
+
+
+
+ +
+ +
+
+

NVM implementation: debugWIRE (alpha)

+

DebugWIRE NVM implementation +NB: This is a stub - not all features are implemented

+
+
+class pymcuprog.nvmdebugwire.NvmAccessProviderCmsisDapDebugwire(transport, device_info)
+

Bases: pymcuprog.nvm.NvmAccessProviderCmsisDapAvr

+

NVM Access the DW way

+
+
+erase(memory_info=None, address=None)
+

Do a chip erase of the device

+
+ +
+
+static read(memory_info, offset, numbytes)
+

Read the memory in chunks

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset in the memory type

  • +
  • numbytes – number of bytes to read

  • +
+
+
Returns
+

array of bytes read

+
+
+
+ +
+
+read_device_id()
+

Read the device info

+
+
Returns
+

Device ID raw bytes (little endian)

+
+
+
+ +
+
+static write(memory_info, offset, data)
+

Write the memory with data

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset within the memory type

  • +
  • data – the data to program

  • +
+
+
+
+ +
+ +
+
+

NVM implementation: SPI (alpha)

+

SPI NVM implementation +NB: This is a stub - not all features are implemented.

+
+
+class pymcuprog.nvmspi.NvmAccessProviderCmsisDapSpi(transport, device_info)
+

Bases: pymcuprog.nvm.NvmAccessProviderCmsisDapAvr

+

NVM Access the SPI way

+
+
+erase(memory_info=None, address=None)
+

Do a chip erase of the device

+
+ +
+
+hold_in_reset()
+

Hold device in reset

+
+ +
+
+read(memory_info, offset, numbytes)
+

Read the memory in chunks

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset in the memory type

  • +
  • numbytes – number of bytes to read

  • +
+
+
Returns
+

array of bytes read

+
+
+
+ +
+
+read_device_id()
+

Read the device info

+
+
Returns
+

Device ID raw bytes (little endian)

+
+
+
+ +
+
+release_from_reset()
+

Release device from reset

+
+ +
+
+stop()
+

Stop programming session

+
+ +
+
+write(memory_info, offset, data)
+

Write the memory with data

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset within the memory type

  • +
  • data – the data to program

  • +
+
+
+
+ +
+ +
+
+

NVM implementation: TPI (alpha)

+

TPI/tinytiny NVM implementation +NB: This is a stub - not all features are implemented

+
+
+class pymcuprog.nvmtpi.NvmAccessProviderCmsisDapTpi(transport, device_info)
+

Bases: pymcuprog.nvm.NvmAccessProviderCmsisDapAvr

+

NVM Access the TPI way

+
+
+erase(memory_info=None, address=None)
+

Do a chip erase of the device

+
+ +
+
+read(memory_info, offset, numbytes)
+

Read the memory in chunks

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset in the memory type

  • +
  • numbytes – number of bytes to read

  • +
+
+
Returns
+

array of bytes read

+
+
+
+ +
+
+read_device_id()
+

Read the device ID

+
+
Returns
+

Device ID raw bytes (little endian)

+
+
+
+ +
+
+stop()
+

Stop programming session

+
+ +
+
+static write(memory_info, offset, data)
+

Write the memory with data

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset within the memory type

  • +
  • data – the data to program

  • +
+
+
+
+ +
+ +
+
+

NVM implementation: megaAVR (JTAG) - (alpha)

+

AVR mega JTAG NVM implementation +NB: This is a stub - not all features are implemented

+
+
+class pymcuprog.nvmmegaavrjtag.NvmAccessProviderCmsisDapMegaAvrJtag(transport, device_info)
+

Bases: pymcuprog.nvm.NvmAccessProviderCmsisDapAvr

+

NVM Access the megaJTAG way

+
+
+erase(memory_info=None, address=None)
+

Do a chip erase of the device

+
+ +
+
+read(memory_info, offset, numbytes)
+

Read the memory in chunks

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset in the memory type

  • +
  • numbytes – number of bytes to read

  • +
+
+
Returns
+

array of bytes read

+
+
+
+ +
+
+read_device_id()
+

Read the device info

+
+
Returns
+

Device ID raw bytes (little endian)

+
+
+
+ +
+
+start(user_interaction_callback=None)
+

Start (activate) session for megaJTAG targets

+
+ +
+
+stop()
+

Stop (deactivate) session for megaJTAG targets

+
+ +
+
+static write(memory_info, offset, data)
+

Write the memory with data

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset within the memory type

  • +
  • data – the data to program

  • +
+
+
+
+ +
+ +
+
+

NVM implementation: XMEGA (alpha)

+

XMEGA NVM implementation +NB: This is a stub - not all features are implemented

+
+
+class pymcuprog.nvmxmega.NvmAccessProviderCmsisDapXmega(transport, device_info)
+

Bases: pymcuprog.nvm.NvmAccessProviderCmsisDapAvr

+

NVM Access the Xmega way

+
+
+erase(memory_info=None, address=None)
+

Do a chip erase of the device

+
+ +
+
+static read(memory_info, offset, numbytes)
+

Read the memory in chunks

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset in the memory type

  • +
  • numbytes – number of bytes to read

  • +
+
+
Returns
+

array of bytes read

+
+
+
+ +
+
+read_device_id()
+

Read the device info

+
+
Returns
+

Device ID raw bytes (little endian)

+
+
+
+ +
+
+start(user_interaction_callback=None)
+

Start (activate) session for XMEGA targets

+
+ +
+
+stop()
+

Stop (deactivate) session for XMEGA targets

+
+ +
+
+static write(memory_info, offset, data)
+

Write the memory with data

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset within the memory type

  • +
  • data – the data to program

  • +
+
+
+
+ +
+ +
+
+

NVM implementation: AVR32 (alpha)

+

AVR32 NVM implementation +NB: This is a stub - not all features are implemented

+
+
+class pymcuprog.nvmavr32.NvmAccessProviderCmsisDapAvr32(transport, device_info, interface='jtag')
+

Bases: pymcuprog.nvm.NvmAccessProviderCmsisDapAvr

+

AVR32 programmer

+
+
+abstract erase(memory_info=None, address=None)
+

Do an erase of the device

+
+ +
+
+abstract read(memory_info, offset, numbytes)
+

Read the memory in chunks

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset in the memory type

  • +
  • numbytes – number of bytes to read

  • +
+
+
Returns
+

array of bytes read

+
+
+
+ +
+
+read_device_id()
+

Read the device info

+
+
Returns
+

Device ID raw bytes (little endian)

+
+
+
+ +
+
+stop()
+

Stop programming session

+
+ +
+
+abstract write(memory_info, offset, data)
+

Write the memory with data

+
+
Parameters
+
    +
  • memory_info – dictionary for the memory as provided by the DeviceMemoryInfo class

  • +
  • offset – relative offset within the memory type

  • +
  • data – the data to program

  • +
+
+
+
+ +
+ +
+
+

Utilities

+

Utility functions for pymcuprog

+
+
+pymcuprog.utils.compare(data0, data1, offset, verify_mask=None)
+

Compares the two byte arrays

+
+
Parameters
+
    +
  • data0 – first array for compare

  • +
  • data1 – second array for compare

  • +
  • offset – address offset in the memory area, for printing

  • +
  • verify_mask – compare mask (for varying instruction width)

  • +
+
+
+
+ +
+
+pymcuprog.utils.enum(**enums)
+

Emulates an Enum type

+

Needed for Python 2.7 compatibility as Python did not get built-in support for enums until version 3.4

+
+ +
+
+pymcuprog.utils.pad_to_size(memory_block, chunk_size, pad_value)
+

Pads a chunk of memory

+
+ +
+
+pymcuprog.utils.pagealign(data, address, page_size, data_size=1)
+

Aligns data to the start of a page

+
+ +
+
+pymcuprog.utils.print_tool_info(info)
+

Print out various tool information

+
+
Parameters
+

info – Dictionary with various tool info as returned from read_tool_info()

+
+
+
+ +
+
+pymcuprog.utils.read_supply_voltage_setpoint(housekeeper)
+

Read supply setpoint

+
+
Parameters
+

housekeeper – instance of pyedbglib.protocols.housekeepingprotocol.Jtagice3HousekeepingProtocol

+
+
+
+ +
+
+pymcuprog.utils.read_target_voltage(housekeeper)
+

Read target voltage

+
+
Parameters
+

housekeeper – instance of pyedbglib.protocols.housekeepingprotocol.Jtagice3HousekeepingProtocol

+
+
+
+ +
+
+pymcuprog.utils.read_tool_info(housekeeper)
+

Interrogates tool (debugger) for useful info

+
+
Returns
+

Dictionary with various info about the connected debugger

+
+
+
+ +
+
+pymcuprog.utils.read_usb_voltage(housekeeper)
+

Read USB voltage

+
+
Parameters
+

housekeeper – instance of pyedbglib.protocols.housekeepingprotocol.Jtagice3HousekeepingProtocol

+
+
+
+ +
+
+pymcuprog.utils.read_voltage_parameter(housekeeper, offset)
+

Generic read voltage from tool parameter

+
+
Parameters
+
    +
  • housekeeper – Instance of pyedbglib.protocols.housekeepingprotocol.Jtagice3HousekeepingProtocol

  • +
  • offset – Tool parameter offset to read

  • +
+
+
+
+ +
+
+pymcuprog.utils.set_supply_voltage_setpoint(housekeeper, voltage)
+

Set supply setpoint

+
+
Parameters
+
    +
  • housekeeper – Instance of pyedbglib.protocols.housekeepingprotocol.Jtagice3HousekeepingProtocol

  • +
  • voltage – New setpoint for target supply

  • +
+
+
+
+ +
+
+pymcuprog.utils.showdata(data, address=0, page_size=None, line_wrap=16, phantom_bytes=0)
+

Show (print) the data

+
+
Parameters
+
    +
  • data – an array/list of data to show

  • +
  • address – byte address to data

  • +
  • page_size – page size in bytes

  • +
  • line_wrap – how many bytes to print per line

  • +
  • phantom_bytes – number of phantom bytes to be added per data byte +Phantom bytes will show up as xx in the output

  • +
+
+
+
+ +
+
+pymcuprog.utils.verify_from_bin(bin_filename, backend, offset=0, memory_name='flash')
+

Verify the contents of flash against a bin-file

+
+
Parameters
+
    +
  • filename – Name/path of bin-file to verify

  • +
  • backend – Reference the Backend class of pymcuprog

  • +
  • offset – Memory offset to start verify from

  • +
  • memory_name – Name of memory as defined in DeviceMemoryInfo (deviceinfo.py)

  • +
+
+
Returns
+

Boolean value indicating success or failure of the operation

+
+
+
+ +
+
+

Hex file utilities

+

Module providing read and write functionality towards hex files with data intended for target device memories

+
+
+pymcuprog.hexfileutils.read_memories_from_hex(filename, device_memory_info)
+

Read the content of a hexfile

+
+
Parameters
+
    +
  • filename – Name/path of hex file to read from

  • +
  • device_memory_info – DeviceMemoryInfo instance for the device the hex file is intended for

  • +
+
+
Returns
+

list of namedtuples with three fields: data, offset and memory_info. data contains a byte array +of raw data bytes, offset is the start address within the memory the data starts at and memory_info +is a dictionary with the memory info as defined in pymcuprog.deviceinfo.deviceinfo

+
+
+
+ +
+
+pymcuprog.hexfileutils.remove_phantom_bytes(data)
+

Remove every 2nd byte from the data

+
+ +
+
+pymcuprog.hexfileutils.write_memories_to_hex(filename, memory_segments)
+

Write a collection of memory segments to a hex file

+

Each segment will be written from relative offset 0 (i.e. start of each memory segment)

+
+
Parameters
+
    +
  • filename – Name/path of hex file to write to

  • +
  • memory_segments – list of namedtuples with two fields: data and memory_info. data contains a +byte array of raw data bytes and memory_info is a dictionary with memory information as defined +in deviceinfo.deviceinfo.DeviceMemoryInfo.

  • +
+
+
+
+ +
+
+pymcuprog.hexfileutils.write_memory_to_hex(filename, memory_segment, offset)
+

Write one memory segment to a hex file with data starting at relative offset given by offset parameter.

+
+
Parameters
+
    +
  • filename – Name/path of hex file to write to

  • +
  • memory_segment – namedtuple with two fields: data and memory_info. data contains a byte array +of raw data bytes and memory_info is a dictionary with memory information as defined in +deviceinfo.deviceinfo.DeviceMemoryInfo).

  • +
  • offset – Relative offset for the data within the memory segment

  • +
+
+
+
+ +
+
+

Tool connections

+

This module includes wrapper classes for Tool connection parameters

+
+
+class pymcuprog.toolconnection.ToolConnection
+

Bases: object

+

Base class for ToolConnection classes used to wrap configuration parameters for tool connections

+
+ +
+
+class pymcuprog.toolconnection.ToolSerialConnection(serialport='COM1')
+

Bases: pymcuprog.toolconnection.ToolConnection

+

Helper class wrapping configuration parameters for a connection to a serial port

+
+
+serialport = None
+
+ +
+ +
+
+class pymcuprog.toolconnection.ToolUsbHidConnection(serialnumber=None, tool_name=None)
+

Bases: pymcuprog.toolconnection.ToolConnection

+

Helper class wrapping configuration parameters for a connection to a USB HID tool

+
+
+serialnumber = None
+
+ +
+
+tool_name = None
+
+ +
+ +
+
+

pymcuprog errors

+

Pymcuprog specific exceptions

+
+
+exception pymcuprog.pymcuprog_errors.PymcuprogDeviceLockedError(msg=None, code=0)
+

Bases: pymcuprog.pymcuprog_errors.PymcuprogError

+

Signals that the device is locked and a chip erase is required to unlock it

+
+ +
+
+exception pymcuprog.pymcuprog_errors.PymcuprogEraseError(msg=None, code=0)
+

Bases: pymcuprog.pymcuprog_errors.PymcuprogError

+

Signals that an erase can’t be executed

+

Either the erase is not possible or the erase can’t be executed without side effects, +i.e. erasing more memories than requested

+
+ +
+
+exception pymcuprog.pymcuprog_errors.PymcuprogError(msg=None, code=0)
+

Bases: Exception

+

Base class for all Pymcuprog specific exceptions

+
+ +
+
+exception pymcuprog.pymcuprog_errors.PymcuprogNotSupportedError(msg=None, code=0)
+

Bases: pymcuprog.pymcuprog_errors.PymcuprogError

+

Signals that an attempted operation is not supported

+
+ +
+
+exception pymcuprog.pymcuprog_errors.PymcuprogSessionConfigError(msg=None, code=0)
+

Bases: pymcuprog.pymcuprog_errors.PymcuprogError

+

Signals that a session is not configured correctly

+
+ +
+
+exception pymcuprog.pymcuprog_errors.PymcuprogSessionError(msg=None, code=0)
+

Bases: pymcuprog.pymcuprog_errors.PymcuprogError

+

Signals that a session is not active

+
+ +
+
+exception pymcuprog.pymcuprog_errors.PymcuprogToolConfigurationError(msg=None, code=0)
+

Bases: pymcuprog.pymcuprog_errors.PymcuprogError

+

Signals that a tool was incorrectly configured

+
+ +
+
+exception pymcuprog.pymcuprog_errors.PymcuprogToolConnectionError(msg=None, code=0)
+

Bases: pymcuprog.pymcuprog_errors.PymcuprogError

+

Signals that an attempted connect failed

+
+ +
+
+

Module contents

+
+

Python MCU programmer utility

+

pymcuprog is a utility for programming various Microchip MCU devices using Microchip CMSIS-DAP based debuggers

+
+
+

Overview

+
+
pymcuprog is available:
+
+
+
+
+

Command-line interface usage

+

For using pymcuprog as a CLI, see help.md (https://github.com/microchip-pic-avr-tools/pymcuprog/blob/main/help.md)

+
+
+

Library usage

+

pymcuprog can be used as a library using its backend API. For example:

+
# Setup logging - pymcuprog uses the Python logging module
+import logging
+logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.WARNING)
+
+# Configure the session:
+from pymcuprog.backend import SessionConfig
+sessionconfig = SessionConfig("atmega4808")
+
+# Instantiate USB transport (only 1 tool connected)
+from pymcuprog.toolconnection import ToolUsbHidConnection
+transport = ToolUsbHidConnection()
+
+# Instantiate backend
+from pymcuprog.backend import Backend
+backend = Backend()
+
+# Connect to tool using transport
+backend.connect_to_tool(transport)
+
+# Start the session
+backend.start_session(sessionconfig)
+
+# Read the target device_id
+device_id = backend.read_device_id()
+print ("Device ID is {0:06X}".format(int.from_bytes(d, byteorder="little")))
+
+# Print the pymcuprog package version:
+from pymcuprog.version import VERSION as pymcuprog_version
+print("pymcuprog version {}".format(pymcuprog_version))
+
+# In addition, the CLI-backend API is versioned for convenience:
+print("pymcuprog backend API version: {}".format(backend.get_api_version()))
+
+
+
+
+

Logging

+

This package uses the Python logging module for publishing log messages to library users. +A basic configuration can be used (see example), but for best results a more thorough configuration is +recommended in order to control the verbosity of output from dependencies in the stack which also use logging. +See logging.yaml which is included in the package (although only used for CLI)

+
+
+

Dependencies

+

pymcuprog depends on pyedbglib for its transport protocol. +pyedbglib requires a USB transport library like libusb. See pyedbglib package for more information.

+
+
+

Supported devices and tools

+

Note: pymcuprog is primarily intended for use with PKOB nano (nEDBG) debuggers which +are found on Curiosity Nano kits and other development boards. This means that it is +continuously tested with a selection of AVR devices with UPDI interface as well as a +selection of PIC devices. However since the protocol is compatible between all +EDBG-based debuggers (pyedbglib) it is possible to use pymcuprog with a wide range of +debuggers and devices, although not all device families/interfaces have been implemented.

+
+
The following Atmel/Microchip debuggers are supported:
    +
  • PKOB nano (nEDBG)

  • +
  • MPLAB PICkit 4 ICD (only when in ‘AVR mode’)

  • +
  • MPLAB Snap ICD (only when in ‘AVR mode’)

  • +
  • Atmel-ICE

  • +
  • Power Debugger

  • +
  • EDBG

  • +
  • mEDBG

  • +
  • JTAGICE3 (only firmware version 3.x)

  • +
+
+
+

Although not all functionality is provided on all debuggers/boards. See device support section below.

+
+
The following device-types are supported:
    +
  • All AVR UPDI devices, whether mounted on kits or standalone

  • +
  • PIC devices mounted on Curiosity Nano kits, or similar board with PKOB nano (nEDBG) debugger

  • +
  • Other devices (eg ATmega328P, ATsamd21e18a) may be partially supported for experimental purposes

  • +
+
+
+
+
+
+ + +
+
+
+
+ + + \ No newline at end of file diff --git a/docs/pymcuprog.serialupdi.html b/docs/pymcuprog.serialupdi.html new file mode 100644 index 0000000..ba42714 --- /dev/null +++ b/docs/pymcuprog.serialupdi.html @@ -0,0 +1,1266 @@ + + + + + + + + + pymcuprog.serialupdi package — pymcuprog documentation + + + + + + + + + + +
+ +
+ + + +
+ + +
+
+
+ +
+

pymcuprog.serialupdi package

+
+

Submodules

+
+
+

pymcuprog.serialupdi.application module

+

Application layer for UPDI stack

+
+
+class pymcuprog.serialupdi.application.UpdiApplication(serialport, baud, device=None)
+

Bases: object

+

Generic application layer for UPDI

+
+
+enter_progmode()
+

Enters into NVM programming mode

+
+ +
+
+in_prog_mode()
+

Checks whether the NVM PROG flag is up

+
+ +
+
+leave_progmode()
+

Disables UPDI which releases any keys enabled

+
+ +
+
+read_data(address, size)
+

Reads a number of bytes of data from UPDI

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • size – number of bytes to read

  • +
+
+
+
+ +
+
+read_data_words(address, words)
+

Reads a number of words of data from UPDI

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • words – number of words to read

  • +
+
+
+
+ +
+
+read_device_info()
+

Reads out device information from various sources

+
+ +
+
+reset(apply_reset)
+

Applies or releases an UPDI reset condition

+
+
Parameters
+

apply_reset – True to apply, False to release

+
+
+
+ +
+
+unlock()
+

Unlock by chip erase

+
+ +
+
+wait_unlocked(timeout_ms)
+

Waits for the device to be unlocked. +All devices boot up as locked until proven otherwise

+
+
Parameters
+

timeout_ms – number of milliseconds to wait

+
+
+
+ +
+
+wait_urow_prog(timeout_ms, wait_for_high)
+

Waits for the device to be in user row write mode +User row is writeable on a locked device using this mechanism

+
+
Parameters
+
    +
  • timeout_ms – number of milliseconds to wait

  • +
  • wait_for_high – set True to wait for bit to go high; False to wait for low

  • +
+
+
+
+ +
+
+write_data(address, data)
+

Writes a number of bytes to memory

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_data_words(address, data)
+

Writes a number of words to memory

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_user_row_locked_device(address, data)
+

Writes data to the user row when the device is locked, using a key.

+
+ +
+ +
+
+pymcuprog.serialupdi.application.decode_sib(sib)
+

Turns the SIB into something readable

+
+
Parameters
+

sib – SIB data to decode

+
+
+
+ +
+
+

pymcuprog.serialupdi.constants module

+

UPDI protocol constants

+
+ +
+

pymcuprog.serialupdi.nvm module

+

NVM implementations on various UPDI device families

+
+
+class pymcuprog.serialupdi.nvm.NvmUpdi(readwrite, device)
+

Bases: object

+

Base class for NVM

+
+
+chip_erase()
+

Does a chip erase using the NVM controller

+
+ +
+
+erase_eeprom()
+

Erase EEPROM memory only

+
+ +
+
+erase_flash_page(address)
+

Erasing single flash page using the NVM controller

+
+
Parameters
+

address (int) – Start address of page to erase

+
+
+
+ +
+
+erase_user_row(address, size)
+

Erase User Row memory only

+
+
Parameters
+
    +
  • address (int) – Start address of user row

  • +
  • size (int) – Size of user row

  • +
+
+
+
+ +
+
+execute_nvm_command(command)
+

Executes an NVM COMMAND on the NVM CTRL

+
+
Parameters
+

command – command to execute

+
+
+
+ +
+
+wait_nvm_ready()
+

Waits for the NVM controller to be ready

+
+ +
+
+write_eeprom(address, data)
+

Write data to EEPROM

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_flash(address, data)
+

Writes data to flash

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_fuse(address, data)
+

Writes one fuse value

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_user_row(address, data)
+

Writes data to user row

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+ +
+
+class pymcuprog.serialupdi.nvm.NvmUpdiAvrV2(readwrite, device)
+

Bases: pymcuprog.serialupdi.nvm.NvmUpdi

+

AKA Version 2 UPDI NVM +Present on, for example, AVR-DA and newer

+
+
+chip_erase()
+

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

+
+ +
+
+erase_eeprom()
+

Erase EEPROM memory only (v1)

+
+ +
+
+erase_flash_page(address)
+

Erasing single flash page using the NVM controller (v1)

+
+
Parameters
+

address (int) – Start address of page to erase

+
+
+
+ +
+
+erase_user_row(address, size)
+

Erase User Row memory only (v1)

+
+
Parameters
+

address (int) – Start address of user row

+
+
+
+ +
+
+write_eeprom(address, data)
+

Writes data to NVM (EEPROM)

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_flash(address, data)
+

Writes data to flash (v1)

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_fuse(address, data)
+

Writes one fuse value +V1 fuses are EEPROM-based

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_nvm(address, data, use_word_access)
+

Writes data to NVM (version 1) +This version of the NVM block has no page buffer, so words are written directly.

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
  • use_word_access – write in whole words?

  • +
+
+
+
+ +
+
+write_user_row(address, data)
+

Writes data to user row (v1)

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+ +
+
+class pymcuprog.serialupdi.nvm.NvmUpdiAvrV3(readwrite, device)
+

Bases: pymcuprog.serialupdi.nvm.NvmUpdi

+

AKA Version 3 UPDI NVM +Present on, for example, AVR-EA

+
+
+chip_erase()
+

Does a chip erase using the NVM controller

+

Note that on locked devices this is not possible +and the ERASE KEY has to be used instead, see the unlock method

+
+ +
+
+erase_eeprom()
+

Erase EEPROM memory only

+
+ +
+
+erase_flash_page(address)
+

Erasing single flash page using the NVM controller (v3)

+
+
Parameters
+

address (int) – Start address of page to erase

+
+
+
+ +
+
+erase_user_row(address, size)
+

Erase User Row memory only

+
+
Parameters
+

address (int) – Start address of user row

+
+
+
+ +
+
+write_eeprom(address, data)
+

Write data to EEPROM (v3)

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_flash(address, data)
+

Writes data to flash (v3)

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_fuse(address, data)
+

Writes one fuse value (v3)

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_nvm(address, data, use_word_access, nvmcommand=4)
+

Writes a page of data to NVM (v3)

+

By default the PAGE_WRITE command is used, which +requires that the page is already erased. +By default word access is used (flash)

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
  • use_word_access – write whole words?

  • +
  • nvmcommand – command to use for commit

  • +
+
+
+
+ +
+
+write_user_row(address, data)
+

Writes data to user row (v3)

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+ +
+
+class pymcuprog.serialupdi.nvm.NvmUpdiV0(readwrite, device)
+

Bases: pymcuprog.serialupdi.nvm.NvmUpdi

+

AKA Version 0 UPDI NVM +Present on, for example, tiny817 -> mega4809

+
+
+chip_erase()
+

Does a chip erase using the NVM controller

+

Note that on locked devices this is not possible +and the ERASE KEY has to be used instead, see the unlock method

+
+ +
+
+erase_eeprom()
+

Erase EEPROM memory only (v0)

+
+ +
+
+erase_flash_page(address)
+

Erasing single flash page using the NVM controller (v0)

+
+
Parameters
+

address (int) – Start address of page to erase

+
+
+
+ +
+
+erase_user_row(address, size)
+

Erase User Row memory only (v0)

+
+
Parameters
+

address (int) – Start address of user row

+
+
+
+ +
+
+write_eeprom(address, data)
+

Write data to EEPROM (v0)

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_flash(address, data)
+

Writes data to flash (v0)

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_fuse(address, data)
+

Writes one fuse value (v0)

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_nvm(address, data, use_word_access, nvmcommand=1)
+

Writes a page of data to NVM (v0)

+

By default the PAGE_WRITE command is used, which +requires that the page is already erased. +By default word access is used (flash)

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
  • use_word_access – write whole words?

  • +
  • nvmcommand – command to use for commit

  • +
+
+
+
+ +
+
+write_user_row(address, data)
+

Writes data to user row (v0)

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+ +
+
+

pymcuprog.serialupdi.physical module

+

Serial driver for UPDI stack

+
+
+class pymcuprog.serialupdi.physical.UpdiPhysical(port, baud=115200)
+

Bases: object

+

PDI physical driver using a given serial port at a given baud

+
+
+initialise_serial(port, baud)
+

Standard serial port initialisation

+
+
Parameters
+
    +
  • port – serial port to use

  • +
  • baud – baud rate

  • +
+
+
+
+ +
+
+receive(size)
+

Receives a frame of a known number of chars from UPDI

+
+
Parameters
+

size – bytes to receive

+
+
+
+ +
+
+send(command)
+

Sends a char array to UPDI without inter-byte delay +Note that the byte will echo back

+
+
Parameters
+

command – command to send

+
+
+
+ +
+
+send_double_break()
+

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

+
+ +
+
+sib()
+

System information block is just a string coming back from a SIB command

+
+ +
+ +
+
+

pymcuprog.serialupdi.readwrite module

+

Read/write access provider for UPDI

+
+
+class pymcuprog.serialupdi.readwrite.UpdiReadWrite(datalink)
+

Bases: object

+

Provides various forms of reads and writes for UPDI applications +Makes us of the datalink provided

+
+
+read_byte(address)
+

Read a single byte from UPDI

+
+
Parameters
+

address – address to read from

+
+
Returns
+

value read

+
+
+
+ +
+
+read_cs(address)
+

Read from Control/Status space

+
+
Parameters
+

address – address (index) to read

+
+
Returns
+

value read

+
+
+
+ +
+
+read_data(address, size)
+

Reads a number of bytes of data from UPDI

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • size – number of bytes to read

  • +
+
+
+
+ +
+
+read_data_words(address, words)
+

Reads a number of words of data from UPDI

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • words – number of words to read

  • +
+
+
+
+ +
+
+read_sib()
+

Read the SIB from UPDI

+
+
Returns
+

SIB string (bytearray) read

+
+
+
+ +
+
+write_byte(address, value)
+

Writes a single byte to UPDI

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • value – value to write

  • +
+
+
+
+ +
+
+write_cs(address, value)
+

Write to Control/Status space

+
+
Parameters
+
    +
  • address – address (index) to write

  • +
  • value – 8-bit value to write

  • +
+
+
+
+ +
+
+write_data(address, data)
+

Writes a number of bytes to memory

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_data_words(address, data)
+

Writes a number of words to memory

+
+
Parameters
+
    +
  • address – address to write to

  • +
  • data – data to write

  • +
+
+
+
+ +
+
+write_key(size, key)
+

Write a KEY into UPDI

+
+
Parameters
+
    +
  • size – size of key to send

  • +
  • key – key value

  • +
+
+
+
+ +
+ +
+
+

pymcuprog.serialupdi.timeout module

+

Simple timer helper for UPDI stack

+
+
+class pymcuprog.serialupdi.timeout.Timeout(timeout_ms)
+

Bases: object

+

Simple timeout helper in milliseconds.

+
+
+expired()
+

Check if the timeout has expired

+
+ +
+ +
+
+

Module contents

+
+
+ + +
+
+
+
+ + + \ No newline at end of file diff --git a/docs/search.html b/docs/search.html new file mode 100644 index 0000000..b8784ea --- /dev/null +++ b/docs/search.html @@ -0,0 +1,100 @@ + + + + + + + + Search — pymcuprog documentation + + + + + + + + + + + + + + + +
+ +
+ + + +
+ + +
+
+
+ +

Search

+ + + + +

+ Searching for multiple words only shows matches that contain + all words. +

+ + +
+ + + +
+ + + +
+ +
+ + +
+
+
+
+ + + \ No newline at end of file diff --git a/docs/searchindex.js b/docs/searchindex.js new file mode 100644 index 0000000..77e6a03 --- /dev/null +++ b/docs/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({docnames:["index","pymcuprog","pymcuprog.deviceinfo","pymcuprog.serialupdi"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":5,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,sphinx:56},filenames:["index.rst","pymcuprog.rst","pymcuprog.deviceinfo.rst","pymcuprog.serialupdi.rst"],objects:{"":[[1,0,0,"-","pymcuprog"]],"pymcuprog.avr32target":[[1,1,1,"","Avr32Device"]],"pymcuprog.avr32target.Avr32Device":[[1,2,1,"","activate_physical"],[1,2,1,"","deactivate_physical"],[1,2,1,"","setup_prog_session"]],"pymcuprog.avr8target":[[1,1,1,"","AvrDevice"],[1,1,1,"","MegaAvrJtagTarget"],[1,1,1,"","TinyAvrTarget"],[1,1,1,"","TinyXAvrTarget"],[1,1,1,"","XmegaAvrTarget"]],"pymcuprog.avr8target.AvrDevice":[[1,2,1,"","activate_physical"],[1,2,1,"","deactivate_physical"],[1,2,1,"","enter_progmode"],[1,2,1,"","erase"],[1,2,1,"","is_blank"],[1,2,1,"","leave_progmode"],[1,2,1,"","memory_read"],[1,2,1,"","memory_write"],[1,2,1,"","memtype_read_from_string"],[1,2,1,"","read_memory_section"],[1,2,1,"","write_memory_section"]],"pymcuprog.avr8target.MegaAvrJtagTarget":[[1,2,1,"","setup_config"],[1,2,1,"","setup_prog_session"]],"pymcuprog.avr8target.TinyAvrTarget":[[1,2,1,"","setup_debug_session"]],"pymcuprog.avr8target.TinyXAvrTarget":[[1,2,1,"","activate_physical"],[1,2,1,"","breakpoint_clear"],[1,2,1,"","breakpoint_set"],[1,2,1,"","execute_instruction"],[1,2,1,"","execute_patch"],[1,2,1,"","memtype_write_from_string"],[1,2,1,"","ocdregfile_read"],[1,2,1,"","read_device_id"],[1,2,1,"","run_with_power_nap"],[1,2,1,"","setup_config"],[1,2,1,"","setup_debug_session"],[1,2,1,"","setup_prog_session"],[1,2,1,"","sib_read"],[1,2,1,"","stack_pointer_read"]],"pymcuprog.avr8target.XmegaAvrTarget":[[1,2,1,"","setup_config"],[1,2,1,"","setup_debug_session"],[1,2,1,"","setup_prog_session"]],"pymcuprog.avrdebugger":[[1,1,1,"","AvrDebugger"]],"pymcuprog.avrdebugger.AvrDebugger":[[1,2,1,"","attach"],[1,2,1,"","detach"],[1,2,1,"","eeprom_read"],[1,2,1,"","eeprom_write"],[1,2,1,"","flash_read"],[1,2,1,"","flush_events"],[1,2,1,"","hardware_breakpoint_clear"],[1,2,1,"","hardware_breakpoint_set"],[1,2,1,"","poll_event"],[1,2,1,"","program_counter_read"],[1,2,1,"","program_counter_write"],[1,2,1,"","register_file_read"],[1,2,1,"","register_file_write"],[1,2,1,"","reset"],[1,2,1,"","run"],[1,2,1,"","run_to"],[1,2,1,"","setup_session"],[1,2,1,"","software_breakpoint_clear"],[1,2,1,"","software_breakpoint_clear_all"],[1,2,1,"","software_breakpoint_set"],[1,2,1,"","sram_read"],[1,2,1,"","sram_write"],[1,2,1,"","stack_pointer_read"],[1,2,1,"","start_debugging"],[1,2,1,"","status_register_read"],[1,2,1,"","step"],[1,2,1,"","stop"],[1,2,1,"","stop_debugging"]],"pymcuprog.backend":[[1,1,1,"","Backend"],[1,1,1,"","SessionConfig"]],"pymcuprog.backend.Backend":[[1,3,1,"","API_VERSION"],[1,2,1,"","connect_to_tool"],[1,2,1,"","disconnect_from_tool"],[1,2,1,"","end_session"],[1,2,1,"","erase"],[1,2,1,"","get_api_version"],[1,2,1,"","get_available_hid_tools"],[1,2,1,"","get_chiperase_effect"],[1,2,1,"","get_device_info"],[1,2,1,"","get_supported_devices"],[1,2,1,"","hold_in_reset"],[1,2,1,"","is_isolated_erase_possible"],[1,2,1,"","read_device_id"],[1,2,1,"","read_kit_device"],[1,2,1,"","read_memory"],[1,2,1,"","read_supply_voltage_setpoint"],[1,2,1,"","read_target_voltage"],[1,2,1,"","read_tool_info"],[1,2,1,"","read_usb_voltage"],[1,2,1,"","reboot_tool"],[1,2,1,"","release_from_reset"],[1,2,1,"","set_supply_voltage_setpoint"],[1,2,1,"","start_session"],[1,2,1,"","verify_hex"],[1,2,1,"","verify_memory"],[1,2,1,"","write_hex_to_target"],[1,2,1,"","write_memory"]],"pymcuprog.backend.SessionConfig":[[1,3,1,"","device"],[1,3,1,"","interface"],[1,3,1,"","interface_speed"],[1,3,1,"","packpath"],[1,3,1,"","special_options"]],"pymcuprog.deviceinfo":[[2,0,0,"-","deviceinfo"],[2,0,0,"-","deviceinfokeys"],[2,0,0,"-","eraseflags"],[2,0,0,"-","memorynames"]],"pymcuprog.deviceinfo.deviceinfo":[[2,1,1,"","DeviceMemoryInfo"],[2,4,1,"","get_supported_devices"],[2,4,1,"","getdeviceinfo"]],"pymcuprog.deviceinfo.deviceinfo.DeviceMemoryInfo":[[2,2,1,"","bytes_or_words"],[2,2,1,"","memory_info_by_address"],[2,2,1,"","memory_info_by_address_range"],[2,2,1,"","memory_info_by_name"]],"pymcuprog.deviceinfo.deviceinfokeys":[[2,1,1,"","DeviceInfoKeys"],[2,1,1,"","DeviceInfoKeysAvr"],[2,1,1,"","DeviceInfoKeysAvr32"],[2,1,1,"","DeviceInfoKeysPic"],[2,1,1,"","DeviceMemoryInfoKeys"]],"pymcuprog.deviceinfo.deviceinfokeys.DeviceInfoKeys":[[2,3,1,"","ARCHITECTURE"],[2,3,1,"","DEVICE_ID"],[2,3,1,"","INTERFACE"],[2,3,1,"","NAME"],[2,2,1,"","get_all"]],"pymcuprog.deviceinfo.deviceinfokeys.DeviceInfoKeysAvr":[[2,3,1,"","ADDRESS_SIZE"],[2,3,1,"","DATA_ADDRESS_SPACE"],[2,3,1,"","HV_IMPLEMENTATION"],[2,3,1,"","NVMCTRL_BASE"],[2,3,1,"","OCD_BASE"],[2,3,1,"","PROG_CLOCK_KHZ"],[2,3,1,"","SYSCFG_BASE"]],"pymcuprog.deviceinfo.deviceinfokeys.DeviceInfoKeysAvr32":[[2,3,1,"","RESET_DOMAINS"]],"pymcuprog.deviceinfo.deviceinfokeys.DeviceInfoKeysPic":[[2,3,1,"","DEFAULT_BULK_ERASE_ADDRESS"]],"pymcuprog.deviceinfo.deviceinfokeys.DeviceMemoryInfoKeys":[[2,3,1,"","ADDRESS"],[2,3,1,"","CHIPERASE_EFFECT"],[2,3,1,"","ERASE_ADDRESS"],[2,3,1,"","HEXFILE_ADDRESS"],[2,3,1,"","HEXFILE_SIZE"],[2,3,1,"","ISOLATED_ERASE"],[2,3,1,"","NAME"],[2,3,1,"","PAGE_SIZE"],[2,3,1,"","READ_SIZE"],[2,3,1,"","SIZE"],[2,3,1,"","VERIFY_MASK"],[2,3,1,"","WRITE_SIZE"],[2,2,1,"","get_all"]],"pymcuprog.deviceinfo.eraseflags":[[2,4,1,"","get_list_of_chiperase_effects"]],"pymcuprog.deviceinfo.memorynames":[[2,1,1,"","MemoryNameAliases"],[2,1,1,"","MemoryNames"]],"pymcuprog.deviceinfo.memorynames.MemoryNameAliases":[[2,3,1,"","ALL"]],"pymcuprog.deviceinfo.memorynames.MemoryNames":[[2,3,1,"","CALIBRATION_ROW"],[2,3,1,"","CONFIG_WORD"],[2,3,1,"","EEPROM"],[2,3,1,"","FLASH"],[2,3,1,"","FUSES"],[2,3,1,"","ICD"],[2,3,1,"","INTERNAL_SRAM"],[2,3,1,"","LOCKBITS"],[2,3,1,"","SIGNATURES"],[2,3,1,"","USER_ID"],[2,3,1,"","USER_ROW"],[2,2,1,"","get_all"]],"pymcuprog.hexfileutils":[[1,4,1,"","read_memories_from_hex"],[1,4,1,"","remove_phantom_bytes"],[1,4,1,"","write_memories_to_hex"],[1,4,1,"","write_memory_to_hex"]],"pymcuprog.nvm":[[1,1,1,"","NvmAccessProvider"],[1,1,1,"","NvmAccessProviderCmsisDapAvr"],[1,1,1,"","NvmAccessProviderCmsisDapTool"],[1,4,1,"","get_nvm_access_provider"]],"pymcuprog.nvm.NvmAccessProvider":[[1,2,1,"","hold_in_reset"],[1,2,1,"","release_from_reset"],[1,2,1,"","start"],[1,2,1,"","stop"]],"pymcuprog.nvmavr32":[[1,1,1,"","NvmAccessProviderCmsisDapAvr32"]],"pymcuprog.nvmavr32.NvmAccessProviderCmsisDapAvr32":[[1,2,1,"","erase"],[1,2,1,"","read"],[1,2,1,"","read_device_id"],[1,2,1,"","stop"],[1,2,1,"","write"]],"pymcuprog.nvmdebugwire":[[1,1,1,"","NvmAccessProviderCmsisDapDebugwire"]],"pymcuprog.nvmdebugwire.NvmAccessProviderCmsisDapDebugwire":[[1,2,1,"","erase"],[1,2,1,"","read"],[1,2,1,"","read_device_id"],[1,2,1,"","write"]],"pymcuprog.nvmmegaavrjtag":[[1,1,1,"","NvmAccessProviderCmsisDapMegaAvrJtag"]],"pymcuprog.nvmmegaavrjtag.NvmAccessProviderCmsisDapMegaAvrJtag":[[1,2,1,"","erase"],[1,2,1,"","read"],[1,2,1,"","read_device_id"],[1,2,1,"","start"],[1,2,1,"","stop"],[1,2,1,"","write"]],"pymcuprog.nvmmzeroplus":[[1,1,1,"","NvmAccessProviderCmsisDapMZeroPlus"]],"pymcuprog.nvmmzeroplus.NvmAccessProviderCmsisDapMZeroPlus":[[1,2,1,"","erase"],[1,2,1,"","read"],[1,2,1,"","read_device_id"],[1,2,1,"","stop"],[1,2,1,"","write"]],"pymcuprog.nvmpic":[[1,1,1,"","NvmAccessProviderCmsisDapPic"]],"pymcuprog.nvmpic.NvmAccessProviderCmsisDapPic":[[1,2,1,"","erase"],[1,2,1,"","hold_in_reset"],[1,2,1,"","read"],[1,2,1,"","read_device_id"],[1,2,1,"","release_from_reset"],[1,2,1,"","stop"],[1,2,1,"","write"]],"pymcuprog.nvmserialupdi":[[1,1,1,"","Dut"],[1,1,1,"","NvmAccessProviderSerial"]],"pymcuprog.nvmserialupdi.NvmAccessProviderSerial":[[1,2,1,"","erase"],[1,2,1,"","hold_in_reset"],[1,2,1,"","read"],[1,2,1,"","read_device_id"],[1,2,1,"","release_from_reset"],[1,2,1,"","start"],[1,2,1,"","stop"],[1,2,1,"","write"]],"pymcuprog.nvmspi":[[1,1,1,"","NvmAccessProviderCmsisDapSpi"]],"pymcuprog.nvmspi.NvmAccessProviderCmsisDapSpi":[[1,2,1,"","erase"],[1,2,1,"","hold_in_reset"],[1,2,1,"","read"],[1,2,1,"","read_device_id"],[1,2,1,"","release_from_reset"],[1,2,1,"","stop"],[1,2,1,"","write"]],"pymcuprog.nvmtpi":[[1,1,1,"","NvmAccessProviderCmsisDapTpi"]],"pymcuprog.nvmtpi.NvmAccessProviderCmsisDapTpi":[[1,2,1,"","erase"],[1,2,1,"","read"],[1,2,1,"","read_device_id"],[1,2,1,"","stop"],[1,2,1,"","write"]],"pymcuprog.nvmupdi":[[1,1,1,"","NvmAccessProviderCmsisDapUpdi"]],"pymcuprog.nvmupdi.NvmAccessProviderCmsisDapUpdi":[[1,2,1,"","erase"],[1,2,1,"","hold_in_reset"],[1,2,1,"","read"],[1,2,1,"","read_device_id"],[1,2,1,"","release_from_reset"],[1,2,1,"","start"],[1,2,1,"","stop"],[1,2,1,"","write"]],"pymcuprog.nvmxmega":[[1,1,1,"","NvmAccessProviderCmsisDapXmega"]],"pymcuprog.nvmxmega.NvmAccessProviderCmsisDapXmega":[[1,2,1,"","erase"],[1,2,1,"","read"],[1,2,1,"","read_device_id"],[1,2,1,"","start"],[1,2,1,"","stop"],[1,2,1,"","write"]],"pymcuprog.programmer":[[1,1,1,"","Programmer"]],"pymcuprog.programmer.Programmer":[[1,2,1,"","erase"],[1,2,1,"","get_device_memory_info"],[1,2,1,"","get_device_model"],[1,2,1,"","hold_in_reset"],[1,2,1,"","load_device"],[1,2,1,"","read_device_id"],[1,2,1,"","read_memory"],[1,2,1,"","release_from_reset"],[1,2,1,"","set_options"],[1,2,1,"","setup_device"],[1,2,1,"","start"],[1,2,1,"","stop"],[1,2,1,"","verify_memory"],[1,2,1,"","write_memory"]],"pymcuprog.pymcuprog_errors":[[1,5,1,"","PymcuprogDeviceLockedError"],[1,5,1,"","PymcuprogEraseError"],[1,5,1,"","PymcuprogError"],[1,5,1,"","PymcuprogNotSupportedError"],[1,5,1,"","PymcuprogSessionConfigError"],[1,5,1,"","PymcuprogSessionError"],[1,5,1,"","PymcuprogToolConfigurationError"],[1,5,1,"","PymcuprogToolConnectionError"]],"pymcuprog.samtarget":[[1,1,1,"","SamD2xTarget"],[1,1,1,"","SamM4Target"],[1,1,1,"","SamTarget"]],"pymcuprog.samtarget.SamD2xTarget":[[1,3,1,"","CMD_EAR"],[1,3,1,"","CMD_LR"],[1,3,1,"","CMD_PBC"],[1,3,1,"","CMD_UR"],[1,3,1,"","CMD_WAP"],[1,3,1,"","CMD_WP"],[1,3,1,"","DAP_TRANSFER_IDLE_CYCLES"],[1,3,1,"","DAP_TRANSFER_MATCH_RETRY"],[1,3,1,"","DAP_TRANSFER_RETRY_COUNT"],[1,3,1,"","DSU_ADDRESS"],[1,3,1,"","DSU_CHIP_ERASE_TIMEOUT_MS"],[1,3,1,"","DSU_CTRL_OFFSET"],[1,3,1,"","DSU_CTRL_STATUS_CE_COMMAND_MASK"],[1,3,1,"","DSU_CTRL_STATUS_DONE_MASK"],[1,3,1,"","DSU_CTRL_STATUS_PROT_MASK"],[1,3,1,"","DSU_DID_OFFSET"],[1,3,1,"","DSU_EXTERNAL_OFFSET"],[1,3,1,"","FLASH_LOCK_REGIONS"],[1,3,1,"","FLASH_LOCK_REGION_SIZE"],[1,3,1,"","NVM_CTRLB_MANW_BIT"],[1,3,1,"","NVM_CTRL_ADDRESS"],[1,3,1,"","NVM_CTRL_ADDR_OFFSET"],[1,3,1,"","NVM_CTRL_CTRLB_OFFSET"],[1,3,1,"","NVM_CTRL_CTRL_OFFSET"],[1,3,1,"","NVM_CTRL_INTFLAG_OFFSET"],[1,3,1,"","NVM_CTRL_LOCK_OFFSET"],[1,3,1,"","NVM_CTRL_STATUS_OFFSET"],[1,3,1,"","NVM_INT_ERROR_BIT"],[1,3,1,"","NVM_INT_READY_BIT"],[1,3,1,"","NVM_STATUS_LOCKE"],[1,3,1,"","NVM_STATUS_NVME"],[1,3,1,"","NVM_STATUS_PROGE"],[1,3,1,"","NVM_STATUS_SB"],[1,2,1,"","chip_erase_dsu"],[1,2,1,"","connect"],[1,2,1,"","disconnect"],[1,2,1,"","erase_user_row"],[1,2,1,"","is_device_locked"],[1,2,1,"","is_flash_ready"],[1,2,1,"","nvm_command"],[1,2,1,"","post_flash_write"],[1,2,1,"","pre_flash_write"],[1,2,1,"","read_device_id"],[1,2,1,"","read_flash"],[1,2,1,"","read_user_row"],[1,2,1,"","reinitialise"],[1,2,1,"","set_nvmctrl_address"],[1,2,1,"","unlock_all_regions"],[1,2,1,"","wait_flash_ready"],[1,2,1,"","write_flash_page"],[1,2,1,"","write_user_row_word"]],"pymcuprog.samtarget.SamTarget":[[1,2,1,"","ap_read"],[1,2,1,"","ap_write"],[1,2,1,"","dp_read"],[1,2,1,"","dp_write"],[1,2,1,"","read_idcode"]],"pymcuprog.serialupdi":[[3,0,0,"-","application"],[3,0,0,"-","constants"],[3,0,0,"-","link"],[3,0,0,"-","nvm"],[3,0,0,"-","physical"],[3,0,0,"-","readwrite"],[3,0,0,"-","timeout"]],"pymcuprog.serialupdi.application":[[3,1,1,"","UpdiApplication"],[3,4,1,"","decode_sib"]],"pymcuprog.serialupdi.application.UpdiApplication":[[3,2,1,"","enter_progmode"],[3,2,1,"","in_prog_mode"],[3,2,1,"","leave_progmode"],[3,2,1,"","read_data"],[3,2,1,"","read_data_words"],[3,2,1,"","read_device_info"],[3,2,1,"","reset"],[3,2,1,"","unlock"],[3,2,1,"","wait_unlocked"],[3,2,1,"","wait_urow_prog"],[3,2,1,"","write_data"],[3,2,1,"","write_data_words"],[3,2,1,"","write_user_row_locked_device"]],"pymcuprog.serialupdi.link":[[3,1,1,"","UpdiDatalink"],[3,1,1,"","UpdiDatalink16bit"],[3,1,1,"","UpdiDatalink24bit"]],"pymcuprog.serialupdi.link.UpdiDatalink":[[3,3,1,"","LDCS_RESPONSE_BYTES"],[3,2,1,"","init_datalink"],[3,2,1,"","key"],[3,2,1,"","ld_ptr_inc"],[3,2,1,"","ld_ptr_inc16"],[3,2,1,"","ldcs"],[3,2,1,"","read_sib"],[3,2,1,"","repeat"],[3,2,1,"","set_physical"],[3,2,1,"","st_ptr_inc"],[3,2,1,"","st_ptr_inc16"],[3,2,1,"","stcs"]],"pymcuprog.serialupdi.link.UpdiDatalink16bit":[[3,2,1,"","ld"],[3,2,1,"","ld16"],[3,2,1,"","st"],[3,2,1,"","st16"],[3,2,1,"","st_ptr"]],"pymcuprog.serialupdi.link.UpdiDatalink24bit":[[3,2,1,"","ld"],[3,2,1,"","ld16"],[3,2,1,"","st"],[3,2,1,"","st16"],[3,2,1,"","st_ptr"]],"pymcuprog.serialupdi.nvm":[[3,1,1,"","NvmUpdi"],[3,1,1,"","NvmUpdiAvrV2"],[3,1,1,"","NvmUpdiAvrV3"],[3,1,1,"","NvmUpdiV0"]],"pymcuprog.serialupdi.nvm.NvmUpdi":[[3,2,1,"","chip_erase"],[3,2,1,"","erase_eeprom"],[3,2,1,"","erase_flash_page"],[3,2,1,"","erase_user_row"],[3,2,1,"","execute_nvm_command"],[3,2,1,"","wait_nvm_ready"],[3,2,1,"","write_eeprom"],[3,2,1,"","write_flash"],[3,2,1,"","write_fuse"],[3,2,1,"","write_user_row"]],"pymcuprog.serialupdi.nvm.NvmUpdiAvrV2":[[3,2,1,"","chip_erase"],[3,2,1,"","erase_eeprom"],[3,2,1,"","erase_flash_page"],[3,2,1,"","erase_user_row"],[3,2,1,"","write_eeprom"],[3,2,1,"","write_flash"],[3,2,1,"","write_fuse"],[3,2,1,"","write_nvm"],[3,2,1,"","write_user_row"]],"pymcuprog.serialupdi.nvm.NvmUpdiAvrV3":[[3,2,1,"","chip_erase"],[3,2,1,"","erase_eeprom"],[3,2,1,"","erase_flash_page"],[3,2,1,"","erase_user_row"],[3,2,1,"","write_eeprom"],[3,2,1,"","write_flash"],[3,2,1,"","write_fuse"],[3,2,1,"","write_nvm"],[3,2,1,"","write_user_row"]],"pymcuprog.serialupdi.nvm.NvmUpdiV0":[[3,2,1,"","chip_erase"],[3,2,1,"","erase_eeprom"],[3,2,1,"","erase_flash_page"],[3,2,1,"","erase_user_row"],[3,2,1,"","write_eeprom"],[3,2,1,"","write_flash"],[3,2,1,"","write_fuse"],[3,2,1,"","write_nvm"],[3,2,1,"","write_user_row"]],"pymcuprog.serialupdi.physical":[[3,1,1,"","UpdiPhysical"]],"pymcuprog.serialupdi.physical.UpdiPhysical":[[3,2,1,"","initialise_serial"],[3,2,1,"","receive"],[3,2,1,"","send"],[3,2,1,"","send_double_break"],[3,2,1,"","sib"]],"pymcuprog.serialupdi.readwrite":[[3,1,1,"","UpdiReadWrite"]],"pymcuprog.serialupdi.readwrite.UpdiReadWrite":[[3,2,1,"","read_byte"],[3,2,1,"","read_cs"],[3,2,1,"","read_data"],[3,2,1,"","read_data_words"],[3,2,1,"","read_sib"],[3,2,1,"","write_byte"],[3,2,1,"","write_cs"],[3,2,1,"","write_data"],[3,2,1,"","write_data_words"],[3,2,1,"","write_key"]],"pymcuprog.serialupdi.timeout":[[3,1,1,"","Timeout"]],"pymcuprog.serialupdi.timeout.Timeout":[[3,2,1,"","expired"]],"pymcuprog.toolconnection":[[1,1,1,"","ToolConnection"],[1,1,1,"","ToolSerialConnection"],[1,1,1,"","ToolUsbHidConnection"]],"pymcuprog.toolconnection.ToolSerialConnection":[[1,3,1,"","serialport"]],"pymcuprog.toolconnection.ToolUsbHidConnection":[[1,3,1,"","serialnumber"],[1,3,1,"","tool_name"]],"pymcuprog.utils":[[1,4,1,"","compare"],[1,4,1,"","enum"],[1,4,1,"","pad_to_size"],[1,4,1,"","pagealign"],[1,4,1,"","print_tool_info"],[1,4,1,"","read_supply_voltage_setpoint"],[1,4,1,"","read_target_voltage"],[1,4,1,"","read_tool_info"],[1,4,1,"","read_usb_voltage"],[1,4,1,"","read_voltage_parameter"],[1,4,1,"","set_supply_voltage_setpoint"],[1,4,1,"","showdata"],[1,4,1,"","verify_from_bin"]],pymcuprog:[[1,0,0,"-","avr32target"],[1,0,0,"-","avr8target"],[1,0,0,"-","avrdebugger"],[1,0,0,"-","backend"],[2,0,0,"-","deviceinfo"],[1,0,0,"-","hexfileutils"],[1,0,0,"-","nvm"],[1,0,0,"-","nvmavr32"],[1,0,0,"-","nvmdebugwire"],[1,0,0,"-","nvmmegaavrjtag"],[1,0,0,"-","nvmmzeroplus"],[1,0,0,"-","nvmpic"],[1,0,0,"-","nvmserialupdi"],[1,0,0,"-","nvmspi"],[1,0,0,"-","nvmtpi"],[1,0,0,"-","nvmupdi"],[1,0,0,"-","nvmxmega"],[1,0,0,"-","programmer"],[1,0,0,"-","pymcuprog_errors"],[1,0,0,"-","samtarget"],[3,0,0,"-","serialupdi"],[1,0,0,"-","toolconnection"],[1,0,0,"-","utils"]]},objnames:{"0":["py","module","Python module"],"1":["py","class","Python class"],"2":["py","method","Python method"],"3":["py","attribute","Python attribute"],"4":["py","function","Python function"],"5":["py","exception","Python exception"]},objtypes:{"0":"py:module","1":"py:class","2":"py:method","3":"py:attribute","4":"py:function","5":"py:exception"},terms:{"0":[0,1,3],"06x":[0,1],"0xff":1,"1":[0,1,3],"100":1,"1000":1,"1090527232":1,"1090535424":1,"115200":3,"123":1,"128b":3,"16":[1,3],"16384":1,"2":[1,3],"20":1,"24":[1,3],"250":1,"256":1,"256b":3,"28":1,"2nd":1,"3":[0,1,3],"3000":1,"32":[1,2],"4":[0,1,3],"4000":1,"5":1,"6":1,"64":1,"64b":3,"65":1,"65536":1,"68":1,"7":1,"8":[1,3],"900":1,"900000":1,"abstract":1,"boolean":1,"break":[1,3],"byte":[1,2,3],"case":1,"char":3,"class":[1,2,3],"default":[1,3],"do":1,"enum":1,"float":1,"function":0,"import":[0,1],"int":[0,1,3],"new":1,"return":[1,2,3],"static":[1,2],"true":[1,3],"try":1,A:[0,1,2,3],By:3,For:[0,1],If:1,In:[0,1],Not:1,One:1,The:[0,1,2],These:2,To:1,about:[1,2],absolut:1,access:[0,3],ack:3,activ:1,activate_phys:1,actual:[1,2,3],ad:1,addit:[0,1],address:[1,2,3],address_param:2,address_s:2,address_typ:2,affect:1,after:1,against:1,aka:3,albeit:3,alia:2,align:1,all:[0,1,2,3],allow:1,allow_blank_skip:1,alpha:0,alreadi:[1,3],also:[0,1],although:[0,1],an:[1,2,3],ani:[1,2,3],anoth:1,ap_read:1,ap_writ:1,api:[0,2],api_vers:1,appli:[1,3],applic:[0,1,2],apply_reset:3,ar:[0,1,2,3],architectur:2,area:1,arrai:[1,3],ask:1,assum:1,atmega328p:[0,1],atmega4808:[0,1],atmel:[0,1],atsamd21e18a:[0,1],attach:1,attempt:1,automat:1,avail:[0,1],avr32:0,avr32devic:1,avr32protocol:1,avr32target:1,avr8:0,avr8_phy_intf_pdi:1,avr8_phy_intf_pdi_1w:1,avr8protocol:1,avr8target:1,avr:[0,2,3],avrdebugg:1,avrdevic:1,awir:1,back:3,backend:0,base:[0,1,2,3],baseclass:2,basic:[0,1],basicconfig:[0,1],baud:[1,3],been:[0,1],befor:1,behavior:1,being:1,below:[0,1],best:[0,1],between:[0,1,2],bin:1,bin_filenam:1,bit:[1,2,3],blank:1,blob:[0,1],block:[1,3],board:[0,1],bool:1,boot:3,bootprot:1,both:1,boundari:1,breakpoint:1,breakpoint_clear:1,breakpoint_set:1,brows:[0,1],brutal:3,buffer:[1,3],build:1,built:1,bulk:1,byte_address:2,bytearrai:[1,3],byteord:[0,1],bytes_or_word:2,bytes_to_read:1,calibration_row:2,call:1,callback:1,can:[0,1,2],capabl:1,certain:1,changelog:[0,1],channel:1,check:[1,2,3],chip:[1,3],chip_eras:[1,3],chip_erase_dsu:1,chiperase_effect:[1,2],chiperaseeffect:[1,2],chunk:1,chunk_siz:1,classmethod:2,clean:1,clear:1,cli:[0,1],client:1,clk:1,clk_hz:1,clock:1,cmd_ear:1,cmd_lr:1,cmd_pbc:1,cmd_ur:1,cmd_wap:1,cmd_wp:1,cmsi:[0,1],code:[0,1],collect:1,com1:1,com:[0,1],come:3,command:3,commit:3,common:2,commun:1,compar:1,compat:[0,1],complet:1,condit:3,config:1,config_word:2,configur:[0,1],connect:0,connect_to_tool:[0,1],constant:[0,1],contain:[1,2,3],continu:[0,1],control:[0,1,3],conveni:[0,1],convert:2,core:1,correctli:1,correspond:[1,2],could:1,counter:[1,3],cpd_n:1,creat:1,ctrl:3,curios:[0,1],current:1,cycl:1,d:[0,1],da:3,dap:[0,1],dap_transfer_idle_cycl:1,dap_transfer_match_retri:1,dap_transfer_retry_count:1,data0:1,data1:1,data:[1,3],data_address_spac:2,data_buff:1,data_s:1,data_space_bas:2,data_to_writ:1,datalink:3,deactiv:1,deactivate_phys:1,debug:1,debugg:0,debugwir:0,decod:3,decode_sib:3,default_bulk_erase_address:2,defin:1,definit:2,delai:3,deprec:1,detach:1,dev_info:1,develop:[0,1],devic:[2,3],device_id:[0,1,2],device_info:[1,2],device_memory_info:1,device_nam:1,deviceinfo:[0,1],deviceinfokei:[0,1],deviceinfokeysavr32:2,deviceinfokeysavr:2,deviceinfokeysp:2,devicememoryinfo:[1,2],devicememoryinfokei:2,devicenam:2,devicesupportscript:1,dfp:2,dict:[1,2],dictionari:[1,2],did:1,direct:3,directli:3,disabl:3,disconnect:1,disconnect_from_tool:1,displai:1,dl:3,do_break:1,document:1,doe:[1,3],done:1,doubl:3,down:1,dp_read:1,dp_write:1,driver:[1,3],dsu:1,dsu_address:1,dsu_chip_erase_timeout_m:1,dsu_ctrl_offset:1,dsu_ctrl_status_ce_command_mask:1,dsu_ctrl_status_done_mask:1,dsu_ctrl_status_prot_mask:1,dsu_did_offset:1,dsu_external_offset:1,due:1,dure:1,dut:1,dw:1,e:1,ea:3,each:[1,2],echo:3,edbg:[0,1],eeprom:[1,2,3],eeprom_read:1,eeprom_writ:1,eesav:1,effect:1,eg:[0,1],either:1,empti:1,emul:1,enabl:[1,3],end:[1,2],end_sess:1,endian:1,enter:[1,3],enter_progmod:[1,3],eras:[1,2,3],erase_address:2,erase_eeprom:3,erase_flash_pag:3,erase_user_row:[1,3],eraseflag:[0,1],error:0,esqu:1,even:1,event:1,everi:1,exampl:[0,1,3],except:1,execut:[1,3],execute_instruct:1,execute_nvm_command:3,execute_patch:1,experiment:[0,1],expir:3,expos:1,extern:1,fail:1,failur:1,fals:[1,3],famili:[0,1,3],featur:1,fetch:2,field:1,file:[0,2],filenam:1,firmwar:[0,1],first:1,flag:[1,2,3],flash:[1,2,3],flash_data:1,flash_lock_region:1,flash_lock_region_s:1,flash_read:1,flush:1,flush_ev:1,folder:[1,2],follow:[0,1],forc:1,form:3,format:[0,1],found:[0,1,2],frame:3,frequenc:1,friend:1,friendli:1,from:[0,1,2,3],from_byt:[0,1],fuse:[1,2,3],g:1,gener:[1,2,3],generate_device_info:2,get:[1,2],get_al:2,get_api_vers:[0,1],get_available_hid_tool:1,get_chiperase_effect:1,get_device_info:1,get_device_memory_info:1,get_device_model:1,get_list_of_chiperase_effect:2,get_nvm_access_provid:1,get_supported_devic:[1,2],getdeviceinfo:[1,2],github:[0,1],given:[1,2,3],go:3,guarante:3,ha:[1,2,3],halt:1,hand:2,handl:[1,3],hardwar:1,hardware_breakpoint_clear:1,hardware_breakpoint_set:1,have:[0,1],help:[0,1],helper:[1,3],hex:[0,2],hexfil:[1,2],hexfile_address:2,hexfile_s:2,hexfileutil:1,hid:1,hid_transport:1,hidtool:1,hidtransport:1,hidtransportbas:1,high:[1,3],hold:1,hold_in_reset:1,host:1,housekeep:1,housekeepingprotocol:1,how:1,howev:[0,1],http:[0,1],hv_implement:2,hz:1,i:1,ic:[0,1],icd:[0,1,2],id:[0,1],ideal:2,identifi:1,ignor:1,immedi:1,implement:[0,3],in_prog_mod:3,includ:[0,1],incom:1,incorrect:1,incorrectli:1,increment:3,index:[0,3],indic:1,info:[1,2],inform:[0,1,2,3],init:3,init_datalink:3,initi:1,initialis:[1,3],initialise_seri:3,inject:3,input:1,insert:1,insid:1,instal:[0,1],instanc:1,instanti:[0,1],instead:[1,3],instruct:1,intend:[0,1],inter:3,interact:1,interfac:2,interface_spe:1,internal_sram:2,interrog:1,io:[0,1],is_blank:1,is_device_lock:1,is_flash_readi:1,is_isolated_erase_poss:1,isolated_eras:2,issu:1,item:1,its:[0,1],job:1,jtag:0,jtagice3:[0,1],jtagice3housekeepingprotocol:1,just:[1,3],kbp:1,kei:[2,3],khz:1,kilohertz:1,kit:[0,1],known:3,last:1,layer:[1,3],ld16:3,ld:3,ld_ptr_inc16:3,ld_ptr_inc:3,ldc:3,ldcs_response_byt:3,leav:1,leave_progmod:[1,3],let:1,level:[0,1],levelnam:[0,1],libusb:[0,1],like:[0,1],line_wrap:1,link:[0,1],list:[1,2],littl:[0,1],load:[1,3],load_devic:1,locat:[1,3],lock:[1,3],lockbit:2,look:2,low:3,m0:0,m4:1,machin:3,mai:[0,1],main:[0,1],make:3,mani:1,map:1,mask:1,match:1,mchp3252000000043123:1,mcp32520001230000000:1,md:[0,1],mean:[0,1,3],measur:1,mechan:3,medbg:[0,1],mega4809:3,mega:1,megaavr:0,megaavrjtagtarget:1,megajtag:1,memori:[1,2,3],memory_block:1,memory_info:1,memory_info_by_address:2,memory_info_by_address_rang:2,memory_info_by_nam:2,memory_nam:1,memory_read:1,memory_seg:1,memory_typ:1,memory_writ:1,memorynam:[0,1],memorynamealias:[1,2],memtyp:1,memtype_read_from_str:1,memtype_str:1,memtype_write_from_str:1,messag:[0,1],method:3,microchip:[0,1],millisecond:3,miss:1,mode:[0,1,3],model:[1,2],modul:0,more:[0,1],mount:[0,1],mplab:[0,1],msg:1,multipli:2,must:1,name:[1,2],namedtupl:1,nano:[0,1],nap:1,nb:1,necessari:1,nedbg:[0,1],need:1,newer:3,none:[1,3],normal:[1,2],not_eras:1,note:[0,1,3],noth:1,number:[1,3],numbyt:1,nvm:0,nvm_command:1,nvm_ctrl_addr_offset:1,nvm_ctrl_address:1,nvm_ctrl_ctrl_offset:1,nvm_ctrl_ctrlb_offset:1,nvm_ctrl_intflag_offset:1,nvm_ctrl_lock_offset:1,nvm_ctrl_status_offset:1,nvm_ctrlb_manw_bit:1,nvm_int_error_bit:1,nvm_int_ready_bit:1,nvm_status_lock:1,nvm_status_nvm:1,nvm_status_prog:1,nvm_status_sb:1,nvmaccessprovid:1,nvmaccessprovidercmsisdapavr32:1,nvmaccessprovidercmsisdapavr:1,nvmaccessprovidercmsisdapdebugwir:1,nvmaccessprovidercmsisdapmegaavrjtag:1,nvmaccessprovidercmsisdapmzeroplu:1,nvmaccessprovidercmsisdapp:1,nvmaccessprovidercmsisdapspi:1,nvmaccessprovidercmsisdaptool:1,nvmaccessprovidercmsisdaptpi:1,nvmaccessprovidercmsisdapupdi:1,nvmaccessprovidercmsisdapxmega:1,nvmaccessproviderseri:1,nvmavr32:1,nvmcommand:3,nvmctrl_base:2,nvmdebugwir:1,nvmmegaavrjtag:1,nvmmzeroplu:1,nvmpic:1,nvmserialupdi:1,nvmspi:1,nvmtpi:1,nvmupdi:[1,3],nvmupdiavrv2:3,nvmupdiavrv3:3,nvmupdiv0:3,nvmxmega:1,object:[1,2,3],ocd:1,ocd_bas:2,ocdregfile_read:1,offset:1,offset_byt:1,onboard:1,onc:1,one:[1,3],onli:[0,1,3],oper:1,option:1,order:[0,1],org:[0,1],origin:1,other:[0,1],otherwis:3,out:[1,3],output:[0,1],outsid:1,overrid:1,pack:1,packag:0,packpath:1,pad:1,pad_to_s:1,pad_valu:1,page:[0,1,3],page_s:[1,2],page_writ:3,pagealign:1,paramet:[1,2,3],part:[1,2],partial:[0,1],path:1,pc:1,pdi:[1,3],per:1,perform:1,phantom:1,phantom_byt:1,physic:[0,1],pic:[0,2],pick:1,pickit:[0,1],pip:[0,1],pkob:[0,1],point:1,pointer:[1,3],poll:1,poll_ev:1,port:[1,3],possibl:[0,1,3],post:3,post_flash_writ:1,power:[0,1],pre_flash_writ:1,prepar:1,present:3,primarili:[0,1],print:[0,1],print_tool_info:1,prog:3,prog_clock_khz:2,program:[0,1,3],program_count:1,program_counter_read:1,program_counter_writ:1,project:[0,1],propag:1,properti:1,protect:1,protocol:[0,1,3],proven:3,provid:[0,1,3],publish:[0,1],puls:1,purpos:[0,1],push:3,put:1,py:[1,2],pyedbglib:[0,1],pymcuprog_error:1,pymcuprog_vers:[0,1],pymcuprogdevicelockederror:1,pymcuprogeraseerror:1,pymcuprogerror:1,pymcuprognotsupportederror:1,pymcuprogsessionconfigerror:1,pymcuprogsessionerror:1,pymcuprogtoolconfigurationerror:1,pymcuprogtoolconnectionerror:1,pypi:[0,1],pyupdi:1,r0:1,r31:1,rais:1,rang:[0,1,2],rate:[1,3],rather:3,raw:1,re:1,reach:1,read:[0,1,3],read_byt:3,read_c:3,read_chunk_s:1,read_data:3,read_data_word:3,read_device_id:[0,1],read_device_info:3,read_flash:1,read_idcod:1,read_kit_devic:1,read_memori:1,read_memories_from_hex:1,read_memory_sect:1,read_sib:3,read_siz:2,read_supply_voltage_setpoint:1,read_target_voltag:1,read_tool_info:1,read_usb_voltag:1,read_user_row:1,read_voltage_paramet:1,readabl:3,readi:[1,3],readwrit:[0,1],real:2,reboot:1,reboot_tool:1,receiv:3,recommend:[0,1],refer:1,reg:1,regardless:1,region:1,regist:1,register_file_read:1,register_file_writ:1,reinitialis:1,rel:1,relat:2,releas:[1,3],release_from_reset:1,remov:1,remove_phantom_byt:1,repeat:3,repres:[1,2],request:[1,2,3],requir:[0,1,3],reset:[1,3],reset_domain:[1,2],respond:1,restor:1,result:[0,1],row:[1,3],run:[1,2],run_to:1,run_with_power_nap:1,s:[0,1],sam:0,samd21:1,samd2xtarget:1,samd:1,samm4target:1,samtarget:1,scratch:1,script:1,search:0,second:1,section:[0,1],see:[0,1,3],segment:[1,2],select:[0,1,2],send:3,send_double_break:3,sensit:1,separ:1,serial:[1,3],serialexcept:1,serialnumb:1,serialnumber_substr:1,serialport:[1,3],serialupdi:0,servic:2,session:[0,1],session_start:1,sessionconfig:[0,1],set:[1,3],set_nvmctrl_address:1,set_opt:1,set_phys:3,set_supply_voltage_setpoint:1,setpoint:1,setup:[0,1],setup_config:1,setup_debug_sess:1,setup_devic:1,setup_prog_sess:1,setup_sess:1,sever:2,should:1,show:1,showdata:1,sib:[1,3],sib_read:1,side:1,signal:1,signatur:2,similar:[0,1],simpl:[2,3],sinc:[0,1],singl:[1,3],size:[1,2,3],size_typ:2,skip:1,slower:3,snap:[0,1],so:3,softwar:1,software_breakpoint_clear:1,software_breakpoint_clear_al:1,software_breakpoint_set:1,some:1,someth:3,sourc:[0,1,3],space:3,special:1,special_opt:1,specif:[1,2],specifi:1,speed:1,spi:0,sram:1,sram_read:1,sram_writ:1,sreg:1,st16:3,st:3,st_ptr:3,st_ptr_inc16:3,st_ptr_inc:3,stack:[0,1,3],stack_pointer_read:1,standalon:[0,1],standard:3,start:[0,1,2,3],start_address:1,start_debug:1,start_sess:[0,1],state:[1,3],statu:[1,3],status_register_read:1,stc:3,step:1,still:1,stop:[1,2],stop_debug:1,store:[1,2,3],str:1,string:[1,3],stub:1,sub:[1,2],subclass:2,submodul:0,subpackag:0,subset:1,success:1,suppli:1,support:2,syscfg_bas:2,system:[1,3],t:1,take:1,target:[0,2],test:[0,1],than:1,thi:[0,1,3],thorough:[0,1],three:1,through:1,thrown:1,timeout:[0,1],timeout_m:[1,3],timer:3,tini:1,tiny817:3,tinyavrtarget:1,tinytini:1,tinyx:1,tinyxavrtarget:1,toggl:1,too:1,tool_nam:1,toolconnect:[0,1],toolserialconnect:1,toolusbhidconnect:[0,1],toward:1,tpi:0,transit:1,transport:[0,1],trigger:1,turn:3,two:1,type:[0,1,2],typic:1,unabl:1,unlock:[1,3],unlock_all_region:1,until:[1,3],up:[1,2,3],updi:[0,3],updi_hv_non:1,updiappl:[1,3],updidatalink16bit:3,updidatalink24bit:3,updidatalink:3,updiphys:3,updireadwrit:3,upon:1,ur:1,us:[0,1,2,3],usb:[0,1],usb_seri:1,use_events_for_run_stop_st:1,use_hv:1,use_reset:1,use_word_access:3,user:[0,1,3],user_id:2,user_interaction_callback:1,user_row:2,v0:3,v1:3,v3:3,valid:2,valu:[1,2,3],valueerror:1,vari:1,variou:[0,1,3],verbos:[0,1],verifi:1,verify_from_bin:1,verify_hex:1,verify_mask:[1,2],verify_memori:1,version:[0,1,3],via:1,voltag:1,vs:2,wa:1,wai:1,wait:[1,3],wait_flash_readi:1,wait_for_high:3,wait_nvm_readi:3,wait_unlock:3,wait_urow_prog:3,warn:[0,1],well:[0,1],when:[0,1,3],whether:[0,1,3],which:[0,1,3],whole:3,wide:[0,1],widest:1,width:1,within:[1,3],without:[1,3],won:1,word:[1,3],wrap:1,wrapper:1,write:[1,3],write_byt:3,write_c:3,write_chunk_s:1,write_data:3,write_data_word:3,write_eeprom:3,write_flash:3,write_flash_pag:1,write_fus:3,write_hex_to_target:1,write_kei:3,write_memori:1,write_memories_to_hex:1,write_memory_sect:1,write_memory_to_hex:1,write_nvm:3,write_s:2,write_user_row:3,write_user_row_locked_devic:3,write_user_row_word:1,writeabl:3,written:[1,3],x:[0,1],xmega:0,xmegaavrtarget:1,xx:1,yaml:[0,1],zero:3},titles:["pymcuprog documentation","pymcuprog package","pymcuprog.deviceinfo package","pymcuprog.serialupdi package"],titleterms:{"function":1,access:1,alpha:1,api:1,applic:3,avr32:1,avr8:1,avr:1,backend:1,command:[0,1],connect:1,constant:3,content:[0,1,2,3],debugg:1,debugwir:1,depend:[0,1],devic:[0,1],deviceinfo:2,deviceinfokei:2,document:0,eraseflag:2,error:1,file:1,hex:1,implement:1,indic:0,interfac:[0,1],jtag:1,librari:[0,1],line:[0,1],link:3,log:[0,1],m0:1,mcu:[0,1],megaavr:1,memorynam:2,modul:[1,2,3],nvm:[1,3],overview:[0,1],packag:[1,2,3],physic:3,pic:1,programm:[0,1],pymcuprog:[0,1,2,3],python:[0,1],readwrit:3,sam:1,serialupdi:[1,3],spi:1,submodul:[1,2,3],subpackag:1,support:[0,1],tabl:0,target:1,timeout:3,tool:[0,1],tpi:1,updi:1,usag:[0,1],util:[0,1],xmega:1}}) \ No newline at end of file diff --git a/help.md b/help.md new file mode 100644 index 0000000..0ab6be7 --- /dev/null +++ b/help.md @@ -0,0 +1,267 @@ +# pymcuprog - Python MCU programmer +pymcuprog is a Python utility for programming various Microchip MCU devices using Microchip CMSIS-DAP based debuggers + +# Usage +pymcuprog is used as a command line interface: + +``` +pymcuprog [switches] action +``` + +## Actions (commands) +The only manadatory positional argument specifies the _action_: + +### Memory access actions +Read the device ID or signature: + +Also functions as a 'connectivity check' +``` +ping +``` +Read memories from a device: +``` +read +``` +Write memories to a device: + +NB: Does NOT erase before write! +``` +write +``` +Erase memories on a device: +``` +erase +``` +Read out memories from a device and compare: +``` +verify +``` + +### Voltage-related actions +Read the actual (sampled) VTG voltage from a kit or debugger: +``` +getvoltage +``` +Read the supply voltage set-point from a kit or debugger: +``` +getsupplyvoltage +``` +Set the supply voltage set-point from a kit or debugger: + +Use -l literal to specify voltage +``` +setsupplyvoltage +``` +Read the USB voltage from a kit or debugger: +``` +getusbvoltage +``` + +### Other misc actions +Reset the application (by entering and leaving programming mode): +``` +reset +``` +Reboot the debugger: +``` +reboot-debugger +``` + +## Memory types +Memory types may vary depending on the device in question. + +Specify the memory type using -m MEMORY or --memory MEMORY + +# Supported memory types +``` +calibration_row +config_words +eeprom +flash +fuses +icd +internal_sram +lockbits +signatures +user_id +user_row +``` + +## Optional arguments and switches + +### Administrative arguments +``` +-h, --help + show this help message and exit + +-V, --version + Print pymcuprog version number and exit + +-R, --release-info + Print pymcuprog release details and exit +``` + +### General arguments +``` +-d DEVICE, --device DEVICE + device to program + +-p PACKPATH, --packpath PACKPATH + path to pack (DFP) to use - mandatory for any action when using a PIC device. + Packs can be installed usign MPLABX Pack Manager (use Tools->Packs) + Pack path is displayed in the status bar. + Packs can be downloaded and unzipped from https://packs.download.microchip.com/ + +-t TOOL, --tool TOOL + tool to connect to + supported tools include: + - uart (for serialUPDI). Use -u argument to specify which serial port to use. + - nedbg (PKOB nano / debugger on Curiosity Nano) + - pickit4 + - snap + - atmelice + - powerdebugger + - edbg (debugger on Xplained Pro/Ultra) + - medbg (debugger on Xplained Mini/Nano) + - JTAGICE3 (firmware version 3.0 or newer) + +-s SERIALNUMBER, --serialnumber SERIALNUMBER + USB serial number of the unit to use + +-v {debug,info,warning,error,critical}, +--verbose {debug,info,warning,error,critical} + Logging verbosity level + +-x, --timing + add timing output +``` + +### Memory access arguments +``` +-o OFFSET, --offset OFFSET + memory byte offset to access + Defaults to 0 (start of memory section) + Only applies to literal and binary-file operations + +-b BYTES, --bytes BYTES + number of bytes to read + Ignored for write operations (cannot be used to truncate a write) + Defaults to entire memory section size + Requires that a memory section is specified + +-l LITERAL [LITERAL ...], --literal LITERAL [LITERAL ...] + literal value(s) to write + +-f FILENAME, --filename FILENAME + file to write / read. + + A specified filename which has .hex extension will be treated as Intel(R) hex + format; all other file extensions are treated as binary files. + + When writing from a .hex file, the memory segment addresses are read from + the file, so the OFFSET argument is not allowed. + + When reading to an Intel hex file, only eeprom, flash, fuses, config_words, + and user_row memories will be written + + Hex file offsets are actual memory section locations for PIC and SAM devices. + AVR device offsets in hex files are (handled by the toolchain): + - flash 0x000000 + - eeprom 0x810000 + - fuses 0x820000 + - lockbits 0x830000 + - signatures 0x840000 + - user signatures 0x850000 + +--verify + verify content after write (by readback and compare) + +--erase + erase device before write (equivalent to pymcuprog erase) + This switch is valid only when writing from an Intel hex file. + A chip erase / bulk erase will be executed before write - note that not all memories will be erased: + for example EEPROM may be preserved on AVR devices if the EESAVE fuse bit is set. +``` +### Programming interface arguments +``` +-i INTERFACE, --interface INTERFACE + Programming interface to use + +-c CLK, --clk CLK + clock frequency in Hz or baud rate in bps for programming interface. + (eg: '-c 32768' or '-c 115k' or '-c 1M') + +-u UART, --uart UART + UART to use for serialUPDI tool (when using -t uart) +``` + +### Special-function UPDI arguments +``` +-H {tool-toggle-power,user-toggle-power,simple-unsafe-pulse}, +--high-voltage {tool-toggle-power,user-toggle-power,simple-unsafe-pulse} + UPDI high-voltage activation mode + +-U, --user-row-locked-device + Writes the User Row on a locked device + +-C, --chip-erase-locked-device + Execute a Chip Erase on a locked device +``` + + +# Examples +Examples of using pymcuprog: +``` +# Ping a device on a kit (checks connectivity by reading its signature): +pymcuprog ping + +# Ping a device using Atmel-ICE (standalone debugger requires more information): +pymcuprog ping -t atmelice -d atmega4809 -i updi + +# Erase and program memories from an Intel hex file using PICkit4: +pymcuprog write -t pickit4 -d atmega4809 -i updi -f myfile.hex --erase + +# Read 64 bytes of flash from offset 0x80 in flash memory space: +pymcuprog read -m flash -o 0x80 -b 64 + +# Write literal values 0x01, 0x02 to EEPROM at offset 16 on a kit: +pymcuprog write -m eeprom -o 16 -l 0x01 0x02 + +# Write fuse byte 1 to 0xE0 on a kit: +pymcuprog write -m fuses -o 1 -l 0xE0 + +# Erase a device on a kit: +pymcuprog erase + +# Erase a locked device on a kit (UPDI only): +pymcuprog erase --chip-erase-locked-device + +# Reset a device on a kit (by entering and leaving programming mode): +pymcuprog reset + +# Read the actual (sampled) VTG voltage from a kit or debugger: +pymcuprog getvoltage + +# Set target supply voltage on a kit (voltage provided by -l literal argument): +pymcuprog setsupplyvoltage -l 3.3 +``` +# serialUPDI usage +SerialUPDI (also known as 'pyupdi') is implemented as a _tool_ in pymcuprog. + +To use it: +- connect a resistor between a serial port adapter's RX, TX and the UPDI pin as shown in the [readme](./README.md) +- specify uart tool using the switch: '--tool uart' +- specify which serial port to use using the switch '--uart {serialport}' +- use the basic actions for accessing memories as shown above + +Example: +``` +# Ping a device using serialUPDI: +pymcuprog ping -t uart -u COM42 -d atmega4809 + +# Erase a device using serialUPDI: +pymcuprog erase -t uart -u COM42 -d atmega4809 + +# Erase and program memories from an Intel hex file using serialUPDI: +pymcuprog write -t uart -u COM42 -d atmega4809 -f myfile.hex --erase +``` \ No newline at end of file diff --git a/images/microchip.png b/images/microchip.png new file mode 100644 index 0000000..9bdf56c Binary files /dev/null and b/images/microchip.png differ diff --git a/pylintrc b/pylintrc new file mode 100644 index 0000000..1391f2a --- /dev/null +++ b/pylintrc @@ -0,0 +1,546 @@ +[MASTER] + +# 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=win32api, hid + +# 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= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +#init-hook= + +# Use multiple processes to speed up Pylint. +jobs=1 + +# List of plugins (as comma separated values of python modules names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Specify a configuration file. +#rcfile= + +# When enabled, pylint would attempt to guess common misconfiguration and emit +# user-friendly hints instead of false-positive error messages +suggestion-mode=yes + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED +confidence= + +# 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=print-statement, + parameter-unpacking, + unpacking-in-except, + old-raise-syntax, + backtick, + long-suffix, + old-ne-operator, + old-octal-literal, + import-star-module-level, + non-ascii-bytes-literal, + raw-checker-failed, + bad-inline-option, + locally-disabled, + locally-enabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + apply-builtin, + basestring-builtin, + buffer-builtin, + cmp-builtin, + coerce-builtin, + execfile-builtin, + file-builtin, + long-builtin, + raw_input-builtin, + reduce-builtin, + standarderror-builtin, + unicode-builtin, + xrange-builtin, + coerce-method, + delslice-method, + getslice-method, + setslice-method, + no-absolute-import, + old-division, + dict-iter-method, + dict-view-method, + next-method-called, + metaclass-assignment, + indexing-exception, + raising-string, + reload-builtin, + oct-method, + hex-method, + nonzero-method, + cmp-method, + input-builtin, + round-builtin, + intern-builtin, + unichr-builtin, + map-builtin-not-iterating, + zip-builtin-not-iterating, + range-builtin-not-iterating, + filter-builtin-not-iterating, + using-cmp-argument, + eq-without-hash, + div-method, + idiv-method, + rdiv-method, + exception-message-attribute, + invalid-str-codec, + sys-max-int, + bad-python3-import, + deprecated-string-function, + deprecated-str-translate-call, + deprecated-itertools-function, + deprecated-types-field, + next-method-defined, + dict-items-not-iterating, + dict-keys-not-iterating, + dict-values-not-iterating, + # There will be duplicate code in device files and this rule can't be disabled on a file level (see https://github.com/PyCQA/pylint/issues/214) + duplicate-code, + # pymcuprog should still be python 2.7 compatible so classes should be allowed to explicitly inherit from object + useless-object-inheritance + +# 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=c-extension-no-member + + +[REPORTS] + +# 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= + +# Set the output format. Available formats are text, parseable, colorized, json +# and msvs (visual studio).You can also give a reporter class, eg +# mypackage.mymodule.MyReporterClass. +output-format=text + +# Tells whether to display a full report or only the messages +reports=yes + +# Activate the evaluation score. +score=yes + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=5 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=optparse.Values,sys.exit + + +[BASIC] + +# Naming style matching correct argument names +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style +#argument-rgx= + +# Naming style matching correct attribute names +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Naming style matching correct class attribute names +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style +#class-attribute-rgx= + +# Naming style matching correct class names +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming-style +#class-rgx= + +# Naming style matching correct constant names +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma +good-names=i, + j, + k, + ex, + Run, + _ + +# Include a hint for the correct naming format with invalid-name +include-naming-hint=yes + +# Naming style matching correct inline iteration names +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style +#inlinevar-rgx= + +# Naming style matching correct method names +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style +#method-rgx= + +# Naming style matching correct module names +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# 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 + +# Naming style matching correct variable names +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style +#variable-rgx= + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module +max-module-lines=1000 + +# 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 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# 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 + + +[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] + +# Ignore comments when computing similarities. +ignore-comments=yes + +# Ignore docstrings when computing similarities. +ignore-docstrings=yes + +# Ignore imports when computing similarities. +ignore-imports=no + +# Minimum lines number of a similarity. +min-similarity-lines=4 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes +max-spelling-suggestions=4 + +# 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] + +# 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 + +# 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= + +# 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 + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# 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 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= + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid to define new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# 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 + +# A regular expression matching the name of dummy variables (i.e. expectedly +# not used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. Default to name +# with leading underscore +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins + + +[CLASSES] + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict, + _fields, + _replace, + _source, + _make + +# 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 + + +[DESIGN] + +# Maximum number of arguments for function / method +max-args=5 + +# Maximum number of attributes for a class (see R0902). +max-attributes=7 + +# Maximum number of boolean expressions in a if statement +max-bool-expr=5 + +# Maximum number of branch for function / method body +max-branches=12 + +# Maximum number of locals for function / method body +max-locals=15 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body +max-returns=6 + +# Maximum number of statements in function / method body +max-statements=50 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=2 + + +[IMPORTS] + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# 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=yes + +# Deprecated modules which should not be used, separated by a comma +deprecated-modules=regsub, + TERMIOS, + Bastion, + rexec + +# Create a graph of external dependencies in the given file (report RP0402 must +# not be disabled) +ext-import-graph= + +# Create a graph of every (i.e. internal and external) dependencies in the +# given file (report RP0402 must not be disabled) +import-graph=import.svg + +# 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 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when being caught. Defaults to +# "Exception" +overgeneral-exceptions=Exception diff --git a/pymcuprog/__init__.py b/pymcuprog/__init__.py new file mode 100644 index 0000000..7f5eb09 --- /dev/null +++ b/pymcuprog/__init__.py @@ -0,0 +1,100 @@ +""" +Python MCU programmer utility +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +pymcuprog is a utility for programming various Microchip MCU devices using Microchip CMSIS-DAP based debuggers + +Overview +~~~~~~~~ + +pymcuprog is available: + * install using pip from pypi: https://pypi.org/project/pymcuprog + * browse source code on github: https://github.com/microchip-pic-avr-tools/pymcuprog + * read API documentation on github: https://microchip-pic-avr-tools.github.io/pymcuprog + * read the changelog on github: https://github.com/microchip-pic-avr-tools/pymcuprog/blob/main/CHANGELOG.md + +Command-line interface usage +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For using pymcuprog as a CLI, see help.md (https://github.com/microchip-pic-avr-tools/pymcuprog/blob/main/help.md) + +Library usage +~~~~~~~~~~~~~ + +pymcuprog can be used as a library using its backend API. For example: + +.. code-block:: python + + # Setup logging - pymcuprog uses the Python logging module + import logging + logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.WARNING) + + # Configure the session: + from pymcuprog.backend import SessionConfig + sessionconfig = SessionConfig("atmega4808") + + # Instantiate USB transport (only 1 tool connected) + from pymcuprog.toolconnection import ToolUsbHidConnection + transport = ToolUsbHidConnection() + + # Instantiate backend + from pymcuprog.backend import Backend + backend = Backend() + + # Connect to tool using transport + backend.connect_to_tool(transport) + + # Start the session + backend.start_session(sessionconfig) + + # Read the target device_id + device_id = backend.read_device_id() + print ("Device ID is {0:06X}".format(int.from_bytes(d, byteorder="little"))) + + # Print the pymcuprog package version: + from pymcuprog.version import VERSION as pymcuprog_version + print("pymcuprog version {}".format(pymcuprog_version)) + + # In addition, the CLI-backend API is versioned for convenience: + print("pymcuprog backend API version: {}".format(backend.get_api_version())) + +Logging +~~~~~~~ +This package uses the Python logging module for publishing log messages to library users. +A basic configuration can be used (see example), but for best results a more thorough configuration is +recommended in order to control the verbosity of output from dependencies in the stack which also use logging. +See logging.yaml which is included in the package (although only used for CLI) + +Dependencies +~~~~~~~~~~~~ +pymcuprog depends on pyedbglib for its transport protocol. +pyedbglib requires a USB transport library like libusb. See pyedbglib package for more information. + +Supported devices and tools +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Note: pymcuprog is primarily intended for use with PKOB nano (nEDBG) debuggers which +are found on Curiosity Nano kits and other development boards. This means that it is +continuously tested with a selection of AVR devices with UPDI interface as well as a +selection of PIC devices. However since the protocol is compatible between all +EDBG-based debuggers (pyedbglib) it is possible to use pymcuprog with a wide range of +debuggers and devices, although not all device families/interfaces have been implemented. + +The following Atmel/Microchip debuggers are supported: + * PKOB nano (nEDBG) + * MPLAB PICkit 4 ICD (only when in 'AVR mode') + * MPLAB Snap ICD (only when in 'AVR mode') + * Atmel-ICE + * Power Debugger + * EDBG + * mEDBG + * JTAGICE3 (only firmware version 3.x) + +Although not all functionality is provided on all debuggers/boards. See device support section below. + +The following device-types are supported: + * All AVR UPDI devices, whether mounted on kits or standalone + * PIC devices mounted on Curiosity Nano kits, or similar board with PKOB nano (nEDBG) debugger + * Other devices (eg ATmega328P, ATsamd21e18a) may be partially supported for experimental purposes +""" +import logging +logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/pymcuprog/avr32target.py b/pymcuprog/avr32target.py new file mode 100644 index 0000000..13537c9 --- /dev/null +++ b/pymcuprog/avr32target.py @@ -0,0 +1,51 @@ +""" +Device Specific Classes which use AVR32Protocol implementation +""" +from pyedbglib.protocols.avr32protocol import Avr32Protocol + +class Avr32Device(object): + """ + Generic AVR32 device wrapper (maps to avr32 protocol) + """ + def __init__(self, transport, reset_domains=5): + """ + :param transport: Transport layer object + :param reset_domains: Number of bits in the RESET register. + This number is device dependant + """ + self.protocol = Avr32Protocol(transport) + self.interface = Avr32Protocol.AVR32_PHY_INTF_JTAG + self.reset_domains = reset_domains + + def setup_prog_session(self, interface="jtag"): + """ + Setup a programming session on AVR32 + + :param interface: jtag or awire + """ + if interface == "awire": + self.interface = Avr32Protocol.AVR32_PHY_INTF_AWIRE + else: + self.interface = Avr32Protocol.AVR32_PHY_INTF_JTAG + # Set clock frequency + self.protocol.set_le16(Avr32Protocol.AVR32_CONTEXT_PHYSICAL, Avr32Protocol.AVR32_PHYSICAL_JTAG_CLOCK, 8000) + + # Set interface + self.protocol.set_byte(Avr32Protocol.AVR32_CONTEXT_PHYSICAL, Avr32Protocol.AVR32_PHYSICAL_PHYSICAL, + self.interface) + + # Set number of reset domains + self.protocol.set_byte(Avr32Protocol.AVR32_CONTEXT_DEVICE, Avr32Protocol.AVR32_RESET_DOMAINS, + self.reset_domains) + + def activate_physical(self): + """ + Activate the physical interface + """ + return self.protocol.activate_physical() + + def deactivate_physical(self): + """ + Deactivate the physical interface + """ + return self.protocol.deactivate_physical() diff --git a/pymcuprog/avr8target.py b/pymcuprog/avr8target.py new file mode 100644 index 0000000..bc231e7 --- /dev/null +++ b/pymcuprog/avr8target.py @@ -0,0 +1,749 @@ +""" +Device Specific Classes which use AVR8Protocol implementation +""" +import time +from logging import getLogger +from pyedbglib.protocols.avr8protocol import Avr8Protocol +from pyedbglib.protocols.jtagice3protocol import Jtagice3ResponseError +from pyedbglib.util import binary + +from .deviceinfo import deviceinfo +from .deviceinfo.memorynames import MemoryNames +from .deviceinfo.deviceinfokeys import DeviceInfoKeys, DeviceInfoKeysAvr, DeviceMemoryInfoKeys +from .pymcuprog_errors import PymcuprogError + +class AvrDevice(object): + """ + Generic AVR device wrapper (maps to avr8 protocol) + """ + def __init__(self, transport): + self.logger = getLogger(__name__) + self.skip_blank_pages = True + self.protocol = Avr8Protocol(transport) + # AVR8 protocol packet framer limit + self.max_read_chunk_size = 512 + self.max_write_chunk_size = 512 + + @staticmethod + def memtype_read_from_string(memtype_string): + """ + Maps from a string to an avr8 memtype for reads + + :param memtype_string: Friendly name of memory + :type memtype_string: str + :returns: Memory type identifier as defined in the protocol + :rtype: int + """ + if memtype_string == MemoryNames.FLASH: + memtype = Avr8Protocol.AVR8_MEMTYPE_FLASH_PAGE + elif memtype_string == MemoryNames.EEPROM: + memtype = Avr8Protocol.AVR8_MEMTYPE_EEPROM + elif memtype_string in [MemoryNames.USER_ROW]: + memtype = Avr8Protocol.AVR8_MEMTYPE_USER_SIGNATURE + elif memtype_string in [MemoryNames.SIGNATURES]: + memtype = Avr8Protocol.AVR8_MEMTYPE_CALIBRATION_SIGNATURE + elif memtype_string == MemoryNames.FUSES: + memtype = Avr8Protocol.AVR8_MEMTYPE_FUSES + elif memtype_string in ("lock", MemoryNames.LOCKBITS): + memtype = Avr8Protocol.AVR8_MEMTYPE_LOCKBITS + elif memtype_string in ("raw", MemoryNames.INTERNAL_SRAM): + memtype = Avr8Protocol.AVR8_MEMTYPE_SRAM + else: + memtype = 0 + return memtype + + def enter_progmode(self): + """ + Enter programming mode + """ + return self.protocol.enter_progmode() + + def leave_progmode(self): + """ + Leave programming mode + """ + return self.protocol.leave_progmode() + + def activate_physical(self): + """ + Activate the physical interface + """ + return self.protocol.activate_physical() + + def deactivate_physical(self): + """ + Deactivate the physical interface + """ + return self.protocol.deactivate_physical() + + def erase(self, mode=0, address=0): + """ + Erase NVM + + :param mode: Flash erase mode to use + :type mode: int + :param address: Start address to erase from (not used by some modes) + :type address: int + """ + return self.protocol.erase(mode, address) + + def memory_read(self, memory_name, start_address, numbytes): + """ + Read device memory + + :param memory_name: Memory type identifier as defined in the protocol + :type memory_name: int + :param start_address: First address to read + :type start_address: int + :param numbytes: Number of bytes to read + :type numbytes: int + :returns: Data read out + :rtype: bytearray + """ + return self.protocol.memory_read(memory_name, start_address, numbytes) + + def memory_write(self, memory_name, start_address, data): + """ + Write device memory + + :param memory_name: Memory type identifier as defined in the protocol + :type memory_name: int + :param start_address: First address to write + :type start_address: int + :param data: Data to write + :type data: bytearray + """ + return self.protocol.memory_write(memory_name, start_address, data) + + @staticmethod + def is_blank(data): + """ + Checks if a buffer represents "blank" flash + + :param data: Contents to check + :type data: bytearray + :returns: True if data represents blank flash + :rtype: boolean + """ + for dat in data: + if not dat == 0xFF: + return False + return True + + def read_memory_section(self, memory_type, start_address, bytes_to_read, read_chunk_size): + """ + Reads a chunked section of memory + + :param memory_type: Memory type identifier as defined in the protocol + :type memory_type: int + :param start_address: First address to read + :type start_address: int + :param bytes_to_read: Number of bytes to read + :type bytes_to_read: int + :param read_chunk_size: Number of bytes in each separate read command to the debugger + :type read_chunk_size: int + """ + # AVR8 protocol packet framer limit + if read_chunk_size > self.max_read_chunk_size: + read_chunk_size = self.max_read_chunk_size + # Check alignment + if bytes_to_read != read_chunk_size and start_address % read_chunk_size != 0: + raise PymcuprogError("Misaligned read") + data = bytearray() + while bytes_to_read: + if bytes_to_read < read_chunk_size: + read_chunk_size = bytes_to_read + self.logger.info("Reading from address 0x%06X", start_address) + data.extend(self.protocol.memory_read(memory_type, start_address, read_chunk_size)) + start_address += read_chunk_size + bytes_to_read -= read_chunk_size + return data + + def write_memory_section(self, memory_type, start_address, data_to_write, write_chunk_size, allow_blank_skip=False): + """ + Writes a chunked section of memory + + :param memory_type: Memory type identifier as defined in the protocol + :type memory_type: int + :param start_address: First address to write to + :type start_address: int + :param data_to_write: Raw data values to write + :type data_to_write: bytearray + :param write_chunk_size: Number of bytes in each separate write command to the debugger + :type write_chunk_size: int + :param allow_blank_skip: Allow skipping write of locations with value 0xFF + :type allow_blank_skip: boolean + """ + # AVR8 protocol packet framer limit + if write_chunk_size > self.max_write_chunk_size: + write_chunk_size = self.max_write_chunk_size + + total_bytes_to_write = len(data_to_write) + if write_chunk_size > total_bytes_to_write: + write_chunk_size = total_bytes_to_write + + while data_to_write: + chunk = data_to_write[0:write_chunk_size] + if not self.skip_blank_pages or not self.is_blank(chunk) or not allow_blank_skip: + self.protocol.memory_write(memory_type, start_address, chunk) + start_address += write_chunk_size + data_to_write = data_to_write[write_chunk_size:] + + +class TinyXAvrTarget(AvrDevice): + """ + Class handling sessions with TinyX AVR targets using the AVR8 generic protocol + """ + + def __init__(self, transport): + super(TinyXAvrTarget, self).__init__(transport) + self.logger = getLogger(__name__) + self.use_hv = Avr8Protocol.UPDI_HV_NONE + + if transport.device.product_string.lower().startswith('edbg'): + # This is a workaround for FW3G-158 which has not been fixed for EDBG (fixed in common, + # but no new EDBG firmware has/will be built) + self.max_read_chunk_size = 256 + + @staticmethod + def memtype_write_from_string(memtype_string): + """ + Maps from a string to an avr8 memtype for writes + + :param memtype_string: Friendly name of memory + :type memtype_string: str + :returns: Memory type identifier as defined in the protocol + :rtype: int + """ + if memtype_string == MemoryNames.EEPROM: + # For UPDI devices erase is not implicit for the normal eeprom memory type so the special + # atomic eeprom memory type must be used to avoid having to do an erase upfront + memtype = Avr8Protocol.AVR8_MEMTYPE_EEPROM_ATOMIC + else: + # For most memories the same memory types are used for both read and write + memtype = AvrDevice.memtype_read_from_string(memtype_string) + return memtype + + def setup_prog_session(self, + interface=Avr8Protocol.AVR8_PHY_INTF_PDI_1W, + khz=900, + use_hv=Avr8Protocol.UPDI_HV_NONE): + """ + Sets up a programming session for a tinyX AVR device + + :param interface: Physical interface to use + :type interface: int + :param khz: Clock speed in kiloHertz / baud in kbps + :type khz: int + :param use_hv: Use high-voltage pulse to activate UPDI + :type use_hv: int + """ + # TinyX variant + self.protocol.set_variant(Avr8Protocol.AVR8_VARIANT_TINYX) + # Prog functionality + self.protocol.set_function(Avr8Protocol.AVR8_FUNC_PROGRAMMING) + # Interface + self.protocol.set_interface(interface) + # UPDI uses XMEGA PDI clock + self.protocol.set_le16(Avr8Protocol.AVR8_CTXT_PHYSICAL, Avr8Protocol.AVR8_PHY_XM_PDI_CLK, khz) + # Set high-voltage activation + # Note that this flag is always set even when use_hv is UPDI_HV_NONE to make it possible to disable high-voltage + # activation again after it has previously been enabled. The side effect is that this parameter might be + # written to tools without high-voltage capability in which case this option parameter is not implemented. + # However the protocol implementation will just consume the parameter value and ignore it. + self.protocol.set_byte(Avr8Protocol.AVR8_CTXT_OPTIONS, Avr8Protocol.AVR8_OPT_HV_UPDI_ENABLE, use_hv) + self.use_hv = use_hv + + def sib_read(self): + """ + Reads the System Information Block + + :return: SIB bytes + """ + return self.protocol.memory_read(Avr8Protocol.AVR8_MEMTYPE_SIB, 0, 32) + + def setup_config(self, device_info): + """ + Sets up the device config for a tinyX AVR device + + :param device_info: Target device information as returned by deviceinfo.deviceinfo.getdeviceinfo + :type device_info: dict + """ + if device_info is None: + device_info = {} + + # Parse the device info for memory descriptions + device_memory_info = deviceinfo.DeviceMemoryInfo(device_info) + + flash_info = device_memory_info.memory_info_by_name(MemoryNames.FLASH) + eeprom_info = device_memory_info.memory_info_by_name(MemoryNames.EEPROM) + # Extract settings + fl_base = flash_info[DeviceMemoryInfoKeys.ADDRESS] + fl_page_size = flash_info[DeviceMemoryInfoKeys.PAGE_SIZE] + fl_size = flash_info[DeviceMemoryInfoKeys.SIZE] + ee_base = eeprom_info[DeviceMemoryInfoKeys.ADDRESS] + ee_page_size = eeprom_info[DeviceMemoryInfoKeys.PAGE_SIZE] + ee_size = eeprom_info[DeviceMemoryInfoKeys.SIZE] + nvmctrl_addr = device_info.get(DeviceInfoKeysAvr.NVMCTRL_BASE) + ocd_addr = device_info.get(DeviceInfoKeysAvr.OCD_BASE) + user_row_base = device_memory_info.memory_info_by_name(MemoryNames.USER_ROW)[DeviceMemoryInfoKeys.ADDRESS] + user_row_size = device_memory_info.memory_info_by_name(MemoryNames.USER_ROW)[DeviceMemoryInfoKeys.SIZE] + sig_row_base = device_memory_info.memory_info_by_name(MemoryNames.SIGNATURES)[DeviceMemoryInfoKeys.ADDRESS] + fuses_base = device_memory_info.memory_info_by_name(MemoryNames.FUSES)[DeviceMemoryInfoKeys.ADDRESS] + fuse_size = device_memory_info.memory_info_by_name(MemoryNames.FUSES)[DeviceMemoryInfoKeys.SIZE] + lock_base = device_memory_info.memory_info_by_name(MemoryNames.LOCKBITS)[DeviceMemoryInfoKeys.ADDRESS] + device_id = device_info.get(DeviceInfoKeys.DEVICE_ID) + hv_implementation = device_info.get(DeviceInfoKeysAvr.HV_IMPLEMENTATION) + + # Setup device structure and write to tool + # TINYX_PROG_BASE + devdata = bytearray([fl_base & 0xff, (fl_base >> 8) & 0xff]) + # TINYX_FLASH_PAGE_BYTES + devdata += bytearray([fl_page_size & 0xff]) + # TINYX_EEPROM_PAGE_BYTES + devdata += bytearray([ee_page_size]) + # TINYX_NVMCTRL_MODULE_ADDRESS + devdata += bytearray([nvmctrl_addr & 0xff, (nvmctrl_addr >> 8) & 0xff]) + # TINYX_OCD_MODULE_ADDRESS + devdata += bytearray([ocd_addr & 0xff, (ocd_addr >> 8) & 0xff]) + + # Pad to get to TINYX_FLASH_BYTES + devdata += bytearray([0x00]*(0x12-len(devdata))) + + # TINYX_FLASH_BYTES + devdata += bytearray([fl_size & 0xFF, (fl_size >> 8) & 0xFF, (fl_size >> 16) & 0xFF, (fl_size >> 24) & 0xFF]) + # TINYX_EEPROM_BYTES + devdata += bytearray([ee_size & 0xff, (ee_size >> 8) & 0xff]) + # TINYX_USER_SIG_BYTES_BYTES + devdata += bytearray([user_row_size & 0xff, (user_row_size >> 8) & 0xff]) + # TINYX_FUSE_BYTES + devdata += bytearray([fuse_size & 0xff]) + + # Pad to get to TINYX_EEPROM_BASE + devdata += bytearray([0x00]*(0x20-len(devdata))) + + # TINYX_EEPROM_BASE + devdata += bytearray([ee_base & 0xFF, (ee_base >> 8) & 0xFF]) + # TINYX_USER_ROW_BASE + devdata += bytearray([user_row_base & 0xFF, (user_row_base >> 8) & 0xFF]) + #TINYX_SIGROW_BASE + devdata += bytearray([sig_row_base & 0xFF, (sig_row_base >> 8) & 0xFF]) + #TINYX_FUSES_BASE + devdata += bytearray([fuses_base & 0xFF, (fuses_base >> 8) & 0xFF]) + # TINYX_LOCK_BASE + devdata += bytearray([lock_base & 0xFF, (lock_base >> 8) & 0xFF]) + # TINYX_DEVICE_ID + devdata += bytearray([device_id & 0xFF, (device_id >> 8) & 0xFF]) + # TINYX_PROG_BASE_MSB + devdata += bytearray([(fl_base >> 16) & 0xFF]) + # TINYX_FLASH_PAGE_BYTES_MSB + devdata += bytearray([(fl_page_size >> 8) & 0xFF]) + # TINYX_ADDRESS_SIZE + if device_info.get(DeviceInfoKeysAvr.ADDRESS_SIZE, '16-bit') == '24-bit': + # Use 24-bit addressing mode + devdata += bytearray([0x01]) + else: + # Default is 16-bit addressing mode + devdata += bytearray([0x00]) + # TINYX_HV_IMPLEMENTATION + devdata += bytearray([hv_implementation & 0xFF]) + + self.protocol.write_device_data(devdata) + + def setup_debug_session(self, + interface=Avr8Protocol.AVR8_PHY_INTF_PDI_1W, + khz=100, + use_hv=Avr8Protocol.UPDI_HV_NONE): + """ + Sets up a debug session for a tinyX AVR device + + :param interface: Physical interface to use + :type interface: int + :param khz: Clock speed in kiloHertz / baud in kbps + :type khz: int + :param use_hv: Use high-voltage pulse to activate UPDI + :type use_hv: int + """ + if interface == Avr8Protocol.AVR8_PHY_INTF_PDI_1W and use_hv != Avr8Protocol.UPDI_HV_NONE: + self.protocol.set_byte(Avr8Protocol.AVR8_CTXT_OPTIONS, Avr8Protocol.AVR8_OPT_HV_UPDI_ENABLE, use_hv) + self.protocol.set_variant(Avr8Protocol.AVR8_VARIANT_TINYX) + self.protocol.set_function(Avr8Protocol.AVR8_FUNC_DEBUGGING) + self.protocol.set_interface(interface) + self.protocol.set_le16(Avr8Protocol.AVR8_CTXT_PHYSICAL, Avr8Protocol.AVR8_PHY_XM_PDI_CLK, khz) + self.use_hv = use_hv + + def stack_pointer_read(self): + """ + Reads the stack pointer + + :returns: Stack pointer + :rtype: bytearray + """ + return self.protocol.memory_read(Avr8Protocol.AVR8_MEMTYPE_OCD, 0x18, 0x02) + + def breakpoint_set(self, address): + """ + Sets the hardware breakpoint + + :param address: Address to break at + :type address: int + """ + resp = self.protocol.jtagice3_command_response( + bytearray([Avr8Protocol.CMD_AVR8_HW_BREAK_SET, Avr8Protocol.CMD_VERSION0, 1, 1]) + + binary.pack_le32(address) + + bytearray([3])) + return self.protocol.check_response(resp) + + def breakpoint_clear(self): + """ + Clears the hardware breakpoint + """ + resp = self.protocol.jtagice3_command_response( + bytearray([Avr8Protocol.CMD_AVR8_HW_BREAK_CLEAR, Avr8Protocol.CMD_VERSION0, 1])) + return self.protocol.check_response(resp) + + def execute_patch(self, instructions, flags=0x00): + """ + Executes an instruction in the AVR core + + :param instructions: Instructions to execute + :type instructions: bytearray + :param flags: Execution flags + :type flags: int + """ + packet = bytearray([0x70, Avr8Protocol.CMD_VERSION0, flags, len(instructions) / 2]) + packet = packet + instructions + resp = self.protocol.jtagice3_command_response(packet) + return self.protocol.check_response(resp) + + def run_with_power_nap(self): + """ + Execute run with power-nap enabled. This command is deprecated. + """ + self.protocol.set_byte(Avr8Protocol.AVR8_CTXT_OPTIONS, Avr8Protocol.AVR8_OPT_POWER_NAP, 0x01) + self.protocol.run() + + def activate_physical(self, use_reset=False, user_interaction_callback=None): + """ + Override function for high-voltage activation for UPDI + + :param use_reset: Use external reset line during activation (only used for Mega JTAG interface) + :type use_reset: boolean + :param user_interaction_callback: Callback to be called when user interaction is required, + for example when doing UPDI high-voltage activation with user target power toggle. + This function could ask the user to toggle power and halt execution waiting for the user + to respond (this is default behavior if the callback is None), or if the user is another + script it could toggle power automatically and then return. + :type user_interaction_callback: function + """ + try: + return self.protocol.activate_physical(use_reset) + except Jtagice3ResponseError as error: + if error.code == Avr8Protocol.AVR8_FAILURE_PLEASE_TOGGLE_POWER: + if self.use_hv == Avr8Protocol.UPDI_HV_USER_POWER_TOGGLE: + if user_interaction_callback is None: + # Default behavior is to wait for the user to toggle power + input("Toggle power now") + else: + user_interaction_callback() + # During pounce, or at window timeout, firmware clears the "user power toggle" flag + # However MPLAB will always set this before each activate, so the parameter is set again here + # to most-accurately reflect front-end behaviour for test purposes + self.protocol.set_byte(Avr8Protocol.AVR8_CTXT_OPTIONS, + Avr8Protocol.AVR8_OPT_HV_UPDI_ENABLE, self.use_hv) + return self.protocol.activate_physical(use_reset) + raise + + def ocdregfile_read(self): + """ + Read OCD registers + + :return: OCD register file + :rtype: bytearray + """ + data = None + for register_index in range(32): + data = self.protocol.memory_read(Avr8Protocol.AVR8_MEMTYPE_OCD, register_index, 1) + self.logger.info("OCDREG%02X=0x%02X", register_index, data) + return data + + def execute_instruction(self, instruction): + """ + Execute an AVR instruction on the core + + :param instruction: Instruction to execute + :type instruction: int + """ + value = 0x04 + self.logger.info("Write 0x%02X to CTRLA", value) + status = self.protocol.memory_write(Avr8Protocol.AVR8_MEMTYPE_OCD, 0x08, value) + self.logger.info("%s", status) + + self.logger.info("Write 0x%04X to ALTINST", instruction) + status = self.protocol.memory_write(Avr8Protocol.AVR8_MEMTYPE_OCD, 0x10, instruction) + self.logger.info("%s", status) + + # Write CS addr 4 (OCD RESTART) + self.logger.info("OCD RESTART") + status = self.protocol.memory_write(Avr8Protocol.AVR8_MEMTYPE_CS, 0x04, (1 << 1)) + self.logger.info("%s", status) + while True: + status = self.protocol.memory_read(Avr8Protocol.AVR8_MEMTYPE_CS, 0x05, 0x01) + self.logger.info("waiting for stopped...(0x%02X)", status) + if status: + break + time.sleep(0.001) + + def read_device_id(self): + """ + Reads the device ID from the part + + :returns: Device ID raw bytes (little endian) + :rtype: bytearray + """ + self.logger.info("Read device ID") + device_id = self.protocol.memory_read(Avr8Protocol.AVR8_MEMTYPE_SRAM, 0x1100, 3) + return device_id + + +class TinyAvrTarget(AvrDevice): + """ + Implements Tiny AVR (debugWIRE) functionality of the AVR8 protocol + """ + + def __init__(self, transport): + super(TinyAvrTarget, self).__init__(transport) + self.logger = getLogger(__name__) + + def setup_debug_session(self): + """ + Sets up a debugging session on an Tiny AVR (debugwire) + """ + self.protocol.set_variant(Avr8Protocol.AVR8_VARIANT_TINYOCD) + self.protocol.set_function(Avr8Protocol.AVR8_FUNC_DEBUGGING) + self.protocol.set_interface(Avr8Protocol.AVR8_PHY_INTF_DW) + +class MegaAvrJtagTarget(AvrDevice): + """ + Implements Mega AVR (JTAG) functionality of the AVR8 protocol + """ + + def __init__(self, transport): + super(MegaAvrJtagTarget, self).__init__(transport) + self.logger = getLogger(__name__) + + def setup_prog_session(self): + """ + Sets up a programming session on an Mega AVR (JTAG) + """ + self.protocol.set_variant(Avr8Protocol.AVR8_VARIANT_MEGAOCD) + self.protocol.set_function(Avr8Protocol.AVR8_FUNC_PROGRAMMING) + self.protocol.set_interface(Avr8Protocol.AVR8_PHY_INTF_JTAG) + + def setup_config(self, device_info): + """ + Sets up the device config for a mega AVR device + + :param device_info: Target device information as returned by deviceinfo.deviceinfo.getdeviceinfo + :type device_info: dict + """ + if device_info is None: + device_info = {} + + # Parse the device info for memory descriptions + device_memory_info = deviceinfo.DeviceMemoryInfo(device_info) + + flash_info = device_memory_info.memory_info_by_name(MemoryNames.FLASH) + eeprom_info = device_memory_info.memory_info_by_name(MemoryNames.EEPROM) + + # Extract settings + fl_base = flash_info[DeviceMemoryInfoKeys.ADDRESS] + fl_page_size = flash_info[DeviceMemoryInfoKeys.PAGE_SIZE] + fl_size = flash_info[DeviceMemoryInfoKeys.SIZE] + ee_page_size = eeprom_info[DeviceMemoryInfoKeys.PAGE_SIZE] + ee_size = eeprom_info[DeviceMemoryInfoKeys.SIZE] + + # Setup device structure and write to tool + + # TMEGA_FLASH_PAGE_BYTES = 0x00, + devdata = bytearray([fl_page_size & 0xff, (fl_page_size >> 8) & 0xff]) + + # TMEGA_FLASH_BYTES = 0x02, + devdata += bytearray([fl_size & 0xFF, (fl_size >> 8) & 0xFF, (fl_size >> 16) & 0xFF, (fl_size >> 24) & 0xFF]) + + # TMEGA_FLASH_BASE = 0x06, + devdata += bytearray([fl_base & 0xFF, (fl_base >> 8) & 0xFF, (fl_base >> 16) & 0xFF, (fl_base >> 24) & 0xFF]) + + # TMEGA_SMALLEST_BOOT_START = 0x0A, + # Use last page for boot size (not correct, but sufficient for programming) + boot_start = fl_size - fl_page_size + devdata += bytearray([1+boot_start & 0xFF, (boot_start >> 8) & 0xFF, (boot_start >> 16) & 0xFF, (boot_start >> 24) & 0xFF]) + + # TMEGA_SRAM_START = 0x0E, + sram_start = 0x100 + devdata += bytearray([sram_start & 0xff, (sram_start >> 8) & 0xff]) + + # TMEGA_EEPROM_BYTES = 0x10, + devdata += bytearray([ee_size & 0xff, (ee_size >> 8) & 0xff]) + + # TMEGA_EEPROM_PAGE_SIZE = 0x12, + devdata += bytearray([ee_page_size]) + + # TMEGA_OCD_REV = 0x13, + # OCD_REVISION in OCD property-group in ATDF + devdata += bytearray([0x03]) + + # TMEGA_PAGEBUFFERS_PER_FLASH_BLOCK = 0x14, + devdata += bytearray([0x01]) + + # TMEGA_FULLPAGE_BITSTREAM = 0x15, + devdata += bytearray([0x00]) + + # TMEGA_DEBUGWIRE_HARDWARE_BREAKPOINTS = 0x16, + devdata += bytearray([0x00]) + + # Free 0x17, + devdata += bytearray([0x00]) + + # TINY/MEGA IO addresses + # TMEGA_OCDREG_ADDR = 0x18, + devdata += bytearray([0x51]) + + # TMEGA_EEARH_ADDR = 0x19, + devdata += bytearray([0x42]) + + # TMEGA_EEARL_ADDR = 0x1A, + devdata += bytearray([0x41]) + + # TMEGA_EECR_ADDR = 0x1B, + devdata += bytearray([0x3F]) + + # TMEGA_EEDR_ADDR = 0x1C, + devdata += bytearray([0x40]) + + # TMEGA_SPMCR_ADDR = 0x1D, + devdata += bytearray([0x57]) + + # TMEGA_OSCCAL_ADDR = 0x1E + devdata += bytearray([0x66]) + + self.protocol.write_device_data(devdata) + +class XmegaAvrTarget(AvrDevice): + """ + Implements XMEGA (PDI) functionality of the AVR8 protocol + """ + + def __init__(self, transport): + super(XmegaAvrTarget, self).__init__(transport) + self.logger = getLogger(__name__) + + if transport.device.product_string.lower().startswith('edbg'): + # This is a workaround for FW3G-158 which has not been fixed for EDBG (fixed in common, + # but no new EDBG firmware has/will be built) + self.max_read_chunk_size = 256 + + def setup_debug_session(self): + """ + Sets up a debugging session on an XMEGA AVR + """ + self.protocol.set_variant(Avr8Protocol.AVR8_VARIANT_XMEGA) + self.protocol.set_function(Avr8Protocol.AVR8_FUNC_DEBUGGING) + self.protocol.set_interface(Avr8Protocol.AVR8_PHY_INTF_PDI) + + def setup_prog_session(self, interface=Avr8Protocol.AVR8_PHY_INTF_PDI, khz=4000): + """ + Sets up a programming session for a XMEGA AVR device + + :param interface: physical interface to use + :param khz: clock speed in kiloHertz + """ + # XMEGA variant + self.protocol.set_variant(Avr8Protocol.AVR8_VARIANT_XMEGA) + # Prog functionality + self.protocol.set_function(Avr8Protocol.AVR8_FUNC_PROGRAMMING) + # Interface + self.protocol.set_interface(interface) + # PDI uses XMEGA PDI clock + self.protocol.set_le16(Avr8Protocol.AVR8_CTXT_PHYSICAL, Avr8Protocol.AVR8_PHY_XM_PDI_CLK, khz) + + def setup_config(self, device_info): + """ + Sets up the device config for a XMEGA AVR device + + :param device_info: target device information as returned by deviceinfo.deviceinfo.getdeviceinfo + """ + if device_info is None: + device_info = {} + + # Parse the device info for memory descriptions + device_memory_info = deviceinfo.DeviceMemoryInfo(device_info) + + flash_info = device_memory_info.memory_info_by_name(MemoryNames.FLASH) + eeprom_info = device_memory_info.memory_info_by_name(MemoryNames.EEPROM) + + # Extract settings + fl_base = flash_info[DeviceMemoryInfoKeys.ADDRESS] + fl_page_size = flash_info[DeviceMemoryInfoKeys.PAGE_SIZE] + fl_size = flash_info[DeviceMemoryInfoKeys.SIZE] + ee_base = eeprom_info[DeviceMemoryInfoKeys.ADDRESS] + ee_page_size = eeprom_info[DeviceMemoryInfoKeys.PAGE_SIZE] + ee_size = eeprom_info[DeviceMemoryInfoKeys.SIZE] + + # Setup device structure and write to tool + # XMEGA_APPL_BASE 0x00 + devdata = bytearray([fl_base & 0xff, (fl_base >> 8) & 0xff, (fl_base >> 16) & 0xff, (fl_base >> 24) & 0xff]) + + # XMEGA_BOOT_BASE 0x04 + boot_base = 0x00820000 + devdata += bytearray([boot_base & 0xff, (boot_base >> 8) & 0xff, (boot_base >> 16) & 0xff, (boot_base >> 24) & 0xff]) + + # XMEGA_EEPROM_BASE 0x08 + devdata += bytearray([ee_base & 0xff, (ee_base >> 8) & 0xff, (ee_base >> 16) & 0xff, (ee_base >> 24) & 0xff]) + + # XMEGA_FUSE_BASE 0x0C + fuses_base = 0x008F0020 + devdata += bytearray([fuses_base & 0xff, (fuses_base >> 8) & 0xff, (fuses_base >> 16) & 0xff, (fuses_base >> 24) & 0xff]) + + # XMEGA_LOCKBIT_BASE 0x10 + lock_base = 0x008F0027 + devdata += bytearray([lock_base & 0xff, (lock_base >> 8) & 0xff, (lock_base >> 16) & 0xff, (lock_base >> 24) & 0xff]) + + # XMEGA_USER_SIGN_BASE 0x14 + user_row_base = 0x008E0400 + devdata += bytearray([user_row_base & 0xff, (user_row_base >> 8) & 0xff, (user_row_base >> 16) & 0xff, (user_row_base >> 24) & 0xff]) + + # XMEGA_PROD_SIGN_BASE 0x18 + sig_row_base = 0x008E0200 + devdata += bytearray([sig_row_base & 0xff, (sig_row_base >> 8) & 0xff, (sig_row_base >> 16) & 0xff, (sig_row_base >> 24) & 0xff]) + + # XMEGA_DATA_BASE 0x1C + data_space_base = 0x01000000 + devdata += bytearray([data_space_base & 0xff, (data_space_base >> 8) & 0xff, (data_space_base >> 16) & 0xff, (data_space_base >> 24) & 0xff]) + + # XMEGA_APPL_BYTES 0x20 + devdata += bytearray([fl_size & 0xFF, (fl_size >> 8) & 0xFF, (fl_size >> 16) & 0xFF, (fl_size >> 24) & 0xFF]) + + # XMEGA_BOOT_BYTES 0x24 + fl_boot_size = 512 + devdata += bytearray([fl_boot_size & 0xff, (fl_boot_size >> 8) & 0xff]) + + # XMEGA_FLASH_PAGE_BYTES 0x26 + devdata += bytearray([fl_page_size & 0xff, (fl_page_size >> 8) & 0xff]) + + # XMEGA_EEPROM_SIZE 0x28 + devdata += bytearray([ee_size & 0xff, (ee_size >> 8) & 0xff]) + + # XMEGA_EEPROM_PAGE_SIZE 0x2A + devdata += bytearray([ee_page_size]) + + # XMEGA_NVM_OFFSET 0x2B + nvm_offset = 0x01C0 + devdata += bytearray([nvm_offset & 0xff, (nvm_offset >> 8) & 0xff]) + + # XMEGA_SIGN_OFFSET 0x2D + signature_offset = 0x0090 + devdata += bytearray([signature_offset & 0xff, (signature_offset >> 8) & 0xff]) + + # Write to the debugger + self.protocol.write_device_data(devdata) diff --git a/pymcuprog/avrdebugger.py b/pymcuprog/avrdebugger.py new file mode 100644 index 0000000..72ef8aa --- /dev/null +++ b/pymcuprog/avrdebugger.py @@ -0,0 +1,407 @@ +""" +Python AVR MCU debugger +""" +import time +from logging import getLogger +from pyedbglib.protocols import housekeepingprotocol +from pyedbglib.protocols.avr8protocol import Avr8Protocol +from pyedbglib.util import binary + +from .deviceinfo import deviceinfo +from .nvmupdi import NvmAccessProviderCmsisDapUpdi +from .pymcuprog_errors import PymcuprogToolConfigurationError, PymcuprogNotSupportedError, PymcuprogError + +class AvrDebugger(): + """ + AVR debugger wrapper + + :param transport: transport object to communicate through + :type transport: object(hid_transport) + :param use_events_for_run_stop_state: True to use HID event channel, False to polling + :type use_events_for_run_stop_state: boolean + """ + def __init__(self, transport, use_events_for_run_stop_state=True): + # Hook onto logger + self.logger = getLogger(__name__) + # Use transport passed in + self.transport = transport + self.device_info = None + self.memory_info = None + self.device_info = None + self.device = None + + # Event polling needs a housekeeping session to subscribe to run/stop events + self.housekeeper = None + self.use_events_for_run_stop_state = use_events_for_run_stop_state + if self.use_events_for_run_stop_state: + self.housekeeper = housekeepingprotocol.Jtagice3HousekeepingProtocol(self.transport) + self.housekeeper.start_session() + + def setup_session(self, device, frequency=900000, options=""): + """ + Sets up the device for a debug session + + :param device: name of the device to debug + :param frequency: UPDI clock frequency in Hz + :type frequency: int + :param options: dictionary of options for starting the session + :type options: dict + """ + self.logger.info("Setting up %s for debugging", device) + + # Gather device info + try: + self.device_info = deviceinfo.getdeviceinfo(device) + except ModuleNotFoundError: + raise PymcuprogNotSupportedError("No device info for device: {}".format(device)) + if self.device_info['interface'].upper() != "UPDI": + raise PymcuprogToolConfigurationError("pymcuprog debug wrapper only supports UPDI devices") + + # Memory info for the device + self.memory_info = deviceinfo.DeviceMemoryInfo(self.device_info) + + # Setup device model and start a session + self.device = NvmAccessProviderCmsisDapUpdi(self.transport, self.device_info, frequency, options) + + # Default setup for NVM Access Provider is prog session - override with debug info + self.device.avr.setup_debug_session(interface=Avr8Protocol.AVR8_PHY_INTF_PDI_1W, + khz=frequency // 1000, + use_hv=Avr8Protocol.UPDI_HV_NONE) + + def start_debugging(self, flash_data=None): + """ + Start the debug session + + :param flash_data: flash data content to program in before debugging + :type flash data: list of bytes + """ + self.logger.info("Starting debug session") + self.device.start() + + # The device is now in prog mode + device_id = self.device.read_device_id() + self.logger.debug("Device ID read: %X", binary.unpack_le24(device_id)) + + # If the user wants content on the AVR, put it there now + if flash_data: + if not isinstance(flash_data, list): + raise PymcuprogNotSupportedError("Content can only be provided as a list of binary values") + # First chip-erase + self.logger.info("Erasing target") + self.device.erase() + + # Then program + self.logger.info("Programming target") + self.device.write(self.memory_info.memory_info_by_name('flash'), 0, flash_data) + + # Flush events before starting + self.flush_events() + + self.logger.info("Leaving prog mode (with auto-attach)") + self.device.avr.protocol.leave_progmode() + + self._wait_for_break() + + # Cleanup code for detatching target + def stop_debugging(self): + """ + Stop the debug session and clean up + """ + self.logger.info("Stop debugging session") + # Halt the core + self.device.avr.protocol.stop() + # Remove all software breakpoints + self.device.avr.protocol.software_breakpoint_clear_all() + # Remove all hardware breakpoints + self.device.avr.breakpoint_clear() + # Detach from the OCD + self.device.avr.protocol.detach() + # De-activate UPDI physical interface + self.device.avr.deactivate_physical() + # Sign off + if self.use_events_for_run_stop_state: + self.housekeeper.end_session() + + def __exit__(self, exc_type, exc_value, traceback): + """ Destructor""" + self.stop_debugging() + + def _read_running_state(self): + """ + Back-channel interface to see what state the AVR is in + This mechanism can be used to replace relying on AVR_EVT events which publish stop conditions + """ + running = self.device.avr.protocol.get_byte(Avr8Protocol.AVR8_CTXT_TEST, Avr8Protocol.AVR8_TEST_TGT_RUNNING) + if running: + self.logger.debug("AVR core is running") + else: + self.logger.debug("AVR core is stopped") + return bool(running) + + def _wait_for_break(self, timeout_ms=1000): + """ + Wait for the AVR core to be in stopped/halt/break state + + :param timeout_ms: number of milliseconds to wait + :type timeout_ms: int + """ + if self.use_events_for_run_stop_state: + while True: + program_counter = self.poll_event() + if program_counter is not None: + return + timeout_ms -= 50 + time.sleep(0.05) + if timeout_ms < 0: + raise PymcuprogError("Timeout waiting for AVR core to halt") + else: + while True: + if not self._read_running_state(): + return + timeout_ms -= 50 + time.sleep(0.05) + if timeout_ms < 0: + raise PymcuprogError("Timeout waiting for AVR core to halt") + + # Debugging functions, using protocol object in device model directly + def attach(self, do_break=False): + """ + Attach to the AVR core + + :param do_break: set to True to force the core to stop during attach + :type do_break: bool + """ + self.logger.debug("Attach debugger to AVR core") + self.device.avr.protocol.attach(do_break) + if do_break: + self._wait_for_break() + + def detach(self): + """ + Detach from the AVR core + """ + self.logger.debug("Detach from AVR core") + self.device.avr.protocol.detach() + + # Flow control + def reset(self): + """ + Reset the AVR core. + The PC will point to the first instruction to be executed. + """ + self.logger.debug("CPU reset") + self.device.avr.protocol.reset() + self._wait_for_break() + + def step(self): + """ + Single-step on protocol level + Executes a single AVR instruction, regardless of number of cycles + """ + self.logger.debug("CPU single-instruction-step") + self.device.avr.protocol.step() + self._wait_for_break() + + def stop(self): + """ + Request the AVR core to halt and wait for it to enter stopped state + """ + self.logger.debug("CPU halt") + self.device.avr.protocol.stop() + + def run(self): + """ + Put the AVR core into run mode + """ + self.logger.debug("CPU resume") + self.device.avr.protocol.run() + + def run_to(self, address): + """ + Insert a breakpoint at the given address and put the core into run mode + Does not wait for the address to be reached + + :param address: byte address of the instruction to break at + """ + self.logger.debug("CPU resume with hardware breakpoint") + word_address = int(address//2) + self.device.avr.protocol.run_to(word_address) + + def stack_pointer_read(self): + """ + Reads the stack pointer + + :returns: Stack pointer + :rtype: bytearray + """ + self.logger.debug("Reading stack pointer") + return self.device.avr.stack_pointer_read() + + def status_register_read(self): + """ + Reads the status register from the AVR + + :return: 8-bit SREG value + """ + self.logger.debug("Reading status register") + return self.device.avr.protocol.memory_read(Avr8Protocol.AVR8_MEMTYPE_OCD, Avr8Protocol.AVR8_MEMTYPE_OCD_SREG, 1) + + def program_counter_read(self): + """ + Reads the program counter register from the AVR + + :return: PC value as word address + """ + self.logger.debug("Reading program counter") + return self.device.avr.protocol.program_counter_read() + + def program_counter_write(self, program_counter): + """ + Write a new program counter value (word) to the AVR + + :param program_counter: new PC value to write (word address) + """ + self.logger.debug("Writing program counter to %X", program_counter) + self.device.avr.protocol.program_counter_write(program_counter) + + def register_file_read(self): + """ + Reads out the AVR register file (R0::R31) + + :return: 32 bytes of register file content as bytearray + """ + self.logger.debug("Reading register file") + return self.device.avr.protocol.regfile_read() + + def register_file_write(self, regs): + """ + Writes the AVR register file (R0::R31) + + :param data: 32 byte register file content as bytearray + :raises ValueError: if 32 bytes are not given + """ + self.logger.debug("Writing register file") + return self.device.avr.protocol.regile_write(regs) + + def sram_read(self, address, numbytes): + """ + Read SRAM content from the AVR + + :param address: absolute address to start reading from + :param numbytes: number of bytes to read + """ + self.logger.debug("Reading %d bytes from SRAM at %X", numbytes, address) + # Subtract offset to match debugger protocol call + offset = (self.memory_info.memory_info_by_name('internal_sram'))['address'] + return self.device.read(self.memory_info.memory_info_by_name('internal_sram'), address-offset, numbytes) + + def sram_write(self, address, data): + """ + Write SRAM content to the AVR + + :param address: absolute address in SRAM to start writing + :param data: content to store to SRAM + """ + self.logger.debug("Writing %d bytes to SRAM at %X", len(data), address) + # Subtract offset to match debugger protocol call + offset = (self.memory_info.memory_info_by_name('internal_sram'))['address'] + return self.device.write(self.memory_info.memory_info_by_name('internal_sram'), address-offset, data) + + def flash_read(self, address, numbytes): + """ + Read flash content from the AVR + + :param address: absolute address to start reading from + :param numbytes: number of bytes to read + """ + self.logger.debug("Reading %d bytes from flash at %X", numbytes, address) + # Subtract offset to match debugger protocol call + offset = (self.memory_info.memory_info_by_name('flash'))['address'] + return self.device.read(self.memory_info.memory_info_by_name('flash'), address-offset, numbytes) + + def eeprom_read(self, address, numbytes): + """ + Read EEPROM content from the AVR + + :param address: absolute address to start reading from + :param numbytes: number of bytes to read + """ + self.logger.debug("Reading %d bytes from EEPROM at %X", numbytes, address) + # Subtract offset to match debugger protocol call + offset = (self.memory_info.memory_info_by_name('eeprom'))['address'] + return self.device.read(self.memory_info.memory_info_by_name('eeprom'), address-offset, numbytes) + + def eeprom_write(self, address, data): + """ + Write EEPROM content to the AVR + + :param address: absolute address in EEPROM to start writing + :param data: content to store to EEPROM + """ + self.logger.debug("Writing %d bytes to EEPROM at %X", len(data), address) + # Subtract offset to match debugger protocol call + offset = (self.memory_info.memory_info_by_name('eeprom'))['address'] + return self.device.write(self.memory_info.memory_info_by_name('eeprom'), address-offset, data) + + + def hardware_breakpoint_set(self, address): + """ + Sets a hardware breakpoint in the AVR OCD + + :param address: byte address to set hardware breakpoint + """ + self.logger.debug("Setting hardware breakpoint at %X", address) + self.device.avr.breakpoint_set(address) + + def hardware_breakpoint_clear(self): + """ + Clears the hardware breakpoint in the AVR OCD + """ + self.logger.debug("Clearing hardware breakpoint") + self.device.avr.breakpoint_clear() + + def software_breakpoint_set(self, address): + """ + Sets a software breakpoint in the AVR flash + + :param address: byte address to set software breakpoint + """ + self.logger.debug("Setting software breakpoint at %X", address) + self.device.avr.protocol.software_breakpoint_set(address) + + def software_breakpoint_clear(self, address): + """ + Clears a software breakpoint in the AVR flash and restores the original instruction + + :param address: byte address to remove software breakpoint + """ + self.logger.debug("Clearing software breakpoint") + self.device.avr.protocol.software_breakpoint_clear(address) + + def software_breakpoint_clear_all(self): + """ + Removes all software breakpoints immediately and restores flash to its original content + """ + self.logger.debug("Clearing all software breakpoints") + self.device.avr.protocol.software_breakpoint_clear_all() + + def poll_event(self): + """ + Poll for events from the debugger + Events are used to signal AVR core transitions from RUN mode to STOPPED mode + """ + # Check for incoming events + event = self.device.avr.protocol.poll_events() + if event: + # Check if this is a break event + program_counter = self.device.avr.protocol.decode_break_event(event) + return program_counter + return None + + def flush_events(self): + """ + Flushes all incoming events before or after a session + """ + while True: + if not self.device.avr.protocol.poll_events(): + return diff --git a/pymcuprog/backend.py b/pymcuprog/backend.py new file mode 100644 index 0000000..08bd25f --- /dev/null +++ b/pymcuprog/backend.py @@ -0,0 +1,701 @@ +""" +Backend interface for the pymcuprog utility. + +This module is the boundary between the Command Line Interface (CLI) part and +the backend part that does the actual job. Any external utility or script that +needs access to the functionality provided by pymcuprog should connect to the +interface provided by this backend module +""" +# Python 3 compatibility for Python 2 +from __future__ import print_function + +import os +from logging import getLogger + +# pyedbglib dependencies +from pyedbglib.hidtransport.hidtransportfactory import hid_transport +from pyedbglib.hidtransport.hidtransportbase import HidTransportBase +from pyedbglib.protocols import housekeepingprotocol +from pyedbglib.protocols.jtagice3protocol import Jtagice3ResponseError + +from .pymcuprog_errors import PymcuprogToolConfigurationError, PymcuprogToolConnectionError +from .pymcuprog_errors import PymcuprogNotSupportedError, PymcuprogEraseError +from .pymcuprog_errors import PymcuprogSessionConfigError, PymcuprogSessionError +from .programmer import Programmer +from .deviceinfo import deviceinfo +from .deviceinfo.memorynames import MemoryNames +from .deviceinfo.memorynames import MemoryNameAliases +from .deviceinfo.eraseflags import ChiperaseEffect +from .deviceinfo.deviceinfokeys import DeviceInfoKeys, DeviceMemoryInfoKeys +from .toolconnection import ToolUsbHidConnection, ToolSerialConnection +from .utils import read_tool_info +from .utils import read_target_voltage, read_supply_voltage_setpoint, read_usb_voltage +from .utils import set_supply_voltage_setpoint +from .hexfileutils import read_memories_from_hex + +# Files in devices folder not representing devices +NON_DEVICEFILES = ["__init__.py"] +DEVICE_FOLDER = os.path.dirname(os.path.abspath(__file__)) + "//deviceinfo//devices" + +# This class is a collection of parameters so no need for any methods +#pylint: disable=too-few-public-methods +class SessionConfig(object): + """ + Collection of all parameters needed when configuring a programming session + + Used as input parameter for the start_session function + """ + device = None + interface = None + # For some interfaces this is baud in bits per second and for other interfaces this is clock frequency in Hz + interface_speed = None + # Path to python devicesupportscripts for PIC devices + packpath = None + + # Content and format of special_options will depend on the device stack implementation. + # Normally these options are not in use. + special_options = None + + def __init__(self, device): + """ + device name is mandatory + """ + self.device = device + +# To achieve a single entry point for users of the backend part of pymcuprog it is accepted to exceed the maximum +# number of methods. +#pylint: disable=too-many-public-methods +class Backend(object): + """ + Backend interface of the pymcuprog utility. + This class provides access to all the functionality provided by pymcuprog + """ + API_VERSION = '2.0' + + def __init__(self): + # Hook onto logger + self.logger = getLogger(__name__) + self.transport = None + self.connected_to_tool = False + self.session_active = False + self.programmer = None + self.device_memory_info = None + self.housekeeper = None + + def get_api_version(self): + """ + Returns the current pymcuprog API version + """ + return self.API_VERSION + + @staticmethod + def get_supported_devices(): + """ + Return a list of devices supported by pymcuprog. + + This will be the list of devices with a corresponding device file + + :returns: List of device names + :rtype: list + """ + devices = [] + for filename in os.listdir(DEVICE_FOLDER): + if filename not in NON_DEVICEFILES and filename.endswith('.py'): + devices.append(filename.split('.py')[0]) + + return devices + + @staticmethod + def get_available_hid_tools(serialnumber_substring='', tool_name=None): + """ + Return a list of Microchip USB HID tools (debuggers) connected to the host + + :param serialnumber_substring: Can be an empty string or a subset of a serial number. Not case sensitive + This function will do matching of the last part of the devices serial numbers to + the serialnumber_substring. Examples: + '123' will match "MCHP3252000000043123" but not "MCP32520001230000000" + '' will match any serial number + :type serialnumber_substring: str + :param tool_name: Tool type to connect to. If None any tool matching the serialnumber_substring + will be returned + :type tool_name: str + :returns: List of pyedbglib.hidtransport.hidtransportbase.HidTool objects + """ + # Just use a temporary transport as the request is only to report connected Microchip HID tools, + # not to connect to any of them + transport = hid_transport() + + return transport.get_matching_tools(serialnumber_substring, tool_name) + + def connect_to_tool(self, toolconnection): + """ + Connect to a tool + + The tool can either be a USB HID tool or a serial port. + + :param toolconnection: This is an instance of one of the ToolConnection sub-classes. This object wraps + parameters needed to identify which tool to connect to like tool name and USB serial or serial port + name (e.g. 'COM1'). + + For USB HID tools there are some special handling: + + * If both tool name and usb_serial are None any tool will be picked. + * If usb_serial is None any tool matching the tool name will be picked + * If tool name is None any tool matching the usb_serial will be picked + * If more than one tool is connected that matches the tool name and usb_serial parameters a + PymcuprogToolConnectionError exception will be raised. + :type toolconnection: object + :raises PymcuprogToolConnectionError: if more than one matching tool is found or if no matching tool is found + :raises PymcuprogToolConfigurationError: if the toolconnection configuration is incorrect + """ + if isinstance(toolconnection, ToolSerialConnection): + # For serial port connection no connection action is needed, just need to store the + # Serial port number to be used (e.g. 'COM1') + self.transport = toolconnection.serialport + elif isinstance(toolconnection, ToolUsbHidConnection): + self.transport = hid_transport() + connect_status = False + try: + connect_status = self.transport.connect(serial_number=toolconnection.serialnumber, + product=toolconnection.tool_name) + except IOError as error: + raise PymcuprogToolConnectionError("Unable to connect to USB device ({})".format(error)) + + if not connect_status: + raise PymcuprogToolConnectionError("Unable to connect to USB device") + + self.housekeeper = housekeepingprotocol.Jtagice3HousekeepingProtocol(self.transport) + self.housekeeper.start_session() + + else: + raise PymcuprogToolConfigurationError("Unknown toolconnection argument type: {})". + format(type(toolconnection))) + + self.connected_to_tool = True + + def disconnect_from_tool(self): + """ + Disconnect the connected tool + + If no tool is connected nothing is done (i.e. no exception raised when not connected) + """ + if self._is_connected_to_hid_tool(): + self.housekeeper.end_session() + self.transport.disconnect() + + self.connected_to_tool = False + + def read_tool_info(self): + """ + Interrogates tool (debugger) for useful info + + :returns: Dictionary with various info about the connected debugger + :rtype: dict + + :raises PymcuprogToolConnectionError: if not connected to any USB HID tool (connect_to_tool not run) + """ + self._is_hid_tool_not_connected_raise() + + return read_tool_info(self.housekeeper) + + def read_kit_device(self): + """ + Read out the device name from kit configuration. + + If the connected tool does not have any kit configuration + (i.e. the tool is not an onboard debugger) None will be returned. + connect_to_tool must have been called before calling read_kit_device, but start_session is not necessary. + Typically read_kit_device is used to get the device name required to configure a session before calling + start_session. + + :returns: Name of target device as given by the kit, None if the tool does not have any device configured. + :rtype: str + + :raises PymcuprogToolConnectionError: if not connected to any USB HID tool (connect_to_tool not run) + """ + self._is_hid_tool_not_connected_raise() + + dap_info = read_tool_info(self.housekeeper) + + device_name = dap_info['device_name'].lower() + + if device_name == '': + device_name = None + + return device_name + + def read_target_voltage(self): + """ + Read target voltage + + :returns: Measured target voltage + :rtype: float + + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + :raises PymcuprogNotSupportedError: if the tool does not have supply capabilities + """ + self._is_hid_tool_not_connected_raise() + + try: + voltage = read_target_voltage(self.housekeeper) + except Jtagice3ResponseError: + raise PymcuprogNotSupportedError("Connected debugger/board does not have target voltage read capability") + + return voltage + + def read_supply_voltage_setpoint(self): + """ + Read tool power supply voltage setpoint + + :returns: Tool power supply voltage setpoint + :rtype: float + + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + :raises PymcuprogNotSupportedError: if the tool does not have supply capabilities + """ + self._is_hid_tool_not_connected_raise() + + try: + voltage = read_supply_voltage_setpoint(self.housekeeper) + except Jtagice3ResponseError: + raise PymcuprogNotSupportedError("Connected debugger/board does not have supply voltage capability.") + + return voltage + + def read_usb_voltage(self): + """ + Read USB voltage + + :returns: Measured USB voltage + :rtype: float + + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + :raises PymcuprogNotSupportedError: if the tool can't measure USB voltage + """ + self._is_hid_tool_not_connected_raise() + + try: + voltage = read_usb_voltage(self.housekeeper) + except Jtagice3ResponseError: + raise PymcuprogNotSupportedError("Connected debugger/board does not have USB voltage read capability.") + + return voltage + + def set_supply_voltage_setpoint(self, setpoint): + """ + Set tool power supply voltage setpoint + + :param setpoint: Power supply setpoint + :type setpoint: float or int + + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + :raises PymcuprogNotSupportedError: if the tool does not have supply capabilities + :raises ValueError: if the setpoint is out of range + """ + self._is_hid_tool_not_connected_raise() + + set_supply_voltage_setpoint(self.housekeeper, setpoint) + + + def reboot_tool(self): + """ + Trigger a reboot of the tool (debugger) + + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + """ + self._is_hid_tool_not_connected_raise() + + self.housekeeper.end_session(reset_tool=True) + + # A tool reboot will automatically disconnect the tool. Calling self.disconnect_from_tool + # would just fail as it would try to talk to a tool while it is rebooting + self.connected_to_tool = False + + @staticmethod + def get_device_info(device): + """ + Get info about a device + + :param device: Name of the device + :type device: str + :returns: Dictionary with device info as defined in the device files in pymcuprog.deviceinfo.devices + :rtype: dict + + :raises PymcuprogNotSupportedError: if device is not supported + """ + try: + info = deviceinfo.getdeviceinfo(device) + except ModuleNotFoundError: + raise PymcuprogNotSupportedError("No device info for device: {}".format(device)) + + return info + + def start_session(self, sessionconfig, user_interaction_callback=None): + """ + Start a programming session. + + This function will build the device model stack and initialize the tool for a + programming session. If a session is already started calling start_session will do an end_session and start + a new session from scratch. + + Note connect_to_tool must have been called before start_session is called. If not an exception will be thrown. + + :param sessionconfig: SessionConfig object wrapping the parameters configuring the session + :type sessionconfig: object + :param user_interaction_callback: Callback to be called when user interaction is required, + for example when doing UPDI high-voltage activation with user target power toggle. + This function could ask the user to toggle power and halt execution waiting for the user + to respond (this is default behavior if the callback is None), or if the user is another + script it could toggle power automatically and then return. + :type user_interaction_callback: function + + :raises PymcuprogSessionConfigError: if starting the session failed due to incorrectly configured session + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + :raises PymcuprogDeviceLockedError: if unable to start the session due to the device being locked + :raises PymcuprogNotSupportedError: if configured device is not supported + """ + self._is_tool_not_connected_raise() + + # Check that all session configuration parameters required are in place + if sessionconfig.device is None or sessionconfig.device == '': + raise PymcuprogSessionConfigError("Device must be specified") + + if self.session_active: + # A session is already active so it must be ended before starting a new session + self.end_session() + + # Setup the programmer + self.programmer = Programmer(self.transport) + + if sessionconfig.special_options is not None: + self.programmer.set_options(sessionconfig.special_options) + + # Try to build the stack for this device + self.programmer.load_device(sessionconfig.device) + + self.programmer.setup_device( + sessionconfig.interface, + sessionconfig.packpath, + sessionconfig.interface_speed) + + # Make contact + self.programmer.start(user_interaction_callback=user_interaction_callback) + + # Get device memory info + self.device_memory_info = self.programmer.get_device_memory_info() + + self.session_active = True + + def end_session(self): + """ + End a programming session + + This will take down the device model stack and stop the programming session on the tool. However the tool will + not be disconnected and it will be possible to do another start_session without another connect_to_tool call. + If no session has been started this function will do nothing (i.e. it won't fail even if a session has + not been started) + """ + if self.session_active: + # Lower the flag first to ensure it is updated as the rest of this function might fail with an exception + # for example if UPDI were disabled during the session + self.session_active = False + self.programmer.stop() + + def read_device_id(self): + """ + Read out the device id + + :returns: Byte array with device ID as raw byte values. Number of bytes will depend upon target type + :rtype: bytearray + + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + :raises PymcuprogSessionError: if a session has not been started (session_start not run) + """ + self._is_tool_not_connected_raise() + self._is_session_not_active_raise() + + return self.programmer.read_device_id() + + + def erase(self, memory_name=MemoryNameAliases.ALL, address=None): + """ + Erase target device memory + + If a single memory is specified it will only be erased if it won't affect other memories + + :param memory_name: Name of memory to erase. To unlock a device use the MemoryNameAliases.ALL + MemoryNameAliases.ALL run the widest erase: + + * For PIC the widest bulk erase will be run. + * For AVR a chip erase will be run + * The following memories will not be erased: + + * AVR fuses + * EEPROM if EESAVE fuse is set for AVR + * EEPROM if the target device does not support EEPROM erase + * EEPROM if Data Code Protection (CPD_n) is not enabled for PIC + * PIC ICD memory (special memory used for Debug Executives) + :type memory_name: object (MemoryNameAliases) + :param address: Optional address for erase command. If address is None the complete memory + segment will be erased. Note that the address parameter will just propagate through the stack down to the + device dependent implementation (devicesupportscripts for PIC and firmware for AVR). Normal use is to + leave the address as None. + :type address: int + + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + :raises PymcuprogSessionError: if a session has not been started (session_start not run) + :raises ValueError: if the specified memory is not defined for the target device + :raises PymcuprogEraseError: if the memory can't be erased or if the memory can't be erased without affecting + other memories + """ + self._is_tool_not_connected_raise() + self._is_session_not_active_raise() + + if memory_name is not None and memory_name != MemoryNameAliases.ALL: + if not self.is_isolated_erase_possible(memory_name): + message = "{} memory can't be erased or can't be erased without side effect".format(memory_name) + raise PymcuprogEraseError(message) + + self.programmer.erase(memory_name, address) + + def is_isolated_erase_possible(self, memory_name): + """ + Can the memory be erased without affecting other memories? + + :param memory_name: Name of memory + :type memory_name: str + :returns: True only if the memory can be erased without side effects, False if memory can't be erased at all or + if erasing it will erase other memories too. + :rtype: boolean + + :raises ValueError: if memory is not defined for the configured device + """ + # The device model must have been loaded upfront + self._is_session_not_active_raise() + + meminfo = self.device_memory_info.memory_info_by_name(memory_name) + isolated_erase_key = DeviceMemoryInfoKeys.ISOLATED_ERASE + if isolated_erase_key in meminfo: + return meminfo[isolated_erase_key] is True + + self.logger.error('%s flag not found for %s memory', isolated_erase_key, memory_name) + return False + + def get_chiperase_effect(self, memory_name): + """ + Get the effect of a chip erase (widest bulk erase) on the given memory + + :param memory_name: Name of memory + :type memory_name: str + :returns: One of the values defined by deviceinfo.eraseflags.ChiperaseEffect depending upon the settings in the + device model for the configured device. If the chiperase_effect flag is missing in the device model + ChiperaseEffect.NOT_ERASED will be returned. + :rtype: object (ChiperaseEffect) + + :raises ValueError: if memory is not defined for the configured device + """ + # The device model must have been loaded upfront + self._is_session_not_active_raise() + + meminfo = self.device_memory_info.memory_info_by_name(memory_name) + chiperase_effect_key = DeviceMemoryInfoKeys.CHIPERASE_EFFECT + if chiperase_effect_key in meminfo: + return meminfo[chiperase_effect_key] + + self.logger.error('%s flag not found for %s memory', chiperase_effect_key, memory_name) + return ChiperaseEffect.NOT_ERASED + + def read_memory(self, memory_name=MemoryNameAliases.ALL, offset_byte=0, numbytes=0): + """ + Read target device memory + + :param memory_name: Name of memory as defined in memorynames.py. MemoryNameAliases.ALL reads all memories + defined in the device model (numbytes and offset_byte will be ignored). + :type memory_name: object (MemoryNameAliases) + :param offset_byte: Byte offset within memory to start reading at. + :type offset_byte: int + :param numbytes: Number of bytes to read. 0 means read all memory locations from offset_byte and until end + of memory + :type numbytes: int + :returns: List of namedtuples with two fields: data and memory_info. data contains a byte array of + raw data bytes and memory_info is a dictionary with memory information (as defined in + deviceinfo.deviceinfo.DeviceMemoryInfo). Normally the list will contain one item, but when + memory_name parameter is MemoryNameAliases.ALL there will be one namedtuple item per memory + type read. + :rtype: list of namedtuple + + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + :raises PymcuprogSessionError: if a session has not been started (session_start not run) + :raises ValueError: if trying to read outside the specified memory + :raises ValueError: if the specified memory is not defined for the target device + """ + self._is_tool_not_connected_raise() + self._is_session_not_active_raise() + + return self.programmer.read_memory(memory_name=memory_name, offset=offset_byte, numbytes=numbytes) + + def write_memory(self, data, memory_name=MemoryNames.FLASH, offset_byte=0): + """ + Write target device memory + + :param memory_name: Name of memory as defined in memorynames.py + :type memory_name: str + :param offset_byte: Byte offset within memory to start writing to. + :type offset_byte: int + :param data: Raw data bytes to write + :type data: bytearray + + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + :raises PymcuprogSessionError: if a session has not been started (session_start not run) + :raises ValueError: if trying to write outside the specified memory + :raises ValueError: if the specified memory is not defined for the target device + """ + self._is_tool_not_connected_raise() + self._is_session_not_active_raise() + + self.programmer.write_memory(data=data, memory_name=memory_name, offset=offset_byte) + + def verify_memory(self, data, memory_name=MemoryNames.FLASH, offset_byte=0): + """ + Verify target device memory + + :param memory_name: Name of memory as defined in DeviceMemoryInfo (deviceinfo.py) + :type memory_name: str + :param offset_byte: Byte offset within memory to start verifying at. + :type offset_byte: int + :param data: Raw data bytes to verify against + :type data: bytearray + :return: True if contents match + :rtype: boolean + + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + :raises PymcuprogSessionError: if a session has not been started (session_start not run) + :raises ValueError: if trying to verify outside the specified memory + :raises ValueError: if the specified memory is not defined for the target device + """ + self._is_tool_not_connected_raise() + self._is_session_not_active_raise() + + return self.programmer.verify_memory(data=data, memory_name=memory_name, offset=offset_byte) + + def hold_in_reset(self): + """ + Hold target device in reset + + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + :raises PymcuprogSessionError: if a session has not been started (session_start not run) + """ + self._is_tool_not_connected_raise() + self._is_session_not_active_raise() + + self.programmer.hold_in_reset() + + def release_from_reset(self): + """ + Release target device from reset + + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + :raises PymcuprogSessionError: if a session has not been started (session_start not run) + """ + self._is_tool_not_connected_raise() + self._is_session_not_active_raise() + + self.programmer.release_from_reset() + + # Releasing the target from reset will take it out of programming mode. In other words the session + # is partly taken down. To keep housekeeping right and to take down the stack properly end_session + # must be called + self.end_session() + + def write_hex_to_target(self, hexfile): + """ + Write hexfile to target device + + Note no erase will be run (i.e. memory is assumed to already be erased) + + :param hexfile: Name of file to write + :type hexfile: str + """ + self._is_tool_not_connected_raise() + self._is_session_not_active_raise() + + hex_memories = read_memories_from_hex(os.path.abspath(hexfile), self.device_memory_info) + for segment in hex_memories: + memory_name = segment.memory_info[DeviceInfoKeys.NAME] + self.logger.debug("Writing %s...", memory_name) + self.write_memory(segment.data, memory_name, segment.offset) + + def verify_hex(self, hexfile): + """ + Verify target memory content against hexfile + + :param hexfile: Name of file to verify against + :type hexfile: str + :return: True if contents match + :rtype: boolean + + :raises PymcuprogToolConnectionError: if not connected to any tool (connect_to_tool not run) + :raises PymcuprogSessionError: if a session has not been started (session_start not run) + """ + self._is_tool_not_connected_raise() + self._is_session_not_active_raise() + + hex_memories = read_memories_from_hex(os.path.abspath(hexfile), self.device_memory_info) + verify_ok = True + for segment in hex_memories: + memory_name = segment.memory_info[DeviceInfoKeys.NAME] + self.logger.debug("Verifying %s...", memory_name) + segment_ok = self.verify_memory(segment.data, memory_name, segment.offset) + if segment_ok: + self.logger.debug("OK!") + else: + verify_ok = False + + return verify_ok + + def _is_tool_not_connected_raise(self): + """ + Check if any tool is connected and if not raise an exception + + :raises PymcuprogToolConnectionError: if not connected to any tool + """ + if not self._is_connected_to_hid_tool() and not self._is_connected_to_serialport(): + raise PymcuprogToolConnectionError("Not connected to any tool") + + def _is_hid_tool_not_connected_raise(self): + """ + Check if a USB HID tool is connected and if not raise an exception + + :raises PymcuprogToolConnectionError: if not connected to any tool + """ + if not self._is_connected_to_hid_tool(): + raise PymcuprogToolConnectionError("Not connected to any USB HID debugger") + + def _is_connected_to_hid_tool(self): + """ + Check if a connection to a USB HID tool is active + + :returns: True if HID connection is active + :rtype: boolean + """ + return self.connected_to_tool and isinstance(self.transport, HidTransportBase) + + def _is_connected_to_serialport(self): + """ + Check if a connection to a Serial port is active + + :returns: True if serial connection is active + :rtype: boolean + """ + # For Serial port communication transport is only set to a string with the name of the serial port + # to use (e.g. 'COM1'). + return self.connected_to_tool and isinstance(self.transport, str) + + def _is_session_not_active_raise(self): + """ + Check if a programming session is active and if not raise an exception + + :raises PymcuprogSessionError: if programming session not active + """ + if not self.session_active: + raise PymcuprogSessionError("No programming session active") diff --git a/pymcuprog/deviceinfo/__init__.py b/pymcuprog/deviceinfo/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pymcuprog/deviceinfo/configgenerator.py b/pymcuprog/deviceinfo/configgenerator.py new file mode 100644 index 0000000..5cd77a6 --- /dev/null +++ b/pymcuprog/deviceinfo/configgenerator.py @@ -0,0 +1,283 @@ +""" +Utilities that uses the pymcuprog stack to build device config blobs for PIC devices + +This is the backend part of the generateconfig utility +""" +import sys +import os +import xml.etree.ElementTree as ETree +import inspect +from xml.dom import minidom +from datetime import datetime +from logging import getLogger + +from pymcuprog.programmer import Programmer +from pymcuprog.pymcuprog_errors import PymcuprogError +from pymcuprog.deviceinfo.memorynames import MemoryNames +from pymcuprog.deviceinfo.deviceinfokeys import DeviceMemoryInfoKeys + + +class ConfigGenerator(object): + """ + This class provides functions to build device config blobs for PIC devices + """ + def __init__(self): + self.pic = None + self.tool = None + self.device_memory_info = None + self.source = None + + def load_device_model(self, device_name, packpath): + """ + Load the device model + + :param device_name: device name + :param packpath: path to pack + """ + + # Locate and load the model from the pack + + if packpath is None: + raise PymcuprogError("No path to pack repo provided!") + + # Import from pack. + sys.path.append(os.path.normpath(packpath)) + sys.path.append(os.path.normpath(packpath + "//common")) + + # Use tool-type indicating a Config generator + try: + from debugprovider import ConfigGeneratorTool #pylint: disable=import-error, import-outside-toplevel + except ModuleNotFoundError: + raise PymcuprogError("Unable to import debugprovider from '{}'! Is this path correct?".format(packpath)) + + self.tool = ConfigGeneratorTool() + + # Load the programming model + programmer = Programmer(self.tool) + # Load the device + programmer.load_device(device_name) + # Setup the device + programmer.setup_device(interface=None, packpath=packpath) + # Fetch the memory model + self.device_memory_info = programmer.get_device_memory_info() + # Extract the pic from level n of the stack + self.pic = programmer.device_model.pic + # Extract the source from level n+1 of the same stack + self.source = inspect.getsource(programmer.device_model.pic.device_model) + + # Check to see if this device model can be abstracted: + # Some devices are so antiquated that they only have support for incrementing the PC and reseting to 0 + # These devices currently abstract in Python in such a way that drag and drop will not work with these scripts. + if 'RESET_ADDRESS' in dir(self.pic.device_model): + raise PymcuprogError("This device model does not support drag and drop by primitives") + + # This function do access some private methods, but it should be acceptable as this utility is using pymcuprog in a + # non-common way and is not a part of the pymcuprog CLI functionality. + #pylint: disable=protected-access + def process_programming_functions(self): + """ + Crunch through all programming functions and accumulate results + """ + if not self.pic: + raise PymcuprogError("Device model not loaded!") + + # Enter TMOD + self.pic.enter_tmod() + # Read ID + self.pic.read_id() + # Exit TMOD + self.pic.exit_tmod() + # Erase + self.pic.erase() + + # Parameterised + + # This import must be late as it depends on the common folder in the packpath which is not available until + # after load_device_model has been called + from primitiveutils import ParametricValueToken #pylint: disable=import-error, import-outside-toplevel + # Flash + if MemoryNames.FLASH in self.device_memory_info.mem_by_name: + flash_info = self.device_memory_info.memory_info_by_name(MemoryNames.FLASH) + flash_page_size = flash_info[DeviceMemoryInfoKeys.PAGE_SIZE] + self.pic._write_flash_page(byte_address=ParametricValueToken(ParametricValueToken.TOKEN_ADDRESS_LE32), + data=bytearray(flash_page_size)) + # Config + if MemoryNames.CONFIG_WORD in self.device_memory_info.mem_by_name: + config_words_info = self.device_memory_info.memory_info_by_name(MemoryNames.CONFIG_WORD) + config_words_write_size = config_words_info[DeviceMemoryInfoKeys.WRITE_SIZE] + self.pic._write_config_word(byte_address=ParametricValueToken(ParametricValueToken.TOKEN_ADDRESS_LE32), + data=bytearray(config_words_write_size)) + # EEPROM + if MemoryNames.EEPROM in self.device_memory_info.mem_by_name: + + eeprom_info = self.device_memory_info.memory_info_by_name(MemoryNames.EEPROM) + eeprom_write_size = eeprom_info[DeviceMemoryInfoKeys.WRITE_SIZE] + # Multiply EEPROM write size by 2, since config has to always deal with phantom bytes which are in the hex files + self.pic._write_eeprom_block(byte_address=ParametricValueToken(ParametricValueToken.TOKEN_ADDRESS_LE32), + data=bytearray(eeprom_write_size * 2)) + + # User IDs + if MemoryNames.USER_ID in self.device_memory_info.mem_by_name: + user_id_info = self.device_memory_info.memory_info_by_name(MemoryNames.USER_ID) + user_id_write_size = user_id_info[DeviceMemoryInfoKeys.WRITE_SIZE] + self.pic._write_user_id_word(byte_address=ParametricValueToken(ParametricValueToken.TOKEN_ADDRESS_LE32), + data=bytearray(user_id_write_size)) + + def get_xml_element(self): + """ + Creates an xml element from the generator output + + :return: xml element + """ + xml_blob = ETree.Element("blob") + # Add source as comment + comment = ETree.Comment("Source code used to generate this blob:\n{}".format(self.source)) + xml_blob.append(comment) + + # Add LIST token: LIST + token = ETree.Element("token") + token.text = "LIST" + xml_blob.append(token) + + # Collect contents from tool and concatenate elements + contents = self.tool.get_contents() + for entry in contents: + # Append elements + xml_blob.append(contents[entry]) + return xml_blob + + def _add_register(self, name, value): + """ + Shortcut to add a register into the XML + + :param name: + :param value: + """ + reg = ETree.Element("register") + reg.attrib["name"] = name + reg.attrib["value"] = value + return reg + + def _add_data(self, name, value): + """ + Shortcut to add a data type into the XML + + :param name: + :param value: value + """ + data = ETree.Element("data") + data.attrib["type"] = name + data.text = value + return data + + def populate_device_config_template(self): + """ + Template for header of device config + """ + # Construct the root + deviceconf = ETree.Element("deviceconf") + deviceconf.attrib["name"] = self.pic.device_name.upper() + comment = ETree.Comment("device config for {} generated {}" + .format(self.pic.device_name, datetime.now().strftime("%Y.%m.%d, %H:%M:%S"))) + deviceconf.append(comment) + + # Construct basic version info: + # These values are hardcoded, and thus need to be updated when the spec updates :/ + # It would be nice to fetch them from somewhere... + deviceconf.append(self._add_register("DEVICE_CONFIG_MAJOR", "1")) + deviceconf.append(self._add_register("DEVICE_CONFIG_MINOR", "8")) + deviceconf.append(self._add_register("DEVICE_CONFIG_BUILD", "60")) + # Not used yet: + deviceconf.append(self._add_register("CONTENT_LENGTH", "0")) + deviceconf.append(self._add_register("CONTENT_CHECKSUM", "0")) + # Default to start at 0 + deviceconf.append(self._add_register("INSTANCE", "0")) + # PIC primitive sequences are always interface type 4 + deviceconf.append(self._add_register("INTERFACE_TYPE", "0x04")) + # Variant: PIC16 is 0, PIC18 is 1 + if "PIC16" in self.pic.device_name.upper(): + deviceconf.append(self._add_register("DEVICE_VARIANT", "0x00")) + elif "PIC18" in self.pic.device_name.upper(): + deviceconf.append(self._add_register("DEVICE_VARIANT", "0x01")) + else: + raise Exception("Unknown device variant - are you sure this is a PIC?") + + return deviceconf + + def add_device_data_template(self): + """ + Injects device data template + This must be populated by hand with valid data + """ + logger = getLogger(__name__) + # Create new entry + entry = ETree.Element("entry") + + # Add type + d_type = ETree.Element("type") + d_type.text = "D_ICSP" + entry.append(d_type) + + # Fetch info providers for retrieving device info + flash_info = self.device_memory_info.memory_info_by_name(MemoryNames.FLASH) + if MemoryNames.EEPROM in self.device_memory_info.mem_by_name: + eeprom_info = self.device_memory_info.memory_info_by_name(MemoryNames.EEPROM) + eeprom_base_w = eeprom_info['address']//2 + eeprom_size_b = eeprom_info['size'] + # Multiply EEPROM write size by 2, since config has to always deal with phantom bytes which are in the hex files + eeprom_write_block_b = eeprom_info['write_size'] * 2 + else: + # Some devices do not have EEPROM memory, but it is good to warn the user if it is missing in case it + # was just forgotten when making the device model file + logger.warning("No EEPROM memory has been specified for this device, does the device have any EEPROM memory?") + eeprom_size_b = 0 + # These values must always be present, so just add some dummy values + eeprom_base_w = 0 + eeprom_write_block_b = 0 + user_id_info = self.device_memory_info.memory_info_by_name(MemoryNames.USER_ID) + config_word_info = self.device_memory_info.memory_info_by_name(MemoryNames.CONFIG_WORD) + device_info = self.device_memory_info.device + + # Add fields + entry.append(self._add_data("PIC_FLASH_BASE_W", "0x{0:08X}".format(flash_info['address']//2))) + entry.append(self._add_data("PIC_EEPROM_BASE_W", "0x{0:08X}".format(eeprom_base_w))) + entry.append(self._add_data("PIC_USER_ID_BASE_W", "0x{0:08X}".format(user_id_info['address']//2))) + entry.append(self._add_data("PIC_CONFIG_BASE_W", "0x{0:08X}".format(config_word_info['address']//2))) + entry.append(self._add_data("PIC_FLASH_SIZE_W", "0x{0:08X}".format(flash_info['size']//2))) + entry.append(self._add_data("PIC_EEPROM_SIZE_B", "0x{0:04X}".format(eeprom_size_b))) + entry.append(self._add_data("PIC_USER_ID_SIZE_W", "{}".format(user_id_info['size']//2))) + entry.append(self._add_data("PIC_CONFIG_SIZE_W", "{}".format(config_word_info['size']//2))) + entry.append(self._add_data("PIC_FLASH_WRITE_BLOCK_B", "{}".format(flash_info['page_size']))) + entry.append(self._add_data("PIC_EEPROM_WRITE_BLOCK_B", "{}".format(eeprom_write_block_b))) + entry.append(self._add_data("PIC_USER_ID_WRITE_BLOCK_B", "{}".format(user_id_info['write_size']))) + entry.append(self._add_data("PIC_CONFIG_WRITE_BLOCK_B", "{}".format(config_word_info['write_size']))) + entry.append(self._add_data("PIC_DEVICE_ID", "0x{0:04X}".format(device_info['device_id']))) + return entry + + def get_xml_string(self): + """ + Convert to string and returns + + :return: string representation of resultant xml + """ + # Generate header etc + deviceconfig = self.populate_device_config_template() + + # Add the blob-holder node + blob = self._add_register("BLOB", "") + + # Fetch the generated primitive blob + scripted_content = self.get_xml_element() + + # Append device info + scripted_content.append(self.add_device_data_template()) + + # Put the result into the blob-holder + blob.append(scripted_content) + + # Add the blob-holder to the main node + deviceconfig.append(blob) + + # Make it look nice and return + return minidom.parseString(ETree.tostring(deviceconfig, + encoding='unicode')).toprettyxml(indent=" ") diff --git a/pymcuprog/deviceinfo/deviceinfo.py b/pymcuprog/deviceinfo/deviceinfo.py new file mode 100644 index 0000000..35459ad --- /dev/null +++ b/pymcuprog/deviceinfo/deviceinfo.py @@ -0,0 +1,296 @@ +""" +deviceinfo.py +A simple Device Information service + +Device information is stored in files named .py in the devices sub-folder +Each device file contains a dict of values +These device files are [ideally] generated from DFP information by [running generate_device_info.py | hand] +""" +# Python 3 compatibility for Python 2 +from __future__ import print_function + +import os +import importlib + +from logging import getLogger + +from pymcuprog.pymcuprog_errors import PymcuprogError +from .memorynames import MemoryNames +from .deviceinfokeys import DeviceMemoryInfoKeys, DeviceInfoKeys, DeviceInfoKeysPic + +def getdeviceinfo(devicename): + """ + Looks up device info for a given part + + :param devicename: device to look up + :return: device information dict + """ + logger = getLogger(__name__) + logger.info("Looking for device %s", devicename) + + devicename = devicename.lower() + + try: + device_module = importlib.import_module("deviceinfo.devices.{}".format(devicename)) + except ImportError: + try: + # When pymcuprog is used as a package in other scripts + # the deviceinfo module is part of the pymcuprog package + device_module = importlib.import_module("pymcuprog.deviceinfo.devices.{}".format(devicename)) + except ImportError: + device_module = importlib.import_module("{}".format(devicename)) + + device_info = getattr(device_module, "DEVICE_INFO") + + # For PIC devices there will be a default_bulk_erase_address outside any memory information + # This address needs to be converted to byte address + default_bulk_erase_address_byte = None + for param in device_info: + if param.startswith(DeviceInfoKeysPic.DEFAULT_BULK_ERASE_ADDRESS): + # Check if it's word or byte oriented data + mul = DeviceMemoryInfo.bytes_or_words(param) + if mul is not None: + default_bulk_erase_address_byte = int(device_info[param] * mul) + else: + default_bulk_erase_address_byte = device_info[param] + + if default_bulk_erase_address_byte is not None: + device_info[DeviceInfoKeysPic.DEFAULT_BULK_ERASE_ADDRESS] = default_bulk_erase_address_byte + + return device_info + +def get_supported_devices(): + """ + Return a list of all supported devices + + A device is supported if it has a device model file in the devices folder + + :return: list of devices + """ + root_folder = os.path.dirname(os.path.abspath(__file__)) + dir_list = os.listdir(root_folder + "//devices") + ignore_list = ['__init__.py'] + device_list = [] + for devicefile in dir_list: + if devicefile.endswith(".py") and devicefile not in ignore_list: + devicename = devicefile.split('.')[0] + device_list.append(devicename) + + return device_list + +class DeviceMemoryInfo: + """ + API to fetch information about device memory segments + """ + def __init__(self, device_info): + self.device = device_info + self.memtypes = MemoryNames.get_all() + + # hexfile_address is the start address for the memory segment in hex files. + # PIC and ARM devices usually does not need the parameter as all locations are mapped in a single address space. + # AVR8 devices does not map all memory types in a single address space. + # Memory types have defined offsets in hex files as defined below + self.avr8_hex_file_offsets = { + MemoryNames.FLASH: 0x000000, + MemoryNames.EEPROM: 0x810000, + MemoryNames.FUSES: 0x820000, + MemoryNames.LOCKBITS: 0x830000, + MemoryNames.SIGNATURES: 0x840000, + MemoryNames.USER_ROW: 0x850000 + } + + # erase_address is the address for the erase of the memory. + # Note that for PIC devices other memories might be erased in the same operation depending on the target, + # see the programming spec for the target device. + + # erase_address, hexfile_address, hexfile_size and verify mask are optional in the device models. + # erase_address will be set to the memory address if it's missing. + # Hex file address will be set to the memory address if it's missing, unless it's an AVR device where + # the hex file offset is used instead. + # Hex file size will be set to the memory size if it's missing except for EEPROM on PIC16 devices where + # the hex file will contain phantom bytes so the hex file will contain twice as many EEPROM bytes as + # the actual EEPROM in the device + # verify_mask is set based on architecture + self.paramtypes = [DeviceMemoryInfoKeys.ADDRESS, + DeviceMemoryInfoKeys.SIZE, + DeviceMemoryInfoKeys.PAGE_SIZE, + DeviceMemoryInfoKeys.WRITE_SIZE, + DeviceMemoryInfoKeys.READ_SIZE, + DeviceMemoryInfoKeys.VERIFY_MASK, + DeviceMemoryInfoKeys.ERASE_ADDRESS, + DeviceMemoryInfoKeys.HEXFILE_ADDRESS, + DeviceMemoryInfoKeys.HEXFILE_SIZE, + DeviceMemoryInfoKeys.CHIPERASE_EFFECT, + DeviceMemoryInfoKeys.ISOLATED_ERASE] + + self.mem_by_name = {} + + # Find information about memory segments + for param in self.device: + for mtype in self.memtypes: + # Does this line describe a memory location? + if param.startswith(mtype): + self._configure_memory_param(mtype, param) + + # erase_address and hexfile_address are optional and should default to the value of the address parameter + optional_params = [DeviceMemoryInfoKeys.VERIFY_MASK, + DeviceMemoryInfoKeys.HEXFILE_ADDRESS, + DeviceMemoryInfoKeys.ERASE_ADDRESS, + DeviceMemoryInfoKeys.HEXFILE_SIZE] + for optional_param in optional_params: + for memtype in self.mem_by_name: + if optional_param not in self.mem_by_name[memtype]: + # Set the verify mask based on architecture + if optional_param == DeviceMemoryInfoKeys.VERIFY_MASK: + verify_mask = self._get_verify_mask(self.device[DeviceInfoKeys.ARCHITECTURE], memtype) + self.mem_by_name[memtype][optional_param] = verify_mask + # Set the hexfile_address + elif optional_param == DeviceMemoryInfoKeys.HEXFILE_ADDRESS: + self._add_hexfile_address(memtype, optional_param) + # Set the hexfile_size + elif optional_param == DeviceMemoryInfoKeys.HEXFILE_SIZE: + self._add_hexfile_size(memtype, optional_param) + # Set the erase_address + elif optional_param == DeviceMemoryInfoKeys.ERASE_ADDRESS: + # By default the erase_address is the same as the address of the memory + address = self.mem_by_name[memtype][DeviceMemoryInfoKeys.ADDRESS] + self.mem_by_name[memtype][optional_param] = address + + def _configure_memory_param(self, memorytype, param): + # Check if it's word or byte oriented data + mul = self.bytes_or_words(param) + # Create a dict for the memory type if it does not exist + if not self.mem_by_name.get(memorytype): + self.mem_by_name[memorytype] = {DeviceMemoryInfoKeys.NAME: memorytype} + # Parse and store parameter + for ptype in self.paramtypes: + if param.startswith("{}_{}".format(memorytype, ptype)): + if mul is not None: + self.mem_by_name[memorytype][ptype] = int(self.device[param] * mul) + else: + self.mem_by_name[memorytype][ptype] = self.device[param] + + def _add_hexfile_address(self, memorytype, paramname): + # Inject hex file addresses for AVR memory areas + if self.device[DeviceInfoKeys.ARCHITECTURE].startswith('avr8'): + if memorytype in self.avr8_hex_file_offsets: + self.mem_by_name[memorytype][paramname] = self.avr8_hex_file_offsets[memorytype] + else: + # The hexfile_address for memory types that doesn't make sense in a hex file like SRAM + # and regular I/O space is defined to an address the other memory types will not reach + self.mem_by_name[memorytype][paramname] = 0xFFFFFF + # All other memory types are mapped 1 to 1 in the hex file + else: + self.mem_by_name[memorytype][paramname] = self.mem_by_name[memorytype][DeviceMemoryInfoKeys.ADDRESS] + + def _add_hexfile_size(self, memorytype, paramname): + if self.device[DeviceInfoKeys.ARCHITECTURE].startswith('PIC16') and memorytype == MemoryNames.EEPROM: + # For PIC16 devices there will be one phantom byte in the hex file for each EEPROM byte, so + # the size of EEPROM in a hex file will be twice the size of the actual EEPROM memory + self.mem_by_name[memorytype][paramname] = self.mem_by_name[memorytype][DeviceMemoryInfoKeys.SIZE] * 2 + else: + self.mem_by_name[memorytype][paramname] = self.mem_by_name[memorytype][DeviceMemoryInfoKeys.SIZE] + + @staticmethod + def _get_verify_mask(architecture, memtype): + # byte oriented memory + mask = [0xFF] + + # PIC16 is word addressed and has 14-bit flash, except EEPROM which is byte oriented + if architecture == 'PIC16' and memtype not in [MemoryNames.EEPROM]: + mask = [0xFF, 0x3F] + + # PIC18 is word addressed and has 16-bit flash, except EEPROM which is byte oriented + elif architecture == 'PIC18' and memtype not in [MemoryNames.EEPROM]: + mask = [0xFF, 0xFF] + + # PIC24 is word addressed and has 24-bit flash, except EEPROM which is word oriented + elif architecture == 'PIC24': + if memtype in [MemoryNames.EEPROM]: + mask = [0xFF, 0xFF] + else: + mask = [0xFF, 0xFF, 0xFF, 0x00] + + return mask + + @staticmethod + def bytes_or_words(address_param): + """ + Return multiplier for address parameter + + The returned multiplier can be used to convert the address parameter to byte address + + :param address_param: Address parameter (used as key in device info dict) + :return: Multiplier to convert the address to byte address + """ + if address_param.endswith("_byte") or address_param.endswith("_bytes"): + mul = 1 + elif address_param.endswith("_word") or address_param.endswith("_words"): + mul = 2 + else: + mul = None + return mul + + def memory_info_by_address_range(self, + start, + stop, + address_type=DeviceMemoryInfoKeys.ADDRESS, + size_type=DeviceMemoryInfoKeys.SIZE): + """ + Returns a list of all memories applicable for the address range(start, stop) + + :param start: Start address (byte) + :param stop: End address (byte) + :param address_type: Selects between normal addresses and addresses used in hex files + (address vs hexfile_address) + :param size_type: Selects between normal size and size used in hexfiles (size vs hexfile_size) + """ + # We do not support negative memory ranges + if start > stop: + raise PymcuprogError("Cannot parse reverse memory range {} to {}".format(start, stop)) + + memtypes = [] + + # Loop through all known memory types for this device + for memtype in self.mem_by_name: + address = self.mem_by_name[memtype][address_type] + size = self.mem_by_name[memtype][size_type] + + # Check if any of the addresses between start and stop is within the memory type range + if start < address+size and stop > address: + memtypes.append(self.mem_by_name[memtype]) + return memtypes + + def memory_info_by_address(self, + byte_address, + address_type=DeviceMemoryInfoKeys.ADDRESS, + size_type=DeviceMemoryInfoKeys.SIZE): + """ + Returns information about the memory type for a given byte address + + :param byte_address: Memory address to check + :param address_type: Selects between normal addresses and addresses used in hex files + (ADDRESS vs HEXFILE_ADDRESS) + :param size_type: Selects between normal size and size used in hexfiles (size vs hexfile_size) + """ + memtype = None + for memory in self.mem_by_name: + if self.mem_by_name[memory][address_type] <= byte_address < \ + self.mem_by_name[memory][address_type] + self.mem_by_name[memory][size_type]: + if memtype is not None: + raise PymcuprogError("Duplicate memory area found for byte address '{}'".format(byte_address)) + memtype = self.mem_by_name[memory] + return memtype + + def memory_info_by_name(self, name): + """ + Returns information about the requested memory + """ + memory = self.mem_by_name.get(name) + if not memory: + message = "Memory type '{}' not defined for device '{}'".format(name, self.device[DeviceInfoKeys.NAME]) + logger = getLogger(__name__) + logger.error(message) + logger.error("Memory types defined: %s", ", ".join(self.mem_by_name.keys())) + raise ValueError(message) + return memory diff --git a/pymcuprog/deviceinfo/deviceinfokeys.py b/pymcuprog/deviceinfo/deviceinfokeys.py new file mode 100644 index 0000000..be1128a --- /dev/null +++ b/pymcuprog/deviceinfo/deviceinfokeys.py @@ -0,0 +1,90 @@ +#pylint: disable=too-few-public-methods +""" +Definitions of keys for device info dictionaries +""" + +class DeviceInfoKeys(object): + """ + Base class with common device info keys + """ + + NAME = 'name' + ARCHITECTURE = 'architecture' + INTERFACE = 'interface' + DEVICE_ID = 'device_id' + + @classmethod + def get_all(cls): + """ + Get a list of all keys + + :return List of all valid keys (baseclass and any subclass keys if run on a subclass) + """ + all_keys = [] + for attribute in dir(cls): + if not attribute.startswith('__') and not callable(getattr(cls, attribute)): + all_keys.append(getattr(cls, attribute)) + + return all_keys + +class DeviceInfoKeysAvr(DeviceInfoKeys): + """ + Keys specific to AVR device info files + """ + + NVMCTRL_BASE = 'nvmctrl_base' + SYSCFG_BASE = 'syscfg_base' + OCD_BASE = 'ocd_base' + PROG_CLOCK_KHZ = 'prog_clock_khz' + ADDRESS_SIZE = 'address_size' + HV_IMPLEMENTATION = 'hv_implementation' + DATA_ADDRESS_SPACE = 'data_space_base' + +class DeviceInfoKeysAvr32(DeviceInfoKeys): + """ + Keys specific to 32-bit AVR device info files + """ + + RESET_DOMAINS = 'reset_domains' + +class DeviceInfoKeysPic(DeviceInfoKeys): + """ + Keys specific to PIC device info files + """ + + # This key should have _byte or _word ending in device info files to specify byte or word address + # This ending will be removed by the getdeviceinfo function before returning the device info dictionary + DEFAULT_BULK_ERASE_ADDRESS = 'default_bulk_erase_address' + +class DeviceMemoryInfoKeys(object): + """ + Keys for device memory info dictionary + + These keys are found in the dictionaries returned by DeviceMemoryInfo for each memory type + """ + NAME = 'name' + ADDRESS = 'address' + SIZE = 'size' + PAGE_SIZE = 'page_size' + WRITE_SIZE = 'write_size' + READ_SIZE = 'read_size' + ERASE_ADDRESS = 'erase_address' + CHIPERASE_EFFECT = 'chiperase_effect' + ISOLATED_ERASE = 'isolated_erase' + HEXFILE_ADDRESS = 'hexfile_address' + HEXFILE_SIZE = 'hexfile_size' + VERIFY_MASK = 'verify_mask' + + @classmethod + def get_all(cls): + """ + Get a list of all keys + + :return: List of all valid keys (baseclass and any subclass keys if run on a subclass) + """ + all_keys = [] + for attribute in dir(cls): + if not attribute.startswith('__') and not callable(getattr(cls, attribute)): + all_keys.append(getattr(cls, attribute)) + + return all_keys diff --git a/pymcuprog/deviceinfo/devices/__init__.py b/pymcuprog/deviceinfo/devices/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pymcuprog/deviceinfo/devices/atmega1608.py b/pymcuprog/deviceinfo/devices/atmega1608.py new file mode 100644 index 0000000..ab6c4ed --- /dev/null +++ b/pymcuprog/deviceinfo/devices/atmega1608.py @@ -0,0 +1,86 @@ + +""" +Required device info for the atmega1608 devices +The following data was collected from device pack Microchip.ATmega_DFP 3.0.143 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'atmega1608', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00004000, + 'flash_size_bytes': 0x4000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3800, + 'internal_sram_size_bytes': 0x0800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '16-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9427, + +} diff --git a/pymcuprog/deviceinfo/devices/atmega1609.py b/pymcuprog/deviceinfo/devices/atmega1609.py new file mode 100644 index 0000000..b9ea1ba --- /dev/null +++ b/pymcuprog/deviceinfo/devices/atmega1609.py @@ -0,0 +1,86 @@ + +""" +Required device info for the atmega1609 devices +The following data was collected from device pack Microchip.ATmega_DFP 3.0.143 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'atmega1609', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00004000, + 'flash_size_bytes': 0x4000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3800, + 'internal_sram_size_bytes': 0x0800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '16-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9426, + +} diff --git a/pymcuprog/deviceinfo/devices/atmega3208.py b/pymcuprog/deviceinfo/devices/atmega3208.py new file mode 100644 index 0000000..494900f --- /dev/null +++ b/pymcuprog/deviceinfo/devices/atmega3208.py @@ -0,0 +1,86 @@ + +""" +Required device info for the atmega3208 devices +The following data was collected from device pack Microchip.ATmega_DFP 3.0.143 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'atmega3208', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x40, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00004000, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x80, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3000, + 'internal_sram_size_bytes': 0x1000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x40, + 'user_row_page_size_bytes': 0x40, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '16-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9530, + +} diff --git a/pymcuprog/deviceinfo/devices/atmega3209.py b/pymcuprog/deviceinfo/devices/atmega3209.py new file mode 100644 index 0000000..570e772 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/atmega3209.py @@ -0,0 +1,86 @@ + +""" +Required device info for the atmega3209 devices +The following data was collected from device pack Microchip.ATmega_DFP 3.0.143 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'atmega3209', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x40, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00004000, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x80, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3000, + 'internal_sram_size_bytes': 0x1000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x40, + 'user_row_page_size_bytes': 0x40, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '16-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9531, + +} diff --git a/pymcuprog/deviceinfo/devices/atmega324pb.py b/pymcuprog/deviceinfo/devices/atmega324pb.py new file mode 100644 index 0000000..8410453 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/atmega324pb.py @@ -0,0 +1,33 @@ +""" +Required device info for the ATmega324PB device +Note: this model is incomplete and is for Microchip internal regression test purposes only + +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'atmega324pb', + 'architecture': 'avr8', + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x80, + 'flash_write_size_bytes': 0x80, + 'flash_read_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # eeprom + 'eeprom_address_byte': 0x0000, + 'eeprom_size_bytes': 0x0400, + 'eeprom_page_size_bytes': 0x04, + 'eeprom_read_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': False, + + # Some extra AVR specific fields + 'interface': 'jtag', + 'device_id': 0x1E9517, +} diff --git a/pymcuprog/deviceinfo/devices/atmega328p.py b/pymcuprog/deviceinfo/devices/atmega328p.py new file mode 100644 index 0000000..e12a48f --- /dev/null +++ b/pymcuprog/deviceinfo/devices/atmega328p.py @@ -0,0 +1,68 @@ +""" +Required device info for the ATmega328P device +Note: this model is incomplete and is for Microchip internal regression test purposes only +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'atmega328p', + 'architecture': 'avr8', + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x80, + 'flash_write_size_bytes': 0x80, + 'flash_read_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0, + 'signatures_size_bytes': 3, + 'signatures_page_size_bytes': 1, + 'signatures_read_size_bytes': 1, + 'signatures_write_size_bytes': 0, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # calibration + 'calibration_row_address_byte': 0, + 'calibration_row_size_bytes': 1, + 'calibration_row_page_size_bytes': 1, + 'calibration_row_read_size_bytes': 1, + 'calibration_row_write_size_bytes': 1, + 'calibration_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'calibration_row_isolated_erase': False, + + # fuses + 'fuses_address_byte': 0, + 'fuses_size_bytes': 0x0003, + 'fuses_page_size_bytes': 1, + 'fuses_read_size_bytes': 1, + 'fuses_write_size_bytes': 1, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0, + 'lockbits_size_bytes': 0x0001, + 'lockbits_page_size_bytes': 1, + 'lockbits_write_size_bytes': 1, + 'lockbits_read_size_bytes': 1, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # eeprom + 'eeprom_address_byte': 0x0000, + 'eeprom_size_bytes': 0x0400, + 'eeprom_page_size_bytes': 0x04, + 'eeprom_read_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': False, + + # Some extra AVR specific fields + 'interface': 'ISP+DW', + 'device_id': 0x1E950F, +} diff --git a/pymcuprog/deviceinfo/devices/atmega4808.py b/pymcuprog/deviceinfo/devices/atmega4808.py new file mode 100644 index 0000000..dcb5760 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/atmega4808.py @@ -0,0 +1,86 @@ + +""" +Required device info for the atmega4808 devices +The following data was collected from device pack Microchip.ATmega_DFP 3.0.143 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'atmega4808', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x40, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00004000, + 'flash_size_bytes': 0xC000, + 'flash_page_size_bytes': 0x80, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x2800, + 'internal_sram_size_bytes': 0x1800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x40, + 'user_row_page_size_bytes': 0x40, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '16-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9650, + +} diff --git a/pymcuprog/deviceinfo/devices/atmega4809.py b/pymcuprog/deviceinfo/devices/atmega4809.py new file mode 100644 index 0000000..b2fe796 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/atmega4809.py @@ -0,0 +1,86 @@ + +""" +Required device info for the atmega4809 devices +The following data was collected from device pack Microchip.ATmega_DFP 3.0.143 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'atmega4809', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x40, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00004000, + 'flash_size_bytes': 0xC000, + 'flash_page_size_bytes': 0x80, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x2800, + 'internal_sram_size_bytes': 0x1800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x40, + 'user_row_page_size_bytes': 0x40, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '16-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9651, + +} diff --git a/pymcuprog/deviceinfo/devices/atmega808.py b/pymcuprog/deviceinfo/devices/atmega808.py new file mode 100644 index 0000000..9571c3a --- /dev/null +++ b/pymcuprog/deviceinfo/devices/atmega808.py @@ -0,0 +1,86 @@ + +""" +Required device info for the atmega808 devices +The following data was collected from device pack Microchip.ATmega_DFP 3.0.143 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'atmega808', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00004000, + 'flash_size_bytes': 0x2000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3c00, + 'internal_sram_size_bytes': 0x0400, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '16-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9326, + +} diff --git a/pymcuprog/deviceinfo/devices/atmega809.py b/pymcuprog/deviceinfo/devices/atmega809.py new file mode 100644 index 0000000..58673ce --- /dev/null +++ b/pymcuprog/deviceinfo/devices/atmega809.py @@ -0,0 +1,86 @@ + +""" +Required device info for the atmega809 devices +The following data was collected from device pack Microchip.ATmega_DFP 3.0.143 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'atmega809', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00004000, + 'flash_size_bytes': 0x2000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3c00, + 'internal_sram_size_bytes': 0x0400, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '16-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E932A, + +} diff --git a/pymcuprog/deviceinfo/devices/atsamd21e18a.py b/pymcuprog/deviceinfo/devices/atsamd21e18a.py new file mode 100644 index 0000000..45cb8bd --- /dev/null +++ b/pymcuprog/deviceinfo/devices/atsamd21e18a.py @@ -0,0 +1,36 @@ +""" +Required device info for the ATSAMD21E18A devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'atsamd21e18a', + 'architecture': 'CORTEX-M0PLUS', + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 0x40000, + 'flash_page_size_bytes': 0x40, + 'flash_write_size_bytes': 4, + 'flash_read_size_bytes': 4, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x00804000, + 'user_row_size_bytes': 256, + 'user_row_page_size_bytes': 64, + 'user_row_read_size_bytes': 4, + 'user_row_write_size_bytes': 4, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # calibration data AKA OTP4 + 'calibration_row_address_byte': 0x00806020, + 'calibration_row_size_bytes': 64, + 'calibration_row_page_size_bytes': 64, + 'calibration_row_read_size_bytes': 4, + 'calibration_row_write_size_bytes': 0, + 'calibration_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'calibration_row_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/devices/atsamd21g18a.py b/pymcuprog/deviceinfo/devices/atsamd21g18a.py new file mode 100644 index 0000000..d871c9b --- /dev/null +++ b/pymcuprog/deviceinfo/devices/atsamd21g18a.py @@ -0,0 +1,36 @@ +""" +Required device info for the ATSAMD21G18A devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'atsamd21g18a', + 'architecture': 'CORTEX-M0PLUS', + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 0x40000, + 'flash_page_size_bytes': 0x100, # Note: this represents the optimal transfer size, not actual flash page. + 'flash_write_size_bytes': 4, + 'flash_read_size_bytes': 4, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x00804000, + 'user_row_size_bytes': 256, + 'user_row_page_size_bytes': 64, + 'user_row_read_size_bytes': 4, + 'user_row_write_size_bytes': 4, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # calibration data AKA OTP4 + 'calibration_row_address_byte': 0x00806020, + 'calibration_row_size_bytes': 64, + 'calibration_row_page_size_bytes': 64, + 'calibration_row_read_size_bytes': 4, + 'calibration_row_write_size_bytes': 0, + 'calibration_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'calibration_row_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/devices/attiny104.py b/pymcuprog/deviceinfo/devices/attiny104.py new file mode 100644 index 0000000..41d8eee --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny104.py @@ -0,0 +1,16 @@ + +""" +Required device info for the ATtiny104 device +Note: this model is incomplete and is for Microchip internal regression test purposes only +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'TPI', + 'name': 'attiny104', + 'architecture': 'avrtinytiny', + + # Some extra AVR specific fields + 'device_id': 0x1E900B, +} diff --git a/pymcuprog/deviceinfo/devices/attiny1604.py b/pymcuprog/deviceinfo/devices/attiny1604.py new file mode 100644 index 0000000..7580394 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny1604.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny1604 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny1604', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x4000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3c00, + 'internal_sram_size_bytes': 0x0400, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9425, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny1606.py b/pymcuprog/deviceinfo/devices/attiny1606.py new file mode 100644 index 0000000..943a822 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny1606.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny1606 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny1606', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x4000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3c00, + 'internal_sram_size_bytes': 0x0400, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9424, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny1607.py b/pymcuprog/deviceinfo/devices/attiny1607.py new file mode 100644 index 0000000..6815b58 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny1607.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny1607 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny1607', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x4000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3c00, + 'internal_sram_size_bytes': 0x0400, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9423, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny1614.py b/pymcuprog/deviceinfo/devices/attiny1614.py new file mode 100644 index 0000000..33498b7 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny1614.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny1614 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny1614', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x4000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3800, + 'internal_sram_size_bytes': 0x0800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9422, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny1616.py b/pymcuprog/deviceinfo/devices/attiny1616.py new file mode 100644 index 0000000..1e49c89 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny1616.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny1616 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny1616', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x4000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3800, + 'internal_sram_size_bytes': 0x0800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9421, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny1617.py b/pymcuprog/deviceinfo/devices/attiny1617.py new file mode 100644 index 0000000..bff286e --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny1617.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny1617 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny1617', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x4000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3800, + 'internal_sram_size_bytes': 0x0800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9420, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny1624.py b/pymcuprog/deviceinfo/devices/attiny1624.py new file mode 100644 index 0000000..0f328f8 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny1624.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny1624 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny1624', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x4000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3800, + 'internal_sram_size_bytes': 0x0800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E942A, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny1626.py b/pymcuprog/deviceinfo/devices/attiny1626.py new file mode 100644 index 0000000..615c9fc --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny1626.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny1626 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny1626', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x4000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3800, + 'internal_sram_size_bytes': 0x0800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9429, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny1627.py b/pymcuprog/deviceinfo/devices/attiny1627.py new file mode 100644 index 0000000..2655c84 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny1627.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny1627 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny1627', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x4000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3800, + 'internal_sram_size_bytes': 0x0800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9428, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny202.py b/pymcuprog/deviceinfo/devices/attiny202.py new file mode 100644 index 0000000..46b7e4d --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny202.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny202 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny202', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0040, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x0800, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3f80, + 'internal_sram_size_bytes': 0x0080, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9123, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny204.py b/pymcuprog/deviceinfo/devices/attiny204.py new file mode 100644 index 0000000..4f6f13e --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny204.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny204 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny204', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0040, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x0800, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3f80, + 'internal_sram_size_bytes': 0x0080, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9122, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny212.py b/pymcuprog/deviceinfo/devices/attiny212.py new file mode 100644 index 0000000..5b27a15 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny212.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny212 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny212', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0040, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x0800, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3f80, + 'internal_sram_size_bytes': 0x0080, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9121, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny214.py b/pymcuprog/deviceinfo/devices/attiny214.py new file mode 100644 index 0000000..a7acc9f --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny214.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny214 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny214', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0040, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x0800, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3f80, + 'internal_sram_size_bytes': 0x0080, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9120, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny3216.py b/pymcuprog/deviceinfo/devices/attiny3216.py new file mode 100644 index 0000000..d354737 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny3216.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny3216 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny3216', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x40, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x80, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3800, + 'internal_sram_size_bytes': 0x0800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9521, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny3217.py b/pymcuprog/deviceinfo/devices/attiny3217.py new file mode 100644 index 0000000..f80c2a5 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny3217.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny3217 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny3217', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x40, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x80, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3800, + 'internal_sram_size_bytes': 0x0800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9522, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny3224.py b/pymcuprog/deviceinfo/devices/attiny3224.py new file mode 100644 index 0000000..6f833da --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny3224.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny3224 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny3224', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x40, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x80, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3400, + 'internal_sram_size_bytes': 0x0C00, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9528, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny3226.py b/pymcuprog/deviceinfo/devices/attiny3226.py new file mode 100644 index 0000000..78af676 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny3226.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny3226 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny3226', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x40, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x80, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3400, + 'internal_sram_size_bytes': 0x0C00, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9527, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny3227.py b/pymcuprog/deviceinfo/devices/attiny3227.py new file mode 100644 index 0000000..ceeef46 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny3227.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny3227 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny3227', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x40, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x80, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3400, + 'internal_sram_size_bytes': 0x0C00, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9526, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny402.py b/pymcuprog/deviceinfo/devices/attiny402.py new file mode 100644 index 0000000..aad704d --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny402.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny402 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny402', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x1000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3f00, + 'internal_sram_size_bytes': 0x0100, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9227, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny404.py b/pymcuprog/deviceinfo/devices/attiny404.py new file mode 100644 index 0000000..fe069c2 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny404.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny404 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny404', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x1000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3f00, + 'internal_sram_size_bytes': 0x0100, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9226, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny406.py b/pymcuprog/deviceinfo/devices/attiny406.py new file mode 100644 index 0000000..fda5313 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny406.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny406 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny406', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x1000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3f00, + 'internal_sram_size_bytes': 0x0100, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9225, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny412.py b/pymcuprog/deviceinfo/devices/attiny412.py new file mode 100644 index 0000000..de82240 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny412.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny412 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny412', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x1000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3f00, + 'internal_sram_size_bytes': 0x0100, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9223, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny414.py b/pymcuprog/deviceinfo/devices/attiny414.py new file mode 100644 index 0000000..21ebb71 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny414.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny414 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny414', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x1000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3f00, + 'internal_sram_size_bytes': 0x0100, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9222, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny416.py b/pymcuprog/deviceinfo/devices/attiny416.py new file mode 100644 index 0000000..839eba8 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny416.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny416 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny416', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x1000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3f00, + 'internal_sram_size_bytes': 0x0100, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9221, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny417.py b/pymcuprog/deviceinfo/devices/attiny417.py new file mode 100644 index 0000000..34be176 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny417.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny417 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny417', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x1000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3f00, + 'internal_sram_size_bytes': 0x0100, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9220, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny424.py b/pymcuprog/deviceinfo/devices/attiny424.py new file mode 100644 index 0000000..ace1fe7 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny424.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny424 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny424', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x1000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3e00, + 'internal_sram_size_bytes': 0x0200, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E922C, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny426.py b/pymcuprog/deviceinfo/devices/attiny426.py new file mode 100644 index 0000000..ca75a75 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny426.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny426 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny426', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x1000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3e00, + 'internal_sram_size_bytes': 0x0200, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E922B, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny427.py b/pymcuprog/deviceinfo/devices/attiny427.py new file mode 100644 index 0000000..08928ab --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny427.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny427 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny427', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x1000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3e00, + 'internal_sram_size_bytes': 0x0200, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E922A, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny804.py b/pymcuprog/deviceinfo/devices/attiny804.py new file mode 100644 index 0000000..9d22536 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny804.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny804 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny804', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x2000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3e00, + 'internal_sram_size_bytes': 0x0200, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9325, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny806.py b/pymcuprog/deviceinfo/devices/attiny806.py new file mode 100644 index 0000000..bfe1cfa --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny806.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny806 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny806', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x2000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3e00, + 'internal_sram_size_bytes': 0x0200, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9324, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny807.py b/pymcuprog/deviceinfo/devices/attiny807.py new file mode 100644 index 0000000..fb611c4 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny807.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny807 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny807', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x2000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3e00, + 'internal_sram_size_bytes': 0x0200, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9323, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny814.py b/pymcuprog/deviceinfo/devices/attiny814.py new file mode 100644 index 0000000..b4fd076 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny814.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny814 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny814', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x2000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3e00, + 'internal_sram_size_bytes': 0x0200, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9322, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny816.py b/pymcuprog/deviceinfo/devices/attiny816.py new file mode 100644 index 0000000..aff4455 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny816.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny816 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny816', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x2000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3e00, + 'internal_sram_size_bytes': 0x0200, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9321, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny817.py b/pymcuprog/deviceinfo/devices/attiny817.py new file mode 100644 index 0000000..b7d0496 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny817.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny817 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny817', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x2000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3e00, + 'internal_sram_size_bytes': 0x0200, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9320, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny824.py b/pymcuprog/deviceinfo/devices/attiny824.py new file mode 100644 index 0000000..2b649da --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny824.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny824 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny824', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x2000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3c00, + 'internal_sram_size_bytes': 0x0400, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9329, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny826.py b/pymcuprog/deviceinfo/devices/attiny826.py new file mode 100644 index 0000000..a7d1483 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny826.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny826 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny826', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x2000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3c00, + 'internal_sram_size_bytes': 0x0400, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9328, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny827.py b/pymcuprog/deviceinfo/devices/attiny827.py new file mode 100644 index 0000000..c397cba --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny827.py @@ -0,0 +1,86 @@ + +""" +Required device info for the attiny827 devices +The following data was collected from device pack Microchip.ATtiny_DFP 3.0.138 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'attiny827', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0080, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00008000, + 'flash_size_bytes': 0x2000, + 'flash_page_size_bytes': 0x40, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x40, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1280, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x3c00, + 'internal_sram_size_bytes': 0x0400, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x0000128A, + 'lockbits_size_bytes': 0x1, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1300, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'prog_clock_khz': 900, + 'address_size': '16-bit', + 'hv_implementation': 0, + 'device_id': 0x1E9327, + +} diff --git a/pymcuprog/deviceinfo/devices/attiny84a.py b/pymcuprog/deviceinfo/devices/attiny84a.py new file mode 100644 index 0000000..fffe245 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/attiny84a.py @@ -0,0 +1,67 @@ +""" +Required device info for the ATtiny84A devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'attiny84a', + 'architecture': 'avr8', + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 0x2000, + 'flash_page_size_bytes': 0x40, + 'flash_write_size_bytes': 2, + 'flash_read_size_bytes': 2, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0, + 'signatures_size_bytes': 3, + 'signatures_page_size_bytes': 1, + 'signatures_read_size_bytes': 1, + 'signatures_write_size_bytes': 0, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # calibration + 'calibration_row_address_byte': 0, + 'calibration_row_size_bytes': 1, + 'calibration_row_page_size_bytes': 1, + 'calibration_row_read_size_bytes': 1, + 'calibration_row_write_size_bytes': 1, + 'calibration_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'calibration_row_isolated_erase': False, + + # fuses + 'fuses_address_byte': 0, + 'fuses_size_bytes': 0x0003, + 'fuses_page_size_bytes': 1, + 'fuses_read_size_bytes': 1, + 'fuses_write_size_bytes': 1, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0, + 'lockbits_size_bytes': 0x0001, + 'lockbits_page_size_bytes': 1, + 'lockbits_write_size_bytes': 1, + 'lockbits_read_size_bytes': 1, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # eeprom + 'eeprom_address_byte': 0x0000, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x04, + 'eeprom_read_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': False, + + # Some extra AVR specific fields + 'interface': 'ISP', + 'device_id': 0x1E930C, +} diff --git a/pymcuprog/deviceinfo/devices/atxmega128a1u.py b/pymcuprog/deviceinfo/devices/atxmega128a1u.py new file mode 100644 index 0000000..2bb47c0 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/atxmega128a1u.py @@ -0,0 +1,39 @@ +""" +Required device info for the ATxmega128A1U device +Note: this model is incomplete and is for Microchip internal regression test purposes only +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'atxmega128a1u', + 'architecture': 'xmega', + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x22000, + 'flash_page_size_bytes': 512, + 'flash_read_size_bytes': 2, + 'flash_write_size_bytes': 512, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # eeprom + 'eeprom_address_byte': 0x008C0000, + 'eeprom_size_bytes': 0x0800, + 'eeprom_page_size_bytes': 0x20, + 'eeprom_read_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x0F80, + 'data_space_base': 0x0090, + 'interface': 'pdi+jtag', + 'address_size': '16-bit', + 'prog_clock_khz': 4000, + 'device_id': 0x1E974C, +} diff --git a/pymcuprog/deviceinfo/devices/avr128da28.py b/pymcuprog/deviceinfo/devices/avr128da28.py new file mode 100644 index 0000000..9c38273 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr128da28.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr128da28 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr128da28', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x20000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x4000, + 'internal_sram_size_bytes': 0x4000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E970A, + +} diff --git a/pymcuprog/deviceinfo/devices/avr128da32.py b/pymcuprog/deviceinfo/devices/avr128da32.py new file mode 100644 index 0000000..76afd16 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr128da32.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr128da32 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr128da32', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x20000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x4000, + 'internal_sram_size_bytes': 0x4000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9709, + +} diff --git a/pymcuprog/deviceinfo/devices/avr128da48.py b/pymcuprog/deviceinfo/devices/avr128da48.py new file mode 100644 index 0000000..d99fb3b --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr128da48.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr128da48 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr128da48', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x20000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x4000, + 'internal_sram_size_bytes': 0x4000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9708, + +} diff --git a/pymcuprog/deviceinfo/devices/avr128da64.py b/pymcuprog/deviceinfo/devices/avr128da64.py new file mode 100644 index 0000000..c0642e6 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr128da64.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr128da64 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr128da64', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x20000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x4000, + 'internal_sram_size_bytes': 0x4000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9707, + +} diff --git a/pymcuprog/deviceinfo/devices/avr128db28.py b/pymcuprog/deviceinfo/devices/avr128db28.py new file mode 100644 index 0000000..db0d303 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr128db28.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr128db28 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr128db28', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x20000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x4000, + 'internal_sram_size_bytes': 0x4000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E970E, + +} diff --git a/pymcuprog/deviceinfo/devices/avr128db32.py b/pymcuprog/deviceinfo/devices/avr128db32.py new file mode 100644 index 0000000..276739a --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr128db32.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr128db32 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr128db32', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x20000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x4000, + 'internal_sram_size_bytes': 0x4000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E970D, + +} diff --git a/pymcuprog/deviceinfo/devices/avr128db48.py b/pymcuprog/deviceinfo/devices/avr128db48.py new file mode 100644 index 0000000..d5d6d5a --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr128db48.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr128db48 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr128db48', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x20000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x4000, + 'internal_sram_size_bytes': 0x4000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E970C, + +} diff --git a/pymcuprog/deviceinfo/devices/avr128db64.py b/pymcuprog/deviceinfo/devices/avr128db64.py new file mode 100644 index 0000000..3a4877c --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr128db64.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr128db64 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr128db64', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x20000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x4000, + 'internal_sram_size_bytes': 0x4000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E970B, + +} diff --git a/pymcuprog/deviceinfo/devices/avr32da28.py b/pymcuprog/deviceinfo/devices/avr32da28.py new file mode 100644 index 0000000..03dcf36 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr32da28.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr32da28 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr32da28', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x7000, + 'internal_sram_size_bytes': 0x1000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9534, + +} diff --git a/pymcuprog/deviceinfo/devices/avr32da32.py b/pymcuprog/deviceinfo/devices/avr32da32.py new file mode 100644 index 0000000..c604d4e --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr32da32.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr32da32 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr32da32', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x7000, + 'internal_sram_size_bytes': 0x1000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9533, + +} diff --git a/pymcuprog/deviceinfo/devices/avr32da48.py b/pymcuprog/deviceinfo/devices/avr32da48.py new file mode 100644 index 0000000..9a76d20 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr32da48.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr32da48 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr32da48', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x7000, + 'internal_sram_size_bytes': 0x1000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9532, + +} diff --git a/pymcuprog/deviceinfo/devices/avr32db28.py b/pymcuprog/deviceinfo/devices/avr32db28.py new file mode 100644 index 0000000..5abe16a --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr32db28.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr32db28 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr32db28', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x7000, + 'internal_sram_size_bytes': 0x1000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9537, + +} diff --git a/pymcuprog/deviceinfo/devices/avr32db32.py b/pymcuprog/deviceinfo/devices/avr32db32.py new file mode 100644 index 0000000..2062870 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr32db32.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr32db32 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr32db32', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x7000, + 'internal_sram_size_bytes': 0x1000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9536, + +} diff --git a/pymcuprog/deviceinfo/devices/avr32db48.py b/pymcuprog/deviceinfo/devices/avr32db48.py new file mode 100644 index 0000000..26262cc --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr32db48.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr32db48 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr32db48', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x8000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x7000, + 'internal_sram_size_bytes': 0x1000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9535, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64da28.py b/pymcuprog/deviceinfo/devices/avr64da28.py new file mode 100644 index 0000000..326fc0d --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64da28.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64da28 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64da28', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6000, + 'internal_sram_size_bytes': 0x2000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9615, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64da32.py b/pymcuprog/deviceinfo/devices/avr64da32.py new file mode 100644 index 0000000..0d13e12 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64da32.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64da32 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64da32', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6000, + 'internal_sram_size_bytes': 0x2000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9614, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64da48.py b/pymcuprog/deviceinfo/devices/avr64da48.py new file mode 100644 index 0000000..4c0354f --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64da48.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64da48 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64da48', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6000, + 'internal_sram_size_bytes': 0x2000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9613, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64da64.py b/pymcuprog/deviceinfo/devices/avr64da64.py new file mode 100644 index 0000000..10ae209 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64da64.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64da64 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64da64', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6000, + 'internal_sram_size_bytes': 0x2000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9612, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64db28.py b/pymcuprog/deviceinfo/devices/avr64db28.py new file mode 100644 index 0000000..80b20cc --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64db28.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64db28 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64db28', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6000, + 'internal_sram_size_bytes': 0x2000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9619, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64db32.py b/pymcuprog/deviceinfo/devices/avr64db32.py new file mode 100644 index 0000000..6034bf2 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64db32.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64db32 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64db32', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6000, + 'internal_sram_size_bytes': 0x2000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9618, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64db48.py b/pymcuprog/deviceinfo/devices/avr64db48.py new file mode 100644 index 0000000..134233e --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64db48.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64db48 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64db48', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6000, + 'internal_sram_size_bytes': 0x2000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9617, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64db64.py b/pymcuprog/deviceinfo/devices/avr64db64.py new file mode 100644 index 0000000..ef79040 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64db64.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64db64 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64db64', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x02, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6000, + 'internal_sram_size_bytes': 0x2000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 1, + 'device_id': 0x1E9616, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64dd14.py b/pymcuprog/deviceinfo/devices/avr64dd14.py new file mode 100644 index 0000000..a222c26 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64dd14.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64dd14 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64dd14', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x200, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6000, + 'internal_sram_size_bytes': 0x2000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 2, + 'device_id': 0x1E961D, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64dd20.py b/pymcuprog/deviceinfo/devices/avr64dd20.py new file mode 100644 index 0000000..5bf2f4c --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64dd20.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64dd20 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64dd20', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x200, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6000, + 'internal_sram_size_bytes': 0x2000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 2, + 'device_id': 0x1E961C, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64dd28.py b/pymcuprog/deviceinfo/devices/avr64dd28.py new file mode 100644 index 0000000..41ff330 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64dd28.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64dd28 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64dd28', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x200, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6000, + 'internal_sram_size_bytes': 0x2000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 2, + 'device_id': 0x1E961B, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64dd32.py b/pymcuprog/deviceinfo/devices/avr64dd32.py new file mode 100644 index 0000000..a5352c5 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64dd32.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64dd32 devices +The following data was collected from device pack Microchip.AVR-Dx_DFP 2.0.137 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64dd32', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0100, + 'eeprom_page_size_bytes': 0x1, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x200, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x200, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6000, + 'internal_sram_size_bytes': 0x2000, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x20, + 'user_row_page_size_bytes': 0x20, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x01, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 2, + 'device_id': 0x1E961A, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64ea28.py b/pymcuprog/deviceinfo/devices/avr64ea28.py new file mode 100644 index 0000000..79f3aa0 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64ea28.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64ea28 devices +The following data was collected from device pack Microchip.AVR-Ex_DFP 1.0.31 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64ea28', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x8, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x80, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6800, + 'internal_sram_size_bytes': 0x1800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x40, + 'user_row_page_size_bytes': 0x40, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x40, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 2, + 'device_id': 0x1E9620, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64ea32.py b/pymcuprog/deviceinfo/devices/avr64ea32.py new file mode 100644 index 0000000..9726a70 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64ea32.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64ea32 devices +The following data was collected from device pack Microchip.AVR-Ex_DFP 1.0.31 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64ea32', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x8, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x80, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6800, + 'internal_sram_size_bytes': 0x1800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x40, + 'user_row_page_size_bytes': 0x40, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x40, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 2, + 'device_id': 0x1E961F, + +} diff --git a/pymcuprog/deviceinfo/devices/avr64ea48.py b/pymcuprog/deviceinfo/devices/avr64ea48.py new file mode 100644 index 0000000..196d5bc --- /dev/null +++ b/pymcuprog/deviceinfo/devices/avr64ea48.py @@ -0,0 +1,86 @@ + +""" +Required device info for the avr64ea48 devices +The following data was collected from device pack Microchip.AVR-Ex_DFP 1.0.31 +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'interface': 'UPDI', + 'name': 'avr64ea48', + 'architecture': 'avr8x', + + # eeprom + 'eeprom_address_byte': 0x00001400, + 'eeprom_size_bytes': 0x0200, + 'eeprom_page_size_bytes': 0x8, + 'eeprom_read_size_bytes': 0x01, + 'eeprom_write_size_bytes': 0x01, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_AVR, + 'eeprom_isolated_erase': True, + + # flash + 'flash_address_byte': 0x00800000, + 'flash_size_bytes': 0x10000, + 'flash_page_size_bytes': 0x80, + 'flash_read_size_bytes': 0x02, + 'flash_write_size_bytes': 0x80, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # fuses + 'fuses_address_byte': 0x1050, + 'fuses_size_bytes': 0x09, + 'fuses_page_size_bytes': 0x01, + 'fuses_read_size_bytes': 0x01, + 'fuses_write_size_bytes': 0x01, + 'fuses_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'fuses_isolated_erase': False, + + # internal_sram + 'internal_sram_address_byte': 0x6800, + 'internal_sram_size_bytes': 0x1800, + 'internal_sram_page_size_bytes': 0x01, + 'internal_sram_read_size_bytes': 0x01, + 'internal_sram_write_size_bytes': 0x01, + 'internal_sram_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'internal_sram_isolated_erase': False, + + # lockbits + 'lockbits_address_byte': 0x1040, + 'lockbits_size_bytes': 0x4, + 'lockbits_page_size_bytes': 0x01, + 'lockbits_read_size_bytes': 0x01, + 'lockbits_write_size_bytes': 0x01, + 'lockbits_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'lockbits_isolated_erase': False, + + # signatures + 'signatures_address_byte': 0x1100, + 'signatures_size_bytes': 0x40, + 'signatures_page_size_bytes': 0x01, + 'signatures_read_size_bytes': 0x01, + 'signatures_write_size_bytes': 0x00, + 'signatures_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'signatures_isolated_erase': False, + + # user_row + 'user_row_address_byte': 0x1080, + 'user_row_size_bytes': 0x40, + 'user_row_page_size_bytes': 0x40, + 'user_row_read_size_bytes': 0x01, + 'user_row_write_size_bytes': 0x40, + 'user_row_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'user_row_isolated_erase': True, + + # Some extra AVR specific fields + 'nvmctrl_base': 0x00001000, + 'syscfg_base': 0x00000F00, + 'ocd_base': 0x00000F80, + 'address_size': '24-bit', + 'prog_clock_khz': 1800, + 'hv_implementation': 2, + 'device_id': 0x1E961E, + +} diff --git a/pymcuprog/deviceinfo/devices/dspic33ck64mc105.py b/pymcuprog/deviceinfo/devices/dspic33ck64mc105.py new file mode 100644 index 0000000..667b510 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/dspic33ck64mc105.py @@ -0,0 +1,31 @@ +""" +Required device info for the dspic33ck64mc105 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'dspic33ck64mc105', + 'device_id': '9912', + 'architecture': 'dsPIC33', + 'interface' : 'icsp', + + # Flash + 'flash_address_byte': 0, + 'flash_size_words': 0x00B000, + 'flash_page_size_bytes': 8, # This architecture has 2-word latches (6 packed bytes / 8 flat bytes) + 'flash_write_size_bytes': 8, # The GEN4 byte-code writes chunks of 6 packed bytes / 2 words / 8 flat bytes + 'flash_read_size_bytes': 16, # The GEN4 byte-code reads chunks of 12 packed bytes / 4 words / 16 flat bytes + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + # Configuration words are integrated in the flash, but since they are not represented + # as a separate memory it is correct to state that flash can be erased in isolation + 'flash_isolated_erase': True, + + # ICD memory + 'icd_address_byte': 0x800000 * 2, + 'icd_size_bytes': 512 * 4, # 2KiB + 'icd_page_size_bytes': 8, + 'icd_write_size_bytes': 8, + 'icd_read_size_bytes': 16, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': True, +} diff --git a/pymcuprog/deviceinfo/devices/pic16f15244.py b/pymcuprog/deviceinfo/devices/pic16f15244.py new file mode 100644 index 0000000..cf8b844 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic16f15244.py @@ -0,0 +1,50 @@ +""" +Required device info for the PIC16F15244 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic16f15244', + 'architecture': 'PIC16', + # Will erase Flash, User ID and Config words + 'default_bulk_erase_address_word': 0x8000, + + # Flash + 'flash_address_word': 0, + 'flash_size_words': 4096, + 'flash_page_size_words': 32, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + # This address will erase only flash + 'flash_erase_address_word': 0x80FE, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # User ID + 'user_id_address_word': 0x8000, + 'user_id_size_words': 4, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_word': 0x8007, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_erase_address_word': 0, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # ICD + 'icd_address_word': 0x8600, + 'icd_size_words': 512, + 'icd_page_size_words': 32, + 'icd_write_size_words': 1, + 'icd_read_size_words': 1, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': True, +} diff --git a/pymcuprog/deviceinfo/devices/pic16f15276.py b/pymcuprog/deviceinfo/devices/pic16f15276.py new file mode 100644 index 0000000..059ae02 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic16f15276.py @@ -0,0 +1,51 @@ +""" +Required device info for the PIC16F15276 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic16f15276', + 'architecture': 'PIC16', + 'device_id': 0x30EC, + # Will erase Flash, User ID and Config words + 'default_bulk_erase_address_word': 0x8000, + + # Flash + 'flash_address_word': 0, + 'flash_size_words': 16*1024, + 'flash_page_size_words': 32, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + # This address will erase only flash + 'flash_erase_address_word': 0x80FE, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # User ID + 'user_id_address_word': 0x8000, + 'user_id_size_words': 4, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_word': 0x8007, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_erase_address_word': 0, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # ICD + 'icd_address_word': 0x8600, + 'icd_size_words': 512, + 'icd_page_size_words': 32, + 'icd_write_size_words': 1, + 'icd_read_size_words': 1, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': True, +} diff --git a/pymcuprog/deviceinfo/devices/pic16f15376.py b/pymcuprog/deviceinfo/devices/pic16f15376.py new file mode 100644 index 0000000..3002231 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic16f15376.py @@ -0,0 +1,51 @@ +""" +Required device info for the PIC16F15376 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic16f15376', + 'architecture': 'PIC16', + # Will erase Flash, User ID and Config words + 'default_bulk_erase_address_word': 0x8000, + + # Flash + 'flash_address_word': 0, + 'flash_size_words': 16384, + 'flash_page_size_words': 32, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + # This address will erase only flash + 'flash_erase_address_word': 0x80FE, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + + # User ID + 'user_id_address_word': 0x8000, + 'user_id_size_words': 4, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_word': 0x8007, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_erase_address_word': 0, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # ICD + 'icd_address_word': 0x8600, + 'icd_size_words': 512, + 'icd_page_size_words': 32, + 'icd_write_size_words': 1, + 'icd_read_size_words': 1, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': True, +} diff --git a/pymcuprog/deviceinfo/devices/pic16f17146.py b/pymcuprog/deviceinfo/devices/pic16f17146.py new file mode 100644 index 0000000..d996eb1 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic16f17146.py @@ -0,0 +1,56 @@ +""" +Required device info for the PIC16F17146 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic16f17146', + 'architecture': 'PIC16', + 'device_id': 0x30E1, + # This device does not use an address as parameter for the bulk erase + + # Flash + 'flash_address_word': 0, + 'flash_size_words': 16*1024, + 'flash_page_size_words': 32, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_word': 0x8000, + 'user_id_size_words': 4, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_word': 0x8007, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_word': 0xF000, + 'eeprom_size_bytes': 256, + 'eeprom_page_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_read_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'eeprom_isolated_erase': False, + + # ICD + 'icd_address_word': 0x8600, + 'icd_size_words': 512, + 'icd_page_size_words': 32, + 'icd_write_size_words': 1, + 'icd_read_size_words': 1, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': True, +} diff --git a/pymcuprog/deviceinfo/devices/pic16f1768.py b/pymcuprog/deviceinfo/devices/pic16f1768.py new file mode 100644 index 0000000..79025a8 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic16f1768.py @@ -0,0 +1,39 @@ +""" +Required device info for the PIC16F1768 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic16f1768', + 'architecture': 'PIC16', + # Will erase Flash, User ID and Config words + 'default_bulk_erase_address_word': 0x8000, + + # Flash + 'flash_address_word': 0, + 'flash_size_words': 4*1024, # 4KW + 'flash_page_size_words': 32, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_word': 0x8000, + 'user_id_size_words': 4, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_word': 0x8007, + 'config_words_size_words': 2, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_erase_address_word': 0, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/devices/pic16f1769.py b/pymcuprog/deviceinfo/devices/pic16f1769.py new file mode 100644 index 0000000..2d682cd --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic16f1769.py @@ -0,0 +1,40 @@ +""" +Required device info for the PIC16F1769 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic16f1769', + 'architecture': 'PIC16', + 'device_id': 0x3085, + # Will erase Flash, User ID and Config words + 'default_bulk_erase_address_word': 0x8000, + + # Flash + 'flash_address_word': 0, + 'flash_size_words': 8*1024, # 4KW + 'flash_page_size_words': 32, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_word': 0x8000, + 'user_id_size_words': 4, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_word': 0x8007, + 'config_words_size_words': 2, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_erase_address_word': 0, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/devices/pic16f1779.py b/pymcuprog/deviceinfo/devices/pic16f1779.py new file mode 100644 index 0000000..ff818dc --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic16f1779.py @@ -0,0 +1,39 @@ +""" +Required device info for the PIC16F1779 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic16f1779', + 'architecture': 'PIC16', + # Will erase Flash, User ID and Config words + 'default_bulk_erase_address_word': 0x8000, + + # Flash + 'flash_address_word': 0, + 'flash_size_words': 16*1024, # 16KW + 'flash_page_size_words': 32, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_word': 0x8000, + 'user_id_size_words': 4, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_word': 0x8007, + 'config_words_size_words': 2, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_erase_address_word': 0, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/devices/pic16f18076.py b/pymcuprog/deviceinfo/devices/pic16f18076.py new file mode 100644 index 0000000..f825c88 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic16f18076.py @@ -0,0 +1,56 @@ +""" +Required device info for the PIC16F18076 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic16f18076', + 'architecture': 'PIC16', + 'device_id': 0x3100, + # This device does not use an address as parameter for the bulk erase + + # Flash + 'flash_address_word': 0, + 'flash_size_words': 16*1024, + 'flash_page_size_words': 32, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_word': 0x8000, + 'user_id_size_words': 4, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_word': 0x8007, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_word': 0xF000, + 'eeprom_size_bytes': 256, + 'eeprom_page_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_read_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'eeprom_isolated_erase': False, + + # ICD + 'icd_address_word': 0x8600, + 'icd_size_words': 512, + 'icd_page_size_words': 32, + 'icd_write_size_words': 1, + 'icd_read_size_words': 1, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': True, +} diff --git a/pymcuprog/deviceinfo/devices/pic16f18146.py b/pymcuprog/deviceinfo/devices/pic16f18146.py new file mode 100644 index 0000000..624164d --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic16f18146.py @@ -0,0 +1,56 @@ +""" +Required device info for the PIC16F18146 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic16f18146', + 'architecture': 'PIC16', + 'device_id': 0x3112, + # This device does not use an address as parameter for the bulk erase + + # Flash + 'flash_address_word': 0, + 'flash_size_words': 16*1024, + 'flash_page_size_words': 32, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_word': 0x8000, + 'user_id_size_words': 4, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_word': 0x8007, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_word': 0xF000, + 'eeprom_size_bytes': 256, + 'eeprom_page_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_read_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'eeprom_isolated_erase': False, + + # ICD + 'icd_address_word': 0x8600, + 'icd_size_words': 512, + 'icd_page_size_words': 32, + 'icd_write_size_words': 1, + 'icd_read_size_words': 1, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': True, +} diff --git a/pymcuprog/deviceinfo/devices/pic16f18446.py b/pymcuprog/deviceinfo/devices/pic16f18446.py new file mode 100644 index 0000000..69a49ba --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic16f18446.py @@ -0,0 +1,59 @@ +""" +Required device info for the PIC16F18446 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic16f18446', + 'architecture': 'PIC16', + # Will erase Flash, User ID and Config words + 'default_bulk_erase_address_word': 0x8000, + + # Flash + 'flash_address_word': 0, + 'flash_size_words': 16384, + 'flash_page_size_words': 32, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + # This address will erase only flash + 'flash_erase_address_word': 0x80FE, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # User ID + 'user_id_address_word': 0x8000, + 'user_id_size_words': 4, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_word': 0x8007, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_erase_address_word': 0, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_word': 0xF000, + 'eeprom_size_bytes': 256, + 'eeprom_page_size_words': 1, + 'eeprom_write_size_words': 1, + 'eeprom_read_size_words': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'eeprom_isolated_erase': False, + + # ICD memory + 'icd_address_byte': 0x8600*2, + 'icd_size_bytes': 512*2, # 1KiB + 'icd_page_size_words': 32, + 'icd_write_size_words': 1, + 'icd_read_size_words': 1, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': True, +} diff --git a/pymcuprog/deviceinfo/devices/pic16f18456.py b/pymcuprog/deviceinfo/devices/pic16f18456.py new file mode 100644 index 0000000..06edff6 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic16f18456.py @@ -0,0 +1,51 @@ +""" +Required device info for the PIC16F18456 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic16f18456', + 'architecture': 'PIC16', + # Will erase Flash, User ID and Config words + 'default_bulk_erase_address_word': 0x8000, + + + # Flash + 'flash_address_word': 0, + 'flash_size_words': 16384, + 'flash_page_size_words': 32, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + # This address will erase only flash + 'flash_erase_address_word': 0x80FE, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # User ID + 'user_id_address_word': 0x8000, + 'user_id_size_words': 4, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_word': 0x8007, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_erase_address_word': 0, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_word': 0xF000, + 'eeprom_size_bytes': 256, + 'eeprom_page_size_words': 1, + 'eeprom_write_size_words': 1, + 'eeprom_read_size_words': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'eeprom_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/devices/pic16lf18456.py b/pymcuprog/deviceinfo/devices/pic16lf18456.py new file mode 100644 index 0000000..033c02d --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic16lf18456.py @@ -0,0 +1,50 @@ +""" +Required device info for the PIC16LF18456 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic16lf18456', + 'architecture': 'PIC16', + # Will erase Flash, User ID and Config words + 'default_bulk_erase_address_word': 0x8000, + + # Flash + 'flash_address_word': 0, + 'flash_size_words': 16384, + 'flash_page_size_words': 32, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + # This address will erase only flash + 'flash_erase_address_word': 0x80FE, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': True, + + # User ID + 'user_id_address_word': 0x8000, + 'user_id_size_words': 4, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_word': 0x8007, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_erase_address_word': 0, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_word': 0xF000, + 'eeprom_size_bytes': 256, + 'eeprom_page_size_words': 1, + 'eeprom_write_size_words': 1, + 'eeprom_read_size_words': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'eeprom_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/devices/pic18f16q20.py b/pymcuprog/deviceinfo/devices/pic18f16q20.py new file mode 100644 index 0000000..6afd66e --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic18f16q20.py @@ -0,0 +1,58 @@ +""" +Required device info for the PIC18F16Q20 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic18f16q20', + 'architecture': 'PIC18', + 'device_id': 0x7A40, + # This device does not use an address as parameter for the bulk erase + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 64*1024, # 64KB + 'flash_erase_size_words': 128, # Sector erase + 'flash_page_size_words': 1, # No page buffer + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_byte': 0x200000, + 'user_id_size_words': 32, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_erase_address_byte': 0x200000, # not used kept for compatibility + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_byte': 0x300000, + 'config_words_size_words': 0x0D, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_byte': 0x380000, + 'eeprom_size_bytes': 256, + 'eeprom_page_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_read_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'eeprom_isolated_erase': False, + + # ICD memory + 'icd_address_byte': 0x240000, + 'icd_size_bytes': 128*4*2, # 1KiB + 'icd_page_size_words': 128,# No page buffer so this is for sector erase + 'icd_write_size_words': 1, + 'icd_read_size_words': 1, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/devices/pic18f16q40.py b/pymcuprog/deviceinfo/devices/pic18f16q40.py new file mode 100644 index 0000000..064e0e3 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic18f16q40.py @@ -0,0 +1,58 @@ +""" +Required device info for the PIC18F16Q40 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic18f16q40', + 'architecture': 'PIC18', + 'device_id': 0x75A0, + # This device does not use an address as parameter for the bulk erase + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 64*1024, # 64KB + 'flash_erase_size_words': 128, # Sector erase + 'flash_page_size_words': 1, # No page buffer + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_byte': 0x200000, + 'user_id_size_words': 32, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_erase_address_byte': 0x300000, # not used kept for compatibility + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_byte': 0x300000, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_byte': 0x380000, + 'eeprom_size_bytes': 512, + 'eeprom_page_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_read_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'eeprom_isolated_erase': False, + + # ICD memory + 'icd_address_byte': 0x240000, + 'icd_size_bytes': 128*4*2, # 1KiB + 'icd_page_size_words': 128,# No page buffer so this is for sector erase + 'icd_write_size_words': 1, + 'icd_read_size_words': 1, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/devices/pic18f16q41.py b/pymcuprog/deviceinfo/devices/pic18f16q41.py new file mode 100644 index 0000000..5e60243 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic18f16q41.py @@ -0,0 +1,58 @@ +""" +Required device info for the PIC18F16Q41 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic18f16q41', + 'architecture': 'PIC18', + 'device_id': 0x7560, + # This device does not use an address as parameter for the bulk erase + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 64*1024, # 64KB + 'flash_erase_size_words': 128, # Sector erase + 'flash_page_size_words': 1, # No page buffer + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_byte': 0x200000, + 'user_id_size_words': 32, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_erase_address_byte': 0x300000, # not used kept for compatibility + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_byte': 0x300000, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_byte': 0x380000, + 'eeprom_size_bytes': 512, + 'eeprom_page_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_read_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'eeprom_isolated_erase': False, + + # ICD memory + 'icd_address_byte': 0x240000, + 'icd_size_bytes': 128*4*2, # 1KiB + 'icd_page_size_words': 128,# No page buffer so this is for sector erase + 'icd_write_size_words': 1, + 'icd_read_size_words': 1, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/devices/pic18f47k40.py b/pymcuprog/deviceinfo/devices/pic18f47k40.py new file mode 100644 index 0000000..7cb1646 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic18f47k40.py @@ -0,0 +1,49 @@ +""" +Required device info for the PIC18F47K40 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic18f47k40', + 'architecture': 'PIC18', + # Will erase Flash, User ID, Config words and EEPROM (if CPD_bar config bit is disabled) + 'default_bulk_erase_address_byte': 0x300000, + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 128*1024, # 128KB + 'flash_page_size_bytes': 128, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_byte': 0x200000, + 'user_id_size_words': 8, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_erase_address_byte': 0x300000, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_byte': 0x300000, + 'config_words_size_words': 6, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_erase_address_byte': 0, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_byte': 0x310000, + 'eeprom_size_bytes': 1024, + 'eeprom_page_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_read_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_PIC, + 'eeprom_isolated_erase': True, +} diff --git a/pymcuprog/deviceinfo/devices/pic18f47k42.py b/pymcuprog/deviceinfo/devices/pic18f47k42.py new file mode 100644 index 0000000..7771191 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic18f47k42.py @@ -0,0 +1,48 @@ +""" +Required device info for the PIC18F47K42 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic18f47k42', + 'architecture': 'PIC18', + # Will erase Flash, User ID, Config words and EEPROM (if CPD_bar config bit is disabled) + 'default_bulk_erase_address_byte': 0x300000, + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 128*1024, # 128KB + 'flash_page_size_bytes': 128, + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_byte': 0x200000, + 'user_id_size_words': 8, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_erase_address_byte': 0x300000, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_byte': 0x300000, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_byte': 0x310000, + 'eeprom_size_bytes': 1024, + 'eeprom_page_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_read_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_PIC, + 'eeprom_isolated_erase': True, +} diff --git a/pymcuprog/deviceinfo/devices/pic18f47q10.py b/pymcuprog/deviceinfo/devices/pic18f47q10.py new file mode 100644 index 0000000..db82904 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic18f47q10.py @@ -0,0 +1,49 @@ +""" +Required device info for the PIC18F47Q10 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic18f47q10', + 'architecture': 'PIC18', + # Will erase Flash, User ID, Config words and EEPROM (if CPD_bar config bit is disabled) + 'default_bulk_erase_address_byte': 0x300000, + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 128*1024, # 64KW + 'flash_page_size_words': 128, # No page buffer so this is for sector erase + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_byte': 0x200000, + 'user_id_size_words': 128, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_erase_address_byte': 0x300000, + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_byte': 0x300000, + 'config_words_size_words': 6, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_erase_address_byte': 0, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_byte': 0x310000, + 'eeprom_size_bytes': 1024, + 'eeprom_page_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_read_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.CONDITIONALLY_ERASED_PIC, + 'eeprom_isolated_erase': True, +} diff --git a/pymcuprog/deviceinfo/devices/pic18f47q43.py b/pymcuprog/deviceinfo/devices/pic18f47q43.py new file mode 100644 index 0000000..f1a01ac --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic18f47q43.py @@ -0,0 +1,57 @@ +""" +Required device info for the PIC18F47Q43 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic18f47q43', + 'architecture': 'PIC18', + # This device does not use an address as parameter for the bulk erase + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 128*1024, # 128KB + 'flash_erase_size_words': 128, # Sector erase + 'flash_page_size_words': 1, # No page buffer + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_byte': 0x200000, + 'user_id_size_words': 32, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_erase_address_byte': 0x300000, # not used kept for compatibility + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_byte': 0x300000, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_byte': 0x380000, + 'eeprom_size_bytes': 1024, + 'eeprom_page_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_read_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'eeprom_isolated_erase': False, + + # ICD memory + 'icd_address_byte': 0x240000, + 'icd_size_bytes': 128*4*2, # 1KiB + 'icd_page_size_words': 128,# No page buffer so this is for sector erase + 'icd_write_size_words': 1, + 'icd_read_size_words': 1, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/devices/pic18f57q43.py b/pymcuprog/deviceinfo/devices/pic18f57q43.py new file mode 100644 index 0000000..0e58419 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic18f57q43.py @@ -0,0 +1,57 @@ +""" +Required device info for the PIC18F57Q43 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic18f57q43', + 'architecture': 'PIC18', + # This device does not use an address as parameter for the bulk erase + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 128*1024, # 128KB + 'flash_erase_size_words': 128, # Sector erase + 'flash_page_size_words': 1, # No page buffer + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_byte': 0x200000, + 'user_id_size_words': 32, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_erase_address_byte': 0x300000, # not used kept for compatibility + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_byte': 0x300000, + 'config_words_size_words': 5, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_byte': 0x380000, + 'eeprom_size_bytes': 1024, + 'eeprom_page_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_read_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'eeprom_isolated_erase': False, + + # ICD memory + 'icd_address_byte': 0x240000, + 'icd_size_bytes': 128*4*2, # 1KiB + 'icd_page_size_words': 128, # No page buffer so this is for sector erase + 'icd_write_size_words': 1, + 'icd_read_size_words': 1, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/devices/pic18f57q84.py b/pymcuprog/deviceinfo/devices/pic18f57q84.py new file mode 100644 index 0000000..239bc67 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic18f57q84.py @@ -0,0 +1,58 @@ +""" +Required device info for the PIC18F57Q84 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic18f57q84', + 'architecture': 'PIC18', + 'device_id': 0x9905, + # This device does not use an address as parameter for the bulk erase + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 128*1024, # 128KB + 'flash_erase_size_words': 128, # Sector erase + 'flash_page_size_words': 1, # No page buffer + 'flash_write_size_words': 1, + 'flash_read_size_words': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, + + # User ID + 'user_id_address_byte': 0x200000, + 'user_id_size_words': 32, + 'user_id_page_size_words': 1, + 'user_id_write_size_words': 1, + 'user_id_read_size_words': 1, + 'user_id_erase_address_byte': 0x300000, # not used kept for compatibility + 'user_id_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'user_id_isolated_erase': False, + + # Config words + 'config_words_address_byte': 0x300000, + 'config_words_size_words': 18, + 'config_words_page_size_words': 1, + 'config_words_write_size_words': 1, + 'config_words_read_size_words': 1, + 'config_words_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'config_words_isolated_erase': False, + + # EEPROM + 'eeprom_address_byte': 0x380000, + 'eeprom_size_bytes': 1024, + 'eeprom_page_size_bytes': 1, + 'eeprom_write_size_bytes': 1, + 'eeprom_read_size_bytes': 1, + 'eeprom_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'eeprom_isolated_erase': False, + + # ICD memory + 'icd_address_byte': 0x240000, + 'icd_size_bytes': 128*4*2, # 1KiB + 'icd_page_size_words': 128, # No page buffer so this is for sector erase + 'icd_write_size_words': 1, + 'icd_read_size_words': 1, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/devices/pic24fj128ga705.py b/pymcuprog/deviceinfo/devices/pic24fj128ga705.py new file mode 100644 index 0000000..8a9550d --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic24fj128ga705.py @@ -0,0 +1,31 @@ +""" +Required device info for the PIC24FJ128GA705 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic24fj128ga705', + 'device_id': '750B', + 'architecture': 'PIC24', + 'interface' : 'icsp', + + # Flash + 'flash_address_byte': 0, + 'flash_size_words': 0x16000, # 44K * 1024 * 2 + 'flash_page_size_bytes': 512, + 'flash_write_size_bytes': 16, + 'flash_read_size_bytes': 16, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + # Configuration words are integrated in the flash, but since they are not represented + # as a separate memory it is correct to state that flash can be erased in isolation + 'flash_isolated_erase': True, + + # ICD memory + 'icd_address_byte': 0x800000 * 2, + 'icd_size_bytes': 512 * 4, # 2KiB + 'icd_page_size_bytes': 512, + 'icd_write_size_bytes': 16, + 'icd_read_size_bytes': 16, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': True, +} diff --git a/pymcuprog/deviceinfo/devices/pic24fj64gu205.py b/pymcuprog/deviceinfo/devices/pic24fj64gu205.py new file mode 100644 index 0000000..c0a3138 --- /dev/null +++ b/pymcuprog/deviceinfo/devices/pic24fj64gu205.py @@ -0,0 +1,31 @@ +""" +Required device info for the PIC24FJ64GU205 device +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'pic24fj64gu205', + 'device_id': '9A19', + 'architecture': 'PIC24', + 'interface' : 'icsp', + + # Flash + 'flash_address_byte': 0, + 'flash_size_words': 0xB000, # 22 * 1024 * 2 + 'flash_page_size_bytes': 512, + 'flash_write_size_bytes': 16, + 'flash_read_size_bytes': 16, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + # Configuration words are integrated in the flash, but since they are not represented + # as a separate memory it is correct to state that flash can be erased in isolation + 'flash_isolated_erase': True, + + # ICD memory + 'icd_address_byte': 0x800000 * 2, + 'icd_size_bytes': 512 * 4, # 2KiB + 'icd_page_size_bytes': 512, + 'icd_write_size_bytes': 16, + 'icd_read_size_bytes': 16, + 'icd_chiperase_effect': ChiperaseEffect.NOT_ERASED, + 'icd_isolated_erase': True, +} diff --git a/pymcuprog/deviceinfo/devices/uc3a3256.py b/pymcuprog/deviceinfo/devices/uc3a3256.py new file mode 100644 index 0000000..4f7a64c --- /dev/null +++ b/pymcuprog/deviceinfo/devices/uc3a3256.py @@ -0,0 +1,19 @@ +""" +Required device info for the EDBG / UC3A3 devices +""" +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { + 'name': 'uc3a3256', + 'architecture': 'avr32', + 'reset_domains': 5, + + # Flash + 'flash_address_byte': 0, + 'flash_size_bytes': 0x40000, + 'flash_page_size_bytes': 512, + 'flash_write_size_bytes': 1, + 'flash_read_size_bytes': 1, + 'flash_chiperase_effect': ChiperaseEffect.ALWAYS_ERASED, + 'flash_isolated_erase': False, +} diff --git a/pymcuprog/deviceinfo/eraseflags.py b/pymcuprog/deviceinfo/eraseflags.py new file mode 100644 index 0000000..9e37e75 --- /dev/null +++ b/pymcuprog/deviceinfo/eraseflags.py @@ -0,0 +1,25 @@ +""" +Definitions of erase related flags for the device models +""" +import inspect + +from pymcuprog.utils import enum + +# Flag used to specify if a memory type will be erased by a chip erase (AVR) or the widest/default bulk erase (PIC) +ChiperaseEffect = enum( + ALWAYS_ERASED='always erased', + CONDITIONALLY_ERASED_AVR='conditionally erased (depending upon EESAVE fuse setting)', + CONDITIONALLY_ERASED_PIC='conditionally erased (depending upon Code Protect configuration bit(s) settings)', + NOT_ERASED='not erased') + +def get_list_of_chiperase_effects(): + """Return a list of all ChiperaseEffect values""" + chiperase_effect_attributes = inspect.getmembers(ChiperaseEffect, lambda a: not inspect.isroutine(a)) + chiperase_effect_values = [] + for attribute in chiperase_effect_attributes: + # Builtin routines always starts and ends with double underscore (__) + if not (attribute[0].startswith('__') and attribute[0].endswith('__')): + # Only the attribute values are returned + chiperase_effect_values.append(attribute[1]) + + return chiperase_effect_values diff --git a/pymcuprog/deviceinfo/generate_device_info.py b/pymcuprog/deviceinfo/generate_device_info.py new file mode 100644 index 0000000..12d0eaf --- /dev/null +++ b/pymcuprog/deviceinfo/generate_device_info.py @@ -0,0 +1,109 @@ +""" +DFP harvester +""" +import os +import tempfile +from string import Template +from zipfile import ZipFile +from requests import get +import harvest + +# Constants used by various servers and artifacts +MICROCHIP_ARTIFACTORY_SERVER_URL = "https://artifacts.microchip.com/artifactory" +REPO = "ivy-local" +ORG = "microchip" +REMOTE = "&remote=1" + +DEVICE_INFO_TEMPLATE = ''' +""" +Required device info for the $device_name devices +The following data was collected from device pack $pack_info +""" + +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect + +DEVICE_INFO = { +$device_data +} +''' + +def fetch_latest_device_atpack(module, res_dir_path, org=ORG, repo=REPO, remote=REMOTE): + """ + Returns path to a temporary folder containing the requested device pack (.atpack format). + + :param module: module name (DFP name) to retrieve + :param res_dir_path: Directory to store the retrieved device pack. + :param org: ivy organisation + :param repo: ivy repo + :param remote: ivy remote + """ + search_url = "/api/search/latestVersion?g={0:s}&a={1:s}&repos={2:s}{3:s}".format(org, module, repo, remote) + version = get(MICROCHIP_ARTIFACTORY_SERVER_URL + search_url).text + target_file = module + "-" + version + ".atpack" + artifact_url = "/{0:s}/{1:s}/{2:s}/{3:s}/{4:s}".format(repo, org, module, version, target_file) + artifact = get(MICROCHIP_ARTIFACTORY_SERVER_URL + artifact_url) + if artifact.status_code == 200: + temp_file = tempfile.NamedTemporaryFile(prefix="{}_".format(module), suffix=".atpack", delete=False, + dir=res_dir_path) + temp_file.write(artifact.content) + temp_file.close() + with ZipFile(temp_file.name, "r") as zipobj: + zipobj.extractall(res_dir_path) + return "{} {}".format(module, version) + return None + +def extract_device_name(device_atdf_path): + harvest_data = harvest.harvest_from_file(device_atdf_path) + name_container_ind = harvest_data.find("'name'") + len("'name'") + name_start_ind = harvest_data[name_container_ind:].find("'") + name_container_ind + 1 + name_end_ind = harvest_data[name_start_ind:].find("'") + name_start_ind + name = harvest_data[name_start_ind:name_end_ind] + return name + + +def add_new_updi_device(device_atdf_path, device_pack_source_str): + """ + Add a new UPDI device + + :param device_atdf_path: Path to .atdf file of the device you wish to add + :param device_pack_source_str: Reference string to the source of the DFP from which the .atdf file was extracted + """ + harvest_data = harvest.harvest_from_file(device_atdf_path) + if not "'interface': 'UPDI'" in harvest_data: + return + device_name = extract_device_name(device_atdf_path) + new_device_template = Template(DEVICE_INFO_TEMPLATE) + new_device_script = new_device_template.substitute(device_name=device_name, device_data=harvest_data, + pack_info=device_pack_source_str) + + f = open(os.getcwd().replace("\\", "/")+"/devices/" + device_name + ".py", "w") + f.write(new_device_script) + f.close() + +def update_updi_devices(): + """ + Update all UPDI devices + """ + packs = [] + PACKS_OF_INTEREST = ['Microchip.ATmega_DFP', 'Microchip.ATtiny_DFP', 'Microchip.AVR-'] + # Search entire path + from artifactory import ArtifactoryPath + path = ArtifactoryPath("{}/{}/{}".format(MICROCHIP_ARTIFACTORY_SERVER_URL, REPO, ORG)) + for a in path: + name = str(a) + packname = name.split('/')[-1] + # Filter: + if name.endswith("_DFP") and not name.endswith("ENG_DFP") and not name.endswith("DEV_DFP") and packname.startswith(tuple(PACKS_OF_INTEREST)): + packs.append(packname) + + for pack in packs: + print("Fetching latest pack for: {}".format(pack)) + temp_dir = tempfile.TemporaryDirectory() + pack_info = fetch_latest_device_atpack(pack, temp_dir.name) + devices = os.listdir(temp_dir.name.replace("\\", "/")+"/atdf") + print("Parsing {} devices".format(len(devices))) + for device in devices: + add_new_updi_device(temp_dir.name.replace("\\", "/") + "/atdf/" + device, pack_info) + print("Success.") + +update_updi_devices() diff --git a/pymcuprog/deviceinfo/generateconfig.py b/pymcuprog/deviceinfo/generateconfig.py new file mode 100644 index 0000000..3f6e9ab --- /dev/null +++ b/pymcuprog/deviceinfo/generateconfig.py @@ -0,0 +1,71 @@ +""" +Generate drag-and-drop configuration files for PIC devices through device support scripts + +The generated device blob can be used to provide drag and drop programming support for kits with +onboard debuggers +""" +# Python 3 compatibility for Python 2 +from __future__ import print_function + +# args, logging +import argparse +import logging +import os +import sys + +from pymcuprog.deviceinfo.configgenerator import ConfigGenerator + +def main(args, loglevel): + """ + Main program + """ + logging.basicConfig(format="%(levelname)s: %(message)s", level=loglevel) + + # Enforce XML output + if args.filename: + if os.path.splitext(args.filename)[1] != '.xml': + print ("Target filename (-f) must be of type .xml") + sys.exit(-1) + + generator = ConfigGenerator() + + generator.load_device_model(args.device, args.packpath) + generator.process_programming_functions() + contents = generator.get_xml_string() + if args.filename: + print("Writing to file '{0:s}'".format(args.filename)) + with open(args.filename, "w") as xmlfile: + xmlfile.write(contents) + else: + print("Config generator output:") + print(contents) + print("Done") + +PARSER = argparse.ArgumentParser(description="Config generator") + +# Device to program +PARSER.add_argument("device", + help="device to use") + +# Pack path +PARSER.add_argument("-p", "--packpath", + type=str, + help="path to pack") + +PARSER.add_argument("-f", "--filename", + type=str, + help="file to write") + +PARSER.add_argument("-v", "--verbose", + help="verbose output", + action="store_true") + +ARGUMENTS = PARSER.parse_args() + +# Setup logging +if ARGUMENTS.verbose: + LOGGING_LEVEL = logging.INFO +else: + LOGGING_LEVEL = logging.WARNING + +main(ARGUMENTS, LOGGING_LEVEL) diff --git a/pymcuprog/deviceinfo/harvest.py b/pymcuprog/deviceinfo/harvest.py new file mode 100644 index 0000000..441cb63 --- /dev/null +++ b/pymcuprog/deviceinfo/harvest.py @@ -0,0 +1,479 @@ +""" +Harvester scripts + +Currently only supports AVR atdf files +""" +# Python 3 compatibility for Python 2 +from __future__ import print_function + +import collections +import argparse +from os import read +import textwrap +from xml.etree import ElementTree +from pymcuprog.deviceinfo import deviceinfokeys + +from pymcuprog.deviceinfo.memorynames import MemoryNames +from pymcuprog.deviceinfo.deviceinfokeys import DeviceMemoryInfoKeys, DeviceInfoKeysAvr + +# High voltage implementations as defined on https://confluence.microchip.com/x/XVxcE +HV_IMPLEMENTATION_SHARED_UPDI = "0" +HV_IMPLEMENTATION_DEDICATED_UPDI = "1" +HV_IMPLEMENTATION_SEPARATE_PIN = "2" + +def map_atdf_memory_name_to_pymcuprog_name(atdf_name): + """ + Mapping a memory name in atdf files to the corresponding memory name used in the pymcuprog device models + + Note that the same memory can have different names in the same atdf file depending on the element used as + definition, i.e. memory-segment element or module element + :param atdf_name: Name of memory in atdf files + :return: Name of memory in pymcuprog device models + """ + pymcuprog_name = 'unknown' + atdf_name = atdf_name.lower() + if atdf_name == 'progmem': + pymcuprog_name = MemoryNames.FLASH + if atdf_name in ['user_signatures', 'userrow']: + # Datasheets actually use user_row for UPDI devices at least + pymcuprog_name = MemoryNames.USER_ROW + if atdf_name == 'eeprom': + pymcuprog_name = MemoryNames.EEPROM + if atdf_name in ['fuses', 'fuse']: + pymcuprog_name = MemoryNames.FUSES + if atdf_name in ['lockbits', 'lock']: + pymcuprog_name = MemoryNames.LOCKBITS + if atdf_name in ['signatures', 'sigrow']: + pymcuprog_name = MemoryNames.SIGNATURES + if atdf_name == 'internal_sram': + pymcuprog_name = MemoryNames.INTERNAL_SRAM + + return pymcuprog_name + +def determine_chiperase_effect(memoryname, architecture): + """ + Determine if memory is erased by a chip erase + + :param memoryname: Name of memory as defined in pymcuprog.deviceinfo.memorynames + :type memoryname: str + :param architecture: Architecture as defined in atdf file + :type architecture: str + :return: Chip erase effect + :rtype: str + """ + if 'avr' in architecture: + if memoryname in [MemoryNames.USER_ROW, MemoryNames.FUSES, MemoryNames.SIGNATURES, MemoryNames.INTERNAL_SRAM]: + return 'ChiperaseEffect.NOT_ERASED' + elif memoryname in [MemoryNames.LOCKBITS, MemoryNames.FLASH]: + return 'ChiperaseEffect.ALWAYS_ERASED' + elif memoryname in [MemoryNames.EEPROM]: + return 'ChiperaseEffect.CONDITIONALLY_ERASED_AVR' + + return '# To be filled in manually' + +def determine_isolated_erase(memoryname, architecture): + """ + Determine if memory can be erased without side effects + + :param memoryname: Name of memory as defined in pymcuprog.deviceinfo.memorynames + :type memoryname: str + :param architecture: Architecture as defined in atdf file + :type architecture: str + :return: 'True' if memory can be erased in isolation, 'False' if not. + :rtype: str + """ + if 'avr' in architecture: + if 'avr8x' in architecture and memoryname in [MemoryNames.FLASH]: + # UPDI devices now supports isolated erase for flash + return 'True' + if memoryname in [MemoryNames.USER_ROW, MemoryNames.EEPROM]: + return 'True' + elif memoryname in [MemoryNames.INTERNAL_SRAM, MemoryNames.LOCKBITS, MemoryNames.FLASH, MemoryNames.FUSES, MemoryNames.SIGNATURES]: + return 'False' + + return '# To be filled in manually' + +def determine_write_size(memoryname, pagesize, devicename): + """ + Determine write granularity for memory + + :param memoryname: Name of memory as defined in pymcuprog.deviceinfo.memorynames + :type memoryname: str + :param pagesize: Page size of memory + :type pagesize: str or int + :return: Write granularity as string + :rtype: str + """ + write_size = "0x01" + devicename = devicename.lower() + if memoryname == 'flash': + if (devicename.find('avr') != -1 and ((devicename.find('da') != -1) or (devicename.find('db') != -1))): + write_size = "0x02" + else: + write_size = pagesize + if memoryname == "user_row": + if devicename.find('avr') != -1 and devicename.find('ea') != -1: + # For AVR EA user row the complete page must be written + write_size = pagesize + elif memoryname == 'signatures': + write_size = "0x00" + return write_size + +def determine_read_size(memoryname): + """ + Determine read granularity for memory + + :param memoryname: Name of memory as defined in pymcuprog.deviceinfo.memorynames + :type memoryname: str + :return: Read granularity as string + :rtype: str + """ + # Read size is always 1 byte except for flash that can only read complete words + readsize = "0x01" + if memoryname in [MemoryNames.FLASH]: + readsize = "0x02" + + return readsize + +def capture_memory_segment_attributes(attributes, memories): + """ + Capture memory attributes for memory segment + + :param attributes: Memory attributes to capture (from atdf) + :type attributes: xml.etree.ElementTree.Element instance + :param memories: Dictionary with memory information. Captured data will be added to this dict. + :type memories: dict + """ + name = attributes['name'].lower() + size = attributes['size'] + start = attributes['start'] + + try: + pagesize = attributes['pagesize'] + except KeyError: + pagesize = "0x01" + # For some AVRs the ATDF gives a pagesize of fuses and lockbits equal to flash or EEPROM page size but fuses and + # lockbits are always byte accessible. + if name in ['fuses', 'lockbits']: + pagesize = '0x01' + output = "" + # These names are the names used in the atdf files and might differ from the pymcuprog MemoryNames + if name in ['progmem', 'eeprom', 'user_signatures', 'fuses', 'lockbits', 'signatures', 'internal_sram']: + print_name = map_atdf_memory_name_to_pymcuprog_name(name) + if not print_name in memories: + memories[print_name] = {} + memories[print_name][DeviceMemoryInfoKeys.ADDRESS] = start + memories[print_name][DeviceMemoryInfoKeys.SIZE] = size + memories[print_name][DeviceMemoryInfoKeys.PAGE_SIZE] = pagesize + +def capture_register_offset(name, offset): + """ + Wrapper to create a string definition + + :param name: register name + :type name: str + :param offset: register offset + :type offset: str + :return: string of register and offset + :rtype: str + """ + return capture_field("{}_base".format(name.lower()), offset) + + +def capture_field(field, value): + """ + Macro to create text format field + + :param field: register name + :type field: str + :param value: register value + :type value: str + :return: string of definition + :rtype: str + """ + try: + _test_value = int(value, 16) + except (ValueError, AttributeError): + # Can't convert string to int, assumed to be string + return " '{}': '{}',\n".format(field, value) + return " '{}': {},\n".format(field, value) + +def capture_device_data_from_device_element(element): + """ + Capture device data from a device element + + :param element: element with tag='device' + :type element: xml.etree.ElementTree.Element instance + :return: captured data from the device element as a string + :rtype: str + """ + architecture = element.attrib['architecture'].lower() + output = capture_field('name', element.attrib['name'].lower()) + output += capture_field('architecture', architecture) + return output + +def capture_memory_segments_from_device_element(element, memories): + """ + Capture memory segment data from a device element + + :param element: element with tag='device' + :type element: xml.etree.ElementTree.Element instance + :return: captured data from the device element as a string + :rtype: str + """ + output = "" + for i in element.iterfind("address-spaces/address-space/memory-segment"): + capture_memory_segment_attributes(i.attrib, memories) + return output + +def capture_module_element(element): + """ + Capture data from a module element + + This function will return data captured from the module element but will also check if the module + element contains info about an UPDI fuse (fuse to configure a shared UPDI pin) + :param element: element with tag='module' + :type element: xml.etree.ElementTree.Element instance + :return: tuple of + * output - captured module element data as a string + * found_updi_fuse - True if the module element contained info about an UPDI fuse + :rtype: tuple + """ + output = "" + found_updi_fuse = False + for i in element.iterfind("instance/register-group"): + name = i.attrib['name'] + offset = "0x{:08X}".format(int(i.attrib['offset'], 16)) + if i.attrib['name'] == 'SYSCFG': + output += capture_register_offset(name, offset) + output += capture_register_offset('OCD', "0x{:08X}".format(int(offset, 16) + 0x80)) + if i.attrib['name'] == 'NVMCTRL': + output += capture_register_offset(name, offset) + for i in element.iterfind("instance/signals/signal"): + if i.attrib['group'] == 'UPDI' and i.attrib['pad'] is not None: + output += capture_field('prog_clock_khz', '900') + found_updi_fuse = True + return output, found_updi_fuse + +def capture_memory_module_element(element, memories): + """ + Capture memory information from a memory module element + + :param element: Element with tag='module' + :type element: xml.etree.ElementTree.Element instance + :param memories: Dictionary with memory information. Captured memory information will be added to this + dictionary + :type memories: dict + """ + output = "" + memoryname = map_atdf_memory_name_to_pymcuprog_name(element.attrib['name']) + if not memoryname in memories: + # Discovered new memory, add it to the dictionary + memories[memoryname] = {} + # All memories defined as memory modules in the device element can be read and written a single byte at a time + memories[memoryname][DeviceMemoryInfoKeys.READ_SIZE] = "0x01" + memories[memoryname][DeviceMemoryInfoKeys.PAGE_SIZE] = "0x01" + if memoryname in ['sigrow']: + # Signatures can't be written at all + memories[memoryname][DeviceMemoryInfoKeys.WRITE_SIZE] = "0x00" + else: + memories[memoryname][DeviceMemoryInfoKeys.WRITE_SIZE] = "0x01" + for rg in element.iterfind("instance/register-group"): + # Offset is found in the module instance register group + memories[memoryname][DeviceMemoryInfoKeys.ADDRESS] = rg.attrib['offset'] + for rg in element.iterfind("register-group"): + # Size is found in the module register group + if 'size' in rg.attrib: + memories[memoryname][DeviceMemoryInfoKeys.SIZE] = rg.attrib['size'] + if element.attrib['name'].lower() in ['userrow']: + # For user row set the page size equal to the size since this makes most sense when printing memory + # content and when erasing, even though the write granularity is one byte + memories[memoryname][DeviceMemoryInfoKeys.PAGE_SIZE] = rg.attrib['size'] + else: + memories[memoryname][DeviceMemoryInfoKeys.SIZE] = "UNKNOWN" + +def capture_signature_from_property_groups_element(element): + """ + Capture signature (Device ID) data from a property-group element + + :param element: element with tag='property-groups' + :type element: xml.etree.ElementTree.Element instance + :return: bytearray with 3 bytes of Device ID data + :rtype: bytearray + """ + signature = bytearray(3) + for i in element.findall('property-group/property'): + if i.attrib['name'] == 'SIGNATURE0': + signature[0] = int(i.attrib['value'], 16) + if i.attrib['name'] == 'SIGNATURE1': + signature[1] = int(i.attrib['value'], 16) + if i.attrib['name'] == 'SIGNATURE2': + signature[2] = int(i.attrib['value'], 16) + return signature + +def get_flash_offset(element): + """ + Fetch flash memory offset from element + + :param element: Element with tag='property-groups' + :type element: xml.etree.ElementTree.Element instance + :return: Flash offset as string + :rtype: str + """ + flash_offset = "0x00000000" + for i in element.iterfind("property-group/property"): + if i.attrib['name'] == 'PROGMEM_OFFSET': + flash_offset = i.attrib['value'] + return flash_offset + +def get_hv_implementation(element): + """ + Fetch High Voltage implementation from element + + :param element: Element with tag='property-groups' + :type element: xml.etree.ElementTree.Element instance + :return: High Voltage implementation as string (defined on https://confluence.microchip.com/x/XVxcE) + :rtype: str + """ + hv_implementation = None + for i in element.iterfind("property-group/property"): + if i.attrib['name'] == 'HV_IMPLEMENTATION': + hv_implementation = i.attrib['value'] + + return hv_implementation + +def determine_address_size(flash_offset): + """ + Determine number of address bits needed for Flash + + :param flash_offset: Flash offset from atdf + :type flash_offset: str + :return: Address size ('16-bit' or '24-bit') + :rtype: str + """ + address_size = '16-bit' + if flash_offset is not None: + flash_offset = int(flash_offset, 16) + if flash_offset > 0xFFFF: + address_size = '24-bit' + return address_size + +def harvest_from_file(filename): + """ + Harvest parameters from a file + + :param filename: path to file to parse + :type filename: str + :return: list of parameters + :rtype: str + """ + xml_iter = ElementTree.iterparse(filename) + output = "" + device_fields = "" + extra_fields = "\n # Some extra AVR specific fields\n" + + shared_updi = False + progmem_offset = None + hv_implementation = None + memories = {} + for event, elem in xml_iter: + if event == 'end': + if elem.tag == 'device': + devicename = elem.attrib['name'] + # Note module elements are part of the device element so the memories represented by modules will + # already be collected when reaching end of device element + capture_memory_segments_from_device_element(elem, memories) + device_fields += capture_device_data_from_device_element(elem) + architecture = elem.attrib['architecture'].lower() + if elem.tag == 'module': + # Some memories are defined as module elements (in addition to memory segments). These module + # definitions are preferred as they give more accurate size definitions for some memories like fuses + # and lockbits. + if elem.attrib['name'].lower() in ['sigrow', 'fuse', 'lock', 'userrow']: + capture_memory_module_element(elem, memories) + module, found_updi_fuse = capture_module_element(elem) + extra_fields += module + if found_updi_fuse: + shared_updi = True + if elem.tag == 'interface': + device_fields += capture_field(elem.tag, elem.attrib['name']) + if elem.tag == 'property-groups': + signature = capture_signature_from_property_groups_element(elem) + progmem_offset = get_flash_offset(elem) + hv_implementation = get_hv_implementation(elem) + + extra_fields += capture_field('address_size', determine_address_size(progmem_offset)) + if not shared_updi: + extra_fields += capture_field(DeviceInfoKeysAvr.PROG_CLOCK_KHZ, '1800') + + hv_comment = None + if not hv_implementation: + if shared_updi: + hv_implementation = HV_IMPLEMENTATION_SHARED_UPDI + hv_comment = f" # Missing hv_implementation property in ATDF file\n # Defaulting to {hv_implementation} for devices with UPDI fuse\n" + else: + hv_implementation = HV_IMPLEMENTATION_DEDICATED_UPDI + hv_comment = f" # Missing hv_implementation property in ATDF file\n # Defaulting to {hv_implementation} for devices without UPDI fuse\n" + + if hv_comment: + extra_fields += hv_comment + extra_fields += capture_field(DeviceInfoKeysAvr.HV_IMPLEMENTATION, hv_implementation) + + + extra_fields += capture_field(DeviceInfoKeysAvr.DEVICE_ID, + "0x{:02X}{:02X}{:02X}".format(signature[0], signature[1], signature[2])) + + # Replace "flash start" with "progmem_offset" + if progmem_offset and int(progmem_offset, 16) > 0: + memories[MemoryNames.FLASH][DeviceMemoryInfoKeys.ADDRESS] = progmem_offset + + # Build the output + output += device_fields + sorted_memories = collections.OrderedDict(sorted(memories.items())) + for memory in sorted_memories: + output += "\n # {}\n".format(memory) + output += capture_field('{}_{}_byte'.format(memory, DeviceMemoryInfoKeys.ADDRESS), + sorted_memories[memory][DeviceMemoryInfoKeys.ADDRESS]) + output += capture_field('{}_{}_bytes'.format(memory, DeviceMemoryInfoKeys.SIZE), + sorted_memories[memory][DeviceMemoryInfoKeys.SIZE]) + output += capture_field('{}_{}_bytes'.format(memory, DeviceMemoryInfoKeys.PAGE_SIZE), + sorted_memories[memory][DeviceMemoryInfoKeys.PAGE_SIZE]) + output += " '{}_{}_bytes': {},\n".format(memory, + DeviceMemoryInfoKeys.READ_SIZE, + determine_read_size(memory)) + output += " '{}_{}_bytes': {},\n".format(memory, + DeviceMemoryInfoKeys.WRITE_SIZE, + determine_write_size(memory, + sorted_memories[memory][DeviceMemoryInfoKeys.PAGE_SIZE], + devicename)) + output += " '{}_{}': {},\n".format(memory, DeviceMemoryInfoKeys.CHIPERASE_EFFECT, determine_chiperase_effect(memory, architecture)) + output += " '{}_{}': {},\n".format(memory, DeviceMemoryInfoKeys.ISOLATED_ERASE, determine_isolated_erase(memory, architecture)) + + output += extra_fields + return output + +def main(): + """ + Main function for the harvest utility + """ + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=textwrap.dedent('''\ + Harvests device data from a device data file (.atdf) for one device. + + The harvested data can be used to populate a device file in deviceinfo.devices + ''')) + + parser.add_argument("filename", + help="name (and path) of file to harvest data from" + ) + + arguments = parser.parse_args() + + dict_content = harvest_from_file(arguments.filename) + content = "\nfrom pymcuprog.deviceinfo.eraseflags import ChiperaseEffect\n\n" + content += "DEVICE_INFO = {{\n{}}}".format(dict_content) + print(content) + +if __name__ == "__main__": + main() diff --git a/pymcuprog/deviceinfo/memorynames.py b/pymcuprog/deviceinfo/memorynames.py new file mode 100644 index 0000000..68e21ca --- /dev/null +++ b/pymcuprog/deviceinfo/memorynames.py @@ -0,0 +1,41 @@ +#pylint: disable=too-few-public-methods +""" +Memory name definitions +""" + +class MemoryNameAliases(object): + """ + Memory names that are actually not real memories but an alias for several memories + """ + ALL = 'all' + +class MemoryNames(object): + """ + Memory names corresponding to target device memories + """ + # Real memories + FLASH = 'flash' + CONFIG_WORD = 'config_words' + USER_ID = 'user_id' + USER_ROW = 'user_row' + EEPROM = 'eeprom' + FUSES = 'fuses' + CALIBRATION_ROW = 'calibration_row' + ICD = 'icd' + LOCKBITS = 'lockbits' + SIGNATURES = 'signatures' + INTERNAL_SRAM = 'internal_sram' + + @classmethod + def get_all(cls): + """ + Get a list of all memories representing actual device memories + + :return List of all memory names representing actual device memories + """ + all_memories = [] + for attribute in dir(cls): + if not attribute.startswith('__') and not callable(getattr(cls, attribute)): + all_memories.append(getattr(cls, attribute)) + + return all_memories diff --git a/pymcuprog/hexfileutils.py b/pymcuprog/hexfileutils.py new file mode 100644 index 0000000..79bf4e2 --- /dev/null +++ b/pymcuprog/hexfileutils.py @@ -0,0 +1,159 @@ +""" +Module providing read and write functionality towards hex files with data intended for target device memories +""" +import copy +import os +from array import array +from collections import namedtuple +from intelhex import IntelHex +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path # python 2 backport + +from .deviceinfo.deviceinfokeys import DeviceMemoryInfoKeys, DeviceInfoKeys + +def write_memories_to_hex(filename, memory_segments): + """ + Write a collection of memory segments to a hex file + + Each segment will be written from relative offset 0 (i.e. start of each memory segment) + + :param filename: Name/path of hex file to write to + :param memory_segments: list of namedtuples with two fields: data and memory_info. data contains a + byte array of raw data bytes and memory_info is a dictionary with memory information as defined + in deviceinfo.deviceinfo.DeviceMemoryInfo. + """ + hexfile = IntelHex() + + for memory_segment in memory_segments: + _add_data_to_hex(hexfile, memory_segment.data, memory_segment.memory_info) + + _write_hex_to_file(hexfile, filename) + +def write_memory_to_hex(filename, memory_segment, offset): + """ + Write one memory segment to a hex file with data starting at relative offset given by offset parameter. + + :param filename: Name/path of hex file to write to + :param memory_segment: namedtuple with two fields: data and memory_info. data contains a byte array + of raw data bytes and memory_info is a dictionary with memory information as defined in + deviceinfo.deviceinfo.DeviceMemoryInfo). + :param offset: Relative offset for the data within the memory segment + """ + hexfile = IntelHex() + + _add_data_to_hex(hexfile, memory_segment.data, memory_segment.memory_info, offset) + + _write_hex_to_file(hexfile, filename) + +def read_memories_from_hex(filename, device_memory_info): + """ + Read the content of a hexfile + + :param filename: Name/path of hex file to read from + :param device_memory_info: DeviceMemoryInfo instance for the device the hex file is intended for + :returns: list of namedtuples with three fields: data, offset and memory_info. data contains a byte array + of raw data bytes, offset is the start address within the memory the data starts at and memory_info + is a dictionary with the memory info as defined in pymcuprog.deviceinfo.deviceinfo + """ + hexfile = IntelHex() + hexfile.fromfile(filename, format='hex') + + memory_segments = [] + for segment in hexfile.segments(): + start = segment[0] + stop = segment[1] + + subsegment_start = start + subsegment_stop = start + while subsegment_stop < stop: + current_memory_info = device_memory_info.memory_info_by_address(subsegment_start, + DeviceMemoryInfoKeys.HEXFILE_ADDRESS, + DeviceMemoryInfoKeys.HEXFILE_SIZE) + + if current_memory_info is None: + raise IndexError( + "Hexfile contains data at hex address 0x{:X} which is outside any memory".format(subsegment_start)) + + current_hexfile_address = current_memory_info[DeviceMemoryInfoKeys.HEXFILE_ADDRESS] + current_hexfile_size = current_memory_info[DeviceMemoryInfoKeys.HEXFILE_SIZE] + subsegment_stop = current_hexfile_address + current_hexfile_size + if stop < subsegment_stop: + # Reached end of segment + subsegment_stop = stop + memory_tuple = namedtuple('MemorySegment', 'data offset memory_info') + + data = hexfile.tobinarray(start=subsegment_start, end=subsegment_stop - 1) + current_size = current_memory_info[DeviceMemoryInfoKeys.SIZE] + if current_hexfile_size == current_size*2: + # There are phantom bytes in the hexfile (PIC16 EEPROM), so every 2nd byte should be removed + data = remove_phantom_bytes(data) + + memory_tuple.data = data + memory_tuple.memory_info = current_memory_info + memory_tuple.offset = subsegment_start - current_hexfile_address + + memory_segments.append(copy.deepcopy(memory_tuple)) + + subsegment_start = subsegment_stop + + return memory_segments + +def remove_phantom_bytes(data): + """ + Remove every 2nd byte from the data + """ + data_stripped = [] + for index in range(0, len(data), 2): + data_stripped.append(data[index]) + # Make a bin array out of the data list to be consistent with the data format of + # the data fetched directly from the hex file + data_stripped_binarray = array('B') + data_stripped_binarray.fromlist(data_stripped) + return data_stripped_binarray + +def _add_data_to_hex(intelhex, data, memory_info, offset=0): + """ + Add given data starting at relative index offset to IntelHex instance intelhex + + :param intelhex: IntelHex object + :param data: raw data bytes + :param memory_info: memory info as provided by pymcuprog.deviceinfo.deviceinfo + :param offset: relative offset within the memory + """ + hexfile_address_key = DeviceMemoryInfoKeys.HEXFILE_ADDRESS + hexfile_size_key = DeviceMemoryInfoKeys.HEXFILE_SIZE + size_key = DeviceMemoryInfoKeys.SIZE + name = memory_info[DeviceInfoKeys.NAME] + + if offset+len(data) > memory_info[hexfile_size_key]: + raise IndexError( + "Attempting to write outside boundary of {} memory ({} bytes starting at offset {})".format(name, + len(data), + offset)) + + hex_offset = memory_info[hexfile_address_key] + offset + if memory_info[hexfile_size_key] == memory_info[size_key]*2: + # Hex file should contain one phantom byte per data byte in the hex file (PIC16 EEPROM) + for i, dat in enumerate(data): + intelhex[i*2 + hex_offset] = data[i] + intelhex[i*2 + 1 + hex_offset] = 0 & 0xFF + else: + for i, dat in enumerate(data): + intelhex[i + hex_offset] = dat + +def _write_hex_to_file(intelhex, filename): + """ + Write intelhex object to file. + + Directories will be created if path does not exist + + :param intelhex: IntelHex instance + :param filename: Name/path to write intelhex object to + """ + directory = os.path.dirname(filename) + if directory != '' and not os.path.exists(directory): + Path(directory).mkdir(exist_ok=True, parents=True) + + intelhex.write_hex_file(filename) diff --git a/pymcuprog/logging.yaml b/pymcuprog/logging.yaml new file mode 100644 index 0000000..5127ade --- /dev/null +++ b/pymcuprog/logging.yaml @@ -0,0 +1,57 @@ +version: 1 +disable_existing_loggers: False +formatters: + timestamped: + format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + detailed: + format: "%(name)s - %(levelname)s - %(message)s" + simple: + format: "%(message)s" + +handlers: + # Logging to the console is default to WARNING with detailed output: + console: + class: logging.StreamHandler + level: WARNING + formatter: detailed + stream: ext://sys.stdout + + # Logging debug output to file + # Handler disabled by default - for reference only + debug_file_handler: + class: logging.FileHandler + level: DEBUG + formatter: timestamped + # File path will be user log directory for this application + filename: debug.log + encoding: utf8 + + # Logging errors to file + # Handler disabled by default - for reference only + error_file_handler: + class: logging.handlers.RotatingFileHandler + level: ERROR + formatter: timestamped + # File path will be user log directory for this application + filename: errors.log + maxBytes: 10485760 # 10MB + backupCount: 20 + encoding: utf8 + +loggers: + # pyedbglib library should be kept to critical errors to console only + pyedbglib: + level: ERROR + handlers: [console] + propagate: no + +root: + # Default level is warning + # this is increased with -v in CLI usage + level: WARNING + # Default handlers is console only + handlers: [console] + # Add debug_file_handler for debug output to file + # Add error_file_handler for error output to file + # See configuration in handlers section above + #handlers: [console, debug_file_handler, error_file_handler] diff --git a/pymcuprog/nvm.py b/pymcuprog/nvm.py new file mode 100644 index 0000000..c4d3ae0 --- /dev/null +++ b/pymcuprog/nvm.py @@ -0,0 +1,148 @@ +""" +NVM layer protocols +""" +# Python 3 compatibility for Python 2 +from __future__ import print_function +from logging import getLogger + +from .deviceinfo.deviceinfokeys import DeviceInfoKeys +from .pymcuprog_errors import PymcuprogSessionConfigError + +def get_nvm_access_provider(transport, device_info, interface="", packpath=None, frequency=None, options=""): + """ + Returns an NVM provider with the requested properties + + :param transport: Transport layer object + :param device_info: Device info dict + :param interface: Physical interface for NVM + :param packpath: Path to pack + :param frequency: Interface clock + :param options: Special options + :return: NVM access object + """ + # Although it is considered best practice to have imports at top level, in this case it makes sense to have the + # imports on the function level as in most cases only one import will be used. Having all imports at the top + # level will then be a waste of resources. + #pylint: disable=import-outside-toplevel + # There will be cyclic imports since the modules imported below containing NVM Access providers will import + # from the current module since all NVM Access providers inherits from the NVM Access provider base classes + # defined in the current module, but this should be ok since the imports below are late. + #pylint: disable=cyclic-import + accessprovider = None + architecture = device_info[DeviceInfoKeys.ARCHITECTURE].lower() + if not interface and DeviceInfoKeys.INTERFACE in device_info: + interface = device_info[DeviceInfoKeys.INTERFACE].lower() + + if architecture in ['pic16', 'pic18', 'pic24', 'dspic33']: + from .nvmpic import NvmAccessProviderCmsisDapPic + accessprovider = NvmAccessProviderCmsisDapPic(transport, device_info, packpath, options=options) + + elif architecture == 'avr8x': + if isinstance(transport, str): + if interface == 'updi': + from .nvmserialupdi import NvmAccessProviderSerial + accessprovider = NvmAccessProviderSerial(transport, device_info, baud=frequency, options=options) + elif interface == 'updi': + from .nvmupdi import NvmAccessProviderCmsisDapUpdi + accessprovider = NvmAccessProviderCmsisDapUpdi(transport, device_info=device_info, + frequency=frequency, options=options) + elif architecture == 'avr8': + if interface == 'isp': + from .nvmspi import NvmAccessProviderCmsisDapSpi + accessprovider = NvmAccessProviderCmsisDapSpi(transport, device_info) + elif interface == "debugwire": + from .nvmdebugwire import NvmAccessProviderCmsisDapDebugwire + accessprovider = NvmAccessProviderCmsisDapDebugwire(transport, device_info) + elif interface == "jtag": + from .nvmmegaavrjtag import NvmAccessProviderCmsisDapMegaAvrJtag + accessprovider = NvmAccessProviderCmsisDapMegaAvrJtag(transport, device_info) + else: + raise PymcuprogSessionConfigError("Interface not specified: use --interface [isp | jtag | debugwire]") + elif architecture == "xmega": + if interface == "pdi": + from .nvmxmega import NvmAccessProviderCmsisDapXmega + accessprovider = NvmAccessProviderCmsisDapXmega(transport, device_info) + elif architecture == "avrtinytiny": + if interface == "tpi": + from .nvmtpi import NvmAccessProviderCmsisDapTpi + accessprovider = NvmAccessProviderCmsisDapTpi(transport, device_info) + elif architecture == 'cortex-m0plus': + from .nvmmzeroplus import NvmAccessProviderCmsisDapMZeroPlus + accessprovider = NvmAccessProviderCmsisDapMZeroPlus(transport, device_info, frequency) + elif architecture == 'avr32': + from .nvmavr32 import NvmAccessProviderCmsisDapAvr32 + accessprovider = NvmAccessProviderCmsisDapAvr32(transport, device_info) + + return accessprovider + +class NvmAccessProvider: + """ + Wrapper for device info + """ + + def __init__(self, device_info): + self.device_info = device_info + self.logger = getLogger(__name__) + + def _log_incomplete_stack(self, device_stack, beta=False): + """ + Used to tell the user this device stack is not completed yet + + :param device_stack: User friendly name of target stack + :param beta: Suppress warnings for beta stacks - info loglevel is used for beta. + """ + if beta: + self.logger.info("%s stack is in Beta state", device_stack) + else: + self.logger.warning("") + self.logger.warning("%s stack is in Alpha state", device_stack) + self.logger.warning("Expect some features to be missing") + self.logger.warning("") + + def start(self, user_interaction_callback=None): + """ + Start (activate) session + + :param user_interaction_callback: Callback to be called when user interaction is required, + for example when doing UPDI high-voltage activation with user target power toggle. + This function could ask the user to toggle power and halt execution waiting for the user + to respond (this is default behavior if the callback is None), or if the user is another + script it could toggle power automatically and then return. + """ + #pylint: disable=unused-argument + self.logger.debug("No specific initializer for this provider") + + def stop(self): + """ + Stop (deactivate) session + """ + self.logger.debug("No specific de-initializer for this provider") + + def hold_in_reset(self): + """ + Hold target in reset + """ + self.logger.debug("hold_in_reset not implemented for this provider") + + def release_from_reset(self): + """ + Release target from reset + """ + self.logger.debug("release_from_reset not implemented for this provider") + +class NvmAccessProviderCmsisDapTool(NvmAccessProvider): + """ + General CMSIS-DAP Tool + """ + + def __init__(self, device_info): + NvmAccessProvider.__init__(self, device_info) + + +class NvmAccessProviderCmsisDapAvr(NvmAccessProviderCmsisDapTool): + """ + AVR CMSIS DAP Tool + """ + + def __init__(self, device_info): + NvmAccessProviderCmsisDapTool.__init__(self, device_info) diff --git a/pymcuprog/nvmavr32.py b/pymcuprog/nvmavr32.py new file mode 100644 index 0000000..d71b7db --- /dev/null +++ b/pymcuprog/nvmavr32.py @@ -0,0 +1,76 @@ +""" +AVR32 NVM implementation +NB: This is a stub - not all features are implemented +""" +from abc import abstractmethod + +from .nvm import NvmAccessProviderCmsisDapAvr +from .avr32target import Avr32Device +from .pymcuprog_errors import PymcuprogError +from .deviceinfo.deviceinfokeys import DeviceInfoKeysAvr32 + +class NvmAccessProviderCmsisDapAvr32(NvmAccessProviderCmsisDapAvr): + """ + AVR32 programmer + """ + + def __init__(self, transport, device_info, interface="jtag"): + NvmAccessProviderCmsisDapAvr.__init__(self, device_info) + + self._log_incomplete_stack('AVR32') + + if DeviceInfoKeysAvr32.RESET_DOMAINS in device_info: + self.avr = Avr32Device(transport, device_info[DeviceInfoKeysAvr32.RESET_DOMAINS]) + else: + self.avr = Avr32Device(transport) + self.avr.setup_prog_session(interface) + + def __del__(self): + pass + + def read_device_id(self): + """ + Read the device info + + :returns: Device ID raw bytes (little endian) + """ + resp = self.avr.activate_physical() + self.logger.info("JTAG ID read: %02X%02X%02X%02X", resp[3], resp[2], resp[1], resp[0]) + self.avr.deactivate_physical() + return bytearray(resp[0:4]) + + @abstractmethod + def read(self, memory_info, offset, numbytes): + """ + Read the memory in chunks + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset in the memory type + :param numbytes: number of bytes to read + :return: array of bytes read + """ + raise PymcuprogError("Implement read") + + @abstractmethod + def write(self, memory_info, offset, data): + """ + Write the memory with data + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset within the memory type + :param data: the data to program + """ + raise PymcuprogError("Implement write") + + @abstractmethod + def erase(self, memory_info=None, address=None): + """ + Do an erase of the device + """ + raise PymcuprogError("Implement erase") + + def stop(self): + """ + Stop programming session + """ + return diff --git a/pymcuprog/nvmdebugwire.py b/pymcuprog/nvmdebugwire.py new file mode 100644 index 0000000..b2d3da4 --- /dev/null +++ b/pymcuprog/nvmdebugwire.py @@ -0,0 +1,78 @@ +""" +DebugWIRE NVM implementation +NB: This is a stub - not all features are implemented +""" +from pyedbglib.protocols.jtagice3protocol import Jtagice3ResponseError + +from .nvm import NvmAccessProviderCmsisDapAvr +from .avr8target import TinyAvrTarget +from .pymcuprog_errors import PymcuprogError + +class NvmAccessProviderCmsisDapDebugwire(NvmAccessProviderCmsisDapAvr): + """ + NVM Access the DW way + """ + + def __init__(self, transport, device_info): + NvmAccessProviderCmsisDapAvr.__init__(self, device_info) + + self._log_incomplete_stack('AVR-debugWIRE') + + self.avr = TinyAvrTarget(transport) + self.avr.setup_debug_session() + + def __del__(self): + pass + + def read_device_id(self): + """ + Read the device info + + :returns: Device ID raw bytes (little endian) + """ + try: + resp = self.avr.activate_physical() + except Jtagice3ResponseError: + msg = "Unable to activate debugWIRE. Maybe ISP is active?" + self.logger.error(msg) + raise PymcuprogError(msg) + self.logger.info("ID read: %02X%02X", resp[1], resp[0]) + self.avr.deactivate_physical() + return bytearray(resp[0:2]) + + def erase(self, memory_info=None, address=None): + """ + Do a chip erase of the device + """ + _dummy = memory_info + _dummy = address + self.logger.info("Unable to erase device using debugWIRE") + + @staticmethod + def write(memory_info, offset, data): + """ + Write the memory with data + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset within the memory type + :param data: the data to program + """ + _dummy = memory_info + _dummy = offset + _dummy = data + # return None + + @staticmethod + def read(memory_info, offset, numbytes): + """ + Read the memory in chunks + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset in the memory type + :param numbytes: number of bytes to read + :return: array of bytes read + """ + _dummy = memory_info + _dummy = offset + _dummy = numbytes + return [] diff --git a/pymcuprog/nvmmegaavrjtag.py b/pymcuprog/nvmmegaavrjtag.py new file mode 100644 index 0000000..01ead48 --- /dev/null +++ b/pymcuprog/nvmmegaavrjtag.py @@ -0,0 +1,119 @@ +""" +AVR mega JTAG NVM implementation +NB: This is a stub - not all features are implemented +""" +from pyedbglib.protocols.jtagice3protocol import Jtagice3ResponseError +from pyedbglib.protocols.avr8protocol import Avr8Protocol + +from .nvm import NvmAccessProviderCmsisDapAvr +from .avr8target import MegaAvrJtagTarget +from .pymcuprog_errors import PymcuprogError, PymcuprogSessionError + +from .deviceinfo.deviceinfokeys import DeviceInfoKeysAvr, DeviceMemoryInfoKeys +from .deviceinfo.memorynames import MemoryNames + +class NvmAccessProviderCmsisDapMegaAvrJtag(NvmAccessProviderCmsisDapAvr): + """ + NVM Access the megaJTAG way + """ + + def __init__(self, transport, device_info): + NvmAccessProviderCmsisDapAvr.__init__(self, device_info) + + self._log_incomplete_stack('megaAVR-JTAG') + self.avr = MegaAvrJtagTarget(transport) + self.avr.setup_config(device_info) + self.avr.setup_prog_session() + + def __del__(self): + pass + + def start(self, user_interaction_callback=None): + """ + Start (activate) session for megaJTAG targets + """ + try: + resp = self.avr.activate_physical() + except Jtagice3ResponseError as error: + # The debugger could be out of sync with the target, retry + if error.code == Avr8Protocol.AVR8_FAILURE_INVALID_PHYSICAL_STATE: + self.logger.info("Physical state out of sync. Retrying.") + self.avr.deactivate_physical() + self.avr.activate_physical() + else: + raise PymcuprogSessionError("Unable to activate JTAG interface. Maybe its disabled?") + + self.logger.info("JTAG ID read: %02X%02X%02X%02X", resp[3], resp[2], resp[1], resp[0]) + if resp[0] != 0x3F: + raise PymcuprogSessionError("Non-Atmel/Microchip JTAG device detected!") + self.avr.enter_progmode() + + def stop(self): + """ + Stop (deactivate) session for megaJTAG targets + """ + self.avr.leave_progmode() + self.avr.deactivate_physical() + + def read_device_id(self): + """ + Read the device info + + :returns: Device ID raw bytes (little endian) + """ + resp = self.avr.memory_read(Avr8Protocol.AVR8_MEMTYPE_SIGNATURE, 0, 3) + self.logger.info("Device signature read: %02X%02X%02X", resp[0], resp[1], resp[2]) + return bytearray([resp[2], resp[1], resp[0]]) + + def erase(self, memory_info=None, address=None): + """ + Do a chip erase of the device + """ + _dummy = memory_info + _dummy = address + self.logger.info("Only full CHIP ERASE is available on mega JTAG") + self.avr.erase(Avr8Protocol.ERASE_CHIP, 0) + + @staticmethod + def write(memory_info, offset, data): + """ + Write the memory with data + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset within the memory type + :param data: the data to program + """ + _dummy = memory_info + _dummy = offset + _dummy = data + raise NotImplementedError("NVM write is not supported for megaJTAG stack") + + def read(self, memory_info, offset, numbytes): + """ + Read the memory in chunks + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset in the memory type + :param numbytes: number of bytes to read + :return: array of bytes read + """ + memtype_string = memory_info[DeviceMemoryInfoKeys.NAME] + memtype = self.avr.memtype_read_from_string(memtype_string) + if memtype == 0: + msg = "Unsupported memory type: {}".format(memtype_string) + self.logger.error(msg) + raise PymcuprogError(msg) + + if not memtype_string == MemoryNames.FLASH: + # Flash is offset by the debugger config + try: + offset += memory_info[DeviceMemoryInfoKeys.ADDRESS] + except TypeError: + pass + + # EEPROM is only accessible as paged EEPROM on megaJTAG interface + if memtype == Avr8Protocol.AVR8_MEMTYPE_EEPROM: + memtype = Avr8Protocol.AVR8_MEMTYPE_EEPROM_PAGE + + data = self.avr.read_memory_section(memtype, offset, numbytes, numbytes) + return data diff --git a/pymcuprog/nvmmzeroplus.py b/pymcuprog/nvmmzeroplus.py new file mode 100644 index 0000000..a1c43f0 --- /dev/null +++ b/pymcuprog/nvmmzeroplus.py @@ -0,0 +1,126 @@ +""" +M0+ NVM implementation +""" +from logging import getLogger + +from pyedbglib.util import binary + +from . import utils +from .nvm import NvmAccessProviderCmsisDapTool +from .samtarget import SamD2xTarget +from .pymcuprog_errors import PymcuprogError +from .deviceinfo.memorynames import MemoryNames +from .deviceinfo.deviceinfokeys import DeviceMemoryInfoKeys + +class NvmAccessProviderCmsisDapMZeroPlus(NvmAccessProviderCmsisDapTool): + """ + SAMD programmer + """ + + def __init__(self, transport, device_info, frequency=None): + self.logger = getLogger(__name__) + NvmAccessProviderCmsisDapTool.__init__(self, device_info) + + self._log_incomplete_stack('SAM-M0+', beta=True) + + self.sam = SamD2xTarget(transport) + if not frequency: + # Default clock to 2MHz + frequency = 2000000 + self.sam.connect(frequency) + + def read_device_id(self): + """ + Read out the DAP ID and Device ID + + :returns: Device ID raw bytes (little endian) + """ + dap_id = self.sam.read_idcode() + self.logger.info("DAP ID detected: 0x%08X", dap_id) + d_id = self.sam.read_device_id() + self.logger.info("Device ID detected: 0x%08X", d_id) + locked = self.sam.is_device_locked() + if locked: + self.logger.error("Device is locked - a chip erase will be required!") + return binary.pack_le32(d_id) + + def read(self, memory_info, offset, numbytes): + """ + Read the memory in chunks + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset in the memory type + :param numbytes: number of bytes to read + :return: array of bytes read + """ + offset += memory_info[DeviceMemoryInfoKeys.ADDRESS] + + return self.sam.read_flash(address=offset, numbytes=numbytes) + + def write(self, memory_info, offset, data): + """ + Write the memory with data + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset within the memory type + :param data: the data to program + """ + locked = self.sam.is_device_locked() + if locked: + raise PymcuprogError("Device is locked - a chip erase is required first!") + + memtype_string = memory_info[DeviceMemoryInfoKeys.NAME] + offset += memory_info[DeviceMemoryInfoKeys.ADDRESS] + + if memtype_string == MemoryNames.FLASH: + write_chunk_size = memory_info[DeviceMemoryInfoKeys.PAGE_SIZE] + elif memtype_string == MemoryNames.USER_ROW: + write_chunk_size = memory_info[DeviceMemoryInfoKeys.WRITE_SIZE] + else: + raise PymcuprogError("Unknown memtype") + + # Align data to always start at chunk boundary + data_aligned, offset_aligned = utils.pagealign(data, + offset, + write_chunk_size, + memory_info[DeviceMemoryInfoKeys.WRITE_SIZE]) + + # Pad + while len(data_aligned) % write_chunk_size: + data_aligned.append(0xFF) + + # Prepare + self.sam.pre_flash_write() + + # Chunk and write + while data_aligned: + self.logger.debug("Writing %d bytes to address 0x%06X", write_chunk_size, offset_aligned) + chunk = data_aligned[0:write_chunk_size] + if memtype_string == MemoryNames.FLASH: + self.sam.write_flash_page(chunk, offset_aligned) + else: + self.sam.write_user_row_word(offset_aligned, binary.unpack_le32(bytearray(chunk))) + offset_aligned += write_chunk_size + data_aligned = data_aligned[write_chunk_size:] + + # Cleanup + self.sam.post_flash_write() + + def erase(self, memory_info=None, address=None): + """ + Do an erase of the device + """ + _dummy = address + if memory_info is not None and memory_info[DeviceMemoryInfoKeys.NAME] == MemoryNames.USER_ROW: + self.sam.erase_user_row(memory_info[DeviceMemoryInfoKeys.ADDRESS]) + else: + # All other erases are DSU chip erase + self.sam.chip_erase_dsu() + # Re-init + self.sam.reinitialise() + + def stop(self): + """ + Stop programming session + """ + self.sam.disconnect() diff --git a/pymcuprog/nvmpic.py b/pymcuprog/nvmpic.py new file mode 100644 index 0000000..865923f --- /dev/null +++ b/pymcuprog/nvmpic.py @@ -0,0 +1,178 @@ +""" +PIC NVM implementation +""" +import os +import sys + +from pyedbglib.util import binary + +from . import utils +from .nvm import NvmAccessProviderCmsisDapTool +from .pymcuprog_errors import PymcuprogNotSupportedError +from .deviceinfo.memorynames import MemoryNames +from .deviceinfo.deviceinfokeys import DeviceMemoryInfoKeys, DeviceInfoKeys + + +class NvmAccessProviderCmsisDapPic(NvmAccessProviderCmsisDapTool): + """ + NVM access the PIC way + """ + + def __init__(self, transport, device_info, packpath, options=""): + """ + :raises ImportError: if packpath is None + """ + self.pic = None + NvmAccessProviderCmsisDapTool.__init__(self, device_info) + self.options = {} + + if packpath is None: + raise ImportError("No path to pack repo provided!") + + # Each part pack ships its own version of the full script stack, including pyedbglib. + # pyedbglib, and other libraries, can be installed in the local python site-packages + # This path hack puts the part pack path at the front of the python import path + system_path = sys.path + sys.path = [os.path.normpath(packpath)] + sys.path + sys.path = [os.path.normpath(packpath + "//common")] + sys.path + + # Create driver for scripted debuggers + self.options['skip_blank_pages'] = True + self.options['overlapped_usb_access'] = False + + # This imports the debugger model from the provided packpath so the import must be late + from common.debugprovider import provide_debugger_model # pylint: disable=import-outside-toplevel, import-error + + devicename = device_info[DeviceInfoKeys.NAME] + self.pic = provide_debugger_model(devicename) + # Start immediately + self.pic.setup_session(transport, self.options) + self.device_info = device_info + # Start the programming session + if 'pic24' in devicename.lower() or 'dspic33' in devicename.lower(): + if 'no_pe' in options: + # Only PIC24 devices support Programming Executives + try: + # Force no Programming Executive usage by setting program_pe flag but not configure a + # PE (i.e. not calling set_program_exec) + self.pic.start_programming_operation(program_pe=options['no_pe']) + except TypeError: + # start_programming_operation does not have program_pe argument (i.e. old + # devicesupportscripts without PE support) + self.pic.start_programming_operation() + else: + self.pic.start_programming_operation(program_pe=False) + else: + self.pic.start_programming_operation() + + # The stack has been built, revert path hacks + sys.path = system_path + + def read(self, memory_info, offset, numbytes): + """ + Read the memory + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset in the memory type + :param numbytes: number of bytes to read + :return: array of bytes read + """ + mem_name = memory_info[DeviceInfoKeys.NAME] + offset += memory_info[DeviceMemoryInfoKeys.ADDRESS] + if mem_name in [MemoryNames.FLASH, MemoryNames.USER_ID, MemoryNames.ICD]: + mem = self.pic.read_flash_memory(offset, numbytes) + return mem + if mem_name == MemoryNames.CONFIG_WORD: + mem = self.pic.read_config_memory(offset, numbytes) + return mem + if mem_name == MemoryNames.EEPROM: + mem = self.pic.read_eeprom_memory(offset, numbytes) + return mem + self.logger.error("Unsupported memtype!") + return [] + + def write(self, memory_info, offset, data): + """ + Write the memory with data + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset within the memory type + :param data: the data to program + """ + # Make sure the data is aligned to a memory page + chunk, address = utils.pagealign(data, + offset, + memory_info[DeviceMemoryInfoKeys.PAGE_SIZE], + memory_info[DeviceMemoryInfoKeys.WRITE_SIZE]) + + mem_name = memory_info[DeviceInfoKeys.NAME] + address += memory_info[DeviceMemoryInfoKeys.ADDRESS] + if mem_name == MemoryNames.FLASH: + self.pic.write_flash_memory(address, chunk) + elif mem_name == MemoryNames.CONFIG_WORD: + self.pic.write_config_memory(address, chunk) + elif mem_name == MemoryNames.USER_ID: + self.pic.write_user_id_memory(address, chunk) + elif mem_name == MemoryNames.EEPROM: + self.pic.write_eeprom_memory(address, chunk) + elif mem_name == MemoryNames.ICD: + try: + self.pic.write_de_memory(address, chunk) + except AttributeError: + # Some PIC devices don't have the write_de_memory but instead a _write_de_block function + self.pic._write_de_block(address, chunk) # pylint: disable=protected-access + else: + raise PymcuprogNotSupportedError("Unsupported memtype: {}!".format(mem_name)) + + def erase(self, memory_info=None, address=None): + """ + Erase the device or parts of it. + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + If memory_info is None the default bulk erase will be run + :param address: address info for erase (optional) + """ + if address is None: + if memory_info is None: + self.pic.erase() + else: + if memory_info[DeviceInfoKeys.NAME] == MemoryNames.ICD: + self.pic.erase_de_memory(memory_info[DeviceMemoryInfoKeys.ADDRESS], + memory_info[DeviceMemoryInfoKeys.SIZE]) + else: + if DeviceMemoryInfoKeys.ERASE_ADDRESS in memory_info: + self.pic.erase(memory_info[DeviceMemoryInfoKeys.ERASE_ADDRESS]) + else: + raise ValueError("Missing erase address for {}".format(memory_info[DeviceInfoKeys.NAME])) + else: + self.pic.erase(address) + + def read_device_id(self): + """ + Get the device info from the device + + :returns: Device ID raw bytes (little endian) + """ + pic_id = self.pic.read_id() + id_array = binary.pack_le16(pic_id) + self.logger.info("Device ID read out: '%04X'", pic_id) + return id_array + + def hold_in_reset(self): + """ + Hold the device in reset + """ + self.pic.hold_in_reset() + + def release_from_reset(self): + """ + Release the device from reset + """ + self.pic.release_from_reset() + + def stop(self): + """ + Stop programming session + """ + if self.pic is not None: + self.pic.end_of_operations() diff --git a/pymcuprog/nvmserialupdi.py b/pymcuprog/nvmserialupdi.py new file mode 100644 index 0000000..8a92880 --- /dev/null +++ b/pymcuprog/nvmserialupdi.py @@ -0,0 +1,236 @@ +""" +pyupdi-esque NVM implementation +""" +import binascii + +from pyedbglib.util import binary + +from . import utils +from .nvm import NvmAccessProvider +from .deviceinfo import deviceinfo +from .deviceinfo.deviceinfokeys import DeviceInfoKeysAvr, DeviceMemoryInfoKeys +from .deviceinfo.memorynames import MemoryNames +from .serialupdi.application import UpdiApplication +from .pymcuprog_errors import PymcuprogSessionError, PymcuprogDeviceLockedError + +# This is a data class so it should not need any methods but will have many instance variables +# pylint: disable=too-many-instance-attributes,too-few-public-methods +class Dut: + """ + Create a device object for UpdiApplication + """ + + def __init__(self, dev_info): + # Parse the device info for memory descriptions + device_memory_info = deviceinfo.DeviceMemoryInfo(dev_info) + + flash_info = device_memory_info.memory_info_by_name(MemoryNames.FLASH) + self.flash_start = flash_info[DeviceMemoryInfoKeys.ADDRESS] + self.flash_size = flash_info[DeviceMemoryInfoKeys.SIZE] + self.flash_pagesize = flash_info[DeviceMemoryInfoKeys.PAGE_SIZE] + self.syscfg_address = dev_info[DeviceInfoKeysAvr.SYSCFG_BASE] + self.nvmctrl_address = dev_info[DeviceInfoKeysAvr.NVMCTRL_BASE] + address_key = DeviceMemoryInfoKeys.ADDRESS + self.sigrow_address = device_memory_info.memory_info_by_name(MemoryNames.SIGNATURES)[address_key] + self.fuses_address = device_memory_info.memory_info_by_name(MemoryNames.FUSES)[address_key] + self.userrow_address = device_memory_info.memory_info_by_name(MemoryNames.USER_ROW)[address_key] + + +class NvmAccessProviderSerial(NvmAccessProvider): + """ + NVM Access the Python AVR way + """ + + def __init__(self, port, device_info, baud, options=""): + self.avr = None + self.options = options + NvmAccessProvider.__init__(self, device_info) + if not baud: + baud = 115200 + self.dut = Dut(device_info) + self.avr = UpdiApplication(port, baud, self.dut) + # Read the device info to set up the UPDI stack variant + self.avr.read_device_info() + + def start(self, user_interaction_callback=None): + """ + Start (activate) session for UPDI serial targets + """ + try: + self.avr.enter_progmode() + except IOError as inst: + if ('user-row-locked-device' in self.options and self.options['user-row-locked-device']): + self.logger.info("Device is locked. Proceding to write USER ROW...") + elif 'chip-erase-locked-device' in self.options and self.options['chip-erase-locked-device']: + self.logger.info("Device is locked. Proceding to chip erase to unlock...") + self.avr.unlock() + else: + raise PymcuprogDeviceLockedError("Unable to enter programming mode: device is locked!") + + def read_device_id(self): + """ + Read and display (log) the device info + + :returns: Device ID raw bytes (little endian) + :raises PymcuprogSessionError: if device ID does not match + """ + sib_info = self.avr.read_device_info() + self.logger.info("Device family: '%s'", sib_info['family']) + + signatures_base = self.dut.sigrow_address + + # Read 3 bytes + sig = self.avr.read_data(signatures_base, 3) + if len(sig) != 3: + self.logger.error("Unable to read signature for detected device in family: '%s'", sib_info['family']) + raise PymcuprogSessionError("Unable to read device ID") + + device_id_read = binary.unpack_be24(sig) + self.logger.info("Device ID: '%06X'", device_id_read) + if not self.device_info.get(DeviceInfoKeysAvr.DEVICE_ID) == device_id_read: + raise PymcuprogSessionError("Device ID mismatch: read out '{0:X}'; expected '{1:X}'.".format(device_id_read, + self.device_info.get(DeviceInfoKeysAvr.DEVICE_ID))) + revision = self.avr.read_data(self.device_info.get(DeviceInfoKeysAvr.SYSCFG_BASE) + 1, 1) + + self.logger.debug("Device revision: 0x%02x", revision[0]) + self.logger.info("Device revision: '%x.%x'", revision[0] >> 4, revision[0] & 0x0F) + + serial = self.avr.read_data(signatures_base + 3, 10) + self.logger.info("Device serial number: '%s'", binascii.hexlify(serial)) + + # Return the raw signature bytes, but swap the endianness as target sends ID as Big endian + return bytearray([sig[2], sig[1], sig[0]]) + + def erase(self, memory_info=None, address=None): + """ + Do a chip erase of the device + """ + if address is None: + address = 0 + + if memory_info is None: + try: + self.avr.nvm.chip_erase() + except IOError as inst: + self.logger.error("Device is locked. Performing unlock with chip erase.\nError: ('%s')", inst) + self.avr.unlock() + else: + # Add the memory offset + address += memory_info[DeviceMemoryInfoKeys.ADDRESS] + memory_name = memory_info[DeviceMemoryInfoKeys.NAME] + if memory_name == MemoryNames.EEPROM: + self.avr.nvm.erase_eeprom() + elif memory_name == MemoryNames.USER_ROW: + self.avr.nvm.erase_user_row(address, memory_info[DeviceMemoryInfoKeys.SIZE]) + elif memory_name == MemoryNames.FLASH: + # There is no single command for flash erase on UPDI parts so each page must be erased individually + page_size = memory_info[DeviceMemoryInfoKeys.PAGE_SIZE] + n_pages = memory_info[DeviceMemoryInfoKeys.SIZE]//page_size + self.logger.debug("Erasing %d pages of flash", n_pages) + for i in range(n_pages): + self.avr.nvm.erase_flash_page(address=address+i*page_size) + else: + try: + self.avr.nvm.chip_erase() + except IOError as inst: + self.logger.error("Device is locked. Performing unlock with chip erase.\nError: ('%s')", inst) + self.avr.unlock() + + def write(self, memory_info, offset, data): + """ + Write the memory with data + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset within the memory type + :param data: the data to program + """ + # Make sure the data is aligned to a memory page + data_aligned, offset_aligned = utils.pagealign(data, + offset, + memory_info[DeviceMemoryInfoKeys.PAGE_SIZE], + memory_info[DeviceMemoryInfoKeys.WRITE_SIZE]) + memtype_string = memory_info[DeviceMemoryInfoKeys.NAME] + + offset_aligned += memory_info[DeviceMemoryInfoKeys.ADDRESS] + + # Is this a write to user row on a locked device? + user_row_write_locked_device = (memtype_string == MemoryNames.USER_ROW) and \ + ('user-row-locked-device' in self.options and self.options['user-row-locked-device']) + + if memtype_string in (MemoryNames.FLASH, MemoryNames.EEPROM, MemoryNames.FUSES, MemoryNames.LOCKBITS) or user_row_write_locked_device: + write_chunk_size = memory_info[DeviceMemoryInfoKeys.PAGE_SIZE] + else: + write_chunk_size = len(data_aligned) + + if user_row_write_locked_device: + self.logger.info("Padding user row to %d bytes", memory_info[DeviceMemoryInfoKeys.PAGE_SIZE]) + data_aligned = utils.pad_to_size(data_aligned, memory_info[DeviceMemoryInfoKeys.PAGE_SIZE], 0xFF) + self.logger.info("Writing USER ROW on locked device...") + self.avr.write_user_row_locked_device(offset_aligned, data_aligned) + return + + while data_aligned: + if len(data_aligned) < write_chunk_size: + write_chunk_size = len(data_aligned) + chunk = data_aligned[0:write_chunk_size] + self.logger.debug("Writing %d bytes to address 0x%06X", write_chunk_size, offset_aligned) + if memtype_string == MemoryNames.FUSES: + self.avr.nvm.write_fuse(offset_aligned, chunk) + elif memtype_string == MemoryNames.LOCKBITS: + # Lockbits are accessed like fuses + self.avr.nvm.write_fuse(offset_aligned, chunk) + elif memtype_string == MemoryNames.EEPROM: + self.avr.nvm.write_eeprom(offset_aligned, chunk) + elif memtype_string == MemoryNames.USER_ROW: + self.avr.nvm.write_user_row(offset_aligned, chunk) + else: + self.avr.nvm.write_flash(offset_aligned, chunk) + offset_aligned += write_chunk_size + data_aligned = data_aligned[write_chunk_size:] + + def read(self, memory_info, offset, numbytes): + """ + Read the memory in chunks + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset in the memory type + :param numbytes: number of bytes to read + :return: array of bytes read + """ + offset += memory_info[DeviceMemoryInfoKeys.ADDRESS] + + data = bytearray() + read_chunk_size = 0x100 + while numbytes: + if numbytes < read_chunk_size: + read_chunk_size = numbytes + self.logger.debug("Reading %d bytes from address 0x%06X", read_chunk_size, offset) + data += self.avr.read_data(offset, read_chunk_size) + offset += read_chunk_size + numbytes -= read_chunk_size + + return data + + def hold_in_reset(self): + """ + Hold device in reset + """ + # For UPDI parts it is sufficient to enter programming mode to hold the target in reset + # Since the start function is a prerequisite to all functions in this file it can be + # assumed that programming mode already has been entered + return + + def release_from_reset(self): + """ + Release device from reset + """ + # Entering programming mode on UPDI parts will hold the device in reset. So to release + # the reset the programming mode must be left. + self.avr.leave_progmode() + + def stop(self): + """ + Stop the debugging session + """ + if self.avr is not None: + self.avr.leave_progmode() diff --git a/pymcuprog/nvmspi.py b/pymcuprog/nvmspi.py new file mode 100644 index 0000000..b90b7cc --- /dev/null +++ b/pymcuprog/nvmspi.py @@ -0,0 +1,155 @@ +""" +SPI NVM implementation +NB: This is a stub - not all features are implemented. +""" +from pyedbglib.protocols.avrispprotocol import AvrIspProtocol + +from . import utils +from .nvm import NvmAccessProviderCmsisDapAvr +from .deviceinfo.memorynames import MemoryNames +from .deviceinfo.deviceinfokeys import DeviceMemoryInfoKeys +from .pymcuprog_errors import PymcuprogNotSupportedError + +class NvmAccessProviderCmsisDapSpi(NvmAccessProviderCmsisDapAvr): + """ + NVM Access the SPI way + """ + + def __init__(self, transport, device_info): + NvmAccessProviderCmsisDapAvr.__init__(self, device_info) + + self._log_incomplete_stack('AVR-ISP/SPI') + + self.isp = AvrIspProtocol(transport) + self.isp.enter_progmode() + + def stop(self): + """ + Stop programming session + """ + self.isp.leave_progmode() + + def read_device_id(self): + """ + Read the device info + + :returns: Device ID raw bytes (little endian) + """ + resp = self.isp.get_id() + self.logger.info("ID read: %02X%02X%02X", resp[0], resp[1], resp[2]) + # Return the raw ID bytes, but swap byte order to get LSB first + return bytearray([resp[2], resp[1], resp[0]]) + + def erase(self, memory_info=None, address=None): + """ + Do a chip erase of the device + """ + _dummy = memory_info + _dummy = address + self.isp.erase() + + def write(self, memory_info, offset, data): + """ + Write the memory with data + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset within the memory type + :param data: the data to program + """ + # Make sure the data is aligned to a memory page + data_aligned, offset_aligned = utils.pagealign(data, + offset, + memory_info[DeviceMemoryInfoKeys.PAGE_SIZE], + memory_info[DeviceMemoryInfoKeys.WRITE_SIZE]) + if memory_info[DeviceMemoryInfoKeys.NAME] == MemoryNames.FLASH: + write_chunk_size = memory_info[DeviceMemoryInfoKeys.PAGE_SIZE] + while data_aligned: + if len(data_aligned) < write_chunk_size: + write_chunk_size = len(data_aligned) + chunk = data_aligned[0:write_chunk_size] + self.logger.debug("Writing %d bytes of flash to address 0x%06X", write_chunk_size, offset_aligned) + self.isp.write_flash_page(offset_aligned, chunk) + offset_aligned += write_chunk_size + data_aligned = data_aligned[write_chunk_size:] + elif memory_info[DeviceMemoryInfoKeys.NAME] == MemoryNames.EEPROM: + write_chunk_size = memory_info[DeviceMemoryInfoKeys.PAGE_SIZE] + while data_aligned: + if len(data_aligned) < write_chunk_size: + write_chunk_size = len(data_aligned) + chunk = data_aligned[0:write_chunk_size] + self.logger.debug("Writing %d bytes of eeprom to address 0x%06X", write_chunk_size, offset_aligned) + self.isp.write_eeprom_page(offset_aligned, chunk) + offset_aligned += write_chunk_size + data_aligned = data_aligned[write_chunk_size:] + elif memory_info[DeviceMemoryInfoKeys.NAME] == MemoryNames.FUSES: + self.isp.write_fuse_byte(offset, data) + elif memory_info[DeviceMemoryInfoKeys.NAME] == MemoryNames.LOCKBITS: + self.isp.write_lockbits(data) + else: + raise PymcuprogNotSupportedError("Memory type '{}' is not supported for writing via SPI/ISP" + .format(memory_info[DeviceMemoryInfoKeys.NAME])) + + def read(self, memory_info, offset, numbytes): + """ + Read the memory in chunks + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset in the memory type + :param numbytes: number of bytes to read + :return: array of bytes read + """ + # Read chunking is tool-protocol limited to 256b + read_chunk_size = 0x100 + if memory_info[DeviceMemoryInfoKeys.NAME] == MemoryNames.FLASH: + _dummy = memory_info + data = [] + while numbytes: + if numbytes < read_chunk_size: + read_chunk_size = numbytes + self.logger.debug("Reading %d bytes of flash from address 0x%06X", read_chunk_size, offset) + data += self.isp.read_flash_chunk(offset, read_chunk_size) + offset += read_chunk_size + numbytes -= read_chunk_size + elif memory_info[DeviceMemoryInfoKeys.NAME] == MemoryNames.EEPROM: + _dummy = memory_info + data = [] + while numbytes: + if numbytes < read_chunk_size: + read_chunk_size = numbytes + self.logger.debug("Reading %d bytes of eeprom from address 0x%06X", read_chunk_size, offset) + data += self.isp.read_eeprom_chunk(offset, read_chunk_size) + offset += read_chunk_size + numbytes -= read_chunk_size + elif memory_info[DeviceMemoryInfoKeys.NAME] == MemoryNames.SIGNATURES: + data = self.isp.read_signature_bytes(offset, numbytes) + elif memory_info[DeviceMemoryInfoKeys.NAME] == MemoryNames.CALIBRATION_ROW: + data = self.isp.read_calibration_bytes(offset, numbytes) + elif memory_info[DeviceMemoryInfoKeys.NAME] == MemoryNames.FUSES: + data = bytearray() + while numbytes: + data += self.isp.read_fuse_byte(offset) + offset += 1 + numbytes -= 1 + elif memory_info[DeviceMemoryInfoKeys.NAME] == MemoryNames.LOCKBITS: + data = self.isp.read_lockbits() + else: + raise PymcuprogNotSupportedError("Memory type '{}' is not supported for reading via SPI/ISP" + .format(memory_info[DeviceMemoryInfoKeys.NAME])) + return data + + def hold_in_reset(self): + """ + Hold device in reset + """ + # For SPI/ISP parts it is sufficient to enter programming mode to hold the target in reset + # Since the start function is a prerequisite to all functions in this file it can be + # assumed that programming mode already has been entered + return + + def release_from_reset(self): + """ + Release device from reset + """ + # Entering programming mode on SPI/ISP parts will hold the device in reset. So to release + # the reset the programming mode must be left. + self.isp.leave_progmode() diff --git a/pymcuprog/nvmtpi.py b/pymcuprog/nvmtpi.py new file mode 100644 index 0000000..6cafc92 --- /dev/null +++ b/pymcuprog/nvmtpi.py @@ -0,0 +1,73 @@ +""" +TPI/tinytiny NVM implementation +NB: This is a stub - not all features are implemented +""" +from pyedbglib.util import binary +from pyedbglib.protocols.tpiprotocol import TpiProtocol +from .nvm import NvmAccessProviderCmsisDapAvr + +class NvmAccessProviderCmsisDapTpi(NvmAccessProviderCmsisDapAvr): + """ + NVM Access the TPI way + """ + + def __init__(self, transport, device_info): + NvmAccessProviderCmsisDapAvr.__init__(self, device_info) + + self._log_incomplete_stack('AVR-TPI') + self.avr = TpiProtocol(transport) + self.avr.enter_progmode() + + def __del__(self): + pass + + def stop(self): + """ + Stop programming session + """ + self.logger.info("TPI-specific de-initialiser") + self.avr.leave_progmode() + + def read_device_id(self): + """ + Read the device ID + + :returns: Device ID raw bytes (little endian) + """ + sig = self.avr.read_memory(TpiProtocol.XPRG_MEM_TYPE_APPL, 0x3FC0, 3) + device_id_read = binary.unpack_be24(sig) + self.logger.info("Device ID: '%06X'", device_id_read) + return bytearray([sig[2], sig[1], sig[0]]) + + def erase(self, memory_info=None, address=None): + """ + Do a chip erase of the device + """ + _dummy = memory_info + _dummy = address + raise NotImplementedError("NVM erase is not supported for TPI stack") + + @staticmethod + def write(memory_info, offset, data): + """ + Write the memory with data + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset within the memory type + :param data: the data to program + """ + _dummy = memory_info + _dummy = offset + _dummy = data + raise NotImplementedError("NVM write is not supported for TPI stack") + + def read(self, memory_info, offset, numbytes): + """ + Read the memory in chunks + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset in the memory type + :param numbytes: number of bytes to read + :return: array of bytes read + """ + raise NotImplementedError("NVM read is not supported for TPI stack") diff --git a/pymcuprog/nvmupdi.py b/pymcuprog/nvmupdi.py new file mode 100644 index 0000000..dcda98f --- /dev/null +++ b/pymcuprog/nvmupdi.py @@ -0,0 +1,293 @@ +""" +UPDI NVM implementation +""" +from pyedbglib.protocols.avr8protocol import Avr8Protocol +from pyedbglib.protocols.housekeepingprotocol import Jtagice3HousekeepingProtocol +from pyedbglib.protocols.jtagice3protocol import Jtagice3ResponseError +from pyedbglib.util import binary + +from . import utils +from .nvm import NvmAccessProviderCmsisDapAvr +from .pymcuprog_errors import PymcuprogDeviceLockedError +from .avr8target import TinyXAvrTarget +from .serialupdi.application import decode_sib +from .pymcuprog_errors import PymcuprogError, PymcuprogSessionError +from .deviceinfo.deviceinfo import DeviceMemoryInfo +from .deviceinfo.deviceinfokeys import DeviceInfoKeysAvr, DeviceMemoryInfoKeys +from .deviceinfo.memorynames import MemoryNames + +class NvmAccessProviderCmsisDapUpdi(NvmAccessProviderCmsisDapAvr): + """ + NVM Access for AVR UPDI devices + """ + + def __init__(self, transport, device_info, frequency=None, options=""): + NvmAccessProviderCmsisDapAvr.__init__(self, device_info) + self.options = options + self.avr = TinyXAvrTarget(transport) + self.avr.setup_config(device_info) + # Default to 900k if not specified + if not frequency: + frequency = 900000 + self.logger.info("UPDI baud rate: %dbps", frequency) + + # High-voltage activation? + use_hv = Avr8Protocol.UPDI_HV_NONE + if 'high-voltage' in self.options: + if self.options['high-voltage'] == 'simple-unsafe-pulse': + self.logger.info("Activating UPDI using simple high-voltage pulse") + use_hv = Avr8Protocol.UPDI_HV_SIMPLE_PULSE + elif self.options['high-voltage'] == 'tool-toggle-power': + self.logger.info("Activating UPDI using high-voltage pulse with tool power-toggle") + use_hv = Avr8Protocol.UPDI_HV_AUTO_POWER_TOGGLE + elif self.options['high-voltage'] == 'user-toggle-power': + self.logger.info("Activating UPDI using high-voltage pulse with user power-toggle") + use_hv = Avr8Protocol.UPDI_HV_USER_POWER_TOGGLE + + if use_hv != Avr8Protocol.UPDI_HV_NONE: + # Check special powers + housekeeper = Jtagice3HousekeepingProtocol(transport) + # Can this tool do high-voltage at all? + abilities = housekeeper.query(Jtagice3HousekeepingProtocol.HOUSEKEEPING_QUERY_SPECIAL_ABILITIES) + if not Jtagice3HousekeepingProtocol.HOUSEKEEPING_ABILITY_HV_UPDI_ENABLE in abilities: + raise PymcuprogError("This tool does not have the ability to activate UPDI using high-voltage.") + + self.avr.setup_prog_session(khz=frequency // 1000, use_hv=use_hv) + + def start(self, user_interaction_callback=None): + """ + Start (activate) session for UPDI targets + + :param user_interaction_callback: Callback to be called when user interaction is required, + for example when doing UPDI high-voltage activation with user target power toggle. + This function could ask the user to toggle power and halt execution waiting for the user + to respond (this is default behavior if the callback is None), or if the user is another + script it could toggle power automatically and then return. + """ + self.logger.info("UPDI-specific initialiser") + + try: + self.avr.activate_physical(user_interaction_callback=user_interaction_callback) + except Jtagice3ResponseError as error: + # The debugger could be out of sync with the target, retry + if error.code == Avr8Protocol.AVR8_FAILURE_INVALID_PHYSICAL_STATE: + self.logger.info("Physical state out of sync. Retrying.") + self.avr.deactivate_physical() + self.avr.activate_physical(user_interaction_callback=user_interaction_callback) + else: + raise + + self.avr.sib_read() + if 'chip-erase-locked-device' in self.options and self.options['chip-erase-locked-device']: + self.logger.info("Activating chip-erase key entry mechanism") + self.avr.protocol.set_byte(Avr8Protocol.AVR8_CTXT_OPTIONS, + Avr8Protocol.AVR8_OPT_CHIP_ERASE_TO_ENTER, 1) + + # There is a lot that can go wrong here, but a few things can be accurately determined: + try: + self.avr.enter_progmode() + except Jtagice3ResponseError as error: + self.logger.warning(error) + # Interesting failure code to intercept and handle gracefully + if error.code == Avr8Protocol.AVR8_FAILURE_OCD_LOCKED: + if 'user-row-locked-device' in self.options and self.options['user-row-locked-device']: + # log a warning because a) the previous log message is a warning and this should match and + # b) this is a special function flag which only applies to user row + # (but at this point we can't police that) + self.logger.warning("Proceding to write user row on a locked device...") + # return before raising exception - this causes the access to attempt to continue + # when using the special option flag for writing user row on locked devices. + return + raise PymcuprogDeviceLockedError("Unable to enter programming mode: device is locked!") + # Other exceptions can just propagate + raise + if "chip-erase-locked-device" in self.options: + self.logger.info("Key chip-erase complete") + + def stop(self): + """ + Stop (deactivate) session for UPDI targets + """ + self.logger.info("UPDI-specific de-initialiser") + self.avr.leave_progmode() + self.avr.deactivate_physical() + + def read_device_id(self): + """ + Read the device info + + :returns: Device ID raw bytes (little endian) + :raises PymcuprogSessionError: if device ID does not match + """ + sib = bytearray(self.avr.sib_read()) + decode_sib(sib) + + self.logger.info("PDI revision = 0x%02X", + self.avr.memory_read(Avr8Protocol.AVR8_MEMTYPE_CS, 0, 1)[0] >> 4) + device_memory_info = DeviceMemoryInfo(self.device_info) + signatures_info = device_memory_info.memory_info_by_name(MemoryNames.SIGNATURES) + signatures_address = signatures_info[DeviceMemoryInfoKeys.ADDRESS] + sig = self.avr.memory_read(self.avr.memtype_read_from_string("raw"), + signatures_address, + 3) + device_id_read = binary.unpack_be24(sig) + + self.logger.info("Device ID: '%06X'", device_id_read) + if not self.device_info.get(DeviceInfoKeysAvr.DEVICE_ID) == device_id_read: + raise PymcuprogSessionError("Device ID mismatch: read out '{0:X}'; expected '{1:X}'.".format(device_id_read, + self.device_info.get(DeviceInfoKeysAvr.DEVICE_ID))) + + revision = self.avr.memory_read(self.avr.memtype_read_from_string("raw"), + self.device_info.get(DeviceInfoKeysAvr.SYSCFG_BASE) + 1, 1) + self.logger.debug("Device revision: 0x%02x", revision[0]) + self.logger.info("Device revision: '%x.%x'", revision[0] >> 4, revision[0] & 0x0F) + + # Return the raw signature bytes, but swap the endianness as target sends ID as Big endian + return bytearray([sig[2], sig[1], sig[0]]) + + def erase(self, memory_info=None, address=None): + """ + Erase device, or parts of it + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class. + If memory_info is None a chip_erase will be run + :param address: address info for erase (optional) + """ + memory_name = None + + if memory_info is None: + erase_mode = Avr8Protocol.ERASE_CHIP + else: + memory_name = memory_info[DeviceMemoryInfoKeys.NAME] + if memory_name == MemoryNames.EEPROM: + erase_mode = Avr8Protocol.ERASE_EEPROM + elif memory_name == MemoryNames.USER_ROW: + erase_mode = Avr8Protocol.ERASE_USERSIG + elif memory_name == MemoryNames.FLASH: + erase_mode = Avr8Protocol.ERASE_APP_PAGE + else: + erase_mode = Avr8Protocol.ERASE_CHIP + + self.logger.info("Erasing") + if address is None: + address = 0 + + if memory_name == MemoryNames.FLASH: + page_size = memory_info[DeviceMemoryInfoKeys.PAGE_SIZE] + n_pages = memory_info[DeviceMemoryInfoKeys.SIZE]//page_size + self.logger.debug("Erasing %d pages of flash", n_pages) + for i in range(n_pages): + self.logger.debug("Erasing page starting at address 0x%08X", i*page_size) + self.avr.erase(erase_mode, address=i*page_size) + else: + self.avr.erase(erase_mode, address) + + def write(self, memory_info, offset, data): + """ + Write the memory with data + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset within the memory type + :param data: the data to program + """ + memtype_string = memory_info[DeviceMemoryInfoKeys.NAME] + memtype = self.avr.memtype_write_from_string(memtype_string) + if memtype == 0: + msg = "Unsupported memory type: {}".format(memtype_string) + self.logger.error(msg) + raise PymcuprogError(msg) + + if memtype_string != MemoryNames.EEPROM: + # For UPDI parts single byte access is enabled for EEPROM so no need to align to page boundaries + data_to_write, address = utils.pagealign(data, + offset, + memory_info[DeviceMemoryInfoKeys.PAGE_SIZE], + memory_info[DeviceMemoryInfoKeys.WRITE_SIZE]) + else: + data_to_write = data + address = offset + + if memtype_string != MemoryNames.FLASH: + # Flash is offset by the debugger config + address += memory_info[DeviceMemoryInfoKeys.ADDRESS] + + allow_blank_skip = False + if memtype_string in MemoryNames.FLASH: + allow_blank_skip = True + + # Is this a write to user row on a locked device? + user_row_write_locked_device = (memtype_string == MemoryNames.USER_ROW) and \ + ('user-row-locked-device' in self.options and self.options['user-row-locked-device']) + + if memtype_string in (MemoryNames.FLASH, MemoryNames.EEPROM, MemoryNames.FUSES, MemoryNames.LOCKBITS) or \ + user_row_write_locked_device: + # For Flash we have to write exactly one page but for EEPROM we could write less than one page, + # but not more. For fuses and lockbits only one byte at a time can be written. + # For user row on a locked device exactly one page must be written + write_chunk_size = memory_info[DeviceMemoryInfoKeys.PAGE_SIZE] + if memtype_string != MemoryNames.EEPROM: + data_to_write = utils.pad_to_size(data_to_write, write_chunk_size, 0xFF) + else: + write_chunk_size = len(data_to_write) + + self.logger.info("Writing %d bytes of data in chunks of %d bytes to %s...", + len(data_to_write), + write_chunk_size, + memory_info[DeviceMemoryInfoKeys.NAME]) + + first_chunk_size = write_chunk_size - address%write_chunk_size + self.avr.write_memory_section(memtype, + address, + data_to_write[:first_chunk_size], + write_chunk_size, + allow_blank_skip=allow_blank_skip) + address += first_chunk_size + if len(data_to_write) > first_chunk_size: + self.avr.write_memory_section(memtype, + address, + data_to_write[first_chunk_size:], + write_chunk_size, + allow_blank_skip=allow_blank_skip) + + def read(self, memory_info, offset, numbytes): + """ + Read the memory in chunks + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset in the memory type + :param numbytes: number of bytes to read + :return: array of bytes read + """ + memtype_string = memory_info[DeviceMemoryInfoKeys.NAME] + memtype = self.avr.memtype_read_from_string(memtype_string) + if memtype == 0: + msg = "Unsupported memory type: {}".format(memtype_string) + self.logger.error(msg) + raise PymcuprogError(msg) + + if not memtype_string == MemoryNames.FLASH: + # Flash is offset by the debugger config + try: + offset += memory_info[DeviceMemoryInfoKeys.ADDRESS] + except TypeError: + pass + + data = self.avr.read_memory_section(memtype, offset, numbytes, numbytes) + return data + + def hold_in_reset(self): + """ + Hold device in reset + """ + # For UPDI parts it is sufficient to enter programming mode to hold the target in reset + # Since the start function is a prerequisite to all functions in this file it can be + # assumed that programming mode already has been entered + return + + def release_from_reset(self): + """ + Release device from reset + """ + # Entering programming mode on UPDI parts will hold the device in reset. So to release + # the reset the programming mode must be left. + self.avr.leave_progmode() diff --git a/pymcuprog/nvmxmega.py b/pymcuprog/nvmxmega.py new file mode 100644 index 0000000..eb79a6d --- /dev/null +++ b/pymcuprog/nvmxmega.py @@ -0,0 +1,101 @@ +""" +XMEGA NVM implementation +NB: This is a stub - not all features are implemented +""" +from pyedbglib.protocols.jtagice3protocol import Jtagice3ResponseError +from pyedbglib.util import binary +from pyedbglib.protocols.avr8protocol import Avr8Protocol + +from .nvm import NvmAccessProviderCmsisDapAvr +from .avr8target import XmegaAvrTarget + +class NvmAccessProviderCmsisDapXmega(NvmAccessProviderCmsisDapAvr): + """ + NVM Access the Xmega way + """ + + def __init__(self, transport, device_info): + NvmAccessProviderCmsisDapAvr.__init__(self, device_info) + + self._log_incomplete_stack('AVR-xmega') + self.avr = XmegaAvrTarget(transport) + self.avr.setup_config(device_info) + self.avr.setup_prog_session() + + def __del__(self): + pass + + def start(self, user_interaction_callback=None): + """ + Start (activate) session for XMEGA targets + """ + self.logger.debug("XMEGA-specific initialiser") + + try: + self.avr.activate_physical() + except Jtagice3ResponseError as error: + # The debugger could be out of sync with the target, retry + if error.code == Avr8Protocol.AVR8_FAILURE_INVALID_PHYSICAL_STATE: + self.logger.info("Physical state out of sync. Retrying.") + self.avr.deactivate_physical() + self.avr.activate_physical() + else: + raise + self.avr.enter_progmode() + + def stop(self): + """ + Stop (deactivate) session for XMEGA targets + """ + self.logger.debug("XMEGA-specific de-initialiser") + self.avr.leave_progmode() + self.avr.deactivate_physical() + + def read_device_id(self): + """ + Read the device info + + :returns: Device ID raw bytes (little endian) + """ + sig = self.avr.memory_read(self.avr.memtype_read_from_string("raw"), 0x01000090, 3) + device_id_read = binary.unpack_be24(sig) + self.logger.info("Device ID: '%06X'", device_id_read) + # Return the raw signature bytes, but swap the endianness as target sends ID as Big endian + return bytearray([sig[2], sig[1], sig[0]]) + + def erase(self, memory_info=None, address=None): + """ + Do a chip erase of the device + """ + _dummy = memory_info + _dummy = address + raise NotImplementedError("NVM erase is not supported for XMEGA stack") + + @staticmethod + def write(memory_info, offset, data): + """ + Write the memory with data + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset within the memory type + :param data: the data to program + """ + _dummy = memory_info + _dummy = offset + _dummy = data + raise NotImplementedError("NVM write is not supported for XMEGA stack") + + @staticmethod + def read(memory_info, offset, numbytes): + """ + Read the memory in chunks + + :param memory_info: dictionary for the memory as provided by the DeviceMemoryInfo class + :param offset: relative offset in the memory type + :param numbytes: number of bytes to read + :return: array of bytes read + """ + _dummy = memory_info + _dummy = offset + _dummy = numbytes + raise NotImplementedError("NVM read is not supported for XMEGA stack") diff --git a/pymcuprog/programmer.py b/pymcuprog/programmer.py new file mode 100644 index 0000000..e233fbd --- /dev/null +++ b/pymcuprog/programmer.py @@ -0,0 +1,332 @@ +""" +Python MCU programmer +""" +import copy +from logging import getLogger +from collections import namedtuple + +# Device data +from .deviceinfo import deviceinfo + +from . import utils +from .pymcuprog_errors import PymcuprogNotSupportedError, PymcuprogSessionConfigError +from .pymcuprog_errors import PymcuprogError +from .nvm import get_nvm_access_provider +from .deviceinfo.memorynames import MemoryNameAliases +from .deviceinfo.deviceinfokeys import DeviceInfoKeysPic, DeviceMemoryInfoKeys + +DEFAULT_BULK_ERASE_ADDRESS_KEY = DeviceInfoKeysPic.DEFAULT_BULK_ERASE_ADDRESS + +class Programmer: + """ + Main programmer class. + """ + + def __init__(self, transport): + # Hook onto logger + self.logger = getLogger(__name__) + # Use transport passed in + self.transport = transport + # Clear device model and mem info objects + self.device_info = None + self.device_model = None + self.device_memory_info = None + self.options = {} + + def set_options(self, options): + """ + Stores options + + :param options: Options to store + :type options: dict + """ + self.options = options + + def load_device(self, device_name): + """ + Loads the device from the device folder + + :param device_name: Name of device to be used + :type device_name: str + :raises PymcuprogNotSupportedError: if device is not supported + """ + # Try to instantiate device info. This will check if there is device support at all + try: + self.logger.info("Setting up programming session for '%s'", device_name) + self.device_info = deviceinfo.getdeviceinfo(device_name) + except ImportError as err: + raise PymcuprogNotSupportedError("Unable to find device info: {}".format(err)) + + # Now build a memory model for this device + self.device_memory_info = deviceinfo.DeviceMemoryInfo(self.device_info) + + def setup_device(self, interface=None, packpath=None, clk=None): + """ + Sets up a programming session with a given device + + :param interface: Physical interface to use + :type interface: str + :param packpath: Path to packs to use (for PIC) + :type packpath: str + :param clk: Clock frequency or baud rate to use + :type clk: int + + :raises SerialException: if unable to connect to serial port (if using serial port instead of physical debugger) + """ + # Device must be loaded first + if self.device_info is None: + raise PymcuprogError("Device must be loaded before setup!") + + # Find a NVM provider that matches the device and transport + try: + self.device_model = get_nvm_access_provider(self.transport, + self.device_info, + interface=interface, + packpath=packpath, + frequency=clk, + options=self.options) + except ImportError: + raise PymcuprogSessionConfigError( + "Unable to setup stack using the given packpath: '{0:s}'".format( + packpath or "None")) + if self.device_model is None: + raise PymcuprogSessionConfigError("Unable to setup stack, check session config parameters") + + def start(self, user_interaction_callback=None): + """ + Starts the programming session with the device model + + :param user_interaction_callback: Callback to be called when user interaction is required, + for example when doing UPDI high-voltage activation with user target power toggle. + This function could ask the user to toggle power and halt execution waiting for the user + to respond (this is default behavior if the callback is None), or if the user is another + script it could toggle power automatically and then return. + :type user_interaction_callback: function + """ + self.device_model.start(user_interaction_callback=user_interaction_callback) + + def stop(self): + """ + Stops the programming session with the device model + """ + return self.device_model.stop() + + def get_device_model(self): + """ + Exposes the device model in use to clients + """ + return self.device_model + + def get_device_memory_info(self): + """ + Exposes the device memory model to clients + """ + return self.device_memory_info + + # Device model API functions + + def read_device_id(self): + """ + Read the device ID + + :returns: Device ID raw bytes (little endian) + :rtype: bytearray + """ + self.logger.info("Reading device ID...") + return self.device_model.read_device_id() + + def erase(self, memory_name, address): + """ + Erase the device + + :param memory_name: Memory region to erase as defined in deviceinfo.memorynames + MemoryNameAliases.ALL will run the widest erase (e.g. chip erase on AVR or the widest bulk erase on PIC) + :type memory_name: object (MemoryNameAliases) + :param address: Address to erase + :type address: int + """ + self.logger.info("Erase...") + if memory_name == MemoryNameAliases.ALL: + # Run default erase which is the widest erase + memory_info = None + if DEFAULT_BULK_ERASE_ADDRESS_KEY in self.device_info: + address = self.device_info[DEFAULT_BULK_ERASE_ADDRESS_KEY] + else: + address = None + else: + memory_info = self.device_memory_info.memory_info_by_name(memory_name) + self.device_model.erase(memory_info=memory_info, address=address) + + def write_memory(self, data, memory_name, offset=0): + """ + Write memory on the device + + :param data: Data to write + :type data: bytearray + :param memory_name: Memory type to write + :type memory_name: str + :param offset: Offset/address within that region to write + :type offset: int + :return: Boolean status + :rtype: boolean + + :raises ValueError: if trying to write outside the specified memory + :raises ValueError: if the specified memory is not defined for the target device + :raises PymcuprogNotSupportedError: if memory can't be written + """ + self.logger.info("Write...") + + # Just some sanity checking of inputs + if offset < 0: + raise ValueError("Write offset can't be negative, requested offset: {}".format(offset)) + + # Get information about the memory area + memory = self.device_memory_info.memory_info_by_name(memory_name) + size = memory[DeviceMemoryInfoKeys.SIZE] + + if memory[DeviceMemoryInfoKeys.WRITE_SIZE] == 0: + raise PymcuprogNotSupportedError("{} memory can't be written".format(memory_name)) + + if offset + len(data) > size: + msg = "{} bytes of data at offset {} is outside the boundaries of '{}' with size {}".format(len(data), + offset, + memory_name, + size) + raise ValueError(msg) + + # Write the data to NVM + self.logger.info("Writing %d bytes of data to %s...", len(data), memory[DeviceMemoryInfoKeys.NAME]) + self.device_model.write(memory, offset, data) + self.logger.info("Write complete.") + return True + + def verify_memory(self, data, memory_name, offset=0): + """ + Verify memory content + + :param data: Data to verify against + :type data: bytearray + :param memory_name: Memory type + :type memory_name: str + :param offset: Offset/address within that memory region + :type offset: int + :return: True if contents match + :rtype: boolean + """ + # Get information about the memory area + memory = self.device_memory_info.memory_info_by_name(memory_name) + verify_mask = memory[DeviceMemoryInfoKeys.VERIFY_MASK] + + # Read back and compare the data to verify + data_verify = self.read_memory(memory_name, offset, len(data))[0].data + + self.logger.info("Verifying...") + try: + # Use the compare util, which throws ValueError on mismatch + utils.compare(data, data_verify, offset, verify_mask) + except ValueError as error: + self.logger.error("Verify failed for %s memory:", memory_name) + self.logger.error("%s (is the memory section erased?)", str(error)) + return False + return True + + def read_memory(self, memory_name, offset, numbytes=0): + """ + Read device memory + + :param memory_name: Memory type to read as defined in deviceinfo.memorynames + MemoryNameAliases.ALL will read all memories defined in the device model for the configured + device (numbytes and offset will be ignored) + :type memory_name: object (MemoryNameAliases) + :param offset: Offset/start address within the memory to start reading from + :type offset: int + :param numbytes: Number of bytes to read. 0 means read all memory locations for given memory + type (offset still applies) + :type numbytes: int + :returns: List of namedtuples with two fields: data and memory_info. data contains a byte array + of raw data bytes and memory_info is a dictionary with memory information as defined in + deviceinfo.deviceinfo.DeviceMemoryInfo. Normally the list will contain one item, + but when memory_name parameter is MemoryNameAliases.ALL there will be one namedtuple + item per memory type read. + :rtype: list of namedtuple + :raises ValueError: if trying to read outside the specified memory + :raises ValueError: if the specified memory is not defined for the target device + """ + # Just some sanity checking of inputs + if offset < 0: + raise ValueError("Read offset can't be negative, requested offset: {}".format(offset)) + if numbytes < 0: + raise ValueError("Can't read negative number of bytes, requested numbytes: {}".format(numbytes)) + + memories_read = [] + + if memory_name == MemoryNameAliases.ALL: + memories = list(self.device_memory_info.mem_by_name.keys()) + + # When reading all memories offset is ignored + offset = 0 + # ...and the same with numbytes + numbytes = 0 + else: + memories = [memory_name] + + for memory in memories: + # Get information about the memory area + meminfo = self.device_memory_info.memory_info_by_name(memory) + + # For each memory type there will be one named tuple with raw data as a bytearray and a dictionary + # with information about the memory + memory_read_tuple = namedtuple("Memory", 'data memory_info') + memory_read_tuple.data = bytearray([]) + memory_read_tuple.memory_info = meminfo + + # Align the read to a page boundary + page_offset = offset % meminfo[DeviceMemoryInfoKeys.PAGE_SIZE] + + offset_adjusted = offset - page_offset + numbytes_adjusted = numbytes + # If number of bytes is not given, default to read the complete memory starting at the given offset + if numbytes == 0: + numbytes_adjusted = meminfo[DeviceMemoryInfoKeys.SIZE] - offset_adjusted + else: + numbytes_adjusted = numbytes_adjusted + page_offset + + # Read size correction + read_size_key = DeviceMemoryInfoKeys.READ_SIZE + if numbytes_adjusted % meminfo[read_size_key]: + extra = meminfo[read_size_key] - numbytes_adjusted % meminfo[read_size_key] + numbytes_adjusted += extra + else: + extra = 0 + + if offset_adjusted + numbytes_adjusted > meminfo[DeviceMemoryInfoKeys.SIZE]: + raise ValueError("{} bytes of data at offset {} is outside the boundaries of '{}' with size {}".format( + numbytes_adjusted, offset, meminfo[DeviceMemoryInfoKeys.NAME], meminfo[DeviceMemoryInfoKeys.SIZE])) + + # Read the data + self.logger.info("Reading %d bytes from %s...", numbytes_adjusted, meminfo[DeviceMemoryInfoKeys.NAME]) + data = self.device_model.read(meminfo, offset_adjusted, numbytes_adjusted) + + # Strip the extra data that was read + memory_read_tuple.data = data[page_offset:numbytes_adjusted - extra] + + # Append a copy of the memory namedtuple to avoid a reference being appended as the memory_read_tuple will + # change for each loop iteration. Note that when using a deepcopy the content of the memory_read_tuple will + # be copied too + memories_read.append(copy.deepcopy(memory_read_tuple)) + + return memories_read + + def hold_in_reset(self): + """ + Hold the device in reset + """ + self.logger.info("Hold in reset") + self.device_model.hold_in_reset() + + def release_from_reset(self): + """ + Release the device from reset (i.e. let the device run) + """ + self.logger.info("Release from reset") + self.device_model.release_from_reset() diff --git a/pymcuprog/pymcuprog.py b/pymcuprog/pymcuprog.py new file mode 100644 index 0000000..0093332 --- /dev/null +++ b/pymcuprog/pymcuprog.py @@ -0,0 +1,298 @@ +""" +Python MCU programmer Command Line Interface utility +""" +# Python 3 compatibility for Python 2 +from __future__ import print_function + +# args, logging +import sys +import argparse +import os +import logging +from logging.config import dictConfig +from logging import getLogger +import textwrap +import yaml +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path # python 2 backport + +from appdirs import user_log_dir +from yaml.scanner import ScannerError + +# pymcuprog main function +from . import pymcuprog_main +from .pymcuprog_main import WRITE_TO_HEX_MEMORIES +from .deviceinfo.memorynames import MemoryNames, MemoryNameAliases + +def setup_logging(user_requested_level=logging.WARNING, default_path='logging.yaml', + env_key='MICROCHIP_PYTHONTOOLS_CONFIG'): + """ + Setup logging configuration for pymcuprog CLI + """ + # Logging config YAML file can be specified via environment variable + value = os.getenv(env_key, None) + if value: + path = value + else: + # Otherwise use the one shipped with this application + path = os.path.join(os.path.dirname(__file__), default_path) + # Load the YAML if possible + if os.path.exists(path): + try: + with open(path, 'rt') as file: + # Load logging configfile from yaml + configfile = yaml.safe_load(file) + # File logging goes to user log directory under Microchip/modulename + logdir = user_log_dir(__name__, "Microchip") + # Look through all handlers, and prepend log directory to redirect all file loggers + num_file_handlers = 0 + for handler in configfile['handlers'].keys(): + # A filename key + if 'filename' in configfile['handlers'][handler].keys(): + configfile['handlers'][handler]['filename'] = os.path.join( + logdir, configfile['handlers'][handler]['filename']) + num_file_handlers += 1 + # If file logging is enabled, it needs a folder + if num_file_handlers > 0: + # Create it if it does not exist + Path(logdir).mkdir(exist_ok=True, parents=True) + # Console logging takes granularity argument from CLI user + configfile['handlers']['console']['level'] = user_requested_level + # Root logger must be the most verbose of the ALL YAML configurations and the CLI user argument + most_verbose_logging = min(user_requested_level, getattr(logging, configfile['root']['level'])) + for handler in configfile['handlers'].keys(): + # A filename key + if 'filename' in configfile['handlers'][handler].keys(): + level = getattr(logging, configfile['handlers'][handler]['level']) + most_verbose_logging = min(most_verbose_logging, level) + configfile['root']['level'] = most_verbose_logging + dictConfig(configfile) + return + except ScannerError: + # Error while parsing YAML + print("Error parsing logging config file '{}'".format(path)) + except KeyError as keyerror: + # Error looking for custom fields in YAML + print("Key {} not found in logging config file".format(keyerror)) + else: + # Config specified by environment variable not found + print("Unable to open logging config file '{}'".format(path)) + + # If all else fails, revert to basic logging at specified level for this application + print("Reverting to basic logging.") + logging.basicConfig(level=user_requested_level) + +# Helper functions +def _parse_literal(literal): + """ + Literals can either be integers or float values. Default is Integer + """ + try: + return int(literal, 0) + except ValueError: + return float(literal) + +def main(): + """ + Entrypoint for installable CLI + + Configures the CLI and parses the arguments + """ + logger = getLogger(__name__) + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description=textwrap.dedent('''\ + Generic programmer of selected AVR, PIC and SAM devices + + Basic actions: + - ping: read the device ID or signature + - read: read memories + - write: write memories + - erase: erase memories + - verify: verify memories + '''), + epilog=textwrap.dedent('''\ + Usage examples: + + Ping a device on a kit (checks connectivity by reading its signature): + - pymcuprog ping + + Ping a device using Atmel-ICE (standalone debugger requires more information): + - pymcuprog ping -t atmelice -d atmega4809 -i updi + + Program memories from a hexfile using PICkit4: + - pymcuprog write -t pickit4 -d atmega4809 -i updi -f myfile.hex + + Read 64 bytes of flash from offset 0x80 in flash memory space: + - pymcuprog read -m flash -o 0x80 -b 64 + + Write literal values 0x01, 0x02 to EEPROM at offset 16 on a kit: + - pymcuprog write -m eeprom -o 16 -l 0x01 0x02 + + Write fuse byte 1 to 0xE0 on a kit: + - pymcuprog write -m fuses -o 1 -l 0xE0 + + Erase a device on a kit: + - pymcuprog erase + + Erase a locked device on a kit (UPDI only): + - pymcuprog erase --chip-erase-locked-device + + Reset a device on a kit (by entering and leaving programming mode): + - pymcuprog reset + + Read the actual (sampled) VTG voltage from a kit or debugger: + - pymcuprog getvoltage + + Set target supply voltage on a kit (voltage provided by -l literal argument): + - pymcuprog setsupplyvoltage -l 3.3 + + SerialUPDI usage: + + Serial UPDI (also known as 'pyupdi') is implemented as a tool in pymcuprog. + To use it: + - connect a resistor between a serial port adapter's RX, TX and the UPDI pin as shown in the README.md + - specify uart tool using the switch: '--tool uart' + - specify which serial port to use using the switch '--uart ' + - use the basic actions for accessing memories as shown above + + Example: + + Ping a device using serialUPDI: + - pymcuprog ping -t uart -u COM42 -d atmega4809 + + Erase a device using serialUPDI: + - pymcuprog erase -t uart -u COM42 -d atmega4809 + + Program memories from a hexfile using serialUPDI: + - pymcuprog write -t uart -u COM42 -d atmega4809 -f myfile.hex + ''')) + + parser.add_argument("action", + help="action to perform", + # This makes the action argument optional + # only if -V/--version or -R/release_info argument is given + nargs="?" if "-V" in sys.argv or "--version" in sys.argv \ + or "-R" in sys.argv or "--release-info" in sys.argv else None, + default="ping", + # nargs='?', # this makes ping the default, and -h the only way to get usage() + choices=['ping', 'erase', 'read', 'write', 'verify', 'getvoltage', 'getsupplyvoltage', + 'reboot-debugger', 'setsupplyvoltage', 'getusbvoltage', 'reset']) + + # Device to program + parser.add_argument("-d", "--device", + type=str, + help="device to program") + + # Pack path + parser.add_argument("-p", "--packpath", + type=str, + help="path to pack") + + # Tool to use + parser.add_argument("-t", "--tool", + type=str, + help="tool to connect to") + + parser.add_argument("-s", "--serialnumber", + type=str, + help="USB serial number of the unit to use") + + # Memtype + memtype_helpstring = "memory area to access: {}".format(MemoryNameAliases.ALL) + for memtype in MemoryNames.get_all(): + memtype_helpstring += ", '{}'".format(memtype) + parser.add_argument("-m", "--memory", + type=str, + default=MemoryNameAliases.ALL, + help=memtype_helpstring) + + parser.add_argument("-o", "--offset", + type=lambda x: int(x, 0), + default="0", + help="memory byte offset to access") + + parser.add_argument("-b", "--bytes", + type=int, + default=0, + help="number of bytes to access") + + parser.add_argument("-l", "--literal", + type=_parse_literal, + nargs='+', + help="literal values to write") + + filename_helpstring_extra = "Note that when reading to hex file only " + filename_helpstring_extra += ", ".join(WRITE_TO_HEX_MEMORIES) + filename_helpstring_extra += " memories will be written to the hex file" + parser.add_argument("-f", "--filename", + type=str, + help="file to write / read. " + "{}".format(filename_helpstring_extra)) + + parser.add_argument("-c", "--clk", + type=str, + help="clock frequency in Hz (bps) for programming interface. " + "(eg: '-c 32768' or '-c 115k' or '-c 1M')") + + parser.add_argument("-u", "--uart", + type=str, + help="UART to use for serial UPDI tool (when using -t uart)") + + parser.add_argument("-i", "--interface", + type=str, + help="Programming interface to use") + + parser.add_argument("-v", "--verbose", + default="warning", choices=['debug', 'info', 'warning', 'error', 'critical'], + help="Logging verbosity level") + + parser.add_argument("-V", "--version", + help="Print pymcuprog version number and exit", + action="store_true") + + parser.add_argument("-R", "--release-info", action="store_true", + help="Print pymcuprog release details and exit") + + parser.add_argument("--erase", + help="erase memory section before writing (from an Intel(R) hex file only)", + action="store_true") + + parser.add_argument("--verify", + help="verify after write from file", + action="store_true") + + parser.add_argument("-x", "--timing", + help="add timing output", + action="store_true") + + # Ex-options + parser.add_argument("-H", "--high-voltage", + choices=['tool-toggle-power', 'user-toggle-power', 'simple-unsafe-pulse'], + help="UPDI high-voltage activation mode") + + parser.add_argument("-U", "--user-row-locked-device", + help="Writes the User Row on a locked device (UPDI devices only)", + action="store_true") + + parser.add_argument("-C", "--chip-erase-locked-device", + help="Execute a Chip Erase on a locked device (UPDI devices only)", + action="store_true") + + # Parse args + arguments = parser.parse_args() + + # Setup logging + setup_logging(user_requested_level=getattr(logging, arguments.verbose.upper())) + + try: + # Call main with args + return pymcuprog_main.pymcuprog(arguments) + except Exception as exc: + logger.error("Operation failed with %s: %s", type(exc).__name__, exc) + logger.debug(exc, exc_info=True) # get traceback if debug loglevel + +if __name__ == "__main__": + sys.exit(main()) diff --git a/pymcuprog/pymcuprog_errors.py b/pymcuprog/pymcuprog_errors.py new file mode 100644 index 0000000..b83501d --- /dev/null +++ b/pymcuprog/pymcuprog_errors.py @@ -0,0 +1,79 @@ +""" +Pymcuprog specific exceptions +""" + +class PymcuprogError(Exception): + """ + Base class for all Pymcuprog specific exceptions + """ + + def __init__(self, msg=None, code=0): + super(PymcuprogError, self).__init__(msg) + self.code = code + +class PymcuprogToolConfigurationError(PymcuprogError): + """ + Signals that a tool was incorrectly configured + """ + + def __init__(self, msg=None, code=0): + super(PymcuprogToolConfigurationError, self).__init__(msg) + self.code = code + +class PymcuprogToolConnectionError(PymcuprogError): + """ + Signals that an attempted connect failed + """ + + def __init__(self, msg=None, code=0): + super(PymcuprogToolConnectionError, self).__init__(msg) + self.code = code + +class PymcuprogNotSupportedError(PymcuprogError): + """ + Signals that an attempted operation is not supported + """ + + def __init__(self, msg=None, code=0): + super(PymcuprogNotSupportedError, self).__init__(msg) + self.code = code + +class PymcuprogSessionError(PymcuprogError): + """ + Signals that a session is not active + """ + + def __init__(self, msg=None, code=0): + super(PymcuprogSessionError, self).__init__(msg) + self.code = code + +class PymcuprogSessionConfigError(PymcuprogError): + """ + Signals that a session is not configured correctly + """ + + def __init__(self, msg=None, code=0): + super(PymcuprogSessionConfigError, self).__init__(msg) + self.code = code + + +class PymcuprogDeviceLockedError(PymcuprogError): + """ + Signals that the device is locked and a chip erase is required to unlock it + """ + + def __init__(self, msg=None, code=0): + super(PymcuprogDeviceLockedError, self).__init__(msg) + self.code = code + +class PymcuprogEraseError(PymcuprogError): + """ + Signals that an erase can't be executed + + Either the erase is not possible or the erase can't be executed without side effects, + i.e. erasing more memories than requested + """ + + def __init__(self, msg=None, code=0): + super(PymcuprogEraseError, self).__init__(msg) + self.code = code diff --git a/pymcuprog/pymcuprog_main.py b/pymcuprog/pymcuprog_main.py new file mode 100644 index 0000000..d891522 --- /dev/null +++ b/pymcuprog/pymcuprog_main.py @@ -0,0 +1,579 @@ +""" +Python MCU programmer, CLI main program +""" +# Python 3 compatibility for Python 2 +from __future__ import print_function + +# utils +import time +import os +from copy import copy +from logging import getLogger + +from .backend import Backend, SessionConfig +from .toolconnection import ToolUsbHidConnection, ToolSerialConnection +from .deviceinfo.memorynames import MemoryNameAliases, MemoryNames +from .deviceinfo.eraseflags import ChiperaseEffect +from .deviceinfo.deviceinfo import get_supported_devices +from .deviceinfo.deviceinfokeys import DeviceMemoryInfoKeys + +from .utils import print_tool_info, showdata, verify_from_bin, compare +from .hexfileutils import write_memories_to_hex, write_memory_to_hex, read_memories_from_hex +from .pymcuprog_errors import PymcuprogNotSupportedError, PymcuprogSessionConfigError, \ + PymcuprogToolConnectionError, PymcuprogDeviceLockedError, PymcuprogError + +try: + from .version import VERSION, BUILD_DATE, COMMIT_ID +except ImportError: + VERSION = "0.0.0" + COMMIT_ID = "N/A" + BUILD_DATE = "N/A" + +STATUS_SUCCESS = 0 +STATUS_FAILURE = 1 + +# Only include memories that can be written when writing memories to hex file +WRITE_TO_HEX_MEMORIES = [MemoryNames.EEPROM, MemoryNames.FLASH, MemoryNames.FUSES, MemoryNames.CONFIG_WORD, MemoryNames.USER_ROW] + +def pymcuprog(args): + """ + Main program + """ + logger = getLogger(__name__) + if args.version or args.release_info: + print("pymcuprog version {}".format(VERSION)) + if args.release_info: + print("Build date: {}".format(BUILD_DATE)) + print("Commit ID: {}".format(COMMIT_ID)) + return STATUS_SUCCESS + + backend = Backend() + + toolconnection = _setup_tool_connection(args) + + try: + backend.connect_to_tool(toolconnection) + except PymcuprogToolConnectionError as error: + print(error) + return STATUS_FAILURE + + status = None + if args.tool not in ['uart']: + # This section can initialise all features requiring non-UART transports + + # DAP info only available on native CMSIS-DAP + dap_info = backend.read_tool_info() + print_tool_info(dap_info) + + # Targetless actions, only available on HID tools + status = _debugger_actions(backend, args) + + if status is not None: + backend.disconnect_from_tool() + return status + else: + print("Connecting to SerialUPDI") + + device_selected = _select_target_device(backend, args) + if device_selected is None: + backend.disconnect_from_tool() + return STATUS_FAILURE + + status = _start_session(backend, device_selected, args) + if status != STATUS_SUCCESS: + backend.disconnect_from_tool() + return status + + # -x timer argument + time_start = None + if args.timing: + print("Starting timer") + time_start = time.time() + try: + _programming_actions(backend, args) + + except PymcuprogError as exc: + logger.error("%s", exc) + status = STATUS_FAILURE + + except Exception as exc: + logger.error("Operation failed with %s: %s", type(exc).__name__, exc) + logger.debug(exc, exc_info=True) # get traceback if debug loglevel + status = STATUS_FAILURE + + backend.end_session() + backend.disconnect_from_tool() + if args.timing: + time_stop = time.time() + print("Operation took {0:.03f}s".format(time_stop - time_start)) + + print("Done.") + return status + +def _action_getvoltage(backend): + voltage = backend.read_target_voltage() + print("Measured voltage: {0:0.2f}V".format(voltage)) + return STATUS_SUCCESS + +def _action_getsupplyvoltage(backend): + voltage = backend.read_supply_voltage_setpoint() + print("Supply voltage set to {0:0.2f}V".format(voltage)) + return STATUS_SUCCESS + +def _action_getusbvoltage(backend): + voltage = backend.read_usb_voltage() + print("USB voltage is {0:0.2f}V".format(voltage)) + return STATUS_SUCCESS + +def _action_setsupplyvoltage(backend, literal): + voltage = backend.read_supply_voltage_setpoint() + print("Supply voltage is currently set to {0:0.2f}V".format(voltage)) + if literal is None: + print("Specify voltage in Volts using -l ") + else: + setvoltage = literal[0] + if setvoltage == voltage: + print("Voltage is already right where you want it.") + else: + print("Setting supply voltage to {0:0.2f}V".format(setvoltage)) + backend.set_supply_voltage_setpoint(setvoltage) + + voltage = backend.read_supply_voltage_setpoint() + print("Supply voltage is now set to {0:0.2f}V".format(voltage)) + + # Static delay to let the target voltage settle before reading it out + # Alternatively a retry loop could be used, but it is difficult to know when to terminate + # the loop as sometimes the final voltage is not known, for example if setting the voltage + # to 5.5V the actual voltage will depend upon the USB voltage. If the USB voltage is only + # 4.9V the target voltage will never reach more than 4.9V + time.sleep(0.5) + voltage = backend.read_target_voltage() + print("Measured voltage: {0:0.2f}V".format(voltage)) + return STATUS_SUCCESS + +def _action_reboot_debugger(backend): + print("Rebooting tool...") + backend.reboot_tool() + return STATUS_SUCCESS + +def _action_ping(backend): + print("Pinging device...") + response = backend.read_device_id() + idstring = '' + for idbyte in response: + idstring = '{:02X}'.format(idbyte) + idstring + print("Ping response: {}".format(idstring)) + return STATUS_SUCCESS + +def _action_erase(backend, args): + if args.memory is None or args.memory == MemoryNameAliases.ALL: + print("Chip/Bulk erase:") + for memname in backend.device_memory_info.mem_by_name: + effect = backend.get_chiperase_effect(memname) + if effect != ChiperaseEffect.NOT_ERASED: + print("- Memory type {} is {}".format(memname, effect)) + else: + if backend.is_isolated_erase_possible(args.memory): + print("Erasing {}...".format(args.memory)) + else: + print("ERROR: {} memory can't be erased or " + "can't be erased without affecting other memories".format(args.memory)) + chiperase_effect = backend.get_chiperase_effect(args.memory) + if chiperase_effect != ChiperaseEffect.NOT_ERASED: + print("{} memory is {} by a chip/bulk erase".format(args.memory, chiperase_effect)) + print("Use erase without -m option to erase this memory") + return STATUS_FAILURE + + backend.erase(args.memory, address=None) + print("Erased.") + return STATUS_SUCCESS + +def _action_read(backend, args): + # Reading with bytes argument requires that memory type is specified + if args.bytes != 0 and args.memory == MemoryNameAliases.ALL: + print("Memory area must be specified when number of bytes is specified.") + return STATUS_FAILURE + + print("Reading...") + result = backend.read_memory(args.memory, args.offset, args.bytes) + + # If a filename is specified, write to it + hexfile = False + binary = False + filepath = None + if args.filename is not None: + filepath = os.path.normpath(args.filename) + prefix, postfix = _get_file_prefix_and_postfix(filepath) + # If it ends in hex, use Intel(R) hex format, else binary + if postfix == 'hex': + hexfile = True + else: + binary = True + + # Print the data or save it to a file + if hexfile: + if args.memory == MemoryNameAliases.ALL: + # Only memories that can be written should go into the hex file + result_to_write = _extract_writeable_memories(result) + write_memories_to_hex(filepath, result_to_write) + else: + write_memory_to_hex(filepath, result[0], args.offset) + print("Data written to hex file: '{0:s}'".format(filepath)) + elif binary: + for item in result: + memory_name = item.memory_info[DeviceMemoryInfoKeys.NAME] + data = item.data + filepath = "{}_{}.{}".format(prefix, memory_name, postfix) + # Binary files does not have addressing, and needs a split on memory type + with open(filepath, "wb") as binfile: + binfile.write(data) + print("Data written to binary file: '{0:s}'".format(filepath)) + else: + for item in result: + memory_info = item.memory_info + memory_name = memory_info[DeviceMemoryInfoKeys.NAME] + memory_hexfile_size = memory_info[DeviceMemoryInfoKeys.HEXFILE_SIZE] + memory_size = memory_info[DeviceMemoryInfoKeys.SIZE] + print("Memory type: {}".format(memory_name)) + showdata(item.data, + args.offset + memory_info[DeviceMemoryInfoKeys.ADDRESS], + memory_info[DeviceMemoryInfoKeys.PAGE_SIZE], + # PIC16 is word (16-bit) addressed, but each word address only contains one byte of actual data + # for EEPROM, the other byte is a phantom byte + phantom_bytes= 1 if memory_hexfile_size == 2*memory_size else 0) + print("\n") + + return STATUS_SUCCESS + +def _action_verify(backend, args): + hexfile = False + binary = False + literal = False + filepath = None + if args.filename is not None: + filepath = os.path.normpath(args.filename) + _, postfix = _get_file_prefix_and_postfix(filepath) + # If it ends in hex, use Intel hex format, else binary + if postfix == 'hex': + hexfile = True + else: + binary = True + if args.literal is not None: + literal = True + if args.filename is not None: + print("Both file and literal value was specified. Literal verify will be ignored in favor of file verify") + literal = False + + if hexfile: + print("Verifying...") + + verify_status = backend.verify_hex(args.filename) + if verify_status is True: + print("Verify successful. Data in device matches data in specified hex-file") + elif binary: + print("Verifying...") + verify_status = verify_from_bin(args.filename, backend, args.offset, args.memory) + if verify_status is True: + print("Verify successful. Data in {} matches data in specified bin-file".format(args.memory)) + elif literal: + print("Verifying...") + data_read = backend.read_memory(args.memory, args.offset, len(args.literal))[0].data + compare(data_read, args.literal, args.offset) + print("Verify successful. Data in {} matches literal data specified".format(args.memory)) + else: + raise Exception('No file or literal specified for verify') + + return STATUS_SUCCESS + +def _get_file_prefix_and_postfix(filepath): + """ + Get file prefix and postfix from the filepath + + If the file name in the filepath has not file extension the file is supposed to be a binary file + :param filepath: File name and full path + :return: prefix, postfix + """ + prefix = filepath.split('.')[0] + postfix = filepath.split('.')[-1].lower() + # If no "." is found in the filepath + if postfix == prefix: + postfix = "bin" + + return prefix, postfix + +def _extract_writeable_memories(memory_segments): + """ + Take a list of memory segments and return the segments that can be written + + :param memory_segments: List of namedtuples with two fields: data and memory_info. data contains a byte array of + raw data bytes and memory_info is a dictionary with memory information (as defined in + deviceinfo.deviceinfo.DeviceMemoryInfo). + :return: List of namedtuples (a subset of the memory_segments input parameter) only containing memory segments + that can be written + """ + writeable_segments = [] + for segment in memory_segments: + if segment.memory_info[DeviceMemoryInfoKeys.NAME] in WRITE_TO_HEX_MEMORIES: + writeable_segments.append(segment) + return writeable_segments + +def _action_write(backend, args): + # If a filename is specified, read from it + if args.filename is not None: + filepath = os.path.normpath(args.filename) + _, postfix = _get_file_prefix_and_postfix(filepath) + # If it ends in hex, use Intel hex format, else binary + if postfix == 'hex': + # Hexfiles contain addressing information that cannot be remapped, so offset/memory are not allowed here + if args.offset: + print("Offset cannot be specified when writing hex file") + return STATUS_FAILURE + + if args.memory != MemoryNameAliases.ALL: + print("Memory area cannot be specified when writing hex file") + return STATUS_FAILURE + + result = read_memories_from_hex(args.filename, backend.device_memory_info) + + if args.erase: + # User has asked for erase before write + # This is done as a chip erase / bulk erase + # Note: this does not necessarily erase all data: + # for example EEPROM on AVR devices can be preserved by setting the EESAVE fuse bit + print("Erasing device before writing from hex file...") + backend.erase(args.memory, address=None) + + print("Writing from hex file...") + + _write_memory_segments(backend, result, args.verify) + else: + with open(filepath, "rb") as binfile: + data_from_file = bytearray(binfile.read()) + + if args.erase: + raise PymcuprogNotSupportedError("Erase switch (--erase) is only supported when writing a hex file!") + # Prepare and write data + print("Writing from binary file...") + # When writing data to target the data might be page-aligned so we make a copy to avoid verifying + # more than needed (in case verify option is enabled) + data_to_write = copy(data_from_file) + backend.write_memory(data_to_write, args.memory, args.offset) + if args.verify: + print("Verifying from binary file...") + # Verify content, an exception is thrown on mismatch + backend.verify_memory(data_from_file, args.memory, args.offset) + elif args.literal: + if args.erase: + raise PymcuprogNotSupportedError("Erase switch (--erase) is only supported when writing a hex file!") + # Prepare and write data + print("Writing literal values...") + backend.write_memory(bytearray(args.literal), args.memory, args.offset) + if args.verify: + print("Verifying literal values...") + # Verify content, an exception is thrown on mismatch + backend.verify_memory(bytearray(args.literal), args.memory, args.offset) + else: + print("Error: for writing use either -f or -l ") + + return STATUS_SUCCESS + +def _write_memory_segments(backend, memory_segments, verify): + """ + Write content of list of memory segments + + :param backend: pymcuprog Backend instance + :param memory_segments: List of namedtuples with two fields: data and memory_info. data contains a byte array of + raw data bytes and memory_info is a dictionary with memory information (as defined in + deviceinfo.deviceinfo.DeviceMemoryInfo). + :param verify: If True verify the written data by reading it back and compare + """ + for segment in memory_segments: + memory_name = segment.memory_info[DeviceMemoryInfoKeys.NAME] + print("Writing {}...".format(memory_name)) + backend.write_memory(segment.data, memory_name, segment.offset) + if verify: + print("Verifying {}...".format(memory_name)) + verify_ok = backend.verify_memory(segment.data, memory_name, segment.offset) + if verify_ok: + print("OK") + else: + print("Verification failed!") + +def _action_reset(backend): + backend.hold_in_reset() + # Wait a bit to make sure the device has entered reset + # If needed this sleep could be made configurable by a CLI parameter, + # but for now a hardcoded value is assumed to be sufficient + time.sleep(0.1) + backend.release_from_reset() + return STATUS_SUCCESS + +def _debugger_actions(backend, args): + """ + Debugger related actions + + Targetless actions only involving the debugger. Only available on HID tools + """ + status = None + logger = getLogger(__name__) + try: + if args.action == 'getvoltage': + status = _action_getvoltage(backend) + if args.action == 'getsupplyvoltage': + status = _action_getsupplyvoltage(backend) + if args.action == 'getusbvoltage': + status = _action_getusbvoltage(backend) + if args.action == 'setsupplyvoltage': + status = _action_setsupplyvoltage(backend, args.literal) + if args.action == 'reboot-debugger': + status = _action_reboot_debugger(backend) + except (PymcuprogNotSupportedError, ValueError) as error: + print("ERROR: {}".format(error)) + return STATUS_FAILURE + except Exception as exc: + logger.error("Operation failed with %s: %s", type(exc).__name__, exc) + logger.debug(exc, exc_info=True) # get traceback if debug loglevel + status = STATUS_FAILURE + + return status + +def _programming_actions(backend, args): + status = None + # Ping: checks that the device is there by reading its ID, or equivalent + # Always ping the device first before continuing. This guarantees connectivity and + # that the device matches the one expected + if not args.user_row_locked_device: + status = _action_ping(backend) + if status != STATUS_SUCCESS: + return status + + # Already pinged + if args.action == "ping": + return status + + # Erase: perform a full chip erase, or memtype-only erase if specified + if args.action == "erase": + status = _action_erase(backend, args) + # Reading data: + elif args.action == "read": + status = _action_read(backend, args) + elif args.action == "write": + status = _action_write(backend, args) + elif args.action == "reset": + status = _action_reset(backend) + elif args.action == "verify": + status = _action_verify(backend, args) + else: + print("Unknown command '{0:s}'".format(args.action)) + status = STATUS_FAILURE + + return status + +def _setup_tool_connection(args): + toolconnection = None + + # Parse the requested tool from the CLI + if args.tool == "uart": + # Embedded GPIO/UART tool (eg: raspberry pi) => no USB connection + toolconnection = ToolSerialConnection(serialport=args.uart) + else: + usb_serial = args.serialnumber + product = args.tool + if usb_serial and product: + print("Connecting to {0:s} ({1:s})'".format(product, usb_serial)) + else: + if usb_serial: + print("Connecting to any tool with USB serial number '{0:s}'".format(usb_serial)) + elif product: + print("Connecting to any {0:s}".format(product)) + else: + print("Connecting to anything possible") + toolconnection = ToolUsbHidConnection(serialnumber=usb_serial, tool_name=product) + + return toolconnection + +def _select_target_device(backend, args): + device_mounted = None + device_selected = None + if args.tool not in ['uart']: + # Find out from the board (kit) if a device is mounted + device_mounted = backend.read_kit_device() + if device_mounted is not None: + device_mounted = device_mounted.lower() + print("Device mounted: '{0:s}'".format(device_mounted)) + + # Parse device field. If unspecified, use the board's device + if args.device: + device_selected = args.device.lower() + else: + if device_mounted is None: + print("Unable to determine on-board target! Please specify device using -d ") + else: + print("No device specified. Using on-board target ({0:s})".format(device_mounted)) + device_selected = device_mounted + + # Mismatch. Allow user to proceed at own risk. + if device_mounted is not None and device_selected != device_mounted: + print("Warning: you are attempting to use a device which is not the one which was mounted on the kit!") + print("Cut all straps between the debugger and the on-board target when accessing an external device!") + + return device_selected + +def _start_session(backend, device, args): + """ + Setup the session and try to build the stack for this device + """ + sessionconfig = SessionConfig(device) + + # -c clock argument + # allow Hz, or kHz ending in 'k' (eg: 100k) or MHz ending in 'M' eg (1M) + if args.clk: + if args.clk[-1] == 'k': + clk = int(args.clk.strip('k')) * 1000 + elif args.clk[-1] == 'M': + clk = int(args.clk.strip('M')) * 1000000 + else: + clk = int(args.clk) + + sessionconfig.interface_speed = clk + + # Translate args into "special_options" to pass down the stack + sessionconfig.special_options = {} + if args.high_voltage: + sessionconfig.special_options['high-voltage'] = args.high_voltage + if args.user_row_locked_device: + sessionconfig.special_options['user-row-locked-device'] = args.user_row_locked_device + if args.chip_erase_locked_device: + sessionconfig.special_options['chip-erase-locked-device'] = args.chip_erase_locked_device + + # Programming user row on locked parts and erasing to unlock are mutually exclusive + if args.chip_erase_locked_device and args.user_row_locked_device: + print("User row cannot be written on a locked device while erasing and unlocking.") + return STATUS_FAILURE + + if args.interface: + sessionconfig.interface = args.interface + + if args.packpath: + sessionconfig.packpath = args.packpath + + status = STATUS_SUCCESS + try: + backend.start_session(sessionconfig) + except PymcuprogDeviceLockedError: + print("The device is in a locked state and is not accessible; a chip erase is required.") + print("Locked AVR UPDI devices can:") + print(" - be unlocked using command: erase --chip-erase-locked-device") + print(" - write user row values using command: write -m user_row --user-row-locked-device") + status = STATUS_FAILURE + except PymcuprogNotSupportedError: + print("Unable to setup stack for device {0:s}".format(sessionconfig.device)) + print("Currently supported devices (in 'devices' folder):") + device_list = get_supported_devices() + print(', '.join(map(str, device_list))) + status = STATUS_FAILURE + except PymcuprogSessionConfigError as error: + print("Unable to start session: {}".format(error)) + status = STATUS_FAILURE + + return status diff --git a/pymcuprog/samtarget.py b/pymcuprog/samtarget.py new file mode 100644 index 0000000..c2f9ee9 --- /dev/null +++ b/pymcuprog/samtarget.py @@ -0,0 +1,369 @@ +""" +Access to SAM devices via CMSIS-DAP + +Transport provided by HID inside pyedbglib +""" +import time +from logging import getLogger + +from pyedbglib.protocols.cmsisdap import CmsisDapSamDebugger + +from .pymcuprog_errors import PymcuprogError + +class SamTarget: + """ + General SAM target interface + """ + def __init__(self, driver): + self.debugger = CmsisDapSamDebugger(driver) + self.logger = getLogger(__name__) + + def read_idcode(self): + """ + Reads the ID code from the DAP (not device ID) + """ + return self.debugger.dap_read_idcode() + + def dp_write(self, reg, value): + """ + Debug Port write + + :param reg: register to write + :param value: value to write + """ + return self.debugger.dap_write_reg(reg >> 2, value) + + def dp_read(self, reg): + """ + Debug Port read + + :param reg: register to read + :return: value read + """ + return self.debugger.dap_read_reg(reg >> 2) + + def ap_write(self, reg, value): + """ + Access Port write + + :param reg: register to write + :param value: value to write + """ + return self.debugger.dap_write_reg(4 + (reg >> 2), value) + + def ap_read(self, reg): + """ + Access port read + + :param reg: register to read + :return: value read + """ + return self.debugger.dap_read_reg(4 + ((reg & 0x0F) >> 2)) + + +class SamD2xTarget(SamTarget): + """ + Programmer for SAMD21 and friends + """ + # DSU + DSU_ADDRESS = 0x41002000 + DSU_EXTERNAL_OFFSET = 0x100 + DSU_CTRL_OFFSET = 0x00 + DSU_DID_OFFSET = 0x18 + DSU_CTRL_STATUS_CE_COMMAND_MASK = 0x10 + DSU_CTRL_STATUS_DONE_MASK = (1 << 8) + DSU_CTRL_STATUS_PROT_MASK = (1 << 16) + + # Timeout for chip erase + DSU_CHIP_ERASE_TIMEOUT_MS = 3000 + + # Flash controller + NVM_CTRL_ADDRESS = 0x41004000 + NVM_CTRL_CTRL_OFFSET = 0x00 + NVM_CTRL_CTRLB_OFFSET = 0x04 + NVM_CTRL_ADDR_OFFSET = 0x1C + NVM_CTRL_INTFLAG_OFFSET = 0x14 + NVM_CTRL_STATUS_OFFSET = 0x18 + NVM_CTRL_LOCK_OFFSET = 0x20 + + CMD_WP = 0x04 + CMD_EAR = 0x05 + CMD_WAP = 0x06 + CMD_LR = 0x40 + CMD_UR = 0x41 + CMD_PBC = 0x44 + + NVM_CTRLB_MANW_BIT = 7 + + NVM_INT_READY_BIT = 0 + NVM_INT_ERROR_BIT = 1 + + NVM_STATUS_SB = 8 + NVM_STATUS_NVME = 4 + NVM_STATUS_LOCKE = 3 + NVM_STATUS_PROGE = 2 + + DAP_TRANSFER_IDLE_CYCLES = 0 + DAP_TRANSFER_RETRY_COUNT = 250 + DAP_TRANSFER_MATCH_RETRY = 0 + + # Lock region size actually depends on flash size + # TODO: abstract using device model if more SAMs are supported + FLASH_LOCK_REGIONS = 16 + FLASH_LOCK_REGION_SIZE = (0x40000 // FLASH_LOCK_REGIONS) + + def __init__(self, driver): + SamTarget.__init__(self, driver) + self.logger = getLogger(__name__) + + def connect(self, clk_hz): + """ + Connect to the DAP and initialise basics + """ + self.logger.info("Connecting to SAMD2x DAP") + self.debugger.dap_connect() + # LED 1 to on + # TODO - fix after FW5G-1121 + #self.debugger.dap_led(1,1) + self.debugger.dap_reset_ext(True) + self.debugger.dap_swj_clock(50000) + self.debugger.dap_transfer_configure(self.DAP_TRANSFER_IDLE_CYCLES, self.DAP_TRANSFER_RETRY_COUNT, + self.DAP_TRANSFER_MATCH_RETRY) + self.debugger.dap_swd_configure(0) + + self.logger.info("Using SWD CLK of %d Hz", clk_hz) + self.debugger.dap_swj_clock(clk_hz) + + self.debugger.init_swj() + self.debugger.dap_target_init() + + def reinitialise(self): + """ + Re-initialise + + Required after certain operations which reset the DAP on the target + """ + self.logger.info("Re-init of SAMD2x DAP") + self.debugger.dap_connect() + self.debugger.dap_reset_ext(True) + self.debugger.dap_swd_configure(0) + self.debugger.init_swj() + self.debugger.dap_target_init() + + def disconnect(self): + """ + Disconnect from the DAP + """ + # LED 1 to off + # TODO - fix after FW5G-1121 + #self.debugger.dap_led(1,0) + # External reset with no reset extension + self.debugger.dap_reset_ext(False) + self.debugger.dap_disconnect() + + def read_device_id(self): + """ + Read the device ID register + + :returns: Device ID raw bytes (little endian) + """ + return self.debugger.read_word(self.DSU_ADDRESS + self.DSU_EXTERNAL_OFFSET + self.DSU_DID_OFFSET) + + def is_flash_ready(self): + """ + Checks if Flash Controller is ready + + :return: boolean ready state + """ + reg = self.debugger.read_word(self.NVM_CTRL_ADDRESS + self.NVM_CTRL_INTFLAG_OFFSET) + # Check for error bit + if reg & (1 << self.NVM_INT_ERROR_BIT): + # Find out what's causing the error + error_cause = self.debugger.read_word(self.NVM_CTRL_ADDRESS + self.NVM_CTRL_STATUS_OFFSET) + self.logger.error("NVM error: status = 0x%02X", error_cause) + if error_cause & (1 << self.NVM_STATUS_SB): + error_message = "Security bit is set" + if error_cause & (1 << self.NVM_STATUS_NVME): + error_message = "NVM controller error" + if error_cause & (1 << self.NVM_STATUS_LOCKE): + error_message = "Region is locked" + if error_cause & (1 << self.NVM_STATUS_PROGE): + error_message = "Invalid NVM command" + raise PymcuprogError("Flash error: ({})".format(error_message)) + # Check ready bit + return reg & (1 << self.NVM_INT_READY_BIT) + + def is_device_locked(self): + """ + Checks if the device is locked + """ + self.logger.info("Is device locked?") + locked = self.debugger.read_word(self.DSU_ADDRESS + self.DSU_EXTERNAL_OFFSET + self.DSU_CTRL_OFFSET) + if locked & self.DSU_CTRL_STATUS_PROT_MASK: + return True + return False + + def wait_flash_ready(self, timeout_ms=1000): + """ + Wait for flash to be ready, or timeout + + :param timeout_ms: + :return: boolean True if flash is ready; False if timeout waiting + """ + while timeout_ms > 0: + # Done? + if self.is_flash_ready(): + return True + # Wait 1ms + time.sleep(0.001) + timeout_ms -= 1 + return False + + def read_flash(self, address, numbytes): + """ + Read flash content + + :param address: start byte address to read from + :param numbytes: number of bytes to read + :return: array of bytes read + """ + if numbytes % 4: + raise PymcuprogError("Invalid read size") + # Wait for flash to be ready + self.wait_flash_ready() + + if bytes == 4: + return self.debugger.read_word(address) + return self.debugger.read_block(address, numbytes) + + def unlock_all_regions(self): + """ + Unlock all regions of flash. + This only issues the NVM UR command, and has no effect on for example BOOTPROT locks. + """ + address = 0 + for a in range(self.FLASH_LOCK_REGIONS): + self.logger.info("Unlock region %d at address 0x%06X", a, address) + # Write address + self.set_nvmctrl_address(address) + + # Apply "Unlock Region" command + self.nvm_command(self.CMD_UR) + address += self.FLASH_LOCK_REGION_SIZE + + def chip_erase_dsu(self): + """ + Perform a chip erase using the DSU + """ + self.logger.info("Chip erase via DSU") + transfer_data = self.DSU_CTRL_STATUS_CE_COMMAND_MASK + self.debugger.write_word(self.DSU_ADDRESS + self.DSU_EXTERNAL_OFFSET + self.DSU_CTRL_OFFSET, transfer_data) + timeout = self.DSU_CHIP_ERASE_TIMEOUT_MS + while timeout: + reg = self.debugger.read_word(self.DSU_ADDRESS + self.DSU_EXTERNAL_OFFSET + self.DSU_CTRL_OFFSET) + if reg & self.DSU_CTRL_STATUS_DONE_MASK: + self.wait_flash_ready() + self.unlock_all_regions() + return + timeout -= 1 + raise PymcuprogError("Timeout erasing device via DSU!") + + def nvm_command(self, command): + """ + Write command in NVM controller + + :param command: command to write + """ + self.debugger.write_word(self.NVM_CTRL_ADDRESS, (0xA5 << 8) | command) + self.wait_flash_ready() + + def set_nvmctrl_address(self, address): + """ + Set address in NVM controller + + :param address: address to write + """ + # Divide 8-bit address by 2, according to the datasheet + self.debugger.write_word(self.NVM_CTRL_ADDRESS + self.NVM_CTRL_ADDR_OFFSET, address >> 1) + + def pre_flash_write(self): + """ + Prepare flash for write (done once) + """ + # Wait for flash to be ready + self.wait_flash_ready() + # Disable manual commit - in this mode each flash page is auto-committed once filled + self.debugger.write_word(self.NVM_CTRL_ADDRESS + self.NVM_CTRL_CTRLB_OFFSET, (0 << self.NVM_CTRLB_MANW_BIT)) + + def post_flash_write(self): + """ + Wrap up flash write (done once) + """ + # Wait for flash to be ready + self.wait_flash_ready() + + def write_flash_page(self, data_buffer, address): + """ + Write a page of flash memory + + :param data_buffer: data to write + :param address: address to write to + """ + + # Wait for flash to be ready + self.wait_flash_ready() + + # Write data + self.debugger.write_block(address, data_buffer) + + def erase_user_row(self, address): + """ + Erase the user row + + :param address: address to erase + """ + # Wait for flash to be ready + self.wait_flash_ready() + # Insert address to erase + self.set_nvmctrl_address(address) + # Apply "unlock region" command + self.nvm_command(self.CMD_UR) + # Apply Erase Auxilliary Row command + self.nvm_command(self.CMD_EAR) + + def write_user_row_word(self, address, value): + """ + Write a word the user row + + :param address: address of the user row + :param value: value to write + """ + self.wait_flash_ready() + self.set_nvmctrl_address(address) + self.nvm_command(self.CMD_UR) + self.debugger.write_word(address, value) + self.nvm_command(self.CMD_WAP) + + def read_user_row(self, address, numbytes): + """ + Read user row values + + :param address: address to read from + :param numbytes: number of bytes to read + :return: bytes read + """ + self.wait_flash_ready() + return self.read_flash(address, numbytes) + + +class SamM4Target(SamTarget): + """ + SAM M4 targets. + + SAM M4 targets are not supported + """ + def __init__(self, driver): + SamTarget.__init__(self, driver) + self.logger = getLogger(__name__) + self.logger.error("SAM M4 targets are not supported") + raise NotImplementedError("SAM M4 targets are not supported") diff --git a/pymcuprog/serialupdi/__init__.py b/pymcuprog/serialupdi/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pymcuprog/serialupdi/application.py b/pymcuprog/serialupdi/application.py new file mode 100644 index 0000000..2e1d4f5 --- /dev/null +++ b/pymcuprog/serialupdi/application.py @@ -0,0 +1,358 @@ +""" +Application layer for UPDI stack +""" +from logging import getLogger +from pymcuprog.pymcuprog_errors import PymcuprogError +from . import constants +from .link import UpdiDatalink16bit, UpdiDatalink24bit +from .nvm import NvmUpdi, NvmUpdiV0, NvmUpdiAvrV2, NvmUpdiAvrV3 +from .readwrite import UpdiReadWrite +from .physical import UpdiPhysical +from .timeout import Timeout + + +def decode_sib(sib): + """ + Turns the SIB into something readable + + :param sib: SIB data to decode + """ + sib_info = {} + logger = getLogger(__name__) + + # Do some simple checks: + try: + # SIB should contain only ASCII characters + sib_string = sib.decode('ascii') + except UnicodeDecodeError: + logger.error("SIB read returned invalid characters") + return None + + # Vital information is stored in the first 19 characters + if len(sib_string) < 19: + logger.error("SIB read returned incomplete string") + return None + + logger.info("SIB: '%s'", sib_string) + + # Parse fixed width fields according to spec + family = sib[0:7].strip().decode() + logger.info("Device family ID: '%s'", family) + sib_info['family'] = family + + nvm = sib[8:11].strip().decode() + logger.info("NVM interface: '%s'", nvm) + sib_info['NVM'] = nvm.split(':')[1] + + ocd = sib[11:14].strip().decode() + logger.info("Debug interface: '%s'", ocd) + sib_info['OCD'] = ocd.split(':')[1] + + osc = sib[15:19].strip().decode() + logger.info("PDI oscillator: '%s'", osc) + sib_info['OSC'] = osc + + extra = sib[19:].strip().decode() + logger.info("Extra info: '%s'", extra) + sib_info['extra'] = extra + + return sib_info + + +class UpdiApplication: + """ + Generic application layer for UPDI + """ + + def __init__(self, serialport, baud, device=None): + self.logger = getLogger(__name__) + self.device = device + # Build the UPDI stack: + # Create a physical + self.phy = UpdiPhysical(serialport, baud) + + # Create a DL - use 16-bit until otherwise known + datalink = UpdiDatalink16bit() + + # Set the physical for use in the datalink + datalink.set_physical(self.phy) + + # Init (active) the datalink + datalink.init_datalink() + + # Create a read write access layer using this data link + self.readwrite = UpdiReadWrite(datalink) + + # Create an NVM driver + self.nvm = NvmUpdi(self.readwrite, self.device) + + def read_device_info(self): + """ + Reads out device information from various sources + """ + sib = self.readwrite.read_sib() + sib_info = decode_sib(sib) + + # Unable to read SIB? + if sib_info is None: + self.logger.warning("Unable to read SIB from device; attempting double-break recovery...") + # Send double break and try again + self.phy.send_double_break() + sib = self.readwrite.read_sib() + sib_info = decode_sib(sib) + if sib_info is None: + self.logger.error("Double-break recovery failed. Unable to contact device.") + raise PymcuprogError("Failed to read device info.") + + # Select correct NVM driver: + # P:0 = tiny0, mega0 (16-bit, page oriented) + # P:1 = N/A + # P:2 = AVR DA, DB, DD (24-bit, word-oriented) + # P:3 = AVR EA (16-bit, page oriented) + if sib_info['NVM'] == '0': + self.logger.info("NVM type 0: 16-bit, page oriented write") + # DL is correctly configured already + # Create new NVM driver + self.nvm = NvmUpdiV0(self.readwrite, self.device) + elif sib_info['NVM'] == '2': + # This is a Dx-family member, and needs new DL and NVM + self.logger.info("NVM type 2: 24-bit, word oriented write") + # Create new DL + datalink = UpdiDatalink24bit() + # Use the existing PHY + datalink.set_physical(self.phy) + # And re-init + datalink.init_datalink() + # Create a read write access layer using this data link + self.readwrite = UpdiReadWrite(datalink) + # Create new NVM driver + self.nvm = NvmUpdiAvrV2(self.readwrite, self.device) + elif sib_info['NVM'] == '3': + self.logger.info("NVM type 3: 16-bit, page oriented") + # DL is correctly configured already + # Create new NVM driver + self.nvm = NvmUpdiAvrV3(self.readwrite, self.device) + else: + self.logger.error("Unsupported NVM revision - update pymcuprog.") + + self.logger.info("PDI revision = 0x%02X", self.readwrite.read_cs(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) + self.logger.info("Device ID from pyupdi = '%02X%02X%02X' rev '%s'", devid[0], devid[1], devid[2], + chr(ord('A') + devrev[0])) + return sib_info + + def read_data(self, address, size): + """ + Reads a number of bytes of data from UPDI + + :param address: address to write to + :param size: number of bytes to read + """ + return self.readwrite.read_data(address, size) + + def read_data_words(self, address, words): + """ + Reads a number of words of data from UPDI + + :param address: address to write to + :param words: number of words to read + """ + return self.readwrite.read_data_words(address, words) + + def write_data_words(self, address, data): + """ + Writes a number of words to memory + + :param address: address to write to + :param data: data to write + """ + return self.readwrite.write_data_words(address, data) + + def write_data(self, address, data): + """ + Writes a number of bytes to memory + + :param address: address to write to + :param data: data to write + """ + return self.write_data(address, data) + + def in_prog_mode(self): + """ + Checks whether the NVM PROG flag is up + """ + if self.readwrite.read_cs(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 + + :param timeout_ms: number of milliseconds to wait + """ + timeout = Timeout(timeout_ms) + + while not timeout.expired(): + if not self.readwrite.read_cs(constants.UPDI_ASI_SYS_STATUS) & ( + 1 << constants.UPDI_ASI_SYS_STATUS_LOCKSTATUS): + return True + + self.logger.info("Timeout waiting for device to unlock") + return False + + def wait_urow_prog(self, timeout_ms, wait_for_high): + """ + Waits for the device to be in user row write mode + User row is writeable on a locked device using this mechanism + + :param timeout_ms: number of milliseconds to wait + :param wait_for_high: set True to wait for bit to go high; False to wait for low + """ + timeout = Timeout(timeout_ms) + + while not timeout.expired(): + status = self.readwrite.read_cs(constants.UPDI_ASI_SYS_STATUS) + if wait_for_high: + if status & (1 << constants.UPDI_ASI_SYS_STATUS_UROWPROG): + return True + else: + if not status & (1 << constants.UPDI_ASI_SYS_STATUS_UROWPROG): + return True + + self.logger.error("Timeout waiting for device to enter UROW WRITE mode") + return False + + + def unlock(self): + """ + Unlock by chip erase + """ + # Put in the key + self.readwrite.write_key(constants.UPDI_KEY_64, constants.UPDI_KEY_CHIPERASE) + + # Check key status + key_status = self.readwrite.read_cs(constants.UPDI_ASI_KEY_STATUS) + self.logger.debug("Key status = 0x%02X", key_status) + + if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_CHIPERASE): + raise PymcuprogError("Key not accepted") + + # Toggle reset + self.reset(apply_reset=True) + self.reset(apply_reset=False) + + # And wait for unlock + if not self.wait_unlocked(500): + raise PymcuprogError("Failed to chip erase using key") + + def write_user_row_locked_device(self, address, data): + """ + Writes data to the user row when the device is locked, using a key. + """ + # Put in the key + self.readwrite.write_key(constants.UPDI_KEY_64, constants.UPDI_KEY_UROW) + + # Check key status + key_status = self.readwrite.read_cs(constants.UPDI_ASI_KEY_STATUS) + self.logger.debug("Key status = 0x%02X", key_status) + + if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_UROWWRITE): + raise PymcuprogError("Key not accepted") + + # Toggle reset + self.reset(apply_reset=True) + self.reset(apply_reset=False) + + # Wait for mode to be entered + if not self.wait_urow_prog(500, wait_for_high=True): + raise PymcuprogError("Failed to enter urow write mode using key") + + # At this point we can write one 'page' to the device, and have it transfered into the user row + # Transfer data + self.readwrite.write_data(address, data) + + # Finalize + self.readwrite.write_cs(constants.UPDI_ASI_SYS_CTRLA, + (1 << constants.UPDI_ASI_SYS_CTRLA_UROW_FINAL) | + (1 << constants.UPDI_CTRLB_CCDETDIS_BIT)) + + # Wait for mode to be exited + if not self.wait_urow_prog(500, wait_for_high=False): + # Toggle reset + self.reset(apply_reset=True) + self.reset(apply_reset=False) + raise PymcuprogError("Failed to exit urow write mode") + + # Clear status + self.readwrite.write_cs(constants.UPDI_ASI_KEY_STATUS, + (1 << constants.UPDI_ASI_KEY_STATUS_UROWWRITE) | + (1 << constants.UPDI_CTRLB_CCDETDIS_BIT)) + + # Toggle reset + self.reset(apply_reset=True) + self.reset(apply_reset=False) + + 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.readwrite.write_key(constants.UPDI_KEY_64, constants.UPDI_KEY_NVM) + + # Check key status + key_status = self.readwrite.read_cs(constants.UPDI_ASI_KEY_STATUS) + self.logger.debug("Key status = 0x%02X", key_status) + + if not key_status & (1 << constants.UPDI_ASI_KEY_STATUS_NVMPROG): + self.logger.error("Key status = 0x%02X", key_status) + raise IOError("Key not accepted") + + # Toggle reset + self.reset(apply_reset=True) + self.reset(apply_reset=False) + + # And wait for unlock + if not self.wait_unlocked(100): + raise IOError("Failed to enter NVM programming mode: device is locked") + + # Check for NVMPROG flag + if not self.in_prog_mode(): + raise IOError("Failed to enter NVM programming mode") + + self.logger.debug("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.reset(apply_reset=True) + self.reset(apply_reset=False) + self.readwrite.write_cs(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 + + :param apply_reset: True to apply, False to release + """ + if apply_reset: + self.logger.info("Apply reset") + self.readwrite.write_cs(constants.UPDI_ASI_RESET_REQ, constants.UPDI_RESET_REQ_VALUE) + else: + self.logger.info("Release reset") + self.readwrite.write_cs(constants.UPDI_ASI_RESET_REQ, 0x00) diff --git a/pymcuprog/serialupdi/constants.py b/pymcuprog/serialupdi/constants.py new file mode 100644 index 0000000..5c0b411 --- /dev/null +++ b/pymcuprog/serialupdi/constants.py @@ -0,0 +1,134 @@ +""" +UPDI protocol constants +""" +# 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_ADDRESS_24 = 0x08 + +UPDI_DATA_8 = 0x00 +UPDI_DATA_16 = 0x01 +UPDI_DATA_24 = 0x02 + +UPDI_KEY_SIB = 0x04 +UPDI_KEY_KEY = 0x00 + +UPDI_KEY_64 = 0x00 +UPDI_KEY_128 = 0x01 +UPDI_KEY_256 = 0x02 + +UPDI_SIB_8BYTES = UPDI_KEY_64 +UPDI_SIB_16BYTES = UPDI_KEY_128 +UPDI_SIB_32BYTES = UPDI_KEY_256 + +UPDI_REPEAT_BYTE = 0x00 +UPDI_REPEAT_WORD = 0x01 + +UPDI_PHY_SYNC = 0x55 +UPDI_PHY_ACK = 0x40 + +UPDI_MAX_REPEAT_SIZE = (0xFF+1) # Repeat counter of 1-byte, with off-by-one counting + +# 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_CTRLA_RSD_BIT = 3 + +UPDI_CTRLB_CCDETDIS_BIT = 3 +UPDI_CTRLB_UPDIDIS_BIT = 2 + +UPDI_KEY_NVM = b"NVMProg " +UPDI_KEY_CHIPERASE = b"NVMErase" +UPDI_KEY_UROW = b"NVMUs&te" + +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_ASI_SYS_CTRLA_UROW_FINAL = 1 + +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 + +# NVMCTRL v0 CTRLA +UPDI_V0_NVMCTRL_CTRLA_NOP = 0x00 +UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE = 0x01 +UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE = 0x02 +UPDI_V0_NVMCTRL_CTRLA_ERASE_WRITE_PAGE = 0x03 +UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR = 0x04 +UPDI_V0_NVMCTRL_CTRLA_CHIP_ERASE = 0x05 +UPDI_V0_NVMCTRL_CTRLA_ERASE_EEPROM = 0x06 +UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE = 0x07 + +# NVMCTRL v2 CTRLA +UPDI_V2_NVMCTRL_CTRLA_NOCMD = 0x00 +UPDI_V2_NVMCTRL_CTRLA_FLASH_WRITE = 0x02 +UPDI_V2_NVMCTRL_CTRLA_FLASH_PAGE_ERASE = 0x08 +UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE_WRITE = 0x13 +UPDI_V2_NVMCTRL_CTRLA_CHIP_ERASE = 0x20 +UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE = 0x30 + +# NVMCTRL v3 CTRLA +UPDI_V3_NVMCTRL_CTRLA_NOCMD = 0x00 +UPDI_V3_NVMCTRL_CTRLA_NOP = 0x01 +UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_WRITE = 0x04 +UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE_WRITE = 0x05 +UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE = 0x08 +UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_BUFFER_CLEAR = 0x0F +UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_WRITE = 0x14 +UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE_WRITE = 0x15 +UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE = 0x17 +UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_BUFFER_CLEAR = 0x1F +UPDI_V3_NVMCTRL_CTRLA_CHIP_ERASE = 0x20 +UPDI_V3_NVMCTRL_CTRLA_EEPROM_ERASE = 0x30 + + + + +UPDI_NVM_STATUS_WRITE_ERROR = 2 +UPDI_NVM_STATUS_EEPROM_BUSY = 1 +UPDI_NVM_STATUS_FLASH_BUSY = 0 diff --git a/pymcuprog/serialupdi/link.py b/pymcuprog/serialupdi/link.py new file mode 100644 index 0000000..91dd626 --- /dev/null +++ b/pymcuprog/serialupdi/link.py @@ -0,0 +1,381 @@ +""" +Link layer in UPDI protocol stack +""" +from logging import getLogger + +from pymcuprog.pymcuprog_errors import PymcuprogError +from . import constants + + +class UpdiDatalink: + """ + UPDI data link class handles the UPDI data protocol within the device + """ + + LDCS_RESPONSE_BYTES = 1 + + def __init__(self): + self.logger = getLogger(__name__) + self.updi_phy = None + + def set_physical(self, physical): + """ + Inject a serial-port based physical layer for use by this DL + """ + self.updi_phy = physical + + def _init_session_parameters(self): + """ + Set the inter-byte delay bit and disable collision detection + """ + self.stcs(constants.UPDI_CS_CTRLB, 1 << constants.UPDI_CTRLB_CCDETDIS_BIT) + self._enable_ack() + + def _disable_ack(self): + """ + Disables ACKs on write to reduce latency for writing blocks + TODO: could read-modify-write + """ + self.stcs(constants.UPDI_CS_CTRLA, 1 << constants.UPDI_CTRLA_IBDLY_BIT | + 1 << constants.UPDI_CTRLA_RSD_BIT) + + def _enable_ack(self): + """ + Enables ACKs on write by default + TODO: could read-modify-write + """ + self.stcs(constants.UPDI_CS_CTRLA, 1 << constants.UPDI_CTRLA_IBDLY_BIT) + + def init_datalink(self): + """ + Init DL layer + """ + self._init_session_parameters() + # Check + if not self._check_datalink(): + # Send double break if all is not well, and re-check + self.updi_phy.send_double_break() + self._init_session_parameters() + if not self._check_datalink(): + raise PymcuprogError("UPDI initialisation failed") + + def _check_datalink(self): + """ + Check UPDI by loading CS STATUSA + """ + try: + if self.ldcs(constants.UPDI_CS_STATUSA) != 0: + self.logger.info("UPDI init OK") + return True + except PymcuprogError: + self.logger.info("UPDI datalink check failed") + return False + self.logger.info("UPDI not OK - reinitialisation required") + return False + + def ldcs(self, address): + """ + Load data from Control/Status space + + :param address: address to load + """ + self.logger.debug("LDCS from 0x%02X", address) + self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_LDCS | (address & 0x0F)]) + response = self.updi_phy.receive(self.LDCS_RESPONSE_BYTES) + numbytes_received = len(response) + if numbytes_received != self.LDCS_RESPONSE_BYTES: + raise PymcuprogError("Unexpected number of bytes in response: " + "{} byte(s) expected {} byte(s)".format(numbytes_received, self.LDCS_RESPONSE_BYTES)) + + return response[0] + + def stcs(self, address, value): + """ + Store a value to Control/Status space + + :param address: address to store to + :param value: value to write + """ + self.logger.debug("STCS to 0x%02X", address) + self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_STCS | (address & 0x0F), value]) + + def ld_ptr_inc(self, size): + """ + Loads a number of bytes from the pointer location with pointer post-increment + + :param size: number of bytes to load + :return: values read + """ + self.logger.debug("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 + + :param words: number of words to load + :return: values read + """ + self.logger.debug("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_inc(self, data): + """ + Store data to the pointer location with pointer post-increment + + :param data: data to store + """ + self.logger.debug("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 PymcuprogError("ACK error with st_ptr_inc") + + num = 1 + while num < len(data): + self.updi_phy.send([data[num]]) + response = self.updi_phy.receive(1) + + if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: + raise PymcuprogError("Error with st_ptr_inc") + num += 1 + + def st_ptr_inc16(self, data): + """ + Store a 16-bit word value to the pointer location with pointer post-increment + ACK is disabled for blocks (> 2 bytes) + + :param data: data to store + """ + self.logger.debug("ST16 to *ptr++") + if len(data) == 2: + # ACKed mode used for 2-byte transfers + self.logger.debug("ACKed block write") + 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 PymcuprogError("ACK error with st_ptr_inc16") + else: + self.logger.debug("ACKless block write") + self._disable_ack() + self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_INC | + constants.UPDI_DATA_16]) + # Send data with no ACK (Response Signature) + self.updi_phy.send(data) + self._enable_ack() + + def repeat(self, repeats): + """ + Store a value to the repeat counter + + :param repeats: number of repeats requested + """ + self.logger.debug("Repeat %d", repeats) + if (repeats - 1) > constants.UPDI_MAX_REPEAT_SIZE: + self.logger.error("Invalid repeat count of %d", repeats) + raise Exception("Invalid repeat count!") + repeats -= 1 + self.updi_phy.send([constants.UPDI_PHY_SYNC, constants.UPDI_REPEAT | constants.UPDI_REPEAT_BYTE, + repeats & 0xFF]) + + def read_sib(self): + """ + Read the SIB + """ + return self.updi_phy.sib() + + def key(self, size, key): + """ + Write a key + + :param size: size of key (0=64B, 1=128B, 2=256B) + :param key: key value + """ + self.logger.debug("Writing key") + if len(key) != 8 << size: + raise PymcuprogError("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)))) + + def _st_data_phase(self, values): + """ + Performs data phase of transaction: + * receive ACK + * send data + + :param values: bytearray of value(s) to send + """ + response = self.updi_phy.receive(1) + if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: + raise PymcuprogError("Error with st") + + self.updi_phy.send(values) + response = self.updi_phy.receive(1) + if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: + raise PymcuprogError("Error with st") + + +class UpdiDatalink16bit(UpdiDatalink): + """ + UPDI data link layer in 16-bit version + This means that all addresses and pointers contain 2 bytes + """ + + def __init__(self): + UpdiDatalink.__init__(self) + self.logger = getLogger(__name__) + + # pylint: disable=invalid-name + def ld(self, address): + """ + Load a single byte direct from a 16-bit address + + :param address: address to load from + :return: value read + """ + self.logger.info("LD from 0x{0:06X}".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 + + :param address: address to load from + :return: values read + """ + self.logger.info("LD from 0x{0:06X}".format(address)) + self.updi_phy.send( + [constants.UPDI_PHY_SYNC, constants.UPDI_LDS | constants.UPDI_ADDRESS_16 | constants.UPDI_DATA_16, + address & 0xFF, (address >> 8) & 0xFF]) + return self.updi_phy.receive(2) + + # pylint: disable=invalid-name + def st(self, address, value): + """ + Store a single byte value directly to a 16-bit address + + :param address: address to write to + :param value: value to write + """ + self.logger.info("ST to 0x{0:06X}".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]) + return self._st_data_phase([value & 0xFF]) + + def st16(self, address, value): + """ + Store a 16-bit word value directly to a 16-bit address + + :param address: address to write to + :param value: value to write + """ + self.logger.info("ST to 0x{0:06X}".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]) + return self._st_data_phase([value & 0xFF, (value >> 8) & 0xFF]) + + def st_ptr(self, address): + """ + Set the pointer location + + :param address: address to write + """ + 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 PymcuprogError("Error with st_ptr") + + +class UpdiDatalink24bit(UpdiDatalink): + """ + UPDI data link layer in 24-bit version + This means that all addresses and pointers contain 3 bytes + """ + + def __init__(self): + UpdiDatalink.__init__(self) + self.logger = getLogger(__name__) + + # pylint: disable=invalid-name + def ld(self, address): + """ + Load a single byte direct from a 24-bit address + + :param address: address to load from + :return: value read + """ + self.logger.info("LD from 0x{0:06X}".format(address)) + self.updi_phy.send( + [constants.UPDI_PHY_SYNC, constants.UPDI_LDS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_8, + address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF]) + return self.updi_phy.receive(1)[0] + + def ld16(self, address): + """ + Load a 16-bit word directly from a 24-bit address + + :param address: address to load from + :return: values read + """ + self.logger.info("LD from 0x{0:06X}".format(address)) + self.updi_phy.send( + [constants.UPDI_PHY_SYNC, constants.UPDI_LDS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_16, + address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF]) + return self.updi_phy.receive(2) + + # pylint: disable=invalid-name + def st(self, address, value): + """ + Store a single byte value directly to a 24-bit address + + :param address: address to write to + :param value: value to write + """ + self.logger.info("ST to 0x{0:06X}".format(address)) + self.updi_phy.send( + [constants.UPDI_PHY_SYNC, constants.UPDI_STS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_8, + address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF]) + return self._st_data_phase([value & 0xFF]) + + def st16(self, address, value): + """ + Store a 16-bit word value directly to a 24-bit address + + :param address: address to write to + :param value: value to write + """ + self.logger.info("ST to 0x{0:06X}".format(address)) + self.updi_phy.send( + [constants.UPDI_PHY_SYNC, constants.UPDI_STS | constants.UPDI_ADDRESS_24 | constants.UPDI_DATA_16, + address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF]) + return self._st_data_phase([value & 0xFF, (value >> 8) & 0xFF]) + + def st_ptr(self, address): + """ + Set the pointer location + + :param address: address to write + """ + self.logger.info("ST to ptr") + self.updi_phy.send( + [constants.UPDI_PHY_SYNC, constants.UPDI_ST | constants.UPDI_PTR_ADDRESS | constants.UPDI_DATA_24, + address & 0xFF, (address >> 8) & 0xFF, (address >> 16) & 0xFF]) + response = self.updi_phy.receive(1) + if len(response) != 1 or response[0] != constants.UPDI_PHY_ACK: + raise PymcuprogError("Error with st_ptr") diff --git a/pymcuprog/serialupdi/nvm.py b/pymcuprog/serialupdi/nvm.py new file mode 100644 index 0000000..022ed3d --- /dev/null +++ b/pymcuprog/serialupdi/nvm.py @@ -0,0 +1,673 @@ +""" +NVM implementations on various UPDI device families +""" +from logging import getLogger +from pymcuprog.pymcuprog_errors import PymcuprogError +from . import constants +from .timeout import Timeout + + +class NvmUpdi(object): + """ + Base class for NVM + """ + + def __init__(self, readwrite, device): + self.logger = getLogger(__name__) + self.readwrite = readwrite + self.device = device + + def chip_erase(self): + """ + Does a chip erase using the NVM controller + """ + raise NotImplementedError("NVM stack not ready") + + def erase_flash_page(self, address): + """ + Erasing single flash page using the NVM controller + + :param address: Start address of page to erase + :type address: int + """ + raise NotImplementedError("NVM stack not ready") + + def erase_eeprom(self): + """ + Erase EEPROM memory only + """ + raise NotImplementedError("NVM stack not ready") + + def erase_user_row(self, address, size): + """ + Erase User Row memory only + + :param address: Start address of user row + :type address: int + :param size: Size of user row + :type size: int + """ + raise NotImplementedError("NVM stack not ready") + + def write_flash(self, address, data): + """ + Writes data to flash + + :param address: address to write to + :param data: data to write + """ + raise NotImplementedError("NVM stack not ready") + + def write_user_row(self, address, data): + """ + Writes data to user row + + :param address: address to write to + :param data: data to write + """ + raise NotImplementedError("NVM stack not ready") + + def write_eeprom(self, address, data): + """ + Write data to EEPROM + + :param address: address to write to + :param data: data to write + """ + raise NotImplementedError("NVM stack not ready") + + def write_fuse(self, address, data): + """ + Writes one fuse value + + :param address: address to write to + :param data: data to write + """ + raise NotImplementedError("NVM stack not ready") + + def wait_nvm_ready(self): + """ + Waits for the NVM controller to be ready + """ + timeout = Timeout(10000) # 10 sec timeout, just to be sure + + self.logger.debug("Wait NVM ready") + while not timeout.expired(): + status = self.readwrite.read_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_STATUS) + if status & (1 << constants.UPDI_NVM_STATUS_WRITE_ERROR): + self.logger.error("NVM error") + return False + + if not status & ((1 << constants.UPDI_NVM_STATUS_EEPROM_BUSY) | + (1 << constants.UPDI_NVM_STATUS_FLASH_BUSY)): + return True + + self.logger.error("Wait NVM ready timed out") + return False + + def execute_nvm_command(self, command): + """ + Executes an NVM COMMAND on the NVM CTRL + + :param command: command to execute + """ + self.logger.debug("NVMCMD %d executing", command) + return self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_CTRLA, command) + + +class NvmUpdiV0(NvmUpdi): + """ + AKA Version 0 UPDI NVM + Present on, for example, tiny817 -> mega4809 + """ + + def __init__(self, readwrite, device): + NvmUpdi.__init__(self, readwrite, device) + self.logger = getLogger(__name__) + + def chip_erase(self): + """ + Does a chip erase using the NVM controller + + Note that on locked devices this is not possible + and the ERASE KEY has to be used instead, see the unlock method + """ + self.logger.info("Chip erase using NVM CTRL") + + # Wait until NVM CTRL is ready to erase + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready before chip erase") + + # Erase + self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_CHIP_ERASE) + + # And wait for it + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready after chip erase") + + return True + + def erase_flash_page(self, address): + """ + Erasing single flash page using the NVM controller (v0) + + :param address: Start address of page to erase + :type address: int + """ + self.logger.info("Erase flash page at address 0x%08X", address) + + # Wait until NVM CTRL is ready to erase + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready before flash page erase") + + # Dummy write + self.readwrite.write_data(address, [0xFF]) + + # Erase + self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE) + + # And wait for it + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready after flash page erase") + + def erase_eeprom(self): + """ + Erase EEPROM memory only (v0) + """ + self.logger.info("Erase EEPROM") + + # Wait until NVM CTRL is ready to erase + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready before EEPROM erase") + + # Erase + self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_EEPROM) + + # And wait for it + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready after EEPROM erase") + + def erase_user_row(self, address, size): + """ + Erase User Row memory only (v0) + + :param address: Start address of user row + :type address: int + """ + self.logger.info("Erase user row") + + # Wait until NVM CTRL is ready to erase + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready before user row erase") + + # On this NVM version user row is implemented as EEPROM + # When erasing single EEPROM pages a dummy write is needed for each location to be erased + for offset in range(size): + self.readwrite.write_data(address+offset, [0xFF]) + + # Erase + self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE) + + # And wait for it + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready after user row erase") + + + def write_flash(self, address, data): + """ + Writes data to flash (v0) + + :param address: address to write to + :param data: data to write + """ + return self.write_nvm(address, data, use_word_access=True) + + def write_user_row(self, address, data): + """ + Writes data to user row (v0) + + :param address: address to write to + :param data: data to write + """ + # On this NVM variant user row is implemented as EEPROM + return self.write_eeprom(address, data) + + def write_eeprom(self, address, data): + """ + Write data to EEPROM (v0) + + :param address: address to write to + :param data: data to write + """ + return self.write_nvm(address, data, use_word_access=False, + nvmcommand=constants.UPDI_V0_NVMCTRL_CTRLA_ERASE_WRITE_PAGE) + + def write_fuse(self, address, data): + """ + Writes one fuse value (v0) + + :param address: address to write to + :param data: data to write + """ + + # Check that NVM controller is ready + if not self.wait_nvm_ready(): + raise PymcuprogError("Timeout waiting for NVM controller to be ready before fuse write") + + # Write address to NVMCTRL ADDR + self.logger.debug("Load NVM address") + self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRL, address & 0xFF) + self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_ADDRH, (address >> 8) & 0xFF) + + # Write data + self.logger.debug("Load fuse data") + self.readwrite.write_byte(self.device.nvmctrl_address + constants.UPDI_NVMCTRL_DATAL, data[0] & 0xFF) + + # Execute + self.logger.debug("Execute fuse write") + self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE) + + if not self.wait_nvm_ready(): + raise PymcuprogError("Timeout waiting for NVM controller to be ready after fuse write") + + def write_nvm(self, address, data, use_word_access, nvmcommand=constants.UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE): + """ + Writes a page of data to NVM (v0) + + By default the PAGE_WRITE command is used, which + requires that the page is already erased. + By default word access is used (flash) + + :param address: address to write to + :param data: data to write + :param use_word_access: write whole words? + :param nvmcommand: command to use for commit + """ + + # Check that NVM controller is ready + if not self.wait_nvm_ready(): + raise PymcuprogError("Timeout waiting for NVM controller to be ready before page buffer clear") + + # Clear the page buffer + self.logger.debug("Clear page buffer") + self.execute_nvm_command(constants.UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR) + + # Wait for NVM controller to be ready + if not self.wait_nvm_ready(): + raise PymcuprogError("Timeout waiting for NVM controller to be ready after page buffer clear") + + # Load the page buffer by writing directly to location + if use_word_access: + self.readwrite.write_data_words(address, data) + else: + self.readwrite.write_data(address, data) + + # Write the page to NVM, maybe erase first + self.logger.debug("Committing data") + self.execute_nvm_command(nvmcommand) + + # Wait for NVM controller to be ready again + if not self.wait_nvm_ready(): + raise PymcuprogError("Timeout waiting for NVM controller to be ready after page write") + + +class NvmUpdiAvrV2(NvmUpdi): + """ + AKA Version 2 UPDI NVM + Present on, for example, AVR-DA and newer + """ + + def __init__(self, readwrite, device): + NvmUpdi.__init__(self, readwrite, device) + self.logger = getLogger(__name__) + + 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.logger.info("Chip erase using NVM CTRL") + + # Wait until NVM CTRL is ready to erase + if not self.wait_nvm_ready(): + raise Exception("Timeout waiting for NVM controller to be ready before chip erase") + + # Erase + self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_CHIP_ERASE) + + # And wait for it + if not self.wait_nvm_ready(): + raise Exception("Timeout waiting for NVM controller to be ready after chip erase") + + return True + + def erase_flash_page(self, address): + """ + Erasing single flash page using the NVM controller (v1) + + :param address: Start address of page to erase + :type address: int + """ + self.logger.info("Erase flash page at address 0x%08X", address) + + # Wait until NVM CTRL is ready to erase + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready before flash page erase") + + # Erase command + self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_FLASH_PAGE_ERASE) + + # Dummy write + self.readwrite.write_data(address, [0xFF]) + + # And wait for it + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready after flash page erase") + + # Remove command from NVM controller + self.logger.debug("Clear NVM command") + self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD) + + def erase_eeprom(self): + """ + Erase EEPROM memory only (v1) + """ + self.logger.info("Erase EEPROM") + + # Wait until NVM CTRL is ready to erase + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready before EEPROM erase") + + # Erase + self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE) + + # And wait for it + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready after EEPROM erase") + + # Remove command from NVM controller + self.logger.debug("Clear NVM command") + self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD) + + def erase_user_row(self, address, size): + """ + Erase User Row memory only (v1) + + :param address: Start address of user row + :type address: int + """ + # size is not used for this NVM version + _dummy = size + # On this NVM version user row is implemented as flash + return self.erase_flash_page(address) + + def write_flash(self, address, data): + """ + Writes data to flash (v1) + + :param address: address to write to + :param data: data to write + """ + return self.write_nvm(address, data, use_word_access=True) + + def write_user_row(self, address, data): + """ + Writes data to user row (v1) + + :param address: address to write to + :param data: data to write + """ + # On this NVM variant user row is implemented as Flash + return self.write_nvm(address, data, use_word_access=False) + + def write_eeprom(self, address, data): + """ + Writes data to NVM (EEPROM) + + :param address: address to write to + :param data: data to write + """ + nvm_command = constants.UPDI_V2_NVMCTRL_CTRLA_EEPROM_ERASE_WRITE + + # Check that NVM controller is ready + if not self.wait_nvm_ready(): + raise Exception("Timeout waiting for NVM ready before command write") + + # Write the command to the NVM controller + self.logger.info("NVM EEPROM erase/write command") + self.execute_nvm_command(nvm_command) + + # Write the data + self.readwrite.write_data(address, data) + + # Wait for NVM controller to be ready again + if not self.wait_nvm_ready(): + raise Exception("Timeout waiting for NVM ready after data write") + + # Remove command from NVM controller + self.logger.info("Clear NVM command") + self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD) + + def write_fuse(self, address, data): + """ + Writes one fuse value + V1 fuses are EEPROM-based + + :param address: address to write to + :param data: data to write + """ + return self.write_eeprom(address, data) + + def write_nvm(self, address, data, use_word_access): + """ + Writes data to NVM (version 1) + This version of the NVM block has no page buffer, so words are written directly. + + :param address: address to write to + :param data: data to write + :param use_word_access: write in whole words? + """ + nvm_command = constants.UPDI_V2_NVMCTRL_CTRLA_FLASH_WRITE + + # Check that NVM controller is ready + if not self.wait_nvm_ready(): + raise Exception("Timeout waiting for NVM controller to be ready before page buffer clear") + + # Write the command to the NVM controller + self.logger.info("NVM write command") + self.execute_nvm_command(nvm_command) + + # Write the data + if use_word_access: + self.readwrite.write_data_words(address, data) + else: + self.readwrite.write_data(address, data) + + # Wait for NVM controller to be ready again + if not self.wait_nvm_ready(): + raise Exception("Timeout waiting for NVM controller to be ready after data write") + + # Remove command from NVM controller + self.logger.info("Clear NVM command") + self.execute_nvm_command(constants.UPDI_V2_NVMCTRL_CTRLA_NOCMD) + +class NvmUpdiAvrV3(NvmUpdi): + """ + AKA Version 3 UPDI NVM + Present on, for example, AVR-EA + """ + + def __init__(self, readwrite, device): + NvmUpdi.__init__(self, readwrite, device) + self.logger = getLogger(__name__) + + def chip_erase(self): + """ + Does a chip erase using the NVM controller + + Note that on locked devices this is not possible + and the ERASE KEY has to be used instead, see the unlock method + """ + self.logger.info("Chip erase using NVM CTRL") + + # Wait until NVM CTRL is ready to erase + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready before chip erase") + + # Erase + self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_CHIP_ERASE) + + # And wait for it + status = self.wait_nvm_ready() + + # Remove command + self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD) + + if not status: + raise IOError("Timeout waiting for NVM controller to be ready after chip erase") + + return True + + def erase_flash_page(self, address): + """ + Erasing single flash page using the NVM controller (v3) + + :param address: Start address of page to erase + :type address: int + """ + self.logger.info("Erase flash page at address 0x%08X", address) + + # Wait until NVM CTRL is ready to erase + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready before flash page erase") + + # Dummy write + self.readwrite.write_data(address, [0xFF]) + + # Erase + self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_ERASE) + + # And wait for it + status = self.wait_nvm_ready() + + # Remove command + self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD) + + if not status: + raise IOError("Timeout waiting for NVM controller to be ready after flash page erase") + + def erase_eeprom(self): + """ + Erase EEPROM memory only + """ + self.logger.info("Erase EEPROM") + + # Wait until NVM CTRL is ready to erase + if not self.wait_nvm_ready(): + raise IOError("Timeout waiting for NVM controller to be ready before EEPROM erase") + + # Erase + self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_EEPROM_ERASE) + + # And wait for it + status = self.wait_nvm_ready() + + # Remove command + self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD) + + if not status: + raise IOError("Timeout waiting for NVM controller to be ready after EEPROM erase") + + def erase_user_row(self, address, size): + """ + Erase User Row memory only + + :param address: Start address of user row + :type address: int + """ + self.logger.info("Erase user row") + + # On this NVM version user row is implemented as FLASH + return self.erase_flash_page(self, address) + + def write_flash(self, address, data): + """ + Writes data to flash (v3) + + :param address: address to write to + :param data: data to write + """ + return self.write_nvm(address, data, use_word_access=True) + + def write_user_row(self, address, data): + """ + Writes data to user row (v3) + + :param address: address to write to + :param data: data to write + """ + # On this NVM variant user row is implemented as FLASH + return self.write_nvm(address, data, use_word_access=True) + + def write_eeprom(self, address, data): + """ + Write data to EEPROM (v3) + + :param address: address to write to + :param data: data to write + """ + return self.write_nvm(address, data, use_word_access=False, + nvmcommand=constants.UPDI_V3_NVMCTRL_CTRLA_EEPROM_PAGE_ERASE_WRITE) + + def write_fuse(self, address, data): + """ + Writes one fuse value (v3) + + :param address: address to write to + :param data: data to write + """ + return self.write_eeprom(address, data) + + def write_nvm(self, address, data, use_word_access, nvmcommand=constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_WRITE): + """ + Writes a page of data to NVM (v3) + + By default the PAGE_WRITE command is used, which + requires that the page is already erased. + By default word access is used (flash) + + :param address: address to write to + :param data: data to write + :param use_word_access: write whole words? + :param nvmcommand: command to use for commit + """ + + # Check that NVM controller is ready + if not self.wait_nvm_ready(): + raise PymcuprogError("Timeout waiting for NVM controller to be ready before page buffer clear") + + # Clear the page buffer + self.logger.debug("Clear page buffer") + self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_FLASH_PAGE_BUFFER_CLEAR) + + # Wait for NVM controller to be ready + if not self.wait_nvm_ready(): + raise PymcuprogError("Timeout waiting for NVM controller to be ready after page buffer clear") + + # Load the page buffer by writing directly to location + if use_word_access: + self.readwrite.write_data_words(address, data) + else: + self.readwrite.write_data(address, data) + + # Write the page to NVM, maybe erase first + self.logger.debug("Committing data") + self.execute_nvm_command(nvmcommand) + + # Wait for NVM controller to be ready again + if not self.wait_nvm_ready(): + raise PymcuprogError("Timeout waiting for NVM controller to be ready after page write") + + # Remove command + self.execute_nvm_command(constants.UPDI_V3_NVMCTRL_CTRLA_NOCMD) diff --git a/pymcuprog/serialupdi/physical.py b/pymcuprog/serialupdi/physical.py new file mode 100644 index 0000000..557905f --- /dev/null +++ b/pymcuprog/serialupdi/physical.py @@ -0,0 +1,142 @@ +""" +Serial driver for UPDI stack +""" +import time +from logging import getLogger +import serial +from serial.serialutil import SerialException + +from . import constants + + +class UpdiPhysical: + """ + PDI physical driver using a given serial port at a given baud + """ + + def __init__(self, port, baud=115200): + """ + Initialise the serial port + """ + self.logger = getLogger(__name__) + + # Inter-byte delay + self.ibdly = 0.0001 + self.port = port + self.baud = baud + self.ser = None + + self.initialise_serial(self.port, self.baud) + # send an initial break as handshake + self.send([constants.UPDI_BREAK]) + + def initialise_serial(self, port, baud): + """ + Standard serial port initialisation + + :param port: serial port to use + :param baud: baud rate + """ + self.logger.info("Opening port '%s' at '%d' baud", port, baud) + try: + self.ser = serial.Serial(port, baud, parity=serial.PARITY_EVEN, timeout=1, stopbits=serial.STOPBITS_TWO) + except SerialException: + self.logger.error("Unable to open serial port '%s'", port) + raise + + def _loginfo(self, msg, data): + if data and isinstance(data[0], str): + i_data = [ord(x) for x in data] + else: + i_data = data + data_str = "[" + ", ".join(["0x{:02X}".format(x) for x in i_data]) + "]" + self.logger.debug("%s : %s", msg, data_str) + + 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 + # At 300 bauds, the break character will pull the line low for 30ms + # Which is slightly above the recommended 24.6ms + self.ser.close() + temporary_serial = serial.Serial(self.port, 300, parity=serial.PARITY_EVEN, timeout=1, + stopbits=serial.STOPBITS_ONE) + + # Send two break characters, with 1 stop bit in between + temporary_serial.write([constants.UPDI_BREAK]) + + # Wait for the double break end + temporary_serial.read(1) + + time.sleep(0.1) + + # Send two break characters, with 1 stop bit in between + temporary_serial.write([constants.UPDI_BREAK]) + + # Wait for the double break end + temporary_serial.read(1) + + # 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 without inter-byte delay + Note that the byte will echo back + + :param command: command to send + """ + self._loginfo("send", command) + + self.ser.write(command) + + # it will echo back. + self.ser.read(len(command)) + + def receive(self, size): + """ + Receives a frame of a known number of chars from UPDI + + :param size: bytes to receive + """ + response = bytearray() + 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._loginfo("receive", 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_32BYTES]) + return self.ser.readline() + + def __del__(self): + if self.ser: + self.logger.info("Closing port '%s'", self.port) + self.ser.close() diff --git a/pymcuprog/serialupdi/readwrite.py b/pymcuprog/serialupdi/readwrite.py new file mode 100644 index 0000000..4a0274b --- /dev/null +++ b/pymcuprog/serialupdi/readwrite.py @@ -0,0 +1,164 @@ +""" +Read/write access provider for UPDI +""" +from logging import getLogger +from pymcuprog.pymcuprog_errors import PymcuprogError +from . import constants + + +class UpdiReadWrite(object): + """ + Provides various forms of reads and writes for UPDI applications + Makes us of the datalink provided + """ + + def __init__(self, datalink): + self.logger = getLogger(__name__) + self.datalink = datalink + + def read_cs(self, address): + """ + Read from Control/Status space + + :param address: address (index) to read + :return: value read + """ + return self.datalink.ldcs(address) + + def write_cs(self, address, value): + """ + Write to Control/Status space + + :param address: address (index) to write + :param value: 8-bit value to write + """ + return self.datalink.stcs(address, value) + + def write_key(self, size, key): + """ + Write a KEY into UPDI + + :param size: size of key to send + :param key: key value + """ + return self.datalink.key(size, key) + + def read_sib(self): + """ + Read the SIB from UPDI + + :return: SIB string (bytearray) read + """ + return self.datalink.read_sib() + + def read_byte(self, address): + """ + Read a single byte from UPDI + + :param address: address to read from + :return: value read + """ + return self.datalink.ld(address) + + def write_byte(self, address, value): + """ + Writes a single byte to UPDI + + :param address: address to write to + :param value: value to write + """ + return self.datalink.st(address, value) + + def read_data(self, address, size): + """ + Reads a number of bytes of data from UPDI + + :param address: address to write to + :param size: number of bytes to read + """ + self.logger.debug("Reading %d bytes from 0x%04X", size, address) + # Range check + if size > constants.UPDI_MAX_REPEAT_SIZE: + raise PymcuprogError("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 + + :param address: address to write to + :param words: number of words to read + """ + self.logger.debug("Reading %d words from 0x%04X", words, address) + + # Range check + if words > constants.UPDI_MAX_REPEAT_SIZE >> 1: + raise PymcuprogError("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) + + def write_data_words(self, address, data): + """ + Writes a number of words to memory + + :param address: address to write to + :param data: data to write + """ + # 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) > constants.UPDI_MAX_REPEAT_SIZE << 1: + raise PymcuprogError("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 + + :param address: address to write to + :param data: data to write + """ + # Special case of 1 byte + if len(data) == 1: + return self.datalink.st(address, data[0]) + # Special case of 2 byte + if 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: + raise PymcuprogError("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) diff --git a/pymcuprog/serialupdi/timeout.py b/pymcuprog/serialupdi/timeout.py new file mode 100644 index 0000000..afc232c --- /dev/null +++ b/pymcuprog/serialupdi/timeout.py @@ -0,0 +1,26 @@ +""" +Simple timer helper for UPDI stack +""" +import time + +#pylint: disable=too-few-public-methods +class Timeout: + """ + Simple timeout helper in milliseconds. + """ + + def __init__(self, timeout_ms): + """ + Start the expired counter instantly + + :param timeout_ms: milliseconds to count + """ + + self.timeout_ms = timeout_ms + self.start_time = time.time() + + def expired(self): + """ + Check if the timeout has expired + """ + return time.time() - self.start_time > self.timeout_ms / 1000.0 diff --git a/pymcuprog/tests/__init__.py b/pymcuprog/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pymcuprog/tests/cmsisupdi_test_with_hardware.py b/pymcuprog/tests/cmsisupdi_test_with_hardware.py new file mode 100644 index 0000000..9a204a2 --- /dev/null +++ b/pymcuprog/tests/cmsisupdi_test_with_hardware.py @@ -0,0 +1,71 @@ +""" +Test for UPDI over CMSIS-DAP + +Requires an UPDI device with at least 1k flash and 64b EEPROM +""" +import os +from random import randint +from intelhex import IntelHex +# import pytest + +FLASHSIZE = 1 * 1024 +EEPROMSIZE = 64 +USERROWSIZE = 32 +TESTFILE = "testtemp.hex" + + +def do_cli_test(teststring): + """ + Executes a CLI command directly on the OS + """ + retcode = os.system(teststring) + if retcode != 0: + raise Exception("Failed!") + + +def generate_test_hexfile(filename, size): + """ + Generates a random hex file + + :param filename: location and name to generate + :param size: number of bytes of data + """ + hexfile = IntelHex() + # Add some data to the hexfile + for i in range(size): + hexfile[i] = randint(0, 255) + # Turn the hexfile object into an actual file + hexfile.write_hex_file(filename) + + +def test_cli(tmpdir, capfd): + """ + Runs the test suite + """ + test_filename = os.path.normpath(tmpdir + '/' + TESTFILE) + + do_cli_test("python pymcuprog.py ping") + assert "Ping response: 1E" in capfd.readouterr().out + + do_cli_test("python pymcuprog.py erase -m eeprom") + assert "Erasing eeprom\r\nErased" in capfd.readouterr().out + + do_cli_test("python pymcuprog.py read -m eeprom -b {} -o 0".format(EEPROMSIZE)) + assert "0x0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF" in capfd.readouterr().out + + do_cli_test("python pymcuprog.py erase -m user_row") + assert "Erasing user_row\r\nErased" in capfd.readouterr().out + + do_cli_test("python pymcuprog.py read -m user_row -b {} -o 0".format(USERROWSIZE)) + assert "0x0000: FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF" in capfd.readouterr().out + + do_cli_test("python pymcuprog.py erase") + assert "Erasing...\r\nErased" in capfd.readouterr().out + + generate_test_hexfile(test_filename, FLASHSIZE) + do_cli_test("python pymcuprog.py write -f {}".format(test_filename)) + assert "Writing...\r\nReading...\r\nVerifying...\r\nOK" in capfd.readouterr().out + + generate_test_hexfile(test_filename, EEPROMSIZE) + do_cli_test("python pymcuprog.py write -m eeprom -f {}".format(test_filename)) + assert "Writing...\r\nReading...\r\nVerifying...\r\nOK" in capfd.readouterr().out diff --git a/pymcuprog/tests/readme.md b/pymcuprog/tests/readme.md new file mode 100644 index 0000000..14b79bc --- /dev/null +++ b/pymcuprog/tests/readme.md @@ -0,0 +1,16 @@ +This folder contains tests based on Python unittest. Tests are run from the root of the repo (pymcuprog not pymcuprog/pymcuprog or pymcuprog/pymcuprog/tests) + +To run all tests: +~~~~ +\pymcuprog>python -m unittest discover +~~~~ + +To run a specific test module: +~~~~ +\pymcuprog>python -m unittest pymcuprog.tests.test_pymcuprogcli +~~~~ + +To run a specific test: +~~~~ +\pymcuprog>python -m unittest pymcuprog.tests.test_pymcuprogcli.TestPymcuprogCLI.test_ping_nedbg_pic16f18446 +~~~~ \ No newline at end of file diff --git a/pymcuprog/tests/test_backend.py b/pymcuprog/tests/test_backend.py new file mode 100644 index 0000000..7d7a829 --- /dev/null +++ b/pymcuprog/tests/test_backend.py @@ -0,0 +1,1375 @@ +# The intention is to make the test names descriptive enough to not need any docstrings for most of them +#pylint: disable=missing-docstring +# It seems better to have all tests for one module in the same file than to split across multiple files, +# so accepting many public methods and many lines makes sense +#pylint: disable=too-many-lines +#pylint: disable=too-many-public-methods +# Some tests require a lot of configurable parameters so there will be many local variables +#pylint: disable=too-many-locals +""" +pymcuprog backend API tests + +These tests validates the API used by external front-ends/scripts +""" +import os +import sys +import shutil +import logging +import unittest +import copy +from distutils.version import LooseVersion +from collections import namedtuple +from mock import patch +from mock import MagicMock +from mock import call +from intelhex import IntelHex + +from pyedbglib.hidtransport.hidtransportbase import HidTool +from pyedbglib.protocols.jtagice3protocol import Jtagice3ResponseError, Jtagice3Protocol +from pyedbglib.protocols.housekeepingprotocol import Jtagice3HousekeepingProtocol + +from pymcuprog.backend import Backend, SessionConfig +from pymcuprog.toolconnection import ToolUsbHidConnection, ToolSerialConnection, ToolConnection +from pymcuprog.pymcuprog_errors import PymcuprogToolConfigurationError, PymcuprogToolConnectionError +from pymcuprog.pymcuprog_errors import PymcuprogNotSupportedError, PymcuprogEraseError +from pymcuprog.pymcuprog_errors import PymcuprogSessionConfigError, PymcuprogSessionError +from pymcuprog.pymcuprog_errors import PymcuprogDeviceLockedError +from pymcuprog.hexfileutils import _write_hex_to_file +from pymcuprog.deviceinfo import deviceinfo +from pymcuprog.deviceinfo.memorynames import MemoryNames +from pymcuprog.deviceinfo.eraseflags import ChiperaseEffect +from pymcuprog.deviceinfo.deviceinfokeys import DeviceInfoKeys, DeviceMemoryInfoKeys + +TESTFILE_FOLDER = "pymcuprog//tests//temphexfiles//" + +class TestBackend(unittest.TestCase): + """ + pymcuprog backend API unit tests + """ + def setUp(self): + # Make sure no relics from previous tests mess up the current test + try: + shutil.rmtree(TESTFILE_FOLDER) + except FileNotFoundError: + # Folder is already gone + pass + + # Mock out the hid module. This is a hack to avoid problems running tests in a docker on Linux where installing + # hidapi fails due to missing libusb.h + sys.modules['hid'] = MagicMock() + self.backend = Backend() + + def mock_hid_transport(self, connect_return_value=True): + """ + Mock hid_transport + + :param connect_return_value: Return value from hid_transport.connect() + :returns: hid_transport instance mock object + """ + mock_hid_transport_patch = patch("pymcuprog.backend.hid_transport") + self.addCleanup(mock_hid_transport_patch.stop) + mock_hid_transport = mock_hid_transport_patch.start() + mock_hid_transport_instance = MagicMock() + mock_hid_transport.return_value = mock_hid_transport_instance + mock_hid_transport_instance.connect.return_value = connect_return_value + + return mock_hid_transport_instance + + def mock_is_connected_to_hid_tool(self, connected_to_hid=True): + """ + Mock out the _is_connected_to_hid_tool() in the API as it won't work when the hid_transport has been mocked out + + :param connected_to_hid: Set to True if _is_connected_to_hid_tool() should return True + """ + mock_is_connected_to_hid_tool_patch = patch("pymcuprog.backend.Backend._is_connected_to_hid_tool") + self.addCleanup(mock_is_connected_to_hid_tool_patch.stop) + mock_is_connected_to_hid_tool = mock_is_connected_to_hid_tool_patch.start() + mock_is_connected_to_hid_tool.return_value = connected_to_hid + + def mock_housekeepingprotocol(self, device=''): + """ + Mock Housekeepingprotocol + + :param device: device name to be returned in dap_info (part of tool_info) + :returns: housekeepingprotocol instance mock object + """ + mock_housekeepingprotocol_patch = patch("pymcuprog.backend.housekeepingprotocol") + self.addCleanup(mock_housekeepingprotocol_patch.stop) + mock_housekeepingprotocol = mock_housekeepingprotocol_patch.start() + mock_housekeepingprotocol_instance = MagicMock() + mock_housekeepingprotocol.Jtagice3HousekeepingProtocol.return_value = mock_housekeepingprotocol_instance + mock_housekeepingprotocol_instance.dap_info.return_value = { + 'device_name': device, + 'product': 'nEDBG', + 'serial': 'MCHP0000111122223333' + } + + return mock_housekeepingprotocol_instance + + def mock_programmer(self): + """ + Mock programmer class + + :returns: programmer instance mock object + """ + mock_programmer_patch = patch("pymcuprog.backend.Programmer") + self.addCleanup(mock_programmer_patch.stop) + mock_programmer = mock_programmer_patch.start() + mock_programmer_instance = MagicMock() + mock_programmer.return_value = mock_programmer_instance + + return mock_programmer_instance + + @staticmethod + def configure_mock_programmer_with_device_memory_info(mock_programmer, devicename): + """ + Configure a mock of the Programmer class with device memory info + + Since the complete programmer class is mocked out the device memory info must be + generated and added to the mock + :param mock_programmer: Mock of Programmer class instance + """ + # Make a copy of the device info to avoid any side effects in other tests in case any test decides + # to mess with the device info object. If the device info is not copied any changes made will be + # on the original device info object which will be the same used by all tests. + device_info = copy.deepcopy(deviceinfo.getdeviceinfo(devicename)) + device_meminfo = deviceinfo.DeviceMemoryInfo(device_info) + mock_programmer.get_device_memory_info.return_value = device_meminfo + + def mock_read_memories_from_hex(self): + """ + Mock read_memories_from_hex helper function + + :returns: read_memories_from_hex instance mock object + """ + mock_read_memories_from_hex_patch = patch("pymcuprog.backend.read_memories_from_hex") + self.addCleanup(mock_read_memories_from_hex_patch.stop) + mock_read_memories_from_hex = mock_read_memories_from_hex_patch.start() + mock_read_memories_from_hex_instance = MagicMock() + mock_read_memories_from_hex.return_value = mock_read_memories_from_hex_instance + + return mock_read_memories_from_hex_instance + + def test_get_api_version_returns_major_version_1_or_higher(self): + api_version_read = self.backend.get_api_version() + + self.assertGreaterEqual(LooseVersion(api_version_read), LooseVersion('1.0')) + + def test_get_supported_devices_returns_list_of_all_devices_with_device_file(self): + supported_devices = self.backend.get_supported_devices() + + num_devices = 0 + for filename in os.listdir("pymcuprog//deviceinfo//devices"): + if filename not in ['__init__.py'] and filename.endswith('.py'): + self.assertIn(filename.split('.py')[0], supported_devices) + num_devices += 1 + + self.assertEqual(num_devices, len(supported_devices)) + + @patch("pyedbglib.hidtransport.cyhidapi.hid") + def test_get_available_hid_tools_when_none_connected_returns_empty_list(self, mock_hid): + expected_tools = [] + connected_tools = [] + + mock_hid.enumerate.return_value = connected_tools + + tools = self.backend.get_available_hid_tools() + + self.assertEqual(expected_tools, tools) + + @patch("pyedbglib.hidtransport.cyhidapi.hid") + def test_get_available_hid_tools_one_microchip_one_other_returns_only_microchip_tool(self, mock_hid): + fake_microchip_tool_dict = { + 'vendor_id': 0x03EB, + 'product_id': 0xBEEF, + 'serial_number': 'DUMMYSERIAL1', + 'product_string': "Some Microchip CMSIS-DAP Tool", + 'manufacturer_string': "Microchip" + } + fake_other_tool_dict = { + 'vendor_id': 0xDEAD, + 'product_id': 0xBEEF, + 'serial_number': 'DUMMYSERIAL2', + 'product_string': "Some other Tool", + 'manufacturer_string': "Some CORP" + } + + fake_microchip_tool = HidTool( + fake_microchip_tool_dict['vendor_id'], + fake_microchip_tool_dict['product_id'], + fake_microchip_tool_dict['serial_number'], + fake_microchip_tool_dict['product_string'], + fake_microchip_tool_dict['manufacturer_string'] + ) + + expected_tools = [fake_microchip_tool] + connected_tools_dicts = [fake_microchip_tool_dict, fake_other_tool_dict] + + mock_hid.enumerate.return_value = connected_tools_dicts + + tools = self.backend.get_available_hid_tools() + + self.assertEqual(len(tools), len(expected_tools), msg="Incorrect number of tools detected") + self.assertEqual(tools[0].serial_number, expected_tools[0].serial_number) + + @patch("pyedbglib.hidtransport.cyhidapi.hid") + def test_get_available_hid_tools_serial_number_matching(self, mock_hid): + # Note: pyedbglib uses "serial_number" instead of "serialnumber" + fake_microchip_tool_1_dict = { + 'vendor_id': 0x03EB, + 'product_id': 0xBEEF, + 'serial_number': 'DUMMYSERIAL1', + 'product_string': "First Microchip CMSIS-DAP Tool", + 'manufacturer_string': "Microchip" + } + fake_microchip_tool_2_dict = { + 'vendor_id': 0x03EB, + 'product_id': 0xBEEF, + 'serial_number': 'DUMMYSERIAL2', + 'product_string': "Second Microchip CMSIS-DAP Tool", + 'manufacturer_string': "Microchip" + } + + fake_microchip_tool_1 = HidTool( + fake_microchip_tool_1_dict['vendor_id'], + fake_microchip_tool_1_dict['product_id'], + fake_microchip_tool_1_dict['serial_number'], + fake_microchip_tool_1_dict['product_string'], + fake_microchip_tool_1_dict['manufacturer_string'] + ) + + expected_tools = [fake_microchip_tool_1] + connected_tools_dicts = [fake_microchip_tool_1_dict, fake_microchip_tool_2_dict] + + mock_hid.enumerate.return_value = connected_tools_dicts + + tools = self.backend.get_available_hid_tools("SERIAL1") + + self.assertEqual(len(tools), len(expected_tools)) + self.assertEqual(tools[0].serial_number, expected_tools[0].serial_number) + + def test_connect_to_tool_usb_hid_connection_calls_hid_transport_with_correct_serial_and_product(self): + mock_hid_transport = self.mock_hid_transport() + self.mock_housekeepingprotocol() + + serial = "DUMMYSERIAL" + tool = "DUMMYTOOL" + connection = ToolUsbHidConnection(serialnumber=serial, tool_name=tool) + + self.backend.connect_to_tool(connection) + + mock_hid_transport.connect.assert_called_with(serial_number=serial, product=tool) + + def test_connect_to_tool_serial_connection_sets_correct_serialport_and_does_not_connect_to_hid_tool(self): + mock_hid_transport = self.mock_hid_transport() + self.mock_housekeepingprotocol() + + serialport = "COM33" + connection = ToolSerialConnection(serialport=serialport) + + self.backend.connect_to_tool(connection) + + mock_hid_transport.connect.assert_not_called() + self.assertEqual(self.backend.transport, serialport) + + def test_connect_to_tool_with_invalid_configuration_raises_pymcuprogtoolconfigurationerror(self): + with self.assertRaises(PymcuprogToolConfigurationError): + self.backend.connect_to_tool(ToolConnection()) + + @patch("pyedbglib.hidtransport.cyhidapi.hid") + def test_connect_to_tool_when_no_tools_connected_raises_pymcuprogtoolconnectionerror(self, mock_hid): + mock_hid.enumerate.return_value = [] + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.connect_to_tool(ToolUsbHidConnection()) + + def test_connect_to_tool_when_hid_transport_connect_fails_raises_pymcuprogtoolconnectionerror(self): + self.mock_hid_transport(connect_return_value=False) + self.mock_housekeepingprotocol() + + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.connect_to_tool(ToolUsbHidConnection()) + + def test_disconnect_from_tool_when_not_connected_does_not_disconnect_hid(self): + mock_hid_transport = self.mock_hid_transport() + self.mock_housekeepingprotocol() + + self.backend.disconnect_from_tool() + + mock_hid_transport.disconnect.assert_not_called() + + def test_disconnect_from_tool_when_connected_to_serialport_does_not_disconnect_hid_nor_end_housekeeping_session( + self): + mock_hid_transport = self.mock_hid_transport() + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolSerialConnection()) + self.backend.disconnect_from_tool() + + mock_hid_transport.disconnect.assert_not_called() + mock_housekeepingprotocol.end_session.assert_not_called() + + def test_disconnect_from_tool_when_connected_to_hid_does_disconnect_hid_and_end_housekeeping_session(self): + mock_hid_transport = self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.disconnect_from_tool() + + mock_hid_transport.disconnect.assert_called() + mock_housekeepingprotocol.end_session.assert_called() + + def test_read_tool_info_when_not_connected_to_any_tool_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.read_tool_info() + + def test_read_tool_info_when_connected_to_serialport_raises_pymcuprogtoolconnectionerror(self): + self.backend.connect_to_tool(ToolSerialConnection()) + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.read_tool_info() + + def test_read_tool_info(self): + device = "DUMMY_DEVICE" + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol(device) + self.backend.connect_to_tool(ToolUsbHidConnection()) + tool_info = self.backend.read_tool_info() + + # Sanity check of some of the tool_info content + self.assertTrue('device_name' in tool_info, msg="No device_name in tool_info") + self.assertEqual(tool_info['device_name'], device, msg="Incorrect device_name in tool_info") + self.assertTrue('product' in tool_info, msg="No product in tool_info") + self.assertEqual(tool_info['product'], 'nEDBG', "Incorrect product in tool_info") + + def test_read_kit_device_when_kit_has_device_in_config_returns_device_name(self): + device_expected = 'DUMMYDEVICE' + + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol(device=device_expected) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + + device_read = self.backend.read_kit_device() + + self.assertEqual(device_read, device_expected.lower()) + + def test_read_kit_device_when_connected_to_serialport_raises_pymcuprogtoolconnectionerror(self): + self.backend.connect_to_tool(ToolSerialConnection()) + + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.read_kit_device() + + def test_read_kit_device_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.read_kit_device() + + def test_read_kit_device_when_kit_does_not_have_device_in_config_returns_none(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + + device_read = self.backend.read_kit_device() + + self.assertIsNone(device_read) + + def test_read_target_voltage_returns_3_3v(self): + voltage_expected = 3.3 + + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + mock_housekeepingprotocol.get_le16.return_value = int(voltage_expected*1000) & 0xFFFF + + self.backend.connect_to_tool(ToolUsbHidConnection()) + voltage_read = self.backend.read_target_voltage() + + self.assertAlmostEqual(voltage_read, voltage_expected) + mock_housekeepingprotocol.get_le16.assert_called_with( + Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONTEXT_ANALOG, + Jtagice3HousekeepingProtocol.HOUSEKEEPING_ANALOG_VTREF) + + def test_read_target_voltage_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.read_target_voltage() + + def test_read_target_voltage_when_connected_to_serialport_raises_pymcuprogtoolconnectionerror(self): + self.backend.connect_to_tool(ToolSerialConnection()) + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.read_target_voltage() + + def test_read_target_voltage_when_not_supported_raises_pymcuprognotsupportederror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + mock_housekeepingprotocol.get_le16.side_effect = Jtagice3ResponseError('', []) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogNotSupportedError): + self.backend.read_target_voltage() + + def test_read_supply_voltage_setpoint_returns_3_3v(self): + voltage_expected = 3.3 + + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + mock_housekeepingprotocol.get_le16.return_value = int(voltage_expected*1000) & 0xFFFF + + self.backend.connect_to_tool(ToolUsbHidConnection()) + voltage_read = self.backend.read_supply_voltage_setpoint() + + self.assertAlmostEqual(voltage_read, voltage_expected) + mock_housekeepingprotocol.get_le16.assert_called_with( + Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONTEXT_ANALOG, + Jtagice3HousekeepingProtocol.HOUSEKEEPING_TSUP_VOLTAGE) + + def test_read_supply_voltage_setpoint_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.read_supply_voltage_setpoint() + + def test_read_supply_voltage_setpoint_when_connected_to_serialport_raises_pymcuprogtoolconnectionerror(self): + self.backend.connect_to_tool(ToolSerialConnection()) + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.read_supply_voltage_setpoint() + + def test_read_supply_voltage_setpoint_when_not_supported_raises_pymcuprognotsupportederror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + mock_housekeepingprotocol.get_le16.side_effect = Jtagice3ResponseError('', []) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogNotSupportedError): + self.backend.read_supply_voltage_setpoint() + + def test_read_usb_voltage_returns_5v(self): + voltage_expected = 5.0 + + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + mock_housekeepingprotocol.get_le16.return_value = int(voltage_expected*1000) & 0xFFFF + + self.backend.connect_to_tool(ToolUsbHidConnection()) + voltage_read = self.backend.read_usb_voltage() + + self.assertAlmostEqual(voltage_read, voltage_expected) + mock_housekeepingprotocol.get_le16.assert_called_with( + Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONTEXT_ANALOG, + Jtagice3HousekeepingProtocol.HOUSEKEEPING_ANALOG_VUSB) + + def test_read_usb_voltage_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.read_usb_voltage() + + def test_read_usb_voltage_when_connected_to_serialport_raises_pymcuprogtoolconnectionerror(self): + self.backend.connect_to_tool(ToolSerialConnection()) + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.read_usb_voltage() + + def test_read_usb_voltage_when_not_supported_raises_pymcuprognotsupportederror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + mock_housekeepingprotocol.get_le16.side_effect = Jtagice3ResponseError('', []) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogNotSupportedError): + self.backend.read_usb_voltage() + + def test_set_supply_voltage_setpoint_5v_ok(self): + voltage_setpoint = 5.0 + + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.set_supply_voltage_setpoint(voltage_setpoint) + + mock_housekeepingprotocol.set_le16.assert_called_with(Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONTEXT_ANALOG, + Jtagice3HousekeepingProtocol.HOUSEKEEPING_TSUP_VOLTAGE, + int(voltage_setpoint*1000) & 0xFFFF) + + def test_set_supply_voltage_setpoint_0v_ok(self): + voltage_setpoint = 0.0 + + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.set_supply_voltage_setpoint(voltage_setpoint) + + mock_housekeepingprotocol.set_le16.assert_called_with( + Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONTEXT_ANALOG, + Jtagice3HousekeepingProtocol.HOUSEKEEPING_TSUP_VOLTAGE, int(voltage_setpoint*1000) & 0xFFFF) + + def test_set_supply_voltage_setpoint_1v_raises_valueerror(self): + voltage_setpoint = 1.0 + + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + + # pyedbglib throws generic Exception when SET command fails (see DSG-1494) + mock_housekeepingprotocol.set_le16.side_effect = Exception( + "Unable to SET (failure code 0x{:02X})".format(Jtagice3Protocol.SETGET_FAILURE_INVALID_VALUE)) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(ValueError): + self.backend.set_supply_voltage_setpoint(voltage_setpoint) + + def test_set_supply_voltage_setpoint_6v_raises_valueerror(self): + voltage_setpoint = 6.0 + + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + + # pyedbglib throws generic Exception when SET command fails (see DSG-1494) + mock_housekeepingprotocol.set_le16.side_effect = Exception( + "Unable to SET (failure code 0x{:02X})".format(Jtagice3Protocol.SETGET_FAILURE_INVALID_VALUE)) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(ValueError): + self.backend.set_supply_voltage_setpoint(voltage_setpoint) + + def test_set_supply_voltage_setpoint_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.set_supply_voltage_setpoint(3.3) + + def test_set_supply_voltage_setpoint_when_connected_to_serialport_raises_pymcuprogtoolconnectionerror(self): + self.backend.connect_to_tool(ToolSerialConnection()) + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.set_supply_voltage_setpoint(3.3) + + def test_set_usb_voltage_setpoint_when_not_supported_raises_pymcuprognotsupportederror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + mock_housekeepingprotocol.get_le16.side_effect = Jtagice3ResponseError('', []) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogNotSupportedError): + self.backend.set_supply_voltage_setpoint(3.3) + + def test_reboot_tool(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + mock_housekeepingprotocol = self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.reboot_tool() + + mock_housekeepingprotocol.end_session.assert_called_with(reset_tool=True) + + def test_reboot_tool_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.reboot_tool() + + def test_reboot_tool_when_connected_to_serial_port_raises_pymcuprogtoolconnectionerror(self): + self.backend.connect_to_tool(ToolSerialConnection()) + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.reboot_tool() + + def test_reboot_tool_the_second_time_without_another_connect_raises_pymcuprogtoolconnectionerror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool(True) + self.mock_housekeepingprotocol() + self.backend.connect_to_tool(ToolUsbHidConnection()) + # After the tool has rebooted the _is_connected_to_hid_tool should return False + self.mock_is_connected_to_hid_tool(False) + with self.assertRaises(PymcuprogToolConnectionError): + # This checks that the backend knows that a reboot_tool disconnects the tool + self.backend.reboot_tool() + + def test_get_device_info_when_device_is_not_supported_raises_pymcuprognotsupportederror(self): + with self.assertRaises(PymcuprogNotSupportedError): + self.backend.get_device_info('nodevice') + + def test_get_device_info_all_lowercase_supported_device(self): + device = 'pic16f18446' + info = self.backend.get_device_info(device) + + # Check that the device info for the correct device was returned. The rest of the content + # won't be checked as that should be tested as part of separate device info tests + self.assertEqual(info[DeviceInfoKeys.NAME], device) + + def test_get_device_info_half_uppercase_supported_device(self): + device = 'piC16F18446' + info = self.backend.get_device_info(device) + + # Check that the device info for the correct device was returned and that the device name was + # converted to lowercase + self.assertEqual(info[DeviceInfoKeys.NAME], device.lower()) + + def test_start_session_when_no_tool_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.start_session(SessionConfig('pic16f18446')) + + def test_start_session_propagates_all_config_parameters(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + config = SessionConfig('pic16f18446') + config.interface = 'icsp' + config.interface_speed = 500000 + config.packpath = "dsspath" + config.special_options = {"dummyoption": True} + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(config) + + mock_programmer.load_device.assert_called_with(config.device) + mock_programmer.setup_device.assert_called_with( + config.interface, + config.packpath, + config.interface_speed) + mock_programmer.set_options.assert_called_with(config.special_options) + mock_programmer.start.assert_called() + + @patch("pymcuprog.backend.Backend.end_session") + def test_start_session_when_already_started_does_end_session_and_start_new_session(self, mock_end_session): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + config1 = SessionConfig('pic16f18446') + config2 = SessionConfig('atmega4809') + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(config1) + self.backend.start_session(config2) + + mock_end_session.assert_called() + mock_programmer.assert_has_calls( + [ + call.load_device(config1.device), + call.load_device(config2.device) + ], + any_order=True + ) + + def test_start_session_when_device_is_empty_string_raises_pymcuprogsessionconfigerror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + + config = SessionConfig('') + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogSessionConfigError): + self.backend.start_session(config) + + @patch("pymcuprog.programmer.get_nvm_access_provider") + def test_start_session_when_setup_device_returns_none_raises_pymcuprogsessoinconfigerror( + self, + mock_get_nvm_access_provider): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_get_nvm_access_provider.return_value = None + + config = SessionConfig('atmega4809') + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogSessionConfigError): + self.backend.start_session(config) + + @patch("pymcuprog.programmer.get_nvm_access_provider") + def test_start_session_when_nvmupdi_raises_pymcuprogdevicelockederror_propagates( + self, + mock_get_nvm_access_provider): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_nvmaccessprovidercmsisdapupdi = MagicMock() + mock_get_nvm_access_provider.return_value = mock_nvmaccessprovidercmsisdapupdi + mock_nvmaccessprovidercmsisdapupdi.start.side_effect = PymcuprogDeviceLockedError() + + config = SessionConfig('atmega4809') + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogDeviceLockedError): + self.backend.start_session(config) + + def test_start_session_when_device_is_unsupported_raises_pymcuprognotsupportederror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + + config = SessionConfig('unsupported_device') + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogNotSupportedError): + self.backend.start_session(config) + + def test_end_session_when_programming_enabled_stops_programmer(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + config = SessionConfig('atmega4809') + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(config) + self.backend.end_session() + + mock_programmer.stop.assert_called() + + def test_end_session_when_session_not_started_does_not_stop_programmer(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + self.backend.end_session() + + mock_programmer.stop.assert_not_called() + + def test_read_device_id_returns_bytearray_with_id(self): + id_expected = bytearray([0x01, 0x02, 0x03, 0x04]) + + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + mock_programmer.read_device_id.return_value = id_expected + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(SessionConfig('atmega4809')) + id_read = self.backend.read_device_id() + + self.assertEqual(id_expected, id_read) + + def test_read_device_id_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.read_device_id() + + def test_read_device_id_when_connected_to_serialport_does_not_raise_pymcuprogtoolconnection(self): + self.mock_programmer() + self.backend.connect_to_tool(ToolSerialConnection()) + self.backend.start_session(SessionConfig('atmega4809')) + try: + self.backend.read_device_id() + except PymcuprogToolConnectionError as error: + self.fail("PymcuprogToolConnectionError raised: {}".format(error)) + + def test_read_device_id_when_session_not_started_raises_pymcuprogsessionerror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogSessionError): + self.backend.read_device_id() + + def test_erase_propagates_all_parameters(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + target = 'atmega4809' + memory_name = MemoryNames.EEPROM + address = 256 + + self.configure_mock_programmer_with_device_memory_info(mock_programmer, target) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(SessionConfig(target)) + self.backend.erase(memory_name=memory_name, address=address) + + mock_programmer.erase.assert_called_with(memory_name, address) + + def test_erase_when_isolated_erase_false_raises_pymcuprogeraseerror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + # lockbits can't be erased in isolation on UPDI parts + memory_name = MemoryNames.LOCKBITS + address = 0 + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(SessionConfig('atmega4809')) + with self.assertRaises(PymcuprogEraseError): + self.backend.erase(memory_name=memory_name, address=address) + + mock_programmer.erase.assert_not_called() + + def test_erase_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.erase() + + def test_erase_when_session_not_started_raises_pymcuprogsessionerror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogSessionError): + self.backend.erase() + + def test_erase_when_connected_to_serialport_does_not_raise_pymcuprogtoolconnection(self): + self.mock_programmer() + self.backend.connect_to_tool(ToolSerialConnection()) + self.backend.start_session(SessionConfig('atmega4809')) + try: + self.backend.erase() + except PymcuprogToolConnectionError as error: + self.fail("PymcuprogToolConnectionError raised: {}".format(error)) + + def test_read_memory_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.read_memory() + + def test_read_memory_when_session_not_started_raises_pymcuprogsessionerror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogSessionError): + self.backend.read_memory() + + def test_read_memory_when_connected_to_serialport_does_not_raise_pymcuprogtoolconnection(self): + self.mock_programmer() + self.backend.connect_to_tool(ToolSerialConnection()) + self.backend.start_session(SessionConfig('atmega4809')) + try: + self.backend.read_memory() + except PymcuprogToolConnectionError as error: + self.fail("PymcuprogToolConnectionError raised: {}".format(error)) + + def test_read_memory_propagates_all_parameters_and_returns_data_and_memoryinfo(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + # The actual read is done by the programmer, the backend just forwards the call. + # So the test just has to check that the backend forwards parameters correctly and returns the result correctly + + memory_name = 'some_memory' + offset = 0x1000 + numbytes = 256 + data_expected = bytearray([]) + # Generate dummy data for the read to return + for index in range(numbytes): + data_expected.append(index & 0xFF) + # Just make some fake memory info + meminfo_expected = {DeviceMemoryInfoKeys.NAME: memory_name} + + memory_read_tuple_expected = namedtuple("Memory", 'data memory_info') + memory_read_tuple_expected.data = data_expected + memory_read_tuple_expected.memory_info = meminfo_expected + + mock_programmer.read_memory.return_value = [memory_read_tuple_expected] + + self.backend.connect_to_tool(ToolUsbHidConnection()) + # Just pick any device + self.backend.start_session(SessionConfig('atmega4809')) + read_list = self.backend.read_memory(memory_name=memory_name, offset_byte=offset, numbytes=numbytes) + + data_read = read_list[0].data + meminfo_read = read_list[0].memory_info + + self.assertEqual(len(read_list), 1, msg="read_memory returned more than one memory segment") + + mock_programmer.read_memory.assert_called_with(memory_name=memory_name, offset=offset, numbytes=numbytes) + + self.assertEqual(data_read, data_expected) + self.assertEqual(meminfo_read, meminfo_expected) + + def test_write_memory_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.write_memory(bytearray([])) + + def test_write_memory_when_connected_to_serialport_does_not_raise_pymcuprogtoolconnection(self): + self.mock_programmer() + self.backend.connect_to_tool(ToolSerialConnection()) + self.backend.start_session(SessionConfig('atmega4809')) + try: + self.backend.write_memory(bytearray([])) + except PymcuprogToolConnectionError as error: + self.fail("PymcuprogToolConnectionError raised: {}".format(error)) + + def test_write_memory_when_session_not_started_raises_pymcuprogsessionerror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogSessionError): + self.backend.write_memory(bytearray([])) + + def test_write_memory_propagates_all_parameters(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + # The actual write is done by the programmer, the backend just forwards the call. + # So the test just has to check that the backend forwards parameters correctly + + memory_name = 'some_memory' + offset = 0x1000 + numbytes = 256 + data_expected = bytearray([]) + # Generate dummy data for the write + for index in range(numbytes): + data_expected.append(index & 0xFF) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + # Just pick any device + self.backend.start_session(SessionConfig('atmega4809')) + self.backend.write_memory(data=data_expected, memory_name=memory_name, offset_byte=offset) + + mock_programmer.write_memory.assert_called_with(data=data_expected, memory_name=memory_name, offset=offset) + + def test_verify_memory_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.verify_memory(bytearray([])) + + def test_verify_memory_when_connected_to_serialport_does_not_raise_pymcuprogtoolconnection(self): + self.mock_programmer() + self.backend.connect_to_tool(ToolSerialConnection()) + self.backend.start_session(SessionConfig('atmega4809')) + try: + self.backend.verify_memory(bytearray([])) + except PymcuprogToolConnectionError as error: + self.fail("PymcuprogToolConnectionError raised: {}".format(error)) + + def test_verify_memory_when_session_not_started_raises_pymcuprogsessionerror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogSessionError): + self.backend.verify_memory(bytearray([])) + + def test_verify_memory_propagates_all_parameters_and_returns_result(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + mock_programmer.verify_memory.return_value = True + + # The actual verify is done by the programmer, the backend just forwards the call. + # So the test just has to check that the backend forwards parameters correctly and returns the result correctly + + memory_name = 'some_memory' + offset = 0x1000 + numbytes = 256 + data_expected = bytearray([]) + # Generate dummy data for the verify + for index in range(numbytes): + data_expected.append(index & 0xFF) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + # Just pick any device + self.backend.start_session(SessionConfig('atmega4809')) + verify_result = self.backend.verify_memory(data=data_expected, memory_name=memory_name, offset_byte=offset) + + mock_programmer.verify_memory.assert_called_with(data=data_expected, memory_name=memory_name, offset=offset) + self.assertTrue(verify_result, msg="Verify did not return success") + + def test_hold_in_reset_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.hold_in_reset() + + def test_hold_in_reset_when_connected_to_serialport_does_not_raise_pymcuprogtoolconnection(self): + self.mock_programmer() + self.backend.connect_to_tool(ToolSerialConnection()) + self.backend.start_session(SessionConfig('atmega4809')) + try: + self.backend.hold_in_reset() + except PymcuprogToolConnectionError as error: + self.fail("PymcuprogToolConnectionError raised: {}".format(error)) + + def test_hold_in_reset_when_session_not_started_raises_pymcuprogsessionerror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogSessionError): + self.backend.hold_in_reset() + + def test_hold_in_reset_propagates(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + # Just pick any device + self.backend.start_session(SessionConfig('atmega4809')) + self.backend.hold_in_reset() + + mock_programmer.hold_in_reset.assert_called() + + def test_release_from_reset_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.release_from_reset() + + def test_release_from_reset_when_session_not_started_raises_pymcuprogsessionerror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogSessionError): + self.backend.release_from_reset() + + def test_release_from_reset_propagates(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + # Just pick any device + self.backend.start_session(SessionConfig('atmega4809')) + self.backend.release_from_reset() + + mock_programmer.release_from_reset.assert_called() + + def test_release_from_reset_twice_raises_pymcuprogsessionerror(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + self.mock_programmer() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + # Just pick any device + self.backend.start_session(SessionConfig('atmega4809')) + self.backend.release_from_reset() + + # Calling the release_from_reset should now fail with a Session error because the previous + # release_from_reset took down the session + with self.assertRaises(PymcuprogSessionError): + self.backend.release_from_reset() + + def test_write_hex_to_target_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.write_hex_to_target('nonexisting.hex') + + def test_write_hex_to_target_when_connected_to_serialport_does_not_raise_pymcuprogtoolconnection(self): + self.mock_programmer() + self.mock_read_memories_from_hex() + self.backend.connect_to_tool(ToolSerialConnection()) + self.backend.start_session(SessionConfig('atmega4809')) + try: + self.backend.write_hex_to_target('nonexisting.hex') + except PymcuprogToolConnectionError as error: + self.fail("PymcuprogToolConnectionError raised: {}".format(error)) + + def test_write_hex_to_target_when_session_not_started_raises_pymcuprogsessionerror(self): + self.mock_hid_transport() + self.mock_read_memories_from_hex() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogSessionError): + self.backend.write_hex_to_target('nonexisting.hex') + + def test_write_hex_to_target_propagates_all_parameters(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + # The actual write is done by the programmer, the backend just forwards the call. + # So the test just has to check that the backend forwards parameters correctly + + target = 'atmega4809' + flash_hexfile_address = 0 + eeprom_hexfile_address = 0x810000 + + filename = "{}flash_eeprom.hex".format(TESTFILE_FOLDER) + + flash_numbytes = 256 + flash_offset = 0 + flash_data = bytearray([]) + for count in range(flash_numbytes): + flash_data.append(count & 0xFF) + + eeprom_numbytes = 32 + eeprom_offset = 8 + eeprom_data = bytearray([]) + for count in range(eeprom_numbytes): + eeprom_data.append(count & 0xFF) + + self.configure_mock_programmer_with_device_memory_info(mock_programmer, target) + + # Make a hex file with a Flash segment and an EEPROM segment + ihex = IntelHex() + hexindex = flash_offset + flash_hexfile_address + for databyte in flash_data: + ihex[hexindex] = databyte + hexindex += 1 + hexindex = eeprom_offset + eeprom_hexfile_address + for databyte in eeprom_data: + ihex[hexindex] = databyte + hexindex += 1 + _write_hex_to_file(ihex, filename) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(SessionConfig(target)) + self.backend.write_hex_to_target(filename) + + mock_programmer.assert_has_calls( + [ + call.write_memory(data=flash_data, memory_name=MemoryNames.FLASH, offset=flash_offset), + call.write_memory(data=eeprom_data, memory_name=MemoryNames.EEPROM, offset=eeprom_offset) + ] + ) + + def test_verify_hex_when_not_connected_raises_pymcuprogtoolconnectionerror(self): + with self.assertRaises(PymcuprogToolConnectionError): + self.backend.verify_hex('nonexisting.hex') + + def test_verify_hex_when_connected_to_serialport_does_not_raise_pymcuprogtoolconnection(self): + self.mock_programmer() + self.mock_read_memories_from_hex() + self.backend.connect_to_tool(ToolSerialConnection()) + self.backend.start_session(SessionConfig('atmega4809')) + try: + self.backend.verify_hex('nonexisting.hex') + except PymcuprogToolConnectionError as error: + self.fail("PymcuprogToolConnectionError raised: {}".format(error)) + + def test_verify_hex_when_session_not_started_raises_pymcuprogsessionerror(self): + self.mock_hid_transport() + self.mock_read_memories_from_hex() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + + self.backend.connect_to_tool(ToolUsbHidConnection()) + with self.assertRaises(PymcuprogSessionError): + self.backend.verify_hex('nonexisting.hex') + + def test_verify_hex_ok_propagates_all_parameters(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + # The actual verify is done by the programmer, the backend just forwards the call. + # So the test just has to check that the backend forwards parameters correctly + + target = 'atmega4809' + flash_hexfile_address = 0 + eeprom_hexfile_address = 0x810000 + + filename = "{}flash_eeprom.hex".format(TESTFILE_FOLDER) + + flash_numbytes = 256 + flash_offset = 0 + flash_data = bytearray([]) + for count in range(flash_numbytes): + flash_data.append(count & 0xFF) + + eeprom_numbytes = 32 + eeprom_offset = 8 + eeprom_data = bytearray([]) + for count in range(eeprom_numbytes): + eeprom_data.append(count & 0xFF) + + self.configure_mock_programmer_with_device_memory_info(mock_programmer, target) + + # Pretend all verifications were successful + mock_programmer.verify_memory.return_value = True + + # Make a hex file with a Flash segment and an EEPROM segment + ihex = IntelHex() + hexindex = flash_offset + flash_hexfile_address + for databyte in flash_data: + ihex[hexindex] = databyte + hexindex += 1 + hexindex = eeprom_offset + eeprom_hexfile_address + for databyte in eeprom_data: + ihex[hexindex] = databyte + hexindex += 1 + _write_hex_to_file(ihex, filename) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(SessionConfig(target)) + verify_result = self.backend.verify_hex(filename) + + mock_programmer.assert_has_calls( + [ + call.verify_memory(data=flash_data, memory_name=MemoryNames.FLASH, offset=flash_offset), + call.verify_memory(data=eeprom_data, memory_name=MemoryNames.EEPROM, offset=eeprom_offset) + ] + ) + + self.assertTrue(verify_result, msg="Successful verify should return True") + + def test_verify_hex_fails_returns_false(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + target = 'atmega4809' + flash_hexfile_address = 0 + + filename = "{}flash.hex".format(TESTFILE_FOLDER) + + flash_numbytes = 4 + flash_offset = 0 + flash_data = bytearray([]) + for count in range(flash_numbytes): + flash_data.append(count & 0xFF) + + self.configure_mock_programmer_with_device_memory_info(mock_programmer, target) + + # Pretend verification failed + mock_programmer.verify_memory.return_value = False + + # Make a hex file with a Flash segment + ihex = IntelHex() + hexindex = flash_offset + flash_hexfile_address + for databyte in flash_data: + ihex[hexindex] = databyte + hexindex += 1 + _write_hex_to_file(ihex, filename) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(SessionConfig(target)) + verify_result = self.backend.verify_hex(filename) + + self.assertFalse(verify_result, msg="Failing verify should return False") + + def test_is_isolated_erase_possible_flash_mega4809_returns_true(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + target = 'atmega4809' + + self.configure_mock_programmer_with_device_memory_info(mock_programmer, target) + + config = SessionConfig(target) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(config) + + self.assertTrue(self.backend.is_isolated_erase_possible(MemoryNames.FLASH)) + + def test_is_isolated_erase_possible_eeprom_mega4809_returns_true(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + target = 'atmega4809' + + self.configure_mock_programmer_with_device_memory_info(mock_programmer, target) + + config = SessionConfig(target) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(config) + + self.assertTrue(self.backend.is_isolated_erase_possible(MemoryNames.EEPROM)) + + def test_is_isolated_erase_possible_when_key_is_missing_returns_false(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + target = 'atmega4809' + + # Copy the device info before messing with it to avoid altering the original device info as it + # will mess up other tests + device_info = copy.deepcopy(deviceinfo.getdeviceinfo(target)) + # Remove the isolated_erase flag for eeprom + device_info.pop('eeprom_isolated_erase') + device_meminfo = deviceinfo.DeviceMemoryInfo(device_info) + # Let the modified memory info be returned by the programmer mock + mock_programmer.get_device_memory_info.return_value = device_meminfo + + config = SessionConfig(target) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(config) + + self.assertFalse(self.backend.is_isolated_erase_possible(MemoryNames.EEPROM)) + + def test_get_chiperase_effect_eeprom_mega4809_returns_conditionally_erased(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + target = 'atmega4809' + + self.configure_mock_programmer_with_device_memory_info(mock_programmer, target) + + config = SessionConfig(target) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(config) + + self.assertEqual(self.backend.get_chiperase_effect(MemoryNames.EEPROM), + ChiperaseEffect.CONDITIONALLY_ERASED_AVR) + + def test_get_chiperase_effect_flash_mega4809_returns_always_erased(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + target = 'atmega4809' + + self.configure_mock_programmer_with_device_memory_info(mock_programmer, target) + + config = SessionConfig(target) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(config) + + self.assertEqual(self.backend.get_chiperase_effect(MemoryNames.FLASH), ChiperaseEffect.ALWAYS_ERASED) + + def test_get_chiperase_effect_when_key_is_missing_returns_not_erased(self): + self.mock_hid_transport() + self.mock_is_connected_to_hid_tool() + self.mock_housekeepingprotocol() + mock_programmer = self.mock_programmer() + + target = 'atmega4809' + + # Copy the device info before messing with it to avoid altering the original device info as it + # will mess up other tests + device_info = copy.deepcopy(deviceinfo.getdeviceinfo(target)) + # Remove the chiperase_effect flag for flash + device_info.pop('flash_chiperase_effect') + device_meminfo = deviceinfo.DeviceMemoryInfo(device_info) + # Let the modified memory info be returned by the programmer mock + mock_programmer.get_device_memory_info.return_value = device_meminfo + + config = SessionConfig(target) + + self.backend.connect_to_tool(ToolUsbHidConnection()) + self.backend.start_session(config) + + self.assertEqual(self.backend.get_chiperase_effect(MemoryNames.FLASH), ChiperaseEffect.NOT_ERASED) diff --git a/pymcuprog/tests/test_device_models.py b/pymcuprog/tests/test_device_models.py new file mode 100644 index 0000000..e5687f1 --- /dev/null +++ b/pymcuprog/tests/test_device_models.py @@ -0,0 +1,184 @@ +# The intention is to make the test names descriptive enough to not need any docstrings for most of them +#pylint: disable=missing-docstring +""" +Content tests for device models found in deviceinfo.devices +""" +import unittest + +from parameterized import parameterized_class + +from pymcuprog.deviceinfo.deviceinfo import get_supported_devices, getdeviceinfo +from pymcuprog.deviceinfo.deviceinfo import DeviceMemoryInfo +from pymcuprog.deviceinfo.memorynames import MemoryNames +from pymcuprog.deviceinfo.eraseflags import get_list_of_chiperase_effects +from pymcuprog.deviceinfo.deviceinfokeys import DeviceMemoryInfoKeys, DeviceInfoKeysPic +from pymcuprog.deviceinfo.deviceinfokeys import DeviceInfoKeys, DeviceInfoKeysAvr, DeviceInfoKeysAvr32 + +DEVICELIST = get_supported_devices() + +DEVICETUPLELIST = [] +for item in DEVICELIST: + DEVICETUPLELIST.append((item,)) + +# Run all tests for each device +@parameterized_class(('devicename'), DEVICETUPLELIST) +class TestDeviceModels(unittest.TestCase): + """ + This test class contains tests that are repeated for each device file + """ + + def setUp(self): + # pylint does not understand the trick done by parameterized which injects the devicename at runtime + #pylint: disable=no-member + self.deviceinfo = getdeviceinfo(self.devicename) + + @staticmethod + def _is_8bit_pic(devicename): + return 'pic' in devicename.lower() and 'pic24' not in devicename.lower() + + def _param_exists(self, param, memory_info): + memname = memory_info[DeviceMemoryInfoKeys.NAME] + self.assertTrue(param in memory_info, + msg="{} memory is missing {} parameter".format(memname, param)) + + def _check_param_type(self, param, paramtype, memory_info): + paramvalue = memory_info[param] + memname = memory_info[DeviceMemoryInfoKeys.NAME] + self.assertIsInstance(paramvalue, paramtype, + msg="Invalid {} parameter of {} memory".format(param, memname)) + + def _check_param_list_of_positive_ints(self, param, memory_info): + self._check_param_type(param, list, memory_info) + paramvalue = memory_info[param] + memname = memory_info[DeviceMemoryInfoKeys.NAME] + for subparam in paramvalue: + message = "Invalid item in {} parameter list of {} memory".format(param, memname) + self.assertIsInstance(subparam, int, msg=message) + message = "Item in {} parameter list of {} memory should be a positive value".format(subparam, memname) + self.assertGreaterEqual(subparam, 0, msg=message) + + def _check_param_is_positive(self, param, memory_info): + paramvalue = memory_info[param] + memname = memory_info[DeviceMemoryInfoKeys.NAME] + self.assertGreaterEqual(paramvalue, 0, + msg="{} parameter of {} memory should be a positive value".format(param, memname)) + + def _check_param_is_greater_than_zero(self, param, memory_info): + paramvalue = memory_info[param] + memname = memory_info[DeviceMemoryInfoKeys.NAME] + message = "{} parameter of {} memory should be a positive value greater than 0".format(param, memname) + self.assertGreater(paramvalue, 0, msg=message) + + def _check_chiperase_effect_param(self, memory_info): + chiperase_effect_key = DeviceMemoryInfoKeys.CHIPERASE_EFFECT + chiperase_effect_valid_values = get_list_of_chiperase_effects() + self._param_exists(chiperase_effect_key, memory_info) + chiperase_effect_value = memory_info[chiperase_effect_key] + memname = memory_info[DeviceMemoryInfoKeys.NAME] + message = "Invalid {} value ({}) for {} memory".format(chiperase_effect_key, chiperase_effect_value, memname) + self.assertTrue(chiperase_effect_value in chiperase_effect_valid_values, msg=message) + + def _check_isolated_erase_param(self, memory_info): + isolated_erase_key = DeviceMemoryInfoKeys.ISOLATED_ERASE + self._param_exists(isolated_erase_key, memory_info) + self._check_param_type(isolated_erase_key, bool, memory_info) + + @staticmethod + def _is_valid_memory_type(param): + valid_memory_types = MemoryNames.get_all() + valid_memory_param = False + for memtype in valid_memory_types: + if param.startswith(memtype): + valid_memory_param = True + break + return valid_memory_param + + @staticmethod + def _strip_size_qualifier(param): + if param.endswith('_byte') or param.endswith('_bytes'): + param = param.split('_byte')[0] + elif param.endswith('_word') or param.endswith('_words'): + param = param.split('_word')[0] + + return param + + def test_device_info_parameters(self): + """Check each parameter in the device info dict for the current device""" + devicename = self.deviceinfo[DeviceInfoKeys.NAME].lower() + valid_device_keys_pic = DeviceInfoKeysPic.get_all() + valid_device_keys_avr32 = DeviceInfoKeysAvr32.get_all() + valid_device_keys_avr = DeviceInfoKeysAvr.get_all() + valid_device_keys_common = DeviceInfoKeys.get_all() + for key in self.deviceinfo: + # The key is a memory parameter for a valid memory type, continue with next. + # Note that the test_device_memories will check that all required memory parameters are in place for + # each memory, so it is sufficient to check that the parameter belongs to a valid memory. + if self._is_valid_memory_type(key): + continue + + # Remove any word/byte qualifiers before checking for valid non-memory related parameters as some + # parameters (like default_bulk_erase_address) will have such a qualifier that will be removed while + # parsing the device info dict + key = self._strip_size_qualifier(key) + + # If not check if it is a valid device info key + if 'pic' in devicename: + if key in valid_device_keys_pic: + # This is a PIC device and the key is a valid PIC device file key, continue with next + continue + elif 'uc3' in devicename: + if key in valid_device_keys_avr32: + # This is a UC3 device (32-bit AVR) and the key is a valid AVR32 device file key, continue with next + continue + elif 'sam' in devicename: + # No extra keys defined for SAM devices so just check the common keys + if key in valid_device_keys_common: + # This is a SAM device and the key is a valid device file key, continue with next + continue + else: + # Everything else is assumed to be AVR devices since these have the least consistent names + if key in valid_device_keys_avr: + # This is an AVR device and the key is a valid AVR device file key, continue with next + continue + + self.fail(msg="{} is not a valid device info parameter for {}".format(key, devicename)) + + def test_device_memories(self): + required_int_params = [DeviceMemoryInfoKeys.ADDRESS, + DeviceMemoryInfoKeys.SIZE, + DeviceMemoryInfoKeys.PAGE_SIZE, + DeviceMemoryInfoKeys.WRITE_SIZE, + DeviceMemoryInfoKeys.READ_SIZE, + DeviceMemoryInfoKeys.ERASE_ADDRESS, + DeviceMemoryInfoKeys.HEXFILE_ADDRESS, + DeviceMemoryInfoKeys.HEXFILE_SIZE] + + required_list_of_int_params = [DeviceMemoryInfoKeys.VERIFY_MASK] + + device_memory_info = DeviceMemoryInfo(self.deviceinfo) + all_memories = MemoryNames.get_all() + for memname in all_memories: + try: + meminfo = device_memory_info.memory_info_by_name(memname) + except ValueError: + # This memory is not defined for the current device, just move on + continue + else: + for param in required_int_params: + self._param_exists(param, meminfo) + self._check_param_type(param, int, meminfo) + if DeviceMemoryInfoKeys.PAGE_SIZE in param: + # Page size should be more than 0 to avoid errors like divide by 0, + # chunking into chunks with size 0 etc. + self._check_param_is_greater_than_zero(param, meminfo) + else: + # Other parameters should also be positive, but can be 0 + self._check_param_is_positive(param, meminfo) + + for param in required_list_of_int_params: + self._param_exists(param, meminfo) + self._check_param_list_of_positive_ints(param, meminfo) + + self._check_chiperase_effect_param(meminfo) + + self._check_isolated_erase_param(meminfo) diff --git a/pymcuprog/tests/test_hexfileutils.py b/pymcuprog/tests/test_hexfileutils.py new file mode 100644 index 0000000..5fce4f8 --- /dev/null +++ b/pymcuprog/tests/test_hexfileutils.py @@ -0,0 +1,382 @@ +# The intention is to make the test names descriptive enough to not need any docstrings for most of them +#pylint: disable=missing-docstring +# Some tests require a lot of configurable parameters so there will be many local variables +#pylint: disable=too-many-locals +import unittest +import shutil +from collections import namedtuple +from intelhex import IntelHex + +from pymcuprog.hexfileutils import write_memories_to_hex, write_memory_to_hex, read_memories_from_hex +from pymcuprog.hexfileutils import _write_hex_to_file +from pymcuprog.deviceinfo import deviceinfo +from pymcuprog.deviceinfo.memorynames import MemoryNames +from pymcuprog.deviceinfo.deviceinfokeys import DeviceMemoryInfoKeys +from pymcuprog.backend import Backend + +TESTFILE_FOLDER = "pymcuprog//tests//temphexfiles//" + +class TestHexfileutils(unittest.TestCase): + def setUp(self): + # Make sure no relics from previous tests mess up the current test + try: + shutil.rmtree(TESTFILE_FOLDER) + except FileNotFoundError: + # Folder is already gone + pass + + @staticmethod + def _generate_dummydata(numbytes): + """ + Generate dummy data to be used for read and write tests. + + Generated data will be an 8-bit counter + """ + data = bytearray([]) + for count in range(numbytes): + data.append(count & 0xFF) + + return data + + @staticmethod + def _generate_memorytuple(numbytes, memory_info): + """ + Generate a namedtuple with two fields: data and memory_info + + :param numbytes: number of data bytes to generate (dummy data) + :param memory_info: memory info dictionary as defined in pymcuprog.deviceinfo.deviceinfo + :returns namedtuple with two fields: data and memory_info + """ + mem_tuple = namedtuple('Memory', "data memory_info") + mem_tuple.data = TestHexfileutils._generate_dummydata(numbytes) + mem_tuple.memory_info = memory_info + + return mem_tuple + + def test_write_atmega4809_eeprom_uses_hexfile_address(self): + filename = "{}eeprom.hex".format(TESTFILE_FOLDER) + offset = 16 + numbytes = 8 + info = Backend.get_device_info('atmega4809') + device_memory_info = deviceinfo.DeviceMemoryInfo(info) + eeprom_info = device_memory_info.memory_info_by_name(MemoryNames.EEPROM) + + mem_tuple = self._generate_memorytuple(numbytes, eeprom_info) + write_memory_to_hex(filename, mem_tuple, offset) + + hexfile = IntelHex() + hexfile.fromfile(filename, format='hex') + + segments = hexfile.segments() + + segment = segments[0] + start = segment[0] + stop = segment[1] + hexadr = eeprom_info[DeviceMemoryInfoKeys.HEXFILE_ADDRESS] + self.assertEqual(start, hexadr+offset, msg="Unexpected start hex address") + self.assertEqual(stop, hexadr+offset+numbytes, msg="Unexpected stop hex address") + + def test_write_and_read_flash_pic16f18446(self): + """ + This test exercises the write_memory_to_hex function (single segment) + """ + filename = "{}flash.hex".format(TESTFILE_FOLDER) + offset = 16 + numbytes = 256 + info = Backend.get_device_info('pic16f18446') + device_memory_info = deviceinfo.DeviceMemoryInfo(info) + flash_info = device_memory_info.memory_info_by_name(MemoryNames.FLASH) + + mem_tuple = self._generate_memorytuple(numbytes, flash_info) + + write_memory_to_hex(filename, mem_tuple, offset=offset) + segments_read = read_memories_from_hex(filename, device_memory_info) + + flash_read = segments_read[0] + self.assertEqual(len(segments_read), 1, msg="There should be only one segment in the hex") + self.assertEqual(flash_read.offset, offset, msg="Incorrect offset") + self.assertEqual(flash_read.memory_info[DeviceMemoryInfoKeys.NAME], + MemoryNames.FLASH, msg="Incorrect memory name") + self.assertEqual(flash_read.data, mem_tuple.data, msg="Incorrect data") + + def test_write_and_read_flash_eeprom_atmega4809(self): + """ + This test exercises the write_memories_to_hex function (multiple segments) + """ + filename = "{}flasheeprom.hex".format(TESTFILE_FOLDER) + numbytes_flash = 256 + numbytes_eeprom = 8 + info = Backend.get_device_info('atmega4809') + device_memory_info = deviceinfo.DeviceMemoryInfo(info) + flash_info = device_memory_info.memory_info_by_name(MemoryNames.FLASH) + eeprom_info = device_memory_info.memory_info_by_name(MemoryNames.EEPROM) + + mem_tuple_flash = self._generate_memorytuple(numbytes_flash, flash_info) + mem_tuple_eeprom = self._generate_memorytuple(numbytes_eeprom, eeprom_info) + memories = [mem_tuple_flash, mem_tuple_eeprom] + + write_memories_to_hex(filename, memories) + segments_read = read_memories_from_hex(filename, device_memory_info) + + for segment_read in segments_read: + memory_name = segment_read.memory_info[DeviceMemoryInfoKeys.NAME] + # When writing multiple segments all will start from relative offset 0 + self.assertEqual(segment_read.offset, 0, msg="Incorrect offset for {}".format(memory_name)) + if memory_name == MemoryNames.FLASH: + self.assertEqual(segment_read.data, mem_tuple_flash.data, msg="Incorrect FLASH data") + elif memory_name == MemoryNames.EEPROM: + self.assertEqual(segment_read.data, mem_tuple_eeprom.data, msg="Incorrect EEPROM data") + else: + self.fail("Unexpected memory: {}".format(memory_name)) + + def test_write_and_read_flash_eeprom_pic16f18446(self): + """ + This test exercises the write_memories_to_hex function (multiple segments) + """ + filename = "{}flasheeprom.hex".format(TESTFILE_FOLDER) + numbytes_flash = 256 + numbytes_eeprom = 8 + info = Backend.get_device_info('pic16f18446') + device_memory_info = deviceinfo.DeviceMemoryInfo(info) + flash_info = device_memory_info.memory_info_by_name(MemoryNames.FLASH) + eeprom_info = device_memory_info.memory_info_by_name(MemoryNames.EEPROM) + + mem_tuple_flash = self._generate_memorytuple(numbytes_flash, flash_info) + mem_tuple_eeprom = self._generate_memorytuple(numbytes_eeprom, eeprom_info) + memories = [mem_tuple_flash, mem_tuple_eeprom] + + write_memories_to_hex(filename, memories) + segments_read = read_memories_from_hex(filename, device_memory_info) + + for segment_read in segments_read: + memory_name = segment_read.memory_info[DeviceMemoryInfoKeys.NAME] + # When writing multiple segments all will start from relative offset 0 + self.assertEqual(segment_read.offset, 0, msg="Incorrect offset for {}".format(memory_name)) + if memory_name == MemoryNames.FLASH: + self.assertEqual(segment_read.data, mem_tuple_flash.data, msg="Incorrect FLASH data") + elif memory_name == MemoryNames.EEPROM: + self.assertEqual(segment_read.data, mem_tuple_eeprom.data, msg="Incorrect EEPROM data") + else: + self.fail("Unexpected memory: {}".format(memory_name)) + + # Sanity check to see that the phantom bytes actually were added by the write to hex + hexfile = IntelHex() + hexfile.fromfile(filename, format='hex') + hexadr = eeprom_info[DeviceMemoryInfoKeys.HEXFILE_ADDRESS] + data_read = hexfile.tobinarray(start=hexadr, + end=hexadr + numbytes_eeprom*2 - 1) + self.assertEqual(len(data_read), + numbytes_eeprom*2, + msg="EEPROM should include twice as many bytes as was written due to the phantom bytes") + index = 0 + for index in range(numbytes_eeprom): + self.assertEqual(data_read[index*2], + mem_tuple_eeprom.data[index], + msg="Incorrect EEROM data in written hex at index {}".format(index*2)) + self.assertEqual(data_read[index*2+1], + 0x00, + msg="Incorrect phantom byte in written hex at index {}".format(index*2+1)) + + def test_write_and_read_eeprom_pic16f18446(self): + """ + This test exercises the write_memory_to_hex function (single segment) + """ + filename = "{}eeprom.hex".format(TESTFILE_FOLDER) + offset = 0 + info = Backend.get_device_info('pic16f18446') + device_memory_info = deviceinfo.DeviceMemoryInfo(info) + eeprom_info = device_memory_info.memory_info_by_name(MemoryNames.EEPROM) + numbytes_eeprom = eeprom_info[DeviceMemoryInfoKeys.SIZE] + + mem_tuple_eeprom = self._generate_memorytuple(numbytes_eeprom, eeprom_info) + + write_memory_to_hex(filename, mem_tuple_eeprom, offset) + segments_read = read_memories_from_hex(filename, device_memory_info) + + for segment_read in segments_read: + memory_name = segment_read.memory_info[DeviceMemoryInfoKeys.NAME] + # When writing multiple segments all will start from relative offset 0 + self.assertEqual(segment_read.offset, offset, msg="Incorrect offset for {}".format(memory_name)) + if memory_name == MemoryNames.EEPROM: + self.assertEqual(segment_read.data, mem_tuple_eeprom.data, msg="Incorrect EEPROM data") + else: + self.fail("Unexpected memory: {}".format(memory_name)) + + # Sanity check to see that the phantom bytes actually were added by the write to hex + hexfile = IntelHex() + hexfile.fromfile(filename, format='hex') + hexadr = eeprom_info[DeviceMemoryInfoKeys.HEXFILE_ADDRESS] + data_read = hexfile.tobinarray(start=hexadr, + end=hexadr + numbytes_eeprom*2 - 1) + self.assertEqual(len(data_read), + numbytes_eeprom*2, + msg="EEPROM should include twice as many bytes as was written due to the phantom bytes") + index = 0 + for index in range(numbytes_eeprom): + self.assertEqual(data_read[index*2], + mem_tuple_eeprom.data[index], + msg="Incorrect EEROM data in written hex at index {}".format(index*2)) + self.assertEqual(data_read[index*2+1], + 0x00, + msg="Incorrect phantom byte in written hex at index {}".format(index*2+1)) + + def test_read_when_file_does_not_exist_raises_filenotfounderror(self): + with self.assertRaises(FileNotFoundError): + read_memories_from_hex('nonexisting', {}) + + def test_read_hex_with_several_segments_within_same_memory(self): + # Just use a PIC device to avoid having to create fake device info + devinfo = Backend.get_device_info('pic16f18446') + device_memory_info = deviceinfo.DeviceMemoryInfo(devinfo) + flashinfo = device_memory_info.memory_info_by_name(MemoryNames.FLASH) + filename = "{}multi_flash.hex".format(TESTFILE_FOLDER) + + flash1_numbytes = 256 + flash1_offset = 0 + flash1_data = self._generate_dummydata(flash1_numbytes) + + flash2_numbytes = 512 + flash2_offset = 1024 + flash2_data = self._generate_dummydata(flash2_numbytes) + + # Make a hex file with the two flash segments + ihex = IntelHex() + hexindex = flash1_offset + for databyte in flash1_data: + ihex[hexindex] = databyte + hexindex += 1 + hexindex = flash2_offset + for databyte in flash2_data: + ihex[hexindex] = databyte + hexindex += 1 + _write_hex_to_file(ihex, filename) + + segments_read = read_memories_from_hex(filename, device_memory_info) + + self.assertEqual(len(segments_read), 2, msg="Too many segments read from the hex") + for segment_read in segments_read: + memory_name = segment_read.memory_info[DeviceMemoryInfoKeys.NAME] + self.assertEqual(memory_name, flashinfo[DeviceMemoryInfoKeys.NAME]) + if segment_read.offset == flash1_offset: + self.assertEqual(segment_read.data, flash1_data, "First flash segment data mistmatch") + elif segment_read.offset == flash2_offset: + self.assertEqual(segment_read.data, flash2_data, "Second flash segment data mistmatch") + else: + self.fail("Segment with invalid offset: {}".format(segment_read.offset)) + + def test_write_and_read_several_memories_in_one_hex_segment(self): + """ + This test hacks the memory information for PIC16F18446 to pretend flash and eeprom memories are next to each + other so that it becomes possible to test writing and reading a hex with several memories in the same hex + segment. + """ + filename = "{}consecutive_memories.hex".format(TESTFILE_FOLDER) + info = Backend.get_device_info('pic16f18446') + + # Hack info to make flash and eeprom segments smaller and next to each other + flash_size_key = MemoryNames.FLASH + '_' + DeviceMemoryInfoKeys.SIZE + flash_size_words_key = flash_size_key + '_words' + flash_size_bytes_key = flash_size_key + '_bytes' + if flash_size_words_key in info: + flash_size_words_original = info[flash_size_words_key] + info[flash_size_words_key] = 1024 + else: + flash_size_bytes_original = info[flash_size_bytes_key] + info[flash_size_bytes_key] = 1024*2 + eeprom_address_key = MemoryNames.EEPROM + '_' + DeviceMemoryInfoKeys.ADDRESS + eeprom_address_word_key = eeprom_address_key + '_word' + eeprom_address_byte_key = eeprom_address_key + '_byte' + if eeprom_address_word_key in info: + eeprom_address_word_original = info[eeprom_address_word_key] + info[eeprom_address_word_key] = 1024 + else: + eeprom_address_byte_original = info[eeprom_address_byte_key] + info[eeprom_address_byte_key] = 1024*2 + + device_memory_info = deviceinfo.DeviceMemoryInfo(info) + flash_info = device_memory_info.memory_info_by_name(MemoryNames.FLASH) + eeprom_info = device_memory_info.memory_info_by_name(MemoryNames.EEPROM) + + mem_tuple_flash = self._generate_memorytuple(flash_info[DeviceMemoryInfoKeys.SIZE], flash_info) + mem_tuple_eeprom = self._generate_memorytuple(eeprom_info[DeviceMemoryInfoKeys.SIZE], eeprom_info) + memories = [mem_tuple_flash, mem_tuple_eeprom] + + write_memories_to_hex(filename, memories) + segments_read = read_memories_from_hex(filename, device_memory_info) + + for segment_read in segments_read: + memory_name = segment_read.memory_info[DeviceMemoryInfoKeys.NAME] + # When writing multiple segments all will start from relative offset 0 + self.assertEqual(segment_read.offset, 0, msg="Incorrect offset for {}".format(memory_name)) + if memory_name == MemoryNames.FLASH: + self.assertEqual(segment_read.data, mem_tuple_flash.data, msg="Incorrect FLASH data") + elif memory_name == MemoryNames.EEPROM: + self.assertEqual(segment_read.data, mem_tuple_eeprom.data, msg="Incorrect EEPROM data") + else: + self.fail("Unexpected memory: {}".format(memory_name)) + + # Write back original values to not mess up later tests + if flash_size_words_key in info: + info[flash_size_words_key] = flash_size_words_original + else: + info[flash_size_bytes_key] = flash_size_bytes_original + if eeprom_address_word_key in info: + info[eeprom_address_word_key] = eeprom_address_word_original + else: + info[eeprom_address_byte_key] = eeprom_address_byte_original + + def test_write_memory_when_data_is_outside_any_memory_raises_indexerror(self): + """ + This test exercises the write_memory_to_hex function (single memory) + """ + info = Backend.get_device_info('attiny817') + device_memory_info = deviceinfo.DeviceMemoryInfo(info) + flash_info = device_memory_info.memory_info_by_name(MemoryNames.FLASH) + numbytes = flash_info[DeviceMemoryInfoKeys.SIZE] + 1 + filename = "{}outside_flash.hex".format(TESTFILE_FOLDER) + + mem_tuple_flash = self._generate_memorytuple(numbytes, flash_info) + + with self.assertRaises(IndexError): + write_memory_to_hex(filename, mem_tuple_flash, 0) + + def test_write_memories_when_data_is_outside_any_memory_raises_indexerror(self): + """ + This test exercises the write_memories_to_hex function (multiple memories) + """ + info = Backend.get_device_info('attiny817') + device_memory_info = deviceinfo.DeviceMemoryInfo(info) + flash_info = device_memory_info.memory_info_by_name(MemoryNames.FLASH) + eeprom_info = device_memory_info.memory_info_by_name(MemoryNames.EEPROM) + numbytes_flash = flash_info[DeviceMemoryInfoKeys.SIZE] + 1 + numbytes_eeprom = eeprom_info[DeviceMemoryInfoKeys.SIZE] + filename = "{}outside_flash_multi.hex".format(TESTFILE_FOLDER) + + mem_tuple_flash = self._generate_memorytuple(numbytes_flash, flash_info) + mem_tuple_eeprom = self._generate_memorytuple(numbytes_eeprom, eeprom_info) + memories = [mem_tuple_flash, mem_tuple_eeprom] + + with self.assertRaises(IndexError): + write_memories_to_hex(filename, memories) + + + def test_read_hex_when_data_is_outside_any_memory_raises_indexerror(self): + # Just use a PIC device to avoid having to create fake device info + devinfo = Backend.get_device_info('pic16f18446') + device_memory_info = deviceinfo.DeviceMemoryInfo(devinfo) + flashinfo = device_memory_info.memory_info_by_name(MemoryNames.FLASH) + filename = "{}out_of_range.hex".format(TESTFILE_FOLDER) + numbytes = 16 + data = self._generate_dummydata(numbytes) + + # Make a hex file with some data inside flash memory and some data just after the + # flash memory (outside any memory boundary) + ihex = IntelHex() + hexindex = flashinfo[DeviceMemoryInfoKeys.HEXFILE_ADDRESS] + flashinfo[DeviceMemoryInfoKeys.SIZE] - 4 + for databyte in data: + ihex[hexindex] = databyte + hexindex += 1 + _write_hex_to_file(ihex, filename) + + with self.assertRaises(IndexError): + read_memories_from_hex(filename, device_memory_info) diff --git a/pymcuprog/tests/test_nvmavr32.py b/pymcuprog/tests/test_nvmavr32.py new file mode 100644 index 0000000..464afd4 --- /dev/null +++ b/pymcuprog/tests/test_nvmavr32.py @@ -0,0 +1,31 @@ +#pylint: disable=too-many-lines +#pylint: disable=too-many-public-methods +#pylint: disable=missing-docstring + +import unittest +from mock import MagicMock +from mock import patch + +from pymcuprog.nvmavr32 import NvmAccessProviderCmsisDapAvr32 +from pymcuprog.deviceinfo.deviceinfokeys import DeviceInfoKeysAvr32 + +class TestNvmAccessProviderCmsisDapAvr32(unittest.TestCase): + def _mock_avr32_device(self): + mock_avr32device_patch = patch("pymcuprog.nvmavr32.Avr32Device") + self.addCleanup(mock_avr32device_patch.stop) + mock_avr32device = mock_avr32device_patch.start() + mock_avr32device_instance = MagicMock() + mock_avr32device.return_value = mock_avr32device_instance + + return mock_avr32device_instance + + def test_read_device_id_returns_bytearray_with_raw_id(self): + id_expected = bytearray([0x01, 0x02, 0x03, 0x04]) + mock_avr32device_instance = self._mock_avr32_device() + mock_avr32device_instance.activate_physical.return_value = id_expected + + avr32 = NvmAccessProviderCmsisDapAvr32(None, {DeviceInfoKeysAvr32.RESET_DOMAINS: 5}) + + id_read = avr32.read_device_id() + + self.assertEqual(id_expected, id_read) diff --git a/pymcuprog/tests/test_nvmdebugwire.py b/pymcuprog/tests/test_nvmdebugwire.py new file mode 100644 index 0000000..4c54aa4 --- /dev/null +++ b/pymcuprog/tests/test_nvmdebugwire.py @@ -0,0 +1,23 @@ +#pylint: disable=too-many-lines +#pylint: disable=too-many-public-methods +#pylint: disable=missing-docstring + +import unittest +from mock import MagicMock +from mock import patch + +from pymcuprog.nvmdebugwire import NvmAccessProviderCmsisDapDebugwire + +class TestNvmAccessProviderCmsisDapDebugwire(unittest.TestCase): + @patch("pymcuprog.nvmdebugwire.TinyAvrTarget") + def test_read_device_id_returns_bytearray_with_raw_id(self, mock_tinyavrtarget): + id_expected = bytearray([0x01, 0x02]) + mock_tinyavrtarget_instance = MagicMock() + mock_tinyavrtarget.return_value = mock_tinyavrtarget_instance + mock_tinyavrtarget_instance.activate_physical.return_value = id_expected + + dwtarget = NvmAccessProviderCmsisDapDebugwire(None, None) + + id_read = dwtarget.read_device_id() + + self.assertEqual(id_expected, id_read) diff --git a/pymcuprog/tests/test_nvmmzeroplus.py b/pymcuprog/tests/test_nvmmzeroplus.py new file mode 100644 index 0000000..eaebb98 --- /dev/null +++ b/pymcuprog/tests/test_nvmmzeroplus.py @@ -0,0 +1,38 @@ +#pylint: disable=too-many-lines +#pylint: disable=too-many-public-methods +#pylint: disable=missing-docstring + +import unittest +from mock import MagicMock +from mock import patch + +from pymcuprog.nvmmzeroplus import NvmAccessProviderCmsisDapMZeroPlus + +class TestNvmAccessProviderCmsisDapMZeroPlus(unittest.TestCase): + + def setUp(self): + self.mock_samd2xtarget_patch = patch('pymcuprog.nvmmzeroplus.SamD2xTarget') + self.addCleanup(self.mock_samd2xtarget_patch.stop) + self.mock_samd2xtarget = self.mock_samd2xtarget_patch.start() + self.mock_samd2xtarget_instance = MagicMock() + self.mock_samd2xtarget.return_value = self.mock_samd2xtarget_instance + + def test_read_device_id_returns_bytearray_with_raw_id(self): + id_expected = bytearray([0x01, 0x02, 0x03, 0x04]) + self.mock_samd2xtarget_instance.read_device_id.return_value = int.from_bytes(id_expected, + byteorder='little', + signed=False) + self.mock_samd2xtarget_instance.read_idcode.return_value = 0 + + mzeroplus = NvmAccessProviderCmsisDapMZeroPlus(None, None) + + id_read = mzeroplus.read_device_id() + + self.assertEqual(id_expected, id_read) + + def test_erase_when_memory_info_is_none_performs_chiperase(self): + mzeroplus = NvmAccessProviderCmsisDapMZeroPlus(None, None) + + mzeroplus.erase(memory_info=None, address=None) + + self.mock_samd2xtarget_instance.chip_erase_dsu.assert_called() diff --git a/pymcuprog/tests/test_nvmpic.py b/pymcuprog/tests/test_nvmpic.py new file mode 100644 index 0000000..a1bd998 --- /dev/null +++ b/pymcuprog/tests/test_nvmpic.py @@ -0,0 +1,61 @@ +# The intention is to make the test names descriptive enough to not need any docstrings for most of them +#pylint: disable=missing-docstring +# It seems better to have all tests for one module in the same file than to split across multiple files, +# so accepting many public methods and many lines makes sense +#pylint: disable=too-many-lines +#pylint: disable=too-many-public-methods +import sys +import unittest +from mock import MagicMock + +from pymcuprog.nvmpic import NvmAccessProviderCmsisDapPic +from pymcuprog.deviceinfo.deviceinfokeys import DeviceInfoKeys + +class TestNvmAccessProviderCmsisDapPic(unittest.TestCase): + + @staticmethod + def _mock_pic_model(): + """ + Create a mock for the device support scripts debugger model + + :returns: Mock object of the device model instance + """ + # Mock out the debugprovider + sys.modules['common.debugprovider'] = MagicMock() + + # Create the mock for provide_debugger_model, + # this will be the same mock that will be imported by the module under test. + from common.debugprovider import provide_debugger_model # pylint: disable=import-error, import-outside-toplevel + + mock_pic_model = provide_debugger_model.return_value + + return mock_pic_model + + + def test_read_device_id_returns_bytearray_with_raw_id(self): + id_expected = bytearray([0x01, 0x02]) + + mock_pic_model = self._mock_pic_model() + mock_pic_model.read_id.return_value = id_expected[0] + (id_expected[1] << 8) + + pic = NvmAccessProviderCmsisDapPic(None, {DeviceInfoKeys.NAME: 'pic'}, '') + + id_read = pic.read_device_id() + + self.assertEqual(id_expected, id_read) + + def test_hold_in_reset_propagates(self): + mock_pic_model = self._mock_pic_model() + + pic = NvmAccessProviderCmsisDapPic(None, {DeviceInfoKeys.NAME: 'pic'}, '') + pic.hold_in_reset() + + mock_pic_model.hold_in_reset.assert_called() + + def test_release_from_reset_propagates(self): + mock_pic_model = self._mock_pic_model() + + pic = NvmAccessProviderCmsisDapPic(None, {DeviceInfoKeys.NAME: 'pic'}, '') + pic.release_from_reset() + + mock_pic_model.release_from_reset.assert_called() diff --git a/pymcuprog/tests/test_nvmserialupdi.py b/pymcuprog/tests/test_nvmserialupdi.py new file mode 100644 index 0000000..d303316 --- /dev/null +++ b/pymcuprog/tests/test_nvmserialupdi.py @@ -0,0 +1,69 @@ +#pylint: disable=too-many-lines +#pylint: disable=too-many-public-methods +#pylint: disable=missing-docstring + +import unittest +from mock import MagicMock +from mock import patch + +from pymcuprog.nvmserialupdi import NvmAccessProviderSerial +from pymcuprog.deviceinfo import deviceinfo +from pymcuprog.pymcuprog_errors import PymcuprogSessionError + +class TestNvmAccessProviderSerial(unittest.TestCase): + def _mock_updiapplication(self): + """ + Create a mock of the UpdiApplication instance in pymcuprog.nvmserialupdi + + :returns: Mock of UpdiApplication instance + """ + mock_updiapplication_patch = patch("pymcuprog.nvmserialupdi.UpdiApplication") + self.addCleanup(mock_updiapplication_patch.stop) + mock_updiapplication = mock_updiapplication_patch.start() + mock_updiapplication_instance = MagicMock() + mock_updiapplication.return_value = mock_updiapplication_instance + + return mock_updiapplication_instance + + def test_read_device_id_returns_bytearray_with_raw_id(self): + # Expected ID must match mega4809 as BE24 + id_expected = bytearray([0x1E, 0x96, 0x51]) + id_expected.reverse() + + mock_updiapplication_instance = self._mock_updiapplication() + mock_updiapplication_instance.read_data.return_value = bytearray([id_expected[2], + id_expected[1], + id_expected[0]]) + + dinfo = deviceinfo.getdeviceinfo('atmega4809') + + serial = NvmAccessProviderSerial(None, dinfo, None) + + id_read = serial.read_device_id() + + # Check normal path + self.assertEqual(id_expected, id_read) + + # Check that an exception is raised on unexpected ID + mock_updiapplication_instance.read_data.return_value = bytearray([0, 0, 0]) + + with self.assertRaises(PymcuprogSessionError): + id_read = serial.read_device_id() + + def test_hold_in_reset_does_nothing(self): + mock_updiapplication = self._mock_updiapplication() + + dinfo = deviceinfo.getdeviceinfo('atmega4809') + serial = NvmAccessProviderSerial(None, dinfo, None) + serial.hold_in_reset() + + mock_updiapplication.hold_in_reset.assert_not_called() + + def test_release_from_reset_leaves_progmode(self): + mock_updiapplication = self._mock_updiapplication() + + dinfo = deviceinfo.getdeviceinfo('atmega4809') + serial = NvmAccessProviderSerial(None, dinfo, None) + serial.release_from_reset() + + mock_updiapplication.leave_progmode.assert_called() diff --git a/pymcuprog/tests/test_nvmspi.py b/pymcuprog/tests/test_nvmspi.py new file mode 100644 index 0000000..58d966f --- /dev/null +++ b/pymcuprog/tests/test_nvmspi.py @@ -0,0 +1,51 @@ +#pylint: disable=too-many-lines +#pylint: disable=too-many-public-methods +#pylint: disable=missing-docstring + +import unittest +from mock import MagicMock +from mock import patch + +from pymcuprog.nvmspi import NvmAccessProviderCmsisDapSpi + +class TestNvmAccessProviderCmsisDapSpi(unittest.TestCase): + def _mock_avrispprotocol(self): + """ + Create a mock of the AvrIspProtocol in pymcuprog.nvmspi + + :returns: Mock of AvrIspProtocol instance + """ + mock_avrispprotocol_patch = patch("pymcuprog.nvmspi.AvrIspProtocol") + self.addCleanup(mock_avrispprotocol_patch.stop) + mock_avrispprotocol = mock_avrispprotocol_patch.start() + mock_avrispprotocol_instance = MagicMock() + mock_avrispprotocol.return_value = mock_avrispprotocol_instance + + return mock_avrispprotocol_instance + + def test_read_device_id_returns_bytearray_with_raw_id(self): + id_expected = bytearray([0x01, 0x02, 0x03]) + mock_avrispprotocol_instance = self._mock_avrispprotocol() + mock_avrispprotocol_instance.get_id.return_value = bytearray([id_expected[2], id_expected[1], id_expected[0]]) + + isp = NvmAccessProviderCmsisDapSpi(None, None) + + id_read = isp.read_device_id() + + self.assertEqual(id_expected, id_read) + + def test_hold_in_reset_does_nothing(self): + mock_avrispprotocol_instance = self._mock_avrispprotocol() + + isp = NvmAccessProviderCmsisDapSpi(None, None) + isp.hold_in_reset() + + mock_avrispprotocol_instance.hold_in_reset.assert_not_called() + + def test_release_from_reset_leaves_progmode(self): + mock_avrispprotocol_instance = self._mock_avrispprotocol() + + isp = NvmAccessProviderCmsisDapSpi(None, None) + isp.release_from_reset() + + mock_avrispprotocol_instance.leave_progmode.assert_called() diff --git a/pymcuprog/tests/test_nvmupdi.py b/pymcuprog/tests/test_nvmupdi.py new file mode 100644 index 0000000..0a61949 --- /dev/null +++ b/pymcuprog/tests/test_nvmupdi.py @@ -0,0 +1,73 @@ +#pylint: disable=too-many-lines +#pylint: disable=too-many-public-methods +#pylint: disable=missing-docstring + +import unittest +from array import array +from mock import MagicMock +from mock import patch + +from pymcuprog.nvmupdi import NvmAccessProviderCmsisDapUpdi +from pymcuprog.deviceinfo import deviceinfo +from pymcuprog.pymcuprog_errors import PymcuprogSessionError + +class TestNvmAccessProviderCmsisDapAvr32(unittest.TestCase): + def _mock_tinyxavrtarget(self): + """ + Create a mock for the TinyXAvrTarget instance in pymcuprog.nvmupdi + + :returns: Mock of TinyXAvrTarget instance + """ + mock_tinyxavrtarget_patch = patch("pymcuprog.nvmupdi.TinyXAvrTarget") + self.addCleanup(mock_tinyxavrtarget_patch.stop) + mock_tinyxavrtarget = mock_tinyxavrtarget_patch.start() + mock_tinyxavrtarget_instance = MagicMock() + mock_tinyxavrtarget.return_value = mock_tinyxavrtarget_instance + + return mock_tinyxavrtarget_instance + + + def test_read_device_id_returns_bytearray_with_raw_id(self): + # Expected SIB+ID must match mega4809 + atmega4809_sib = array('B', map(ord, "megaAVR P:0D:1-3M2 (01.59B20.0)")) + id_expected = bytearray([0x1E, 0x96, 0x51]) + id_expected.reverse() + + mock_tinyxavrtarget_instance = self._mock_tinyxavrtarget() + # ID is BE24 from the target + mock_tinyxavrtarget_instance.memory_read.return_value = bytearray([id_expected[2], + id_expected[1], + id_expected[0]]) + mock_tinyxavrtarget_instance.sib_read.return_value = atmega4809_sib + + dinfo = deviceinfo.getdeviceinfo('atmega4809') + + updi = NvmAccessProviderCmsisDapUpdi(None, dinfo) + + id_read = updi.read_device_id() + + # Check normal path + self.assertEqual(id_expected, id_read) + + # Check that an exception is raised on unexpected ID + mock_tinyxavrtarget_instance.memory_read.return_value = bytearray([0, 0, 0]) + with self.assertRaises(PymcuprogSessionError): + id_read = updi.read_device_id() + + def test_hold_in_reset_does_nothing(self): + mock_tinyxavrtarget = self._mock_tinyxavrtarget() + + dinfo = deviceinfo.getdeviceinfo('atmega4809') + updi = NvmAccessProviderCmsisDapUpdi(None, dinfo) + updi.hold_in_reset() + + mock_tinyxavrtarget.hold_in_reset.assert_not_called() + + def test_release_from_reset_leaves_progmode(self): + mock_tinyxavrtarget = self._mock_tinyxavrtarget() + + dinfo = deviceinfo.getdeviceinfo('atmega4809') + updi = NvmAccessProviderCmsisDapUpdi(None, dinfo) + updi.release_from_reset() + + mock_tinyxavrtarget.leave_progmode.assert_called() diff --git a/pymcuprog/tests/test_programmer.py b/pymcuprog/tests/test_programmer.py new file mode 100644 index 0000000..5250baa --- /dev/null +++ b/pymcuprog/tests/test_programmer.py @@ -0,0 +1,405 @@ +# The intention is to make the test names descriptive enough to not need any docstrings for most of them +#pylint: disable=missing-docstring +# It seems better to have all tests for one module in the same file than to split across multiple files, +# so accepting many public methods and many lines makes sense +#pylint: disable=too-many-lines +#pylint: disable=too-many-public-methods +# Some tests require a lot of configurable parameters so there will be many local variables +#pylint: disable=too-many-locals +import unittest +from mock import MagicMock +from mock import patch +from mock import call + +from pymcuprog.programmer import Programmer +from pymcuprog.deviceinfo.memorynames import MemoryNames, MemoryNameAliases +from pymcuprog.deviceinfo import deviceinfo +from pymcuprog.deviceinfo.deviceinfokeys import DeviceMemoryInfoKeys +from pymcuprog.pymcuprog_errors import PymcuprogNotSupportedError + +class TestProgrammer(unittest.TestCase): + def setUp(self): + self.programmer = Programmer(None) + + def _mock_nvmaccessprovider(self): + """ + Mock out the nvmaccessprovider instance returned by get_nvm_access_provider in pymcuprog.programmer + + :returns: nvmaccessprovider mock + """ + mock_get_nvm_access_provider_patch = patch("pymcuprog.programmer.get_nvm_access_provider") + self.addCleanup(mock_get_nvm_access_provider_patch.stop) + mock_get_nvm_access_provider = mock_get_nvm_access_provider_patch.start() + mock_nvmaccessprovider = MagicMock() + mock_get_nvm_access_provider.return_value = mock_nvmaccessprovider + + return mock_nvmaccessprovider + + def _mock_devicememoryinfo(self): + """ + Mock the DeviceMemoryInfo instance of pymcuprog.programmer + + :returns: DeviceMemoryInfo instance mock + """ + mock_deviceinfo_patch = patch("pymcuprog.programmer.deviceinfo") + self.addCleanup(mock_deviceinfo_patch.stop) + mock_deviceinfo = mock_deviceinfo_patch.start() + mock_devicememoryinfo = MagicMock() + mock_deviceinfo.DeviceMemoryInfo.return_value = mock_devicememoryinfo + + return mock_devicememoryinfo + + @staticmethod + def _generate_dummydata(numbytes): + """ + Generate dummy data to be used for read and write tests. + + Generated data will be an 8-bit counter + """ + data = bytearray([]) + for count in range(numbytes): + data.append(count & 0xFF) + + return data + + @staticmethod + def _read_memory_stub(memory, offset, numbytes): + """ + Stub to use when mocking a device model read_memory function. + + The stub will return data according to the numbytes parameter + """ + #pylint: disable=unused-argument + return TestProgrammer._generate_dummydata(numbytes) + + @staticmethod + def _get_memory_info(device, memory_name): + device_info = deviceinfo.getdeviceinfo(device) + device_meminfo = deviceinfo.DeviceMemoryInfo(device_info) + return device_meminfo.memory_info_by_name(memory_name) + + def test_erase_propagates_all_parameters(self): + memoryname = 'some_memory' + address = 256 + + mock_devicememoryinfo = self._mock_devicememoryinfo() + + mock_devicememoryinfo.memory_info_by_name.return_value = memoryname + + mock_nvmaccessprovider = self._mock_nvmaccessprovider() + + self.programmer.load_device('dummydevice') + self.programmer.setup_device() + self.programmer.erase(memoryname, address) + + mock_nvmaccessprovider.erase.assert_called_with(memory_info=memoryname, address=address) + + def test_erase_when_memory_is_all_calls_erase_with_memory_info_and_address_as_none(self): + """ + If memory_info is set to None when calling erase on the device models the default erase will + be used. This default erase will erase all memories (i.e. chip erase for AVR or bulk erase + with default bulk erase address for PIC). + """ + self._mock_devicememoryinfo() + mock_nvmaccessprovider = self._mock_nvmaccessprovider() + + self.programmer.load_device('dummydevice') + self.programmer.setup_device() + self.programmer.erase(MemoryNameAliases.ALL, 0) + + mock_nvmaccessprovider.erase.assert_called_with(memory_info=None, address=None) + + def test_erase_when_memory_is_undefined_raises_valueerror(self): + """ + If memory_info is set to None when calling erase on the device models the default erase will + be used. This default erase will erase all memories (i.e. chip erase for AVR or bulk erase + with default bulk erase address for PIC). + """ + self._mock_nvmaccessprovider() + + self.programmer.load_device('atmega4809') + self.programmer.setup_device() + with self.assertRaises(ValueError): + self.programmer.erase('undefined', 0) + + def test_read_flash(self): + offset = 0x1000 + numbytes = 256 + memory_name = MemoryNames.FLASH + # Just pick any device + device = 'atmega4809' + # Generate dummy data for the read to return + data_expected = self._generate_dummydata(numbytes) + + mock_nvmaccessprovider = self._mock_nvmaccessprovider() + mock_nvmaccessprovider.read.return_value = data_expected + meminfo_expected = self._get_memory_info(device, memory_name) + + self.programmer.load_device(device) + self.programmer.setup_device() + mem_read_list = self.programmer.read_memory(memory_name=memory_name, offset=offset, numbytes=numbytes) + + data_read = mem_read_list[0].data + meminfo_read = mem_read_list[0].memory_info + + self.assertEqual(data_read, data_expected) + self.assertEqual(meminfo_read[DeviceMemoryInfoKeys.NAME], memory_name) + self.assertEqual(meminfo_read, meminfo_expected) + mock_nvmaccessprovider.read.assert_called_with(meminfo_expected, offset, numbytes) + + def test_read_when_offset_is_outside_memory_boundary_raises_valueerror(self): + numbytes = 256 + # ATmega4809 has 0xC000 bytes of flash, make sure the memory read is one byte outside the memory range + offset = 0xC000 - numbytes + 1 + memory_name = MemoryNames.FLASH + + self._mock_nvmaccessprovider() + # Just pick any device + self.programmer.load_device('atmega4809') + self.programmer.setup_device() + with self.assertRaises(ValueError): + self.programmer.read_memory(memory_name=memory_name, offset=offset, numbytes=numbytes) + + def test_read_undefined_memory_raises_valueerror(self): + self._mock_nvmaccessprovider() + # Just pick any device + self.programmer.load_device('atmega4809') + self.programmer.setup_device() + with self.assertRaises(ValueError): + self.programmer.read_memory(memory_name='undefined', offset=0, numbytes=256) + + def test_read_negative_offset_raises_valueerror(self): + self._mock_nvmaccessprovider() + # Just pick any device + self.programmer.load_device('atmega4809') + self.programmer.setup_device() + with self.assertRaises(ValueError): + self.programmer.read_memory(memory_name=MemoryNames.FLASH, offset=-10, numbytes=256) + + def test_read_negative_numbytes_raises_valuerror(self): + self._mock_nvmaccessprovider() + # Just pick any device + self.programmer.load_device('atmega4809') + self.programmer.setup_device() + with self.assertRaises(ValueError): + self.programmer.read_memory(memory_name=MemoryNames.FLASH, offset=0, numbytes=-256) + + def test_read_numbytes_0_reads_complete_memory_starting_at_offset(self): + # Pick any offset, but do not use 0 to check that the offset is taken into account + offset = 0x1000 + numbytes = 0 + memory_name = MemoryNames.FLASH + # Just pick any device + device = 'atmega4809' + + memory_info_expected = self._get_memory_info(device, memory_name) + + # Generate dummy data for the read to return + data_expected = self._generate_dummydata(memory_info_expected[DeviceMemoryInfoKeys.SIZE]-offset) + + mock_nvmaccessprovider = self._mock_nvmaccessprovider() + mock_nvmaccessprovider.read.side_effect = self._read_memory_stub + + # Just pick any device + self.programmer.load_device(device) + self.programmer.setup_device() + mem_read_list = self.programmer.read_memory(memory_name=memory_name, offset=offset, numbytes=numbytes) + + data_read = mem_read_list[0].data + meminfo_read = mem_read_list[0].memory_info + + self.assertEqual(data_read, data_expected) + self.assertEqual(meminfo_read[DeviceMemoryInfoKeys.NAME], memory_name) + mock_nvmaccessprovider.read.assert_called_with(memory_info_expected, + offset, + memory_info_expected[DeviceMemoryInfoKeys.SIZE]-offset) + + def test_read_all_reads_all_memories(self): + # Pick any offset, but do not use 0 to check that the offset is ignored + offset = 0x1000 + # Pick any number of bytes to check that numbytes is ignored + numbytes = 256 + # Just pick any device with more than one memory segment + device = 'atmega4809' + + device_info = deviceinfo.getdeviceinfo(device) + device_meminfo = deviceinfo.DeviceMemoryInfo(device_info) + + mock_nvmaccessprovider = self._mock_nvmaccessprovider() + mock_nvmaccessprovider.read.side_effect = self._read_memory_stub + + self.programmer.load_device(device) + self.programmer.setup_device() + memory_read_list = self.programmer.read_memory(memory_name=MemoryNameAliases.ALL, + offset=offset, + numbytes=numbytes) + + memories = device_meminfo.mem_by_name.keys() + read_calls = [] + for memory_name_expected in memories: + meminfo_expected = device_meminfo.memory_info_by_name(memory_name_expected) + + found_memory = False + for memory_read_tuple in memory_read_list: + if memory_read_tuple.memory_info[DeviceMemoryInfoKeys.NAME] == memory_name_expected: + data_expected = self._generate_dummydata(meminfo_expected[DeviceMemoryInfoKeys.SIZE]) + found_memory = True + data_read = memory_read_tuple.data + meminfo_read = memory_read_tuple.memory_info + + self.assertEqual(data_read, data_expected) + self.assertEqual(meminfo_read, meminfo_expected) + read_calls.append(call.read(meminfo_expected, 0, meminfo_expected[DeviceMemoryInfoKeys.SIZE])) + + self.assertTrue(found_memory, msg="Did not find {} memory in returned data".format(memory_name_expected)) + + mock_nvmaccessprovider.assert_has_calls(read_calls) + + def test_write_flash(self): + offset = 0x1000 + numbytes = 256 + memory_name = MemoryNames.FLASH + # Just pick any device + device = 'atmega4809' + # Generate dummy data for the write call + data_expected = self._generate_dummydata(numbytes) + + mock_nvmaccessprovider = self._mock_nvmaccessprovider() + + meminfo_expected = self._get_memory_info(device, memory_name) + + self.programmer.load_device(device) + self.programmer.setup_device() + self.programmer.write_memory(data=data_expected, memory_name=memory_name, offset=offset) + + mock_nvmaccessprovider.write.assert_called_with(meminfo_expected, offset, data_expected) + + def test_write_when_write_is_unsupported_raises_pymcuprognotsupportederror(self): + device = 'atsamd21e18a' + memory_name = MemoryNames.CALIBRATION_ROW + + self._mock_nvmaccessprovider() + self.programmer.load_device(device) + self.programmer.setup_device() + with self.assertRaises(PymcuprogNotSupportedError): + self.programmer.write_memory(data=[], memory_name=memory_name, offset=0) + + def test_write_when_offset_is_outside_memory_boundary_raises_valueerror(self): + numbytes = 256 + device = 'atmega4809' + # ATmega4809 has 0xC000 bytes of flash, make sure the memory written is one byte outside the memory range + offset = 0xC000 - numbytes + 1 + memory_name = MemoryNames.FLASH + data = self._generate_dummydata(numbytes) + + self._mock_nvmaccessprovider() + self.programmer.load_device(device) + self.programmer.setup_device() + with self.assertRaises(ValueError): + self.programmer.write_memory(data=data, memory_name=memory_name, offset=offset) + + def test_write_undefined_memory_raises_valueerror(self): + self._mock_nvmaccessprovider() + # Just pick any device + self.programmer.load_device('atmega4809') + self.programmer.setup_device() + with self.assertRaises(ValueError): + self.programmer.write_memory(data=bytearray([0x00, 0x01]), memory_name='undefined', offset=0) + + def test_write_negative_offset_raises_valueerror(self): + self._mock_nvmaccessprovider() + # Just pick any device + self.programmer.load_device('atmega4809') + self.programmer.setup_device() + with self.assertRaises(ValueError): + self.programmer.write_memory(data=bytearray([0x00, 0x01]), memory_name=MemoryNames.FLASH, offset=-10) + + def test_verify_flash_success(self): + offset = 0x1000 + numbytes = 256 + memory_name = MemoryNames.FLASH + # Just pick any device + device = 'atmega4809' + # Generate dummy data for the verify call + data_expected = self._generate_dummydata(numbytes) + + mock_nvmaccessprovider = self._mock_nvmaccessprovider() + mock_nvmaccessprovider.read.return_value = data_expected + meminfo_expected = self._get_memory_info(device, memory_name) + + self.programmer.load_device(device) + self.programmer.setup_device() + verify_result = self.programmer.verify_memory(data=data_expected, memory_name=memory_name, offset=offset) + + mock_nvmaccessprovider.read.assert_called_with(meminfo_expected, offset, numbytes) + self.assertTrue(verify_result, msg="Verify did not return success") + + def test_verify_flash_failure(self): + offset = 0x1000 + numbytes = 256 + memory_name = MemoryNames.FLASH + # Just pick any device + device = 'atmega4809' + # Generate dummy data to be used as input for the verify call + data_input = self._generate_dummydata(numbytes) + # Generate different data to be used as output from the device model read + data_read_output = bytearray([0x00]*numbytes) + + mock_nvmaccessprovider = self._mock_nvmaccessprovider() + mock_nvmaccessprovider.read.return_value = data_read_output + meminfo_expected = self._get_memory_info(device, memory_name) + + self.programmer.load_device(device) + self.programmer.setup_device() + verify_result = self.programmer.verify_memory(data=data_input, memory_name=memory_name, offset=offset) + + mock_nvmaccessprovider.read.assert_called_with(meminfo_expected, offset, numbytes) + self.assertFalse(verify_result, msg="Verify did not return failure") + + def test_verify_when_offset_is_outside_memory_boundary_raises_valueerror(self): + numbytes = 256 + device = 'atmega4809' + # ATmega4809 has 0xC000 bytes of flash, make sure the memory written is one byte outside the memory range + offset = 0xC000 - numbytes + 1 + memory_name = MemoryNames.FLASH + data = self._generate_dummydata(numbytes) + + self._mock_nvmaccessprovider() + self.programmer.load_device(device) + self.programmer.setup_device() + with self.assertRaises(ValueError): + self.programmer.verify_memory(data=data, memory_name=memory_name, offset=offset) + + def test_verify_undefined_memory_raises_valueerror(self): + self._mock_nvmaccessprovider() + # Just pick any device + self.programmer.load_device('atmega4809') + self.programmer.setup_device() + with self.assertRaises(ValueError): + self.programmer.verify_memory(data=bytearray([0x00, 0x01]), memory_name='undefined', offset=0) + + def test_verify_negative_offset_raises_valueerror(self): + self._mock_nvmaccessprovider() + # Just pick any device + self.programmer.load_device('atmega4809') + self.programmer.setup_device() + with self.assertRaises(ValueError): + self.programmer.verify_memory(data=bytearray([0x00, 0x01]), memory_name=MemoryNames.FLASH, offset=-10) + + def test_hold_in_reset_propagates(self): + mock_nvmaccessprovider = self._mock_nvmaccessprovider() + # Just pick any device + self.programmer.load_device('atmega4809') + self.programmer.setup_device() + self.programmer.hold_in_reset() + + mock_nvmaccessprovider.hold_in_reset.assert_called() + + def test_release_from_reset_propagates(self): + mock_nvmaccessprovider = self._mock_nvmaccessprovider() + # Just pick any device + self.programmer.load_device('atmega4809') + self.programmer.setup_device() + self.programmer.release_from_reset() + + mock_nvmaccessprovider.release_from_reset.assert_called() diff --git a/pymcuprog/tests/test_pymcuprogcli.py b/pymcuprog/tests/test_pymcuprogcli.py new file mode 100644 index 0000000..d9d6889 --- /dev/null +++ b/pymcuprog/tests/test_pymcuprogcli.py @@ -0,0 +1,1381 @@ +# The intention is to make the test names descriptive enough to not need any docstrings for most of them +#pylint: disable=missing-docstring +# It seems better to have all tests for one module in the same file than to split across multiple files, +# so accepting many public methods and many lines makes sense +#pylint: disable=too-many-lines +#pylint: disable=too-many-public-methods +# Some tests require a lot of configurable parameters so there will be many local variables +#pylint: disable=too-many-locals +""" +pymcuprog CLI (integration) tests +""" +import unittest +import sys +import os + +import shutil +import io +import logging +import time +import tempfile +from copy import copy +from array import array +from argparse import Namespace +from intelhex import IntelHex +try: + from pathlib import Path +except ImportError: + from pathlib2 import Path # python 2 backport + +from mock import patch +from mock import Mock +from mock import call + +from pyedbglib.protocols.avr8protocol import Avr8Protocol + +# pymcuprog main function +import pymcuprog.pymcuprog_main as pymcuprog + +from pymcuprog.deviceinfo.memorynames import MemoryNames + +# CLI return codes +STATUS_SUCCESS = 0 +STATUS_FAILURE = 1 + +# Hex file offsets for AVR for all memories that can be written +HEX_OFFSETS_AVR = { + MemoryNames.FLASH: 0x000000, + MemoryNames.EEPROM: 0x810000, + MemoryNames.FUSES: 0x820000, + MemoryNames.LOCKBITS: 0x830000, + MemoryNames.USER_ROW: 0x850000} + +# Hex file offsets for PIC for all memories that can be written +HEX_OFFSETS_PIC = { + MemoryNames.FLASH: 0x000000, + MemoryNames.EEPROM: 0xF000*2, + MemoryNames.ICD: 0x8600*2, + MemoryNames.CONFIG_WORD: 0x8007*2, + MemoryNames.USER_ID: 0x8000*2} + +PAGE_SIZES_AVR = { + MemoryNames.FLASH: 128, + MemoryNames.EEPROM: 64, + MemoryNames.FUSES: 1, + MemoryNames.LOCKBITS: 1, + MemoryNames.USER_ROW: 64} + +PAGE_SIZES_PIC = { + MemoryNames.FLASH: 64, + MemoryNames.EEPROM: 2, + MemoryNames.ICD: 64, + MemoryNames.CONFIG_WORD: 2, + MemoryNames.USER_ID: 2} + +MEMTYPES_AVR_READ = { + MemoryNames.FLASH: 'AVR8_MEMTYPE_FLASH_PAGE', + MemoryNames.EEPROM: 'AVR8_MEMTYPE_EEPROM', + MemoryNames.FUSES: 'AVR8_MEMTYPE_FUSES', + MemoryNames.LOCKBITS: 'AVR8_MEMTYPE_LOCKBITS', + MemoryNames.USER_ROW: 'AVR8_MEMTYPE_USER_SIGNATURE'} + +MEMTYPES_AVR_WRITE = { + MemoryNames.FLASH: MEMTYPES_AVR_READ[MemoryNames.FLASH], + MemoryNames.EEPROM: 'AVR8_MEMTYPE_EEPROM_ATOMIC', + MemoryNames.FUSES: MEMTYPES_AVR_READ[MemoryNames.FUSES], + MemoryNames.LOCKBITS: MEMTYPES_AVR_READ[MemoryNames.LOCKBITS], + MemoryNames.USER_ROW: MEMTYPES_AVR_READ[MemoryNames.USER_ROW]} + +# Memory offsets in the target device for AVR +MEM_OFFSETS_AVR = { + MemoryNames.FLASH: 0x4000, + MemoryNames.EEPROM: 0x1400, + MemoryNames.FUSES: 0x1280, + MemoryNames.LOCKBITS: 0x128A, + MemoryNames.USER_ROW: 0x1300} + +# Memory offsets in the target device for PIC +MEM_OFFSETS_PIC = { + MemoryNames.FLASH: 0x000000, + MemoryNames.EEPROM: 0xF000*2, + MemoryNames.ICD: 0x8600*2, + MemoryNames.CONFIG_WORD: 0x8007*2, + MemoryNames.USER_ID: 0x8000*2} + +# Data values are hardcoded to different values for each memory to be able to differentiate the various memories +MEM_READ_VALUES_PIC = { + MemoryNames.FLASH: 0xBB, + MemoryNames.EEPROM: 0xEE, + MemoryNames.ICD: 0xDE, + MemoryNames.CONFIG_WORD: 0xCC, + MemoryNames.USER_ID: 0xAA} + +TESTFILE_FOLDER = os.path.realpath(os.path.normpath("{}//testpymcuprogcli".format(tempfile.gettempdir()))) + +DEFAULT_ARGS = Namespace( + action='ping', + bytes=0, + clk=None, + device=None, + erase=False, + filename=None, + interface=None, + literal=None, + memory='all', + offset=0, + packpath=None, + serialnumber=None, + timing=False, + tool=None, + uart=None, + verbose=False, + verbosedebug=False, + verify=False, + version=False, + release_info=False, + high_voltage=None, + user_row_locked_device=False, + chip_erase_locked_device=False) + +# This test class does a lot of mocking that is common to all tests so there need to be many instance attributes +#pylint: disable=too-many-instance-attributes +class TestPymcuprogCLI(unittest.TestCase): + """ + Integration tests testing the complete pymcuprog command line utility stack + except the lower layers which are mocked out. + """ + def setUp(self): + self.mock_stdout_patch = patch('sys.stdout', new_callable=io.StringIO) + self.mock_stdout = self.mock_stdout_patch.start() + self.mock_hidtransport_patch = patch('pymcuprog.backend.hid_transport') + self.mock_hidtransport = self.mock_hidtransport_patch.start() + self.mock_hidtransport_obj = self.mock_hidtransport.return_value + self.mock_hidtransport_obj.connect.return_value = True + self.mock_hidtransport_obj.get_report_size.return_value = 64 + self.stub_data = {MemoryNames.FLASH: [], + MemoryNames.EEPROM: [], + MemoryNames.FUSES: [], + MemoryNames.LOCKBITS: [], + MemoryNames.USER_ROW: []} + self.mock_print_tool_info_patch = patch('pymcuprog.pymcuprog_main.print_tool_info') + self.mock_print_tool_info = self.mock_print_tool_info_patch.start() + # mock out the _is_connected_to_hid_tool in the API as it won't work when the hid_transport has been mocked out + self.mock_is_connected_to_hid_tool_patch = patch("pymcuprog.backend.Backend._is_connected_to_hid_tool") + self.mock_is_connected_to_hid_tool = self.mock_is_connected_to_hid_tool_patch.start() + self.mock_is_connected_to_hid_tool.return_value = True + + + # Remove temp folder were files are stored during the tests to get rid of any residue from previous test runs + try: + shutil.rmtree(TESTFILE_FOLDER) + except FileNotFoundError: + # Folder is already gone + pass + + # Create an empty temp folder were files can be stored during test runs + Path(TESTFILE_FOLDER).mkdir(exist_ok=True, parents=True) + + def tearDown(self): + logging.info( + "Captured output (note it arrives at the end because it was caught by a mock): \n%s", + self.mock_stdout.getvalue()) + self.mock_stdout_patch.stop() + self.mock_hidtransport_patch.stop() + self.mock_print_tool_info_patch.stop() + self.mock_is_connected_to_hid_tool_patch.stop() + + def updi_memory_read_stub(self, memtype, start, numbytes): + """ + Stub to be used as side_effect for memory_read on UPDI devices + """ + # pylint: disable=unused-argument + atmega4809_sib = array('B', map(ord, "megaAVR P:0D:1-3M2 (01.59B20.0)")) + atmega4809_id = bytearray([0x1E, 0x96, 0x51]) + updi_revision = bytearray([0x30]) + read_data = bytearray() + # memtype might be a mock (which will return a string, see avr8protocol_mock_configure()) or + # the real memtype value so we must check for both + if memtype in ['AVR8_MEMTYPE_SIB', Avr8Protocol.AVR8_MEMTYPE_SIB]: + read_data = atmega4809_sib + elif memtype in ['AVR8_MEMTYPE_SRAM', Avr8Protocol.AVR8_MEMTYPE_SRAM]: + read_data = atmega4809_id + elif memtype in ['AVR8_MEMTYPE_CS', Avr8Protocol.AVR8_MEMTYPE_CS]: + read_data = updi_revision + elif memtype in ['AVR8_MEMTYPE_FLASH_PAGE', Avr8Protocol.AVR8_MEMTYPE_FLASH_PAGE]: + read_data = self.stub_data[MemoryNames.FLASH] + elif memtype in ['AVR8_MEMTYPE_EEPROM', Avr8Protocol.AVR8_MEMTYPE_EEPROM]: + read_data = self.stub_data[MemoryNames.EEPROM] + elif memtype in ['AVR8_MEMTYPE_FUSES', Avr8Protocol.AVR8_MEMTYPE_FUSES]: + read_data = self.stub_data[MemoryNames.FUSES] + elif memtype in ['AVR8_MEMTYPE_LOCKBITS', Avr8Protocol.AVR8_MEMTYPE_LOCKBITS]: + read_data = self.stub_data[MemoryNames.LOCKBITS] + elif memtype in ['AVR8_MEMTYPE_USER_SIGNATURE', Avr8Protocol.AVR8_MEMTYPE_USER_SIGNATURE]: + read_data = self.stub_data[MemoryNames.USER_ROW] + + # Return only the data being asked for. + # Note that this function does not do any address checking at all, this must be done by the test + return read_data[0:numbytes] + + def pic16f18446_read_flash_memory_stub(self, offset, numbytes): + """ + Stub to be used as side_effect for read_flash_memory on PIC16F18446 devices + """ + # pylint: disable=no-self-use + data = bytearray() + if offset >= (0x8600*2): + # ICD memory + data = bytearray([MEM_READ_VALUES_PIC[MemoryNames.ICD]]*numbytes) + elif offset >= (0x8000*2): + # user_id memory + data = bytearray([MEM_READ_VALUES_PIC[MemoryNames.USER_ID]]*numbytes) + else: + # normal flash + data = bytearray([MEM_READ_VALUES_PIC[MemoryNames.FLASH]]*numbytes) + return data[0:numbytes] + + def delayed_memory_read_stub(self, memory_name, start, numbytes): + """ + Stub to be used as side_effect for memory_read when testing the timer. + + This stub will return data after a time delay + """ + # pylint: disable=unused-argument + # pylint: disable=no-self-use + + # Add sleep to make timing testable + time.sleep(1) + + # Forward call to make sense + return self.updi_memory_read_stub(memory_name, start, numbytes) + + @staticmethod + def create_pic_model_mock(): + """Creates a mock for the pic debugger model""" + # Mock out the debugprovider + sys.modules['common.debugprovider'] = Mock() + + # Create the mock for provide_debugger_model, + # this will be the same mock that will be imported by the module under test. + from common.debugprovider import provide_debugger_model # pylint: disable=import-error, import-outside-toplevel + + mock_pic_model = provide_debugger_model.return_value + + return mock_pic_model + + @staticmethod + def avr8protocol_mock_configure(mock_avr8protocol): + """ + To be able to check for various attributes in the Avr8Protocol class the attributes used must return a string + instead of just returning another MagicMock. This function configures an Avr8protocol mock to + achieve this. + """ + mock_avr8protocol.configure_mock(AVR8_MEMTYPE_SIB='AVR8_MEMTYPE_SIB') + mock_avr8protocol.configure_mock(AVR8_MEMTYPE_SRAM='AVR8_MEMTYPE_SRAM') + mock_avr8protocol.configure_mock(AVR8_MEMTYPE_CS='AVR8_MEMTYPE_CS') + mock_avr8protocol.configure_mock(AVR8_VARIANT_TINYX='AVR8_VARIANT_TINYX') + mock_avr8protocol.configure_mock(AVR8_FUNC_PROGRAMMING='AVR8_FUNC_PROGRAMMING') + mock_avr8protocol.configure_mock(AVR8_MEMTYPE_FLASH_PAGE='AVR8_MEMTYPE_FLASH_PAGE') + mock_avr8protocol.configure_mock(AVR8_MEMTYPE_EEPROM='AVR8_MEMTYPE_EEPROM') + mock_avr8protocol.configure_mock(AVR8_MEMTYPE_EEPROM_ATOMIC='AVR8_MEMTYPE_EEPROM_ATOMIC') + mock_avr8protocol.configure_mock(AVR8_MEMTYPE_FUSES='AVR8_MEMTYPE_FUSES') + mock_avr8protocol.configure_mock(AVR8_MEMTYPE_LOCKBITS='AVR8_MEMTYPE_LOCKBITS') + mock_avr8protocol.configure_mock(AVR8_MEMTYPE_USER_SIGNATURE='AVR8_MEMTYPE_USER_SIGNATURE') + + @staticmethod + def updi_session_and_configuration_check(mock_avr8protocol_obj): + """ + Asserts that the tool was configured properly for UPDI programming and + that the session was started and ended properly + """ + # Checking that the tool was set up correctly + mock_avr8protocol_obj.set_variant.assert_called_with('AVR8_VARIANT_TINYX') + mock_avr8protocol_obj.set_function.assert_called_with('AVR8_FUNC_PROGRAMMING') + mock_avr8protocol_obj.set_interface.assert_called_with(Avr8Protocol.AVR8_PHY_INTF_PDI_1W) + mock_avr8protocol_obj.activate_physical.assert_called() + mock_avr8protocol_obj.enter_progmode.assert_called() + + # Checking that the tool was taken down correctly + mock_avr8protocol_obj.leave_progmode.assert_called() + mock_avr8protocol_obj.deactivate_physical.assert_called() + + @staticmethod + def pic_session_check(mock_pic_model): + """ + Asserts that a PIC session was correctly started and ended + """ + # Check session initialization + mock_pic_model.setup_session.assert_called() + mock_pic_model.start_programming_operation.assert_called() + + # Check session tear down + mock_pic_model.end_of_operations.assert_called() + + @staticmethod + def generate_intelhex_with_dummy_data(memories, datasizes, offsets, memtype_indicator_start=0x00): + """ + Generate some data for each memory type + + Using the upper nibble as a memory type indicator to make it possible to identify which + memory the data was meant for by looking at the data values + :param memories: Dictionary with memory names as keys and hexfile addresses as values + :param datasizes: Dictionary with memory names as keys and number of bytes to generate as values + :param offsets: Dictionary with memory names as keys and offsets to start generating data at + :param memtype_indicator_start: Memory type indicator of first memory (subsequent memories will + add 0x10 to this start value) + :return: IntelHex instance with generated data + """ + hexfile = IntelHex() + + datamask = 0x0F + memindex = memtype_indicator_start + for memname in memories: + offset = 0 + if memname in offsets: + offset = offsets[memname] + for dataindex in range(datasizes[memname]): + databyte = ((datamask & dataindex) + memindex) & 0xFF + hexfile[dataindex + memories[memname] + offset] = databyte + memindex = (memindex + 0x10) & 0xFF + + return hexfile + + @staticmethod + def _is_blank(data): + """ + Checks if a buffer represents "blank" flash + """ + for dat in data: + if not dat == 0xFF: + return False + return True + + @staticmethod + def _pad_and_align(data, address, chunksize): + """ + Pad the data to start on chunk boundary and to contain complete chunks + + :param data: raw data bytes + :param address: address of the data (i.e. the start address in the memory data will be written to) + :param chunksize: number of bytes in each chunk of data + :return padded_data, aligned_address: + padded_data: Original data but pre- and post-padded with 0xFF to match chunk boundaries + aligned_address: Address adjusted to the start of a chunk + """ + num_databytes = len(data) + padded_data = [] + num_prepad_bytes = address % chunksize + aligned_address = address - num_prepad_bytes + # Add padding to align to chunk start + padded_data.extend([0xFF] * num_prepad_bytes) + # Add the actual data + for index in range(num_databytes): + padded_data.append(data[index]) + # Add padding to complete the last chunk + num_databytes_in_last_chunk = len(padded_data) % chunksize + num_bytes_to_pad_in_last_page = 0 + if num_databytes_in_last_chunk > 0: + num_bytes_to_pad_in_last_page = chunksize - num_databytes_in_last_chunk + padded_data.extend([0xFF]*num_bytes_to_pad_in_last_page) + + return padded_data, aligned_address + + @staticmethod + def find_memoryname_pic(segment_start): + """ + Find the memory name corresponding to the segment start address (start address in device not hexfile address) + + Note the segment_start must be the first location of a memory type + """ + memname = None + for memory in MEM_OFFSETS_PIC: + if MEM_OFFSETS_PIC[memory] == segment_start: + memname = memory + + return memname + + @patch('pymcuprog.avr8target.Avr8Protocol') + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_ping_nedbg_mega4809(self, mock_read_tool_info, mock_housekeepingprotocol, mock_avr8protocol): + # pylint: disable=unused-argument + self.avr8protocol_mock_configure(mock_avr8protocol) + + mock_avr8protocol_obj = mock_avr8protocol.return_value + + mock_avr8protocol_obj.memory_read.side_effect = self.updi_memory_read_stub + mock_read_tool_info.return_value = {'device_name': 'atmega4809'} + + arguments = copy(DEFAULT_ARGS) + arguments.action = 'ping' + arguments.tool = 'nedbg' + arguments.device = 'aTmega4809' + arguments.interface = 'updi' + + pymcuprog.pymcuprog(arguments) + + self.updi_session_and_configuration_check(mock_avr8protocol_obj) + + self.assertTrue("Ping response: 1E9651" in self.mock_stdout.getvalue()) + + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_ping_nedbg_pic16f18446(self, mock_read_tool_info, mock_housekeepingprotocol): + # pylint: disable=unused-argument + mock_read_tool_info.return_value = {'device_name': 'PIC16F18446'} + + mock_pic_model = self.create_pic_model_mock() + + # PIC16F18446 ID + mock_pic_model.read_id.return_value = 0x30D4 + + # The device support scripts will be mocked out so the packpath can be anything + arguments = copy(DEFAULT_ARGS) + arguments.action = 'ping' + arguments.tool = 'nedbg' + arguments.device = 'PIC16F18446' + arguments.interface = 'icsp' + arguments.packpath = 'anypath' + + pymcuprog.pymcuprog(arguments) + + self.pic_session_check(mock_pic_model) + + self.assertTrue("Ping response: 30D4" in self.mock_stdout.getvalue()) + + @patch('pymcuprog.avr8target.Avr8Protocol') + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_write_and_verify_literal_nedbg_mega4809( + self, mock_read_tool_info, mock_housekeepingprotocol, mock_avr8protocol): + # pylint: disable=unused-argument + self.avr8protocol_mock_configure(mock_avr8protocol) + + mock_avr8protocol_obj = mock_avr8protocol.return_value + + mock_avr8protocol_obj.memory_read.side_effect = self.updi_memory_read_stub + mock_read_tool_info.return_value = {'device_name': 'atmega4809'} + + data_in = [0x01, 0x02, 0x03, 0x04] + write_offset = 4 + memname = MemoryNames.FLASH + flash_pagesize = 128 + + # The data actually written to the target should be a complete page with 0xFF padding for + # bytes not to be written + data_out = [0xFF]*flash_pagesize + for index in range(write_offset, write_offset + len(data_in)): + data_out[index] = data_in[index - write_offset] + + # Data sent to the lower layers will be converted to bytearray + data_out = bytearray(data_out) + + # Prepare data for the memory read stub (pymcuprog will read data back since the verify option is enabled) + self.stub_data[MemoryNames.FLASH] = data_out + + arguments = copy(DEFAULT_ARGS) + arguments.action = 'write' + arguments.tool = 'nedbg' + arguments.device = 'atmega4809' + arguments.interface = 'updi' + arguments.literal = data_in + arguments.memory = memname + arguments.offset = write_offset + arguments.verify = True + + pymcuprog.pymcuprog(arguments) + + self.updi_session_and_configuration_check(mock_avr8protocol_obj) + + # Check that the correct data was written. Note that start address should be aligned with page boundary + mock_avr8protocol_obj.memory_write.assert_called_with( + 'AVR8_MEMTYPE_FLASH_PAGE', write_offset//flash_pagesize, data_out) + + # Check that the correct memory was read during the verification + mock_avr8protocol_obj.memory_read.assert_called_with( + 'AVR8_MEMTYPE_FLASH_PAGE', write_offset//flash_pagesize, len(data_in)+write_offset) + + # Make sure erase was not done (should only be done if erase option was specified) + mock_avr8protocol_obj.erase.assert_not_called() + + self.assertTrue("Writing literal values..." in self.mock_stdout.getvalue()) + self.assertTrue("Verifying literal values..." in self.mock_stdout.getvalue()) + + @patch('pymcuprog.avr8target.Avr8Protocol') + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_read_literal_nedbg_mega4809(self, mock_read_tool_info, mock_housekeepingprotocol, mock_avr8protocol): + # pylint: disable=unused-argument + self.avr8protocol_mock_configure(mock_avr8protocol) + + mock_avr8protocol_obj = mock_avr8protocol.return_value + + mock_avr8protocol_obj.memory_read.side_effect = self.updi_memory_read_stub + mock_read_tool_info.return_value = {'device_name': 'atmega4809'} + + read_offset = 4 + numbytes = 8 + memname = MemoryNames.FLASH + flash_pagesize = 128 + + data_out = [] + for index in range(flash_pagesize): + data_out.append(index & 0xFF) + + # Data returned from the lower layers will be in the format of a bytearray + self.stub_data[MemoryNames.FLASH] = bytearray(data_out) + + arguments = copy(DEFAULT_ARGS) + arguments.action = 'read' + arguments.tool = 'nedbg' + arguments.device = 'atmega4809' + arguments.interface = 'updi' + arguments.memory = memname + arguments.offset = read_offset + arguments.bytes = numbytes + + pymcuprog.pymcuprog(arguments) + + self.updi_session_and_configuration_check(mock_avr8protocol_obj) + + # pymcuprog should read from start of a flash page, + # so it will have to read the bytes up to the offset and then the number of bytes requested + mock_avr8protocol_obj.memory_read.assert_called_with( + 'AVR8_MEMTYPE_FLASH_PAGE', read_offset//flash_pagesize, numbytes+read_offset%flash_pagesize) + + # Pymcuprog should always print complete lines of 16 data bytes. + # The bytes not being requested should be shown as XX + printout_data = 'xx '*read_offset + for index in range(read_offset, read_offset+numbytes): + printout_data += "{:02X} ".format(data_out[index]) + printout_data += 'xx '*(16-read_offset-numbytes) + self.assertTrue(printout_data in self.mock_stdout.getvalue()) + + @patch('pymcuprog.avr8target.Avr8Protocol') + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_write_hex_nedbg_mega4809(self, mock_read_tool_info, mock_housekeepingprotocol, mock_avr8protocol): + # pylint: disable=unused-argument + self.avr8protocol_mock_configure(mock_avr8protocol) + + mock_avr8protocol_obj = mock_avr8protocol.return_value + + mock_avr8protocol_obj.memory_read.side_effect = self.updi_memory_read_stub + mock_read_tool_info.return_value = {'device_name': 'atmega4809'} + + # Writing several pages of Flash and EEPROM to be able to + # check skipping of blank pages for Flash but not for EEPROM. + datasizes = { + MemoryNames.FLASH: PAGE_SIZES_AVR[MemoryNames.FLASH]*3, + MemoryNames.EEPROM: PAGE_SIZES_AVR[MemoryNames.EEPROM]*3, + MemoryNames.FUSES: 1, MemoryNames.LOCKBITS: 1, + MemoryNames.USER_ROW: 4} + + # Setting offset for Flash and EEPROM to not be page boundary aligned to + # check that pymcuprog aligns the write to page boundary for flash but not for EEPROM. + # This will also result in writing only a few bytes in one of the pages for + # FLASH to check that a complete page is written while for EEPROM it is allowed to write + # less than a complete page + write_offsets = {MemoryNames.FLASH: 4, + MemoryNames.EEPROM: 2, + MemoryNames.FUSES: 0, + MemoryNames.LOCKBITS: 0, + MemoryNames.USER_ROW: 0} + + hexfile = self.generate_intelhex_with_dummy_data(HEX_OFFSETS_AVR, datasizes, write_offsets) + + # To be able to check that blank pages (containing only 0xFF values) are + # skipped for flash and not EEPROM we blank the second page for both memories + for memname in [MemoryNames.FLASH, MemoryNames.EEPROM]: + start_address = HEX_OFFSETS_AVR[memname] + PAGE_SIZES_AVR[memname] + for dataindex in range(start_address, start_address + PAGE_SIZES_AVR[memname]): + hexfile[dataindex] = 0xFF + + # Turn the hexfile object into an actual file + temp_filename = os.path.realpath( + os.path.normpath("{}//temp_memory_write_updi.hex".format(TESTFILE_FOLDER))) + hexfile.write_hex_file(temp_filename) + + arguments = copy(DEFAULT_ARGS) + arguments.action = 'write' + arguments.tool = 'nedbg' + arguments.device = 'atmega4809' + arguments.interface = 'updi' + arguments.filename = temp_filename + + pymcuprog.pymcuprog(arguments) + + self.updi_session_and_configuration_check(mock_avr8protocol_obj) + + # Check that the correct data was written. + for memname in HEX_OFFSETS_AVR: + writemem_calls = [] + expected_data = [] + + hex_index = HEX_OFFSETS_AVR[memname] + write_offsets[memname] + hex_data = hexfile.tobinarray(start=hex_index, size=datasizes[memname]) + if memname == MemoryNames.FLASH: + expected_data, start_address = self._pad_and_align(hex_data, + write_offsets[memname], + PAGE_SIZES_AVR[memname]) + else: + expected_data = hex_data + start_address = write_offsets[memname] + + # For Flash the memory offset will be added by the tool, + # for the other memories the offset should be added by pymcuprog + if memname != MemoryNames.FLASH: + start_address += MEM_OFFSETS_AVR[memname] + + while expected_data: + current_chunk_bytes = PAGE_SIZES_AVR[memname] - start_address % PAGE_SIZES_AVR[memname] + remaining_bytes = len(expected_data) + if current_chunk_bytes > remaining_bytes: + current_chunk_bytes = remaining_bytes + current_chunk = expected_data[:current_chunk_bytes] + + # For flash blank pages (only containing 0xFF) should be skipped + if memname != MemoryNames.FLASH or not self._is_blank(current_chunk): + current_chunk_array = array('B') + current_chunk_array.extend(current_chunk) + writemem_calls.append(call.memory_write(MEMTYPES_AVR_WRITE[memname], + start_address, + current_chunk_array)) + + expected_data = expected_data[current_chunk_bytes:] + start_address += current_chunk_bytes + + mock_avr8protocol_obj.assert_has_calls(writemem_calls, any_order=False) + + # Make sure erase was not done (should only be done if erase option was specified) + mock_avr8protocol_obj.erase.assert_not_called() + + for memname in HEX_OFFSETS_AVR: + self.assertTrue("Writing {}...".format(memname) in self.mock_stdout.getvalue()) + + @patch('pymcuprog.avr8target.Avr8Protocol') + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_write_verify_hex_nedbg_mega4809(self, mock_read_tool_info, mock_housekeepingprotocol, mock_avr8protocol): + # pylint: disable=unused-argument + self.avr8protocol_mock_configure(mock_avr8protocol) + + mock_avr8protocol_obj = mock_avr8protocol.return_value + + mock_avr8protocol_obj.memory_read.side_effect = self.updi_memory_read_stub + mock_read_tool_info.return_value = {'device_name': 'atmega4809'} + + datasizes = { + MemoryNames.FLASH: PAGE_SIZES_AVR[MemoryNames.FLASH], + MemoryNames.EEPROM: PAGE_SIZES_AVR[MemoryNames.EEPROM], + MemoryNames.FUSES: 1, + MemoryNames.LOCKBITS: 1, + MemoryNames.USER_ROW: PAGE_SIZES_AVR[MemoryNames.USER_ROW]} + + hexfile = self.generate_intelhex_with_dummy_data(HEX_OFFSETS_AVR, datasizes, {}) + + # Turn the hexfile object into an actual file + temp_filename = os.path.realpath( + os.path.normpath("{}//temp_memory_write_verify_updi.hex".format(TESTFILE_FOLDER))) + hexfile.write_hex_file(temp_filename) + + arguments = copy(DEFAULT_ARGS) + arguments.action = 'write' + arguments.tool = 'nedbg' + arguments.device = 'atmega4809' + arguments.interface = 'updi' + arguments.filename = temp_filename + arguments.verify = True + + pymcuprog.pymcuprog(arguments) + + self.updi_session_and_configuration_check(mock_avr8protocol_obj) + + # Check that the correct data was written. + for memname in HEX_OFFSETS_AVR: + current_page = [0xFF]*PAGE_SIZES_AVR[memname] + for page_offset in range(PAGE_SIZES_AVR[memname]): + current_page[page_offset] = hexfile[page_offset + HEX_OFFSETS_AVR[memname]] + current_page_array = array('B') + current_page_array.fromlist(current_page) + start_address = 0 + # For Flash the memory offset will be added by the tool, + # for the other memories the offset should be added by pymcuprog + if memname != MemoryNames.FLASH: + start_address += MEM_OFFSETS_AVR[memname] + mock_avr8protocol_obj.assert_has_calls( + [call.memory_write(MEMTYPES_AVR_WRITE[memname], start_address, current_page_array)], + any_order=False) + + # Check that the correct memories were read + for memname in HEX_OFFSETS_AVR: + # For Flash the memory offset will be added by the tool, + # for the other memories the offset should be added by pymcuprog + start_address = 0 + if memname != MemoryNames.FLASH: + start_address = MEM_OFFSETS_AVR[memname] + mock_avr8protocol_obj.assert_has_calls( + [call.memory_read(MEMTYPES_AVR_READ[memname], start_address, datasizes[memname])]) + + for memname in HEX_OFFSETS_AVR: + self.assertTrue("Writing {}...".format(memname) in self.mock_stdout.getvalue()) + + @patch('pymcuprog.avr8target.Avr8Protocol') + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_read_flash_to_hex_nedbg_mega4809(self, mock_read_tool_info, mock_housekeepingprotocol, mock_avr8protocol): + # pylint: disable=unused-argument + self.avr8protocol_mock_configure(mock_avr8protocol) + + mock_avr8protocol_obj = mock_avr8protocol.return_value + + mock_avr8protocol_obj.memory_read.side_effect = self.updi_memory_read_stub + mock_read_tool_info.return_value = {'device_name': 'atmega4809'} + + flash_pagesize = 128 + + # Flash reads should always be page aligned so we give a non-aligned offset to + # check that pymcuprog handles this properly + read_offset = 8 + + # The read in this test spans data in two flash pages so we prepare two pages for the read stub + data_out = [0xFF]*(flash_pagesize*2) + for index in range(flash_pagesize): + data_out[index + read_offset] = index & 0xFF + + # Data returned from the lower layers will be in the format of a bytearray + self.stub_data[MemoryNames.FLASH] = bytearray(data_out) + + # Use a temporary file to store the output + temp_filename = os.path.realpath(os.path.normpath("{}//temp_memory_read.hex".format(TESTFILE_FOLDER))) + + arguments = copy(DEFAULT_ARGS) + arguments.action = 'read' + arguments.tool = 'nedbg' + arguments.device = 'atmega4809' + arguments.interface = 'updi' + arguments.memory = MemoryNames.FLASH + arguments.filename = temp_filename + arguments.offset = read_offset + arguments.bytes = flash_pagesize + + pymcuprog.pymcuprog(arguments) + + self.updi_session_and_configuration_check(mock_avr8protocol_obj) + + generated_hex = IntelHex() + generated_hex.fromfile(temp_filename, format='hex') + + self.assertEqual( + len(generated_hex.segments()), + 1, + msg="There should only be one segment in the generated hexfile") + + flash_segment = generated_hex.segments()[0] + + hex_data = generated_hex.tobinarray(start=flash_segment[0], end=flash_segment[1]-1) + + for index, datavalue in enumerate(hex_data): + self.assertEqual(datavalue, index & 0xFF, msg="Unexpected data at index: {}".format(index)) + + # Flash reads should be aligned to page boundaries so pymcuprog should read the + # requested data + the bytes from the page start up to the read offset + mock_avr8protocol_obj.memory_read.assert_called_with('AVR8_MEMTYPE_FLASH_PAGE', 0, flash_pagesize+read_offset) + + self.assertTrue("Data written to hex file: '{}'".format(temp_filename) in self.mock_stdout.getvalue()) + + @patch('pymcuprog.avr8target.Avr8Protocol') + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_write_and_verify_bin_to_flash_nedbg_mega4809( + self, mock_read_tool_info, mock_housekeepingprotocol, mock_avr8protocol): + # pylint: disable=unused-argument + self.avr8protocol_mock_configure(mock_avr8protocol) + + mock_avr8protocol_obj = mock_avr8protocol.return_value + + mock_avr8protocol_obj.memory_read.side_effect = self.updi_memory_read_stub + mock_read_tool_info.return_value = {'device_name': 'atmega4809'} + + data_in = [0xAA, 0xBB, 0xCC, 0xDD] + write_offset = 2 + flash_pagesize = 128 + + # Prepare data for the memory read stub (pymcuprog will read data back since the verify option is enabled) + # Pymcuprog should always read from the start of a flash page so we have to pad the data to match the flash page + verify_data = [0xFF]*(write_offset) + verify_data.extend(data_in) + self.stub_data[MemoryNames.FLASH] = bytearray(verify_data) + + # Use a temporary file to store the data to be written + temp_filename = os.path.realpath(os.path.normpath("{}//temp_memory_write.bin".format(TESTFILE_FOLDER))) + + with open(temp_filename, "wb") as binfile: + binfile.write(bytearray(data_in)) + + arguments = copy(DEFAULT_ARGS) + arguments.action = 'write' + arguments.tool = 'nedbg' + arguments.device = 'atmega4809' + arguments.interface = 'updi' + arguments.memory = MemoryNames.FLASH + arguments.filename = temp_filename + arguments.offset = write_offset + arguments.verify = True + + pymcuprog.pymcuprog(arguments) + + self.updi_session_and_configuration_check(mock_avr8protocol_obj) + + # Check that the correct data was written (a complete page including the data from the bin file) + data_expected = [0xFF]*flash_pagesize + for index, datavalue in enumerate(data_in): + data_expected[write_offset+index] = datavalue + + mock_avr8protocol_obj.memory_write.assert_called_with('AVR8_MEMTYPE_FLASH_PAGE', 0, bytearray(data_expected)) + + # Check that the correct memory was read during the verification + mock_avr8protocol_obj.memory_read.assert_called_with( + 'AVR8_MEMTYPE_FLASH_PAGE', write_offset//flash_pagesize, len(data_in)+write_offset) + + # Make sure erase was not done (should only be done if erase option was specified) + mock_avr8protocol_obj.erase.assert_not_called() + + self.assertTrue("Writing from binary file..." in self.mock_stdout.getvalue()) + self.assertTrue("Verifying from binary file..." in self.mock_stdout.getvalue()) + + @patch('pymcuprog.avr8target.Avr8Protocol') + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_read_flash_to_bin_nedbg_mega4809(self, mock_read_tool_info, mock_housekeepingprotocol, mock_avr8protocol): + # pylint: disable=unused-argument + self.avr8protocol_mock_configure(mock_avr8protocol) + + mock_avr8protocol_obj = mock_avr8protocol.return_value + + mock_avr8protocol_obj.memory_read.side_effect = self.updi_memory_read_stub + mock_read_tool_info.return_value = {'device_name': 'atmega4809'} + + numbytes = 8 + + # Flash reads should always be page aligned so we give a non-aligned offset to + # check that pymcuprog handles this properly + read_offset = 4 + + # Prepare some data for the read memory stub + data_out = [0xFF]*(numbytes+read_offset) + for index in range(numbytes): + data_out[index + read_offset] = index & 0xFF + + # Data returned from the lower layers will be in the format of a bytearray + data_out = bytearray(data_out) + + self.stub_data[MemoryNames.FLASH] = data_out + + # Use a temporary file to store the output + temp_filename = os.path.realpath(os.path.normpath("{}//temp_memory_read.bin".format(TESTFILE_FOLDER))) + + arguments = copy(DEFAULT_ARGS) + arguments.action = 'read' + arguments.tool = 'nedbg' + arguments.device = 'atmega4809' + arguments.interface = 'updi' + arguments.memory = MemoryNames.FLASH + arguments.filename = temp_filename + arguments.offset = read_offset + arguments.bytes = numbytes + + pymcuprog.pymcuprog(arguments) + + self.updi_session_and_configuration_check(mock_avr8protocol_obj) + + # Add memory name to file name as pymcuprog generates one bin file for each + # memory type each with the memory name included in the file name + filenamelist = temp_filename.split('.') + actual_filename = "{}_flash.{}".format(filenamelist[0], filenamelist[1]) + + with open(actual_filename, "rb") as binfile: + data_read = bytearray(binfile.read()) + + self.assertEqual(data_read, data_out[read_offset:]) + + # Flash reads should be aligned to page boundaries so pymcuprog should read the + # requested data + the bytes from the page start up to the read offset + mock_avr8protocol_obj.memory_read.assert_called_with('AVR8_MEMTYPE_FLASH_PAGE', 0, numbytes+read_offset) + + self.assertTrue("Data written to binary file: '{}'".format(actual_filename) in self.mock_stdout.getvalue()) + + @patch('pymcuprog.avr8target.Avr8Protocol') + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_erase_single_memories_nedbg_mega4809(self, mock_read_tool_info, + mock_housekeepingprotocol, mock_avr8protocol): + # pylint: disable=unused-argument + self.avr8protocol_mock_configure(mock_avr8protocol) + + mock_avr8protocol_obj = mock_avr8protocol.return_value + + mock_avr8protocol_obj.memory_read.side_effect = self.updi_memory_read_stub + mock_read_tool_info.return_value = {'device_name': 'atmega4809'} + + erase_modes = { + MemoryNames.EEPROM: Avr8Protocol.ERASE_EEPROM, + MemoryNames.USER_ROW: Avr8Protocol.ERASE_USERSIG} + + for memname in erase_modes: + arguments = copy(DEFAULT_ARGS) + arguments.action = 'erase' + arguments.tool = 'nedbg' + arguments.device = 'atmega4809' + arguments.interface = 'updi' + arguments.memory = memname + + pymcuprog.pymcuprog(arguments) + + self.updi_session_and_configuration_check(mock_avr8protocol_obj) + + mock_avr8protocol_obj.erase.assert_called_with(erase_modes[memname], 0) + + self.assertTrue("Erasing {}...".format(memname) in self.mock_stdout.getvalue()) + + @patch('pymcuprog.avr8target.Avr8Protocol') + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_default_erase_nedbg_mega4809(self, mock_read_tool_info, mock_housekeepingprotocol, mock_avr8protocol): + # pylint: disable=unused-argument + self.avr8protocol_mock_configure(mock_avr8protocol) + + mock_avr8protocol_obj = mock_avr8protocol.return_value + + mock_avr8protocol_obj.memory_read.side_effect = self.updi_memory_read_stub + mock_read_tool_info.return_value = {'device_name': 'atmega4809'} + + arguments = copy(DEFAULT_ARGS) + arguments.action = 'erase' + arguments.tool = 'nedbg' + arguments.device = 'atmega4809' + arguments.interface = 'updi' + + pymcuprog.pymcuprog(arguments) + + self.updi_session_and_configuration_check(mock_avr8protocol_obj) + + mock_avr8protocol_obj.erase.assert_called_with(Avr8Protocol.ERASE_CHIP, 0) + + @patch('pymcuprog.avr8target.Avr8Protocol') + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_timer_option(self, mock_read_tool_info, mock_housekeepingprotocol, mock_avr8protocol): + # pylint: disable=unused-argument + self.avr8protocol_mock_configure(mock_avr8protocol) + + mock_avr8protocol_obj = mock_avr8protocol.return_value + + mock_read_tool_info.return_value = {'device_name': 'atmega4809'} + + mock_avr8protocol_obj.memory_read.side_effect = self.delayed_memory_read_stub + + arguments = copy(DEFAULT_ARGS) + arguments.action = 'ping' + arguments.tool = 'nedbg' + arguments.device = 'atmega4809' + arguments.interface = 'updi' + arguments.timing = True + + pymcuprog.pymcuprog(arguments) + + self.updi_session_and_configuration_check(mock_avr8protocol_obj) + + # The delayed_memory_read_stub puts in a 1s delay per memory access call + # There are 4 calls on session start + ping: SIB, CS, SRAM x 2 + # This means that the time took should start with "4."" (leaving decimals to overheads) + self.assertTrue("Operation took 4." in self.mock_stdout.getvalue(), msg=f"Actual output {self.mock_stdout.getvalue()}") + + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_write_hex_nedbg_pic16f18446(self, mock_read_tool_info, mock_housekeepingprotocol): + # pylint: disable=unused-argument + mock_read_tool_info.return_value = {'device_name': 'PIC16F18446'} + + mock_pic_model = self.create_pic_model_mock() + + # PIC16F18446 ID + mock_pic_model.read_id.return_value = 0x30D4 + + datasizes = { + MemoryNames.FLASH: PAGE_SIZES_PIC[MemoryNames.FLASH]*3, + MemoryNames.EEPROM: 8, + MemoryNames.ICD: PAGE_SIZES_PIC[MemoryNames.ICD], + MemoryNames.CONFIG_WORD: PAGE_SIZES_PIC[MemoryNames.CONFIG_WORD], + MemoryNames.USER_ID: PAGE_SIZES_PIC[MemoryNames.USER_ID]} + + # Setting offsets for EEPROM and Flash to not be page boundary aligned to + # check that pymcuprog aligns the writes to page boundaries. + write_offsets = {MemoryNames.FLASH: 4, + MemoryNames.EEPROM: 2, + MemoryNames.ICD: 0, + MemoryNames.CONFIG_WORD: 0, + MemoryNames.USER_ID: 0} + + hexfile = self.generate_intelhex_with_dummy_data(HEX_OFFSETS_PIC, datasizes, write_offsets) + + # Turn the hexfile object into an actual file + temp_filename = os.path.realpath( + os.path.normpath("{}//temp_memory_write_pic.hex".format(TESTFILE_FOLDER))) + hexfile.write_hex_file(temp_filename) + + # The device support scripts will be mocked out so the packpath can be anything + arguments = copy(DEFAULT_ARGS) + arguments.action = 'write' + arguments.tool = 'nedbg' + arguments.device = 'PIC16F18446' + arguments.interface = 'icsp' + arguments.packpath = 'anypath' + arguments.filename = temp_filename + + pymcuprog.pymcuprog(arguments) + + self.pic_session_check(mock_pic_model) + + # Check that the correct data was written. + writemem_calls = [] + for memname in HEX_OFFSETS_PIC: + # Memory writes should do page aligned writes so we should expect some padding + current_mem_data = [0xFF]*(datasizes[memname] + write_offsets[memname] % PAGE_SIZES_PIC[memname]) + for data_index in range(datasizes[memname]): + hexfile_index = data_index + HEX_OFFSETS_PIC[memname] + write_offsets[memname] + current_data_index = write_offsets[memname] % PAGE_SIZES_PIC[memname] + data_index + current_mem_data[current_data_index] = hexfile[hexfile_index] + + if memname == MemoryNames.EEPROM: + # PIC16 devices have one phantom byte per EEPROM byte in the hex file. + # These phantom bytes should be removed before sending the data to the device model + data_stripped = [] + for index in range(0, len(current_mem_data), 2): + data_stripped.append(current_mem_data[index]) + current_mem_data = data_stripped + + current_mem_data_array = array('B') + current_mem_data_array.fromlist(current_mem_data) + + # Memory writes should match page boundaries + write_offset_page_aligned = (write_offsets[memname] // PAGE_SIZES_PIC[memname]) * PAGE_SIZES_PIC[memname] + write_start_address = MEM_OFFSETS_PIC[memname] + write_offset_page_aligned + + if memname == MemoryNames.FLASH: + writemem_calls.append(call.write_flash_memory(write_start_address, current_mem_data_array)) + elif memname == MemoryNames.EEPROM: + writemem_calls.append(call.write_eeprom_memory(write_start_address, current_mem_data_array)) + elif memname == MemoryNames.ICD: + writemem_calls.append(call.write_de_memory(write_start_address, current_mem_data_array)) + elif memname == MemoryNames.CONFIG_WORD: + writemem_calls.append(call.write_config_memory(write_start_address, current_mem_data_array)) + elif memname == MemoryNames.USER_ID: + writemem_calls.append(call.write_user_id_memory(write_start_address, current_mem_data_array)) + + mock_pic_model.assert_has_calls(writemem_calls, any_order=True) + + # Make sure erase was not done (should only be done if erase option was specified) + mock_pic_model.erase.assert_not_called() + mock_pic_model.erase_de_memory.assert_not_called() + + for memname in HEX_OFFSETS_PIC: + self.assertTrue("Writing {}...".format(memname) in self.mock_stdout.getvalue()) + + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_erase_single_memories_nedbg_pic16f18446(self, mock_read_tool_info, mock_housekeepingprotocol): + # pylint: disable=unused-argument + mock_read_tool_info.return_value = {'device_name': 'PIC16F18446'} + + mock_pic_model = self.create_pic_model_mock() + + # PIC16F18446 ID + mock_pic_model.read_id.return_value = 0x30D4 + + erase_addresses = { + MemoryNames.FLASH: 0x80FE*2, + MemoryNames.ICD: 0x8600*2} + + for memname in erase_addresses: + # The device support scripts will be mocked out so the packpath can be anything + arguments = copy(DEFAULT_ARGS) + arguments.action = 'erase' + arguments.tool = 'nedbg' + arguments.device = 'PIC16F18446' + arguments.interface = 'icsp' + arguments.packpath = 'anypath' + arguments.memory = memname + + pymcuprog.pymcuprog(arguments) + + self.pic_session_check(mock_pic_model) + + if memname == MemoryNames.ICD: + # For ICD memory the number of bytes to be erased must be given. + # The ICD memory has a size of 1024 bytes + mock_pic_model.erase_de_memory.assert_called_with(erase_addresses[memname], 1024) + else: + mock_pic_model.erase.assert_called_with(erase_addresses[memname]) + + for memname in erase_addresses: + self.assertTrue("Erasing {}...".format(memname) in self.mock_stdout.getvalue()) + + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_default_erase_nedbg_pic16f18446(self, mock_read_tool_info, mock_housekeepingprotocol): + # pylint: disable=unused-argument + mock_read_tool_info.return_value = {'device_name': 'PIC16F18446'} + + default_bulk_erase_address = 0x8000 * 2 + + mock_pic_model = self.create_pic_model_mock() + + # PIC16F18446 ID + mock_pic_model.read_id.return_value = 0x30D4 + + # The device support scripts will be mocked out so the packpath can be anything + arguments = copy(DEFAULT_ARGS) + arguments.action = 'erase' + arguments.tool = 'nedbg' + arguments.device = 'PIC16F18446' + arguments.interface = 'icsp' + arguments.packpath = 'anypath' + + pymcuprog.pymcuprog(arguments) + + self.pic_session_check(mock_pic_model) + + mock_pic_model.erase.assert_called_with(default_bulk_erase_address) + + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_erase_eeprom_nedbg_pic16f18446_is_refused(self, mock_read_tool_info, mock_housekeepingprotocol): + # pylint: disable=unused-argument + mock_read_tool_info.return_value = {'device_name': 'PIC16F18446'} + + mock_pic_model = self.create_pic_model_mock() + + # PIC16F18446 ID + mock_pic_model.read_id.return_value = 0x30D4 + + # The device support scripts will be mocked out so the packpath can be anything + arguments = copy(DEFAULT_ARGS) + arguments.action = 'erase' + arguments.tool = 'nedbg' + arguments.device = 'PIC16F18446' + arguments.interface = 'icsp' + arguments.packpath = 'anypath' + arguments.memory = MemoryNames.EEPROM + + pymcuprog.pymcuprog(arguments) + + self.pic_session_check(mock_pic_model) + + mock_pic_model.erase.assert_not_called() + + self.assertFalse("erasing eeprom" in self.mock_stdout.getvalue().lower()) + + @patch('pymcuprog.avr8target.Avr8Protocol') + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_write_out_of_range_mega4809_causes_error(self, + mock_read_tool_info, + mock_housekeepingprotocol, + mock_avr8protocol): + # pylint: disable=unused-argument + self.avr8protocol_mock_configure(mock_avr8protocol) + + mock_avr8protocol_obj = mock_avr8protocol.return_value + + mock_read_tool_info.return_value = {'device_name': 'atmega4809'} + + arguments = copy(DEFAULT_ARGS) + arguments.action = 'write' + arguments.tool = 'nedbg' + arguments.device = 'atmega4809' + arguments.interface = 'updi' + arguments.literal = [0x11, 0x22] + arguments.memory = MemoryNames.FLASH + arguments.offset = 0xC001 + + # Error should return - no exception + status = pymcuprog.pymcuprog(arguments) + self.assertEqual(status, STATUS_FAILURE) + mock_avr8protocol_obj.memory_write.assert_not_called() + + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_read_all_to_hex_nedbg_pic16f18446(self, mock_read_tool_info, mock_housekeepingprotocol): + # pylint: disable=unused-argument + mock_read_tool_info.return_value = {'device_name': 'PIC16F18446'} + + mock_pic_model = self.create_pic_model_mock() + + # PIC16F18446 ID + mock_pic_model.read_id.return_value = 0x30D4 + + mock_pic_model.read_flash_memory.side_effect = self.pic16f18446_read_flash_memory_stub + + datasizes = { + MemoryNames.FLASH: 16384*2, + MemoryNames.EEPROM: 256, + MemoryNames.ICD: 512*2, + MemoryNames.CONFIG_WORD: 5*2, + MemoryNames.USER_ID: 4*2} + + config_word_return_value = MEM_READ_VALUES_PIC[MemoryNames.CONFIG_WORD] + mock_pic_model.read_config_memory.return_value = [config_word_return_value]*datasizes[MemoryNames.CONFIG_WORD] + eeprom_return_value = MEM_READ_VALUES_PIC[MemoryNames.EEPROM] + mock_pic_model.read_eeprom_memory.return_value = [eeprom_return_value]*datasizes[MemoryNames.EEPROM] + + temp_filename = os.path.realpath( + os.path.normpath("{}//temp_all_memory_read_pic.hex".format(TESTFILE_FOLDER))) + + + # The device support scripts will be mocked out so the packpath can be anything + arguments = copy(DEFAULT_ARGS) + arguments.action = 'read' + arguments.tool = 'nedbg' + arguments.device = 'PIC16F18446' + arguments.interface = 'icsp' + arguments.packpath = 'anypath' + arguments.memory = 'all' + arguments.filename = temp_filename + + pymcuprog.pymcuprog(arguments) + + self.pic_session_check(mock_pic_model) + + # Check that the hex contains the correct data + generated_hex = IntelHex() + generated_hex.fromfile(temp_filename, format='hex') + + for segment in generated_hex.segments(): + segment_start = segment[0] + segment_stop = segment[1] + memname = self.find_memoryname_pic(segment_start) + self.assertIsNotNone(memname, msg="Could not identify memory starting at address {}".format(segment_start)) + + hex_data = generated_hex.tobinarray(start=segment_start, end=segment_stop-1) + + if memname == MemoryNames.EEPROM: + # EEPROM memory should contain one extra phantom byte per EEPROM byte so number of bytes should be twice + # the data read from the device + self.assertEqual(len(hex_data), + datasizes[memname]*2, + msg="Unexpected length of {} data in hex".format(memname)) + else: + self.assertEqual(len(hex_data), + datasizes[memname], + msg="Unexpected length of {} data in hex".format(memname)) + + expected_value = MEM_READ_VALUES_PIC[memname] + + if memname == MemoryNames.EEPROM: + # EEPROM memory should contain one extra phantom byte per EEPROM byte + index = 0 + for databyte in hex_data: + if index % 2: + # Odd bytes are phantom bytes + self.assertEqual(0x00, databyte, "EEPROM phantom byte mismatch @index {}".format(index)) + else: + self.assertEqual(expected_value, databyte, "{} data mismatch in hex".format(memname)) + index += 1 + else: + for databyte in hex_data: + self.assertEqual(expected_value, databyte, "{} data mismatch in hex".format(memname)) + + # Check that the correct memories were read + mock_pic_model.read_eeprom_memory.assert_called_with( + MEM_OFFSETS_PIC[MemoryNames.EEPROM], datasizes[MemoryNames.EEPROM]) + mock_pic_model.read_config_memory.assert_called_with( + MEM_OFFSETS_PIC[MemoryNames.CONFIG_WORD], datasizes[MemoryNames.CONFIG_WORD]) + mock_pic_model.assert_has_calls([ + call.read_flash_memory(MEM_OFFSETS_PIC[MemoryNames.FLASH], datasizes[MemoryNames.FLASH]), + call.read_flash_memory(MEM_OFFSETS_PIC[MemoryNames.USER_ID], datasizes[MemoryNames.USER_ID]), + call.read_flash_memory(MEM_OFFSETS_PIC[MemoryNames.ICD], datasizes[MemoryNames.ICD])], any_order=True) + + for memname in MEM_OFFSETS_PIC: + self.assertTrue("Reading..." in self.mock_stdout.getvalue()) + + self.assertTrue("Data written to hex file: '{}'".format(temp_filename) in self.mock_stdout.getvalue()) + + @patch('pymcuprog.backend.housekeepingprotocol') + @patch('pymcuprog.backend.read_tool_info') + def test_read_all_to_bin_nedbg_pic16f18446(self, mock_read_tool_info, mock_housekeepingprotocol): + # pylint: disable=unused-argument + mock_read_tool_info.return_value = {'device_name': 'PIC16F18446'} + + mock_pic_model = self.create_pic_model_mock() + + # PIC16F18446 ID + mock_pic_model.read_id.return_value = 0x30D4 + + mock_pic_model.read_flash_memory.side_effect = self.pic16f18446_read_flash_memory_stub + + datasizes = { + MemoryNames.FLASH: 16384*2, + MemoryNames.EEPROM: 256, + MemoryNames.ICD: 512*2, + MemoryNames.CONFIG_WORD: 5*2, + MemoryNames.USER_ID: 4*2} + + mock_pic_model.read_config_memory.return_value = bytearray( + [MEM_READ_VALUES_PIC[MemoryNames.CONFIG_WORD]]*datasizes[MemoryNames.CONFIG_WORD]) + mock_pic_model.read_eeprom_memory.return_value = bytearray( + [MEM_READ_VALUES_PIC[MemoryNames.EEPROM]]*datasizes[MemoryNames.EEPROM]) + + temp_filename = os.path.realpath( + os.path.normpath("{}//temp_all_memory_read_pic.bin".format(TESTFILE_FOLDER))) + + # The device support scripts will be mocked out so the packpath can be anything + arguments = copy(DEFAULT_ARGS) + arguments.action = 'read' + arguments.tool = 'nedbg' + arguments.device = 'PIC16F18446' + arguments.interface = 'icsp' + arguments.packpath = 'anypath' + arguments.memory = 'all' + arguments.filename = temp_filename + + pymcuprog.pymcuprog(arguments) + + self.pic_session_check(mock_pic_model) + + for memname in MEM_OFFSETS_PIC: + memory_filename = temp_filename.split('.')[0] + memory_filename += "_{}".format(memname) + ".bin" + with open(memory_filename, "rb") as binfile: + data_read = bytearray(binfile.read()) + + self.assertTrue("Data written to binary file: '{}'".format(memory_filename) in self.mock_stdout.getvalue()) + + expected_value = MEM_READ_VALUES_PIC[memname] + + for databyte in data_read: + self.assertEqual(expected_value, databyte, msg="Incorrect data value for {} memory".format(memname)) + + # Check that the correct memories were read + mock_pic_model.read_eeprom_memory.assert_called_with( + MEM_OFFSETS_PIC[MemoryNames.EEPROM], datasizes[MemoryNames.EEPROM]) + mock_pic_model.read_config_memory.assert_called_with( + MEM_OFFSETS_PIC[MemoryNames.CONFIG_WORD], datasizes[MemoryNames.CONFIG_WORD]) + mock_pic_model.assert_has_calls([ + call.read_flash_memory(MEM_OFFSETS_PIC[MemoryNames.FLASH], datasizes[MemoryNames.FLASH]), + call.read_flash_memory(MEM_OFFSETS_PIC[MemoryNames.USER_ID], datasizes[MemoryNames.USER_ID]), + call.read_flash_memory(MEM_OFFSETS_PIC[MemoryNames.ICD], datasizes[MemoryNames.ICD])], any_order=True) + + for memname in MEM_OFFSETS_PIC: + self.assertTrue("Reading..." in self.mock_stdout.getvalue()) + # Add memory name to file name before checking printout + filenamelist = temp_filename.split('.') + actual_filename = "{}_{}.{}".format(filenamelist[0], memname, filenamelist[1]) + self.assertTrue("Data written to binary file: '{}'".format(actual_filename) in self.mock_stdout.getvalue()) + + def test_print_version(self): + arguments = copy(DEFAULT_ARGS) + arguments.version = True + + pymcuprog.pymcuprog(arguments) + + self.assertTrue("pymcuprog version" in self.mock_stdout.getvalue()) diff --git a/pymcuprog/toolconnection.py b/pymcuprog/toolconnection.py new file mode 100644 index 0000000..9d548e2 --- /dev/null +++ b/pymcuprog/toolconnection.py @@ -0,0 +1,36 @@ +""" +This module includes wrapper classes for Tool connection parameters +""" + +#pylint: disable=too-few-public-methods +class ToolConnection(object): + """ + Base class for ToolConnection classes used to wrap configuration parameters for tool connections + """ + +#pylint: disable=too-few-public-methods +class ToolUsbHidConnection(ToolConnection): + """ + Helper class wrapping configuration parameters for a connection to a USB HID tool + """ + serialnumber = None + tool_name = None + + def __init__(self, serialnumber=None, tool_name=None): + """ + :param tool_name: Tool name as given in USB Product string. Some shortnames are also supported + as defined in pyedbglib.hidtransport.toolinfo.py. Set to None if don't care + :param serialnumber: USB serial number string. Set to None if don't care + """ + self.serialnumber = serialnumber + self.tool_name = tool_name + +#pylint: disable=too-few-public-methods +class ToolSerialConnection(ToolConnection): + """ + Helper class wrapping configuration parameters for a connection to a serial port + """ + serialport = None + + def __init__(self, serialport="COM1"): + self.serialport = serialport diff --git a/pymcuprog/utils.py b/pymcuprog/utils.py new file mode 100644 index 0000000..bf75020 --- /dev/null +++ b/pymcuprog/utils.py @@ -0,0 +1,302 @@ +""" +Utility functions for pymcuprog +""" +# Python 3 compatibility for Python 2 +from __future__ import print_function + +import copy +from pyedbglib.protocols.housekeepingprotocol import Jtagice3HousekeepingProtocol +from pyedbglib.protocols.jtagice3protocol import Jtagice3ResponseError +from pyedbglib.protocols.jtagice3protocol import Jtagice3Protocol + +from .pymcuprog_errors import PymcuprogNotSupportedError +from .deviceinfo.memorynames import MemoryNames + +def read_tool_info(housekeeper): + """ + Interrogates tool (debugger) for useful info + + :returns: Dictionary with various info about the connected debugger + """ + dap_info = housekeeper.dap_info() + + # Add alias for serialnumber(==serial) + dap_info['serialnumber'] = dap_info['serial'] + + # Read FW versions + dap_info['firmware_major'] = housekeeper.get_byte(Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONTEXT_CONFIG, + Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONFIG_FWREV_MAJ) + dap_info['firmware_minor'] = housekeeper.get_byte(Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONTEXT_CONFIG, + Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONFIG_FWREV_MIN) + dap_info['build'] = housekeeper.get_le16(Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONTEXT_CONFIG, + Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONFIG_BUILD) + + # Read HW revision + dap_info['hardware_rev'] = housekeeper.get_byte(Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONTEXT_CONFIG, + Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONFIG_HWREV) + + # Some EDBG versions do NOT have the dap_info 'device' tag populated for non-ARM parts. + # Sneak in and collect the data from the EDBG config instead + if dap_info['product'][:4] == 'EDBG' and dap_info['device_name'] == '': + try: + # Vendor command + cmd = bytearray([0x83]) + # Add cnt of '1' element: + cmd.append(1) + # Add tag of 'TARGET DEVICE NAME' + cmd.append(0x04) + # Add dummy 'param' + cmd.append(ord('?')) + # Add the offset + offset = 0 + cmd.extend([offset & 0xFF, offset >> 8]) + + # Add the chunk size + numbytes = 32 + cmd.extend([numbytes & 0xFF, numbytes >> 8]) + + # raw command routed via the HK interface + response = housekeeper.dap_command_response(cmd) + dap_info['device_name'] = response[6:6 + numbytes].split(b'\0')[0].decode() + except: #pylint: disable=bare-except + # resort to '' + pass + return dap_info + +def print_tool_info(info): + """ + Print out various tool information + + :param info: Dictionary with various tool info as returned from read_tool_info() + """ + print("Connected to {0:s} from {1:s} (serial number {2:s})".format(info['product'], info['vendor'], + info['serial'])) + + print("Debugger firmware version {0:d}.{1:d}.{2:d}".format(info['firmware_major'], + info['firmware_minor'], + info['build'])) + + print("Debugger hardware revision {0:d}".format(info['hardware_rev'])) + +def read_target_voltage(housekeeper): + """ + Read target voltage + + :param housekeeper: instance of pyedbglib.protocols.housekeepingprotocol.Jtagice3HousekeepingProtocol + """ + return read_voltage_parameter(housekeeper, Jtagice3HousekeepingProtocol.HOUSEKEEPING_ANALOG_VTREF) + +def read_supply_voltage_setpoint(housekeeper): + """ + Read supply setpoint + + :param housekeeper: instance of pyedbglib.protocols.housekeepingprotocol.Jtagice3HousekeepingProtocol + """ + return read_voltage_parameter(housekeeper, Jtagice3HousekeepingProtocol.HOUSEKEEPING_TSUP_VOLTAGE) + +def read_usb_voltage(housekeeper): + """ + Read USB voltage + + :param housekeeper: instance of pyedbglib.protocols.housekeepingprotocol.Jtagice3HousekeepingProtocol + """ + return read_voltage_parameter(housekeeper, Jtagice3HousekeepingProtocol.HOUSEKEEPING_ANALOG_VUSB) + +def read_voltage_parameter(housekeeper, offset): + """ + Generic read voltage from tool parameter + + :param housekeeper: Instance of pyedbglib.protocols.housekeepingprotocol.Jtagice3HousekeepingProtocol + :param offset: Tool parameter offset to read + """ + housekeeper.start_session() + voltage = housekeeper.get_le16(Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONTEXT_ANALOG, offset) + voltage = voltage / 1000.0 + housekeeper.end_session() + return voltage + +def set_supply_voltage_setpoint(housekeeper, voltage): + """ + Set supply setpoint + + :param housekeeper: Instance of pyedbglib.protocols.housekeepingprotocol.Jtagice3HousekeepingProtocol + :param voltage: New setpoint for target supply + """ + try: + housekeeper.get_le16(Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONTEXT_ANALOG, + Jtagice3HousekeepingProtocol.HOUSEKEEPING_TSUP_VOLTAGE) + except Jtagice3ResponseError: + raise PymcuprogNotSupportedError("Connected debugger/board does not have supply voltage capability.") + + setpoint_mv = int(voltage*1000) + + # Check LE16 max value + # TODO - move to pyedbglib + if setpoint_mv.bit_length() > 16: + raise ValueError("Specified voltage out of range!") + + try: + housekeeper.set_le16(Jtagice3HousekeepingProtocol.HOUSEKEEPING_CONTEXT_ANALOG, + Jtagice3HousekeepingProtocol.HOUSEKEEPING_TSUP_VOLTAGE, setpoint_mv) + # Unfortunately pyedbglib only throws a generic Exception in this case so no specific Exceptions can be caught + # See DSG-1494 + #pylint: disable=broad-except + except Exception as error: + if "failure code 0x{:02x}".format(Jtagice3Protocol.SETGET_FAILURE_INVALID_VALUE) in str(error).lower(): + raise ValueError("Specified voltage out of range!") + # Voltage was within range but something else went wrong. Just forward the exception. + raise + +def compare(data0, data1, offset, verify_mask=None): + """ + Compares the two byte arrays + + :param data0: first array for compare + :param data1: second array for compare + :param offset: address offset in the memory area, for printing + :param verify_mask: compare mask (for varying instruction width) + """ + if verify_mask is None: + verify_mask = [0xFF] + + # Check first that lengths match + if len(data0) != len(data1): + raise ValueError("Length mismatch on verify") + + mask_len = len(verify_mask) + + for i in range(0, len(data0), mask_len): + for dat in range(0, mask_len): + if (data0[i+dat] & verify_mask[dat]) != (data1[i+dat] & verify_mask[dat]): + raise ValueError("Verify mismatch starting at location 0x{:06X}: 0x{:02X} vs 0x{:02X}". + format(i+dat+offset, data0[i+dat] & verify_mask[dat], data1[i+dat] & verify_mask[dat])) + + +def showdata(data, address=0, page_size=None, line_wrap=16, phantom_bytes=0): + """ + Show (print) the data + + :param data: an array/list of data to show + :param address: byte address to data + :param page_size: page size in bytes + :param line_wrap: how many bytes to print per line + :param phantom_bytes: number of phantom bytes to be added per data byte + Phantom bytes will show up as xx in the output + """ + # If start address matches phantom byte location it should just be moved to the first location representing actual + # data + word_index = address % (1 + phantom_bytes) + if word_index: + address += (1 + phantom_bytes) - word_index + + # Cannot print more per line than the page size + if page_size is not None: + if line_wrap > page_size: + line_wrap = page_size + + print("-"*(line_wrap*3+9)) + + # Page alignment + rows = 0 + if page_size is not None: + page = address % page_size + rows = int(page / line_wrap) + for row in range(rows): + print("0x{0:06X}: ".format(address-page+row*line_wrap), end='') + print("xx "*line_wrap, end='') + print("") + + print_index = 0 + + # Calculate offset from aligned data + div = address % line_wrap + + print("0x{0:06X}: ".format(address-div), end='') + # Add some empty bytes + print("xx "*div, end='') + print_index += div + + # keep track of page wraps + wrap = False + + for data_index, value in enumerate(data, div+1): + print("{0:02X} ".format(value), end='') + for i in range(phantom_bytes): + print("xx ", end='') + print_index += 1 + phantom_bytes + if page_size is not None: + if (print_index+(rows*line_wrap)) % page_size == 0 and data_index != len(data)+div: + print("") + wrap = True + if print_index % line_wrap == 0 and data_index != len(data)+div or wrap: + print("") + print("0x{0:06X}: ".format(address-div + print_index), end='') + wrap = False + + # Figure out how many extra empty data positions to print + extra = line_wrap - print_index % line_wrap + if extra % line_wrap == 0: + extra = 0 + + print("xx "*extra, end='') + print("") + print("-"*(line_wrap*3+9)) + + +def pagealign(data, address, page_size, data_size=1): + """ + Aligns data to the start of a page + """ + # Deep copy of input array + data = copy.deepcopy(data) + # Pre-pad the data if it does not start at the start of a page + offset = address % page_size + for _ in range(offset): + data.insert(0, 0xFF) + # In case of other data sizes, post-pad the data + while len(data) % data_size: + data.append(0xFF) + + return data, address-offset + +def pad_to_size(memory_block, chunk_size, pad_value): + """ + Pads a chunk of memory + """ + # Deep copy of input array + memory_block = copy.deepcopy(memory_block) + + while len(memory_block) % chunk_size > 0: + memory_block.append(pad_value) + + return memory_block + + +def enum(**enums): + """ + Emulates an Enum type + + Needed for Python 2.7 compatibility as Python did not get built-in support for enums until version 3.4 + """ + return type('Enum', (), enums) + + +def verify_from_bin(bin_filename, backend, offset=0, memory_name=MemoryNames.FLASH): + """ + Verify the contents of flash against a bin-file + + :param filename: Name/path of bin-file to verify + :param backend: Reference the Backend class of pymcuprog + :param offset: Memory offset to start verify from + :param memory_name: Name of memory as defined in DeviceMemoryInfo (deviceinfo.py) + :returns: Boolean value indicating success or failure of the operation + """ + bin_file = open(bin_filename, 'rb') + bin_data = bytearray() + for line in bin_file.readlines(): + bin_data.extend(line) + + verify_status = backend.verify_memory(bin_data, memory_name, offset) + if verify_status is False: + return False + return True diff --git a/pymcuprog/version.py b/pymcuprog/version.py new file mode 100644 index 0000000..99fd39e --- /dev/null +++ b/pymcuprog/version.py @@ -0,0 +1,4 @@ +""" This file was generated when pymcuprog was built """ +VERSION = '3.13.3.166' +COMMIT_ID = '811c003389c6f1184fc6af0ac17fd5dc895d3c73' +BUILD_DATE = '2022-05-20 12:02:11 +0000' diff --git a/pypi.md b/pypi.md new file mode 100644 index 0000000..2daf642 --- /dev/null +++ b/pypi.md @@ -0,0 +1,260 @@ +# pymcuprog - Python MCU programmer +pymcuprog is a utility for programming various Microchip MCU devices using Microchip CMSIS-DAP based debuggers + +## Overview +pymcuprog is available: + +* install using pip from pypi: https://pypi.org/project/pymcuprog +* browse source code on github: https://github.com/microchip-pic-avr-tools/pymcuprog +* read API documentation on github: https://microchip-pic-avr-tools.github.io/pymcuprog +* read the changelog on github: https://github.com/microchip-pic-avr-tools/pymcuprog/blob/main/CHANGELOG.md + +## Usage +pymcuprog can be used as a command-line interface or a library + +### Command-line programming +for help, use: +```bash +pymcuprog --help +``` + +#### Action: ping +checks connectivity by reading the device identity + +Example: +```bash +pymcuprog ping +``` +#### Action: erase +erases device memories +* use -m to erase only a specified memory region (if available) + +Example: chip erase the device +```bash +pymcuprog erase +``` +#### Action: write +writes device memories +* use -f for writing from a file, or +* use -l for writing literal values +* use -m to specify memory type for literal writing +* use -o to specify offset for literal writing + +Example: writes the content of an Intel(R) hex file to the appropriate memory areas on the device +```bash +pymcuprog write -f app.hex +``` +Note: memory is not erased automatically before writing! + +Example: erases memories and then writes an Intel hex file: +```bash +pymcuprog write -f app.hex --erase +``` + +Example: erases memories, writes an Intel hex file and then verifies the content: +```bash +pymcuprog write -f app.hex --erase --verify +``` + +#### Action: read +reads device memories +* use -m to specify memory type +* use -o to specify offset to read from +* use -b to specify number of bytes to read +* use -f to read to a file + +Example: reads 64 bytes of flash memory from offset 0x1000 +```bash +pymcuprog read -m flash -o 0x1000 -b 64 +``` + +#### Action: reset +resets the target device + +Example: +```bash +pymcuprog reset +``` + +### Command-line board utilities + +#### Action: getvoltage +reads the actual target operating voltage + +Example: +```bash +pymcuprog getvoltage +``` + +#### Action: getsupplyvoltage +reads the supply voltage (set-point) + +Example: +```bash +pymcuprog getsupplyvoltage +``` + +#### Action: getusbvoltage +reads the USB voltage (Vbus) + +Example: +```bash +pymcuprog getusbvoltage +``` + +#### Action: setsupplyvoltage +sets the target supply voltage +* use -l to specify a literal supply voltage value + +Example: sets the target supply voltage on a Curiosity Nano kit to 3.3V +```bash +pymcuprog setsupplyvoltage -l 3.3 +``` + +#### Action: reboot-debugger +reboots the debugger + +Example: reboots a Curiosity Nano kit +```bash +pymcuprog reboot-debugger +``` + +### Command-line switches +Many of these switches are optional, and many parameters are automatically set when using a Curiosity Nano or Xplained Pro kit. +* -t TOOL to select which tool to use. Optional if only one is connected. +* -s SERIALNUMBER to select which tool instance to use. Optional if only one is connected. +* -d DEVICE to specify the device to program. Optional when using a kit. +* -i INTERFACE to specify the target communication interface. Optional. +* -p PACKPATH to specify the path to the DFP for PIC devices* +* -c CLK to specify the programming interface clock speed. Optional. +* --verify to verify after programming +* -u UART to use native host serial port UART for UPDI instead of a USB-based tool. +* -H MODE to select UPDI high-voltage entry mode ('tool-toggle-power', 'user-toggle-power', 'simple-unsafe-pulse') +* -U to write user row values when the device is locked (UPDI only) +* -C to erase and unlock a locked device (UPDI only) +* -v LEVEL for selecting logging verbosity ('debug', 'info', 'warning', 'error', 'critical') + + +#### +*Notes regarding PACKPATH argument + +While pymcuprog itself contains sufficient information to program AVR devices (with UPDI interface), it is unable to program a PIC device without access to programming scripts for that device. These scripts are deployed in Device Family Packs (DFP) on https://packs.download.microchip.com and are only provided for PIC devices mounted on Curiosity Nano boards or other boards with the PKOB nano (nEDBG) debugger. To use pymcuprog with PIC devices, you will either need to download a DFP for the PIC in question, or have MPLAB X v5.25 or later installed. In either case the path to the particular device in the scripts folder inside the DFP must be passed into pymcuprog using the -p PACKPATH argument. Remember to use "" if the path itself contains spaces. + +Example: Ping the device on a PIC16F15244 Curiosity Nano +```bash +pymcuprog ping -p "c:\Program Files (x86)\Microchip\MPLABX\v5.40\packs\Microchip\PIC16F1xxxx_DFP\1.4.119\scripts\pic16f15244" +``` + +### Serial port UPDI (pyupdi) +The AVR UPDI interface implements a UART protocol, which means that it can be used by simply connecting TX and RX pins of a serial port together with the UPDI pin; with a series resistor (eg: 1k) between TX and UPDI to handle contention. (This configuration is also known as "pyupdi".) Be sure to connect a common ground, and use a TTL serial adapter running at the same voltage as the AVR device. + +
+                        Vcc                     Vcc
+                        +-+                     +-+
+                         |                       |
+ +---------------------+ |                       | +--------------------+
+ | Serial port         +-+                       +-+  AVR device        |
+ |                     |      +----------+         |                    |
+ |                  TX +------+   1k     +---------+ UPDI               |
+ |                     |      +----------+    |    |                    |
+ |                     |                      |    |                    |
+ |                  RX +----------------------+    |                    |
+ |                     |                           |                    |
+ |                     +--+                     +--+                    |
+ +---------------------+  |                     |  +--------------------+
+                         +-+                   +-+
+                         GND                   GND
+
+ +pymcuprog includes this implementation as an alternative to USB/EDBG-based tools. To connect via a serial port, use the "uart" tool type with the UART switch in addition. + +Example: checks connectivity by reading the device identity +```bash +pymcuprog ping -d avr128da48 -t uart -u com35 +``` + + + +### Library +pymcuprog can be used as a library using its backend API. For example: +```python +# pymcuprog uses the Python logging module +import logging +logging.basicConfig(format="%(levelname)s: %(message)s", level=logging.WARNING) + +# Configure the session +from pymcuprog.backend import SessionConfig +sessionconfig = SessionConfig("atmega4808") + +# Instantiate USB transport (only 1 tool connected) +from pymcuprog.toolconnection import ToolUsbHidConnection +transport = ToolUsbHidConnection() + +# Instantiate backend +from pymcuprog.backend import Backend +backend = Backend() + +# Connect to tool using transport +backend.connect_to_tool(transport) + +# Start the session +backend.start_session(sessionconfig) + +# Read the target device_id +device_id = backend.read_device_id() +print ("Device ID is {0:06X}".format(int.from_bytes(device_id, byteorder="little"))) +``` + +## Logging +This package uses the Python logging module for publishing log messages to library users. +A basic configuration can be used (see example), but for best results a more thorough configuration is recommended in order to control the verbosity of output from dependencies in the stack which also use logging. +See logging.yaml which is included in the package (although only used for CLI) + +## Dependencies +pymcuprog depends on pyedbglib for its transport protocol. +pyedbglib requires a USB transport library like libusb. See pyedbglib package for more information. + +## Versioning +pymcuprog version can be determined using the CLI: +```bash +pymcuprog -V +``` + +or using the library: +```python +from pymcuprog.version import VERSION as pymcuprog_version +print("pymcuprog version {}".format(pymcuprog_version)) +``` + +In addition, the CLI-backend API is versioned for convenience: +```python +from pymcuprog.backend import Backend +backend = Backend() +print("pymcuprog backend API version: {}".format(backend.get_api_version())) +``` + +## Supported devices and tools +pymcuprog is primarily intended for use with PKOB nano (nEDBG) debuggers which are found on Curiosity Nano kits and other development boards. This means that it is continuously tested with a selection of AVR devices with UPDI interface as well as a selection of PIC devices. However since the protocol is compatible between all EDBG-based debuggers (pyedbglib) it is possible to use pymcuprog with a wide range of debuggers and devices, although not all device families/interfaces have been implemented. + +### Debuggers / Tools +pymcuprog supports: +* PKOB nano (nEDBG) - on-board debugger on Curiosity Nano +* MPLAB PICkit 4 In-Circuit Debugger (when in 'AVR mode') +* MPLAB Snap In-Circuit Debugger (when in 'AVR mode') +* Atmel-ICE +* Power Debugger +* EDBG - on-board debugger on Xplained Pro/Ultra +* mEDBG - on-board debugger on Xplained Mini/Nano +* JTAGICE3 (firmware version 3.0 or newer) + +Although not all functionality is provided on all debuggers/boards. See device support section below. + +### Devices +pymcuprog supports: +* All UPDI devices, whether mounted on kits or standalone +* PIC devices mounted on Curiosity Nano kits, or similar board with PKOB nano (nEDBG) debugger + +Other devices (eg ATmega328P, ATsamd21e18a) may be partially supported for experimental purposes + +## Notes for Linux® systems +This package uses pyedbglib and other libraries for USB transport and some udev rules are required. For details see the pyedbglib package: https://pypi.org/project/pyedbglib diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..883ea7e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +# External packages +pyserial +intelhex +appdirs +# pathlib2 is needed for Python 2.7 compatibility +pathlib2; python_version < "3" +pyyaml + +# Microchip packages +pyedbglib>=2.20 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..74b0d3c --- /dev/null +++ b/setup.py @@ -0,0 +1,106 @@ +""" +pymcuprog +Tools for programming of MCUs using Microchip CMSIS-DAP based debuggers +""" +from os import path +from os import chdir +from os import popen +import time +# To use a consistent encoding +from codecs import open +# Always prefer setuptools over distutils +from setuptools import setup, find_packages + +here = path.abspath(path.dirname(__file__)) +chdir(here) + +# Get the long description from the pypi file +# Using UTF8 and single newlines +with open(path.join(here, 'pypi.md'), 'rb') as f: + long_description = f.read().decode("utf-8").replace('\r\n', '\n') + +# Set the package name: +name = 'pymcuprog' + +""" +Package version : +The version number follow the format major.minor.patch.build +major, minor and patch are set manually according to semantic versioning 2.0.0: https://semver.org +build is an incrementing number set by a build server +in case of installing from source, a Local Version Identifier (see PEP 440) is added +""" + +# Package version setup +PACKAGE_VERSION = { + "major": 3, + "minor": 13, + "patch": 3, + # Will be replaced by build number from Jenkins. For local builds the build number is 0 and the 'snapshot' is + # added as Local Version Identifier + "build": '166', +} + +version = "{}.{}.{}.{}".format(PACKAGE_VERSION['major'], PACKAGE_VERSION['minor'], + PACKAGE_VERSION['patch'], PACKAGE_VERSION['build']) +print("Building {} version: {}".format(name, version)) + +# Create a "version.py" file in the package +fname = "{}/version.py".format(name) +with open(path.join(here, fname), 'w') as f: + f.write("\"\"\" This file was generated when {} was built \"\"\"\n".format(name)) + f.write("VERSION = '{}'\n".format(version)) + # The command below can fail if git command not available, or not in a git workspace folder + result = popen("git rev-parse HEAD").read() + commit_id = result.splitlines()[0] if result else "N/A" + f.write("COMMIT_ID = '{}'\n".format(commit_id)) + f.write("BUILD_DATE = '{}'\n".format(time.strftime("%Y-%m-%d %H:%M:%S %z"))) + +# Read in requirements (dependencies) file +with open('requirements.txt') as f: + install_requires = f.read() + +setup( + name=name, + version=version, + description='Tools for programming of MCUs using Microchip CMSIS-DAP based debuggers', + long_description=long_description, + long_description_content_type='text/markdown', + url='https://github.com/microchip-pic-avr-tools/pymcuprog', + license='MIT', + author='Microchip Technology', + author_email='support@microchip.com', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Embedded Systems', + 'License :: OSI Approved :: MIT License', + # pymcuprog does also work on Python 2.7 but it is not officially supported + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX :: Linux', + 'Operating System :: MacOS', + ], + packages=find_packages(exclude=['tests']), + + # List of packages required to use this package + install_requires=install_requires, + + # List of packages required to develop and test this package + extras_require={ + 'dev': ['pylint', 'mock', 'parameterized', 'pytest'], + }, + + # Include files from MANIFEST.in + include_package_data=True, + + # Installable CLI entry point + entry_points={ + 'console_scripts': [ + 'pymcuprog=pymcuprog.pymcuprog:main', + ], + }, +)