tinyupdi 1.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tinyupdi-1.5.0/LICENSE +21 -0
- tinyupdi-1.5.0/PKG-INFO +97 -0
- tinyupdi-1.5.0/README.md +53 -0
- tinyupdi-1.5.0/pyproject.toml +37 -0
- tinyupdi-1.5.0/setup.cfg +4 -0
- tinyupdi-1.5.0/setup.py +15 -0
- tinyupdi-1.5.0/tinyupdi/__init__.py +1 -0
- tinyupdi-1.5.0/tinyupdi/tinyupdi.py +769 -0
- tinyupdi-1.5.0/tinyupdi.egg-info/PKG-INFO +97 -0
- tinyupdi-1.5.0/tinyupdi.egg-info/SOURCES.txt +12 -0
- tinyupdi-1.5.0/tinyupdi.egg-info/dependency_links.txt +1 -0
- tinyupdi-1.5.0/tinyupdi.egg-info/entry_points.txt +2 -0
- tinyupdi-1.5.0/tinyupdi.egg-info/requires.txt +1 -0
- tinyupdi-1.5.0/tinyupdi.egg-info/top_level.txt +1 -0
tinyupdi-1.5.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Stefan Wagner
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tinyupdi-1.5.0/PKG-INFO
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: tinyupdi
|
|
3
|
+
Version: 1.5.0
|
|
4
|
+
Summary: Minimal UPDI Programming Tool for tinyAVR
|
|
5
|
+
Home-page: https://github.com/wagiminator/MCU-Flash-Tools
|
|
6
|
+
Author: Stefan Wagner
|
|
7
|
+
Author-email: Stefan Wagner <wagiminator@web.de>
|
|
8
|
+
Maintainer-email: Stefan Wagner <wagiminator@web.de>
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2022 Stefan Wagner
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
Project-URL: Homepage, https://wagiminator.github.io/
|
|
31
|
+
Project-URL: Repository, https://github.com/wagiminator/MCU-Flash-Tools
|
|
32
|
+
Project-URL: Issues, https://github.com/wagiminator/MCU-Flash-Tools/issues
|
|
33
|
+
Keywords: AVR,Microchip,ATtiny,tinyAVR,UPDI,microcontroller
|
|
34
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Intended Audience :: Developers
|
|
39
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
40
|
+
Requires-Python: >=3.8
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE
|
|
43
|
+
Requires-Dist: pyserial
|
|
44
|
+
|
|
45
|
+
# tinyUPDI - Minimal UPDI Programming Tool for tinyAVR
|
|
46
|
+
## Description
|
|
47
|
+
This tool allows tinyAVR series 0, 1, and 2 microcontrollers to be programmed using a USB-to-serial converter connected in a special way to the UPDI pin (also called SerialUPDI). More information can be found [here](https://github.com/SpenceKonde/AVR-Guidance/blob/master/UPDI/jtag2updi.md).
|
|
48
|
+
|
|
49
|
+
## Preparations
|
|
50
|
+
If necessary, a driver for the USB-to-serial converter used must be installed.
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
- Install [Python3](https://www.pythontutorial.net/getting-started/install-python/).
|
|
54
|
+
- Install [pip](https://pip.pypa.io/en/stable/installation/).
|
|
55
|
+
- Install rvprog:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
pip install tinyupdi
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
Connect the USB-to-serial converter via USB to the PC and via the circuit described below to the UPDI pin of the microcontroller.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
USB2SERIAL tinyAVR
|
|
66
|
+
+--------+ +-----------+
|
|
67
|
+
| RXD| <------------+---> |UPDI (PA0) |
|
|
68
|
+
| | | | |
|
|
69
|
+
| TXD| ---|1kOhm|---+ | |
|
|
70
|
+
| | | |
|
|
71
|
+
| VDD| -----------------> |VDD |
|
|
72
|
+
| GND| -----------------> |GND |
|
|
73
|
+
+--------+ +-----------+
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Alternatively, a pre-assembled [SerialUPDI programmer](https://github.com/wagiminator/AVR-Programmer) can be used.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
Usage: tinyupdi [-h] [-d DEVICE] [-e] [-f FLASH] [-fs [FUSES [FUSES ...]]] [-t TRIM]
|
|
80
|
+
|
|
81
|
+
Optional arguments:
|
|
82
|
+
-h, --help show help message and exit
|
|
83
|
+
-d, --device set target device (if not set, it will be auto-detected)
|
|
84
|
+
-e, --erase perform a chip erase (implied with --flash)
|
|
85
|
+
-f FLASH, --flash FLASH BIN file to flash
|
|
86
|
+
-fs [FUSES [FUSES ...]], --fuses [FUSES [FUSES ...]]
|
|
87
|
+
fuses to set (syntax: fuse_nr:0xvalue)
|
|
88
|
+
-t TRIM, --trim TRIM configure oscillator for given frequency (set fuse 2)
|
|
89
|
+
Example:
|
|
90
|
+
tinyupdi -f firmware.bin -fs 6:0x04 7:0x00 8:0x00 -t 8000000
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Links
|
|
94
|
+
1. [MCU Templates](https://github.com/wagiminator/MCU-Templates)
|
|
95
|
+
2. [MCU Development Boards](https://github.com/wagiminator/Development-Boards)
|
|
96
|
+
3. [AVR Development Boards](https://github.com/wagiminator/AVR-Development-Boards)
|
|
97
|
+
4. [SerialUPDI Programmers](https://github.com/wagiminator/AVR-Programmer)
|
tinyupdi-1.5.0/README.md
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# tinyUPDI - Minimal UPDI Programming Tool for tinyAVR
|
|
2
|
+
## Description
|
|
3
|
+
This tool allows tinyAVR series 0, 1, and 2 microcontrollers to be programmed using a USB-to-serial converter connected in a special way to the UPDI pin (also called SerialUPDI). More information can be found [here](https://github.com/SpenceKonde/AVR-Guidance/blob/master/UPDI/jtag2updi.md).
|
|
4
|
+
|
|
5
|
+
## Preparations
|
|
6
|
+
If necessary, a driver for the USB-to-serial converter used must be installed.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
- Install [Python3](https://www.pythontutorial.net/getting-started/install-python/).
|
|
10
|
+
- Install [pip](https://pip.pypa.io/en/stable/installation/).
|
|
11
|
+
- Install rvprog:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
pip install tinyupdi
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
Connect the USB-to-serial converter via USB to the PC and via the circuit described below to the UPDI pin of the microcontroller.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
USB2SERIAL tinyAVR
|
|
22
|
+
+--------+ +-----------+
|
|
23
|
+
| RXD| <------------+---> |UPDI (PA0) |
|
|
24
|
+
| | | | |
|
|
25
|
+
| TXD| ---|1kOhm|---+ | |
|
|
26
|
+
| | | |
|
|
27
|
+
| VDD| -----------------> |VDD |
|
|
28
|
+
| GND| -----------------> |GND |
|
|
29
|
+
+--------+ +-----------+
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Alternatively, a pre-assembled [SerialUPDI programmer](https://github.com/wagiminator/AVR-Programmer) can be used.
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Usage: tinyupdi [-h] [-d DEVICE] [-e] [-f FLASH] [-fs [FUSES [FUSES ...]]] [-t TRIM]
|
|
36
|
+
|
|
37
|
+
Optional arguments:
|
|
38
|
+
-h, --help show help message and exit
|
|
39
|
+
-d, --device set target device (if not set, it will be auto-detected)
|
|
40
|
+
-e, --erase perform a chip erase (implied with --flash)
|
|
41
|
+
-f FLASH, --flash FLASH BIN file to flash
|
|
42
|
+
-fs [FUSES [FUSES ...]], --fuses [FUSES [FUSES ...]]
|
|
43
|
+
fuses to set (syntax: fuse_nr:0xvalue)
|
|
44
|
+
-t TRIM, --trim TRIM configure oscillator for given frequency (set fuse 2)
|
|
45
|
+
Example:
|
|
46
|
+
tinyupdi -f firmware.bin -fs 6:0x04 7:0x00 8:0x00 -t 8000000
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Links
|
|
50
|
+
1. [MCU Templates](https://github.com/wagiminator/MCU-Templates)
|
|
51
|
+
2. [MCU Development Boards](https://github.com/wagiminator/Development-Boards)
|
|
52
|
+
3. [AVR Development Boards](https://github.com/wagiminator/AVR-Development-Boards)
|
|
53
|
+
4. [SerialUPDI Programmers](https://github.com/wagiminator/AVR-Programmer)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tinyupdi"
|
|
7
|
+
version = "1.5.0"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Stefan Wagner", email="wagiminator@web.de" },
|
|
10
|
+
]
|
|
11
|
+
maintainers = [
|
|
12
|
+
{ name="Stefan Wagner", email="wagiminator@web.de" },
|
|
13
|
+
]
|
|
14
|
+
description = "Minimal UPDI Programming Tool for tinyAVR"
|
|
15
|
+
readme = "README.md"
|
|
16
|
+
license = {file = "LICENSE"}
|
|
17
|
+
requires-python = ">=3.8"
|
|
18
|
+
dependencies = [
|
|
19
|
+
"pyserial",
|
|
20
|
+
]
|
|
21
|
+
keywords = ["AVR", "Microchip", "ATtiny", "tinyAVR", "UPDI", "microcontroller"]
|
|
22
|
+
classifiers = [
|
|
23
|
+
"Development Status :: 5 - Production/Stable",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"License :: OSI Approved :: MIT License",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Intended Audience :: Developers",
|
|
28
|
+
"Topic :: Software Development :: Embedded Systems",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
tinyupdi = "tinyupdi:_main"
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://wagiminator.github.io/"
|
|
36
|
+
Repository = "https://github.com/wagiminator/MCU-Flash-Tools"
|
|
37
|
+
Issues = "https://github.com/wagiminator/MCU-Flash-Tools/issues"
|
tinyupdi-1.5.0/setup.cfg
ADDED
tinyupdi-1.5.0/setup.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from setuptools import setup
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name='tinyupdi',
|
|
5
|
+
version='1.5.0',
|
|
6
|
+
description='Minimal UPDI Programming Tool for tinyAVR',
|
|
7
|
+
url='https://github.com/wagiminator/MCU-Flash-Tools',
|
|
8
|
+
author='Stefan Wagner',
|
|
9
|
+
author_email='wagiminator@web.de',
|
|
10
|
+
license='MIT License',
|
|
11
|
+
packages=['tinyupdi'],
|
|
12
|
+
install_requires=[
|
|
13
|
+
'pyserial',
|
|
14
|
+
],
|
|
15
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .tinyupdi import _main
|
|
@@ -0,0 +1,769 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# ===================================================================================
|
|
3
|
+
# Project: TinyUPDI - Minimal application-specific UPDI programmer based on pyupdi
|
|
4
|
+
# Version: v1.5
|
|
5
|
+
# Year: 2022
|
|
6
|
+
# Author: Stefan Wagner
|
|
7
|
+
# Github: https://github.com/wagiminator
|
|
8
|
+
# License: MIT License
|
|
9
|
+
# ===================================================================================
|
|
10
|
+
#
|
|
11
|
+
# Description:
|
|
12
|
+
# ------------
|
|
13
|
+
# TinyUPDI is a serial UPDI programmer software that is adapted to a specific
|
|
14
|
+
# application and reduced to the functions required for this. It is normally used
|
|
15
|
+
# for projects with a tinyAVR and a built-in USB to serial adapter to simplify
|
|
16
|
+
# firmware uploading.
|
|
17
|
+
#
|
|
18
|
+
# References:
|
|
19
|
+
# -----------
|
|
20
|
+
# TinyUPDI is essentially based on pyupdi and pymcuprog:
|
|
21
|
+
# - https://github.com/mraardvark/pyupdi
|
|
22
|
+
# - https://github.com/microchip-pic-avr-tools/pymcuprog
|
|
23
|
+
#
|
|
24
|
+
# Dependencies:
|
|
25
|
+
# -------------
|
|
26
|
+
# - pyserial
|
|
27
|
+
#
|
|
28
|
+
# Restrictions:
|
|
29
|
+
# -------------
|
|
30
|
+
# - Target MCU must be a 0, 1, or 2-series tinyAVR.
|
|
31
|
+
# - Only flash and fuses can be programmed.
|
|
32
|
+
# - Firmware file must be a binary (not ELF or HEX!).
|
|
33
|
+
#
|
|
34
|
+
# Operating Instructions:
|
|
35
|
+
# -----------------------
|
|
36
|
+
# - python3 tinyupdi.py [-h] [-d DEVICE] [-e] [-f FLASH] [-fs [FUSES [FUSES ...]]]
|
|
37
|
+
# -h, --help show help message and exit
|
|
38
|
+
# -d, --device set target device (if not set, it will be auto-detected)
|
|
39
|
+
# -e, --erase perform a chip erase (implied with --flash)
|
|
40
|
+
# -f FLASH, --flash FLASH BIN file to flash
|
|
41
|
+
# -fs [FUSES [FUSES ...]], --fuses [FUSES [FUSES ...]]
|
|
42
|
+
# fuses to set (syntax: fuse_nr:0xvalue)
|
|
43
|
+
# -t TRIM, --trim TRIM configure oscillator for given frequency (fuse 2)
|
|
44
|
+
# - Example:
|
|
45
|
+
# python3 tinyupdi.py -d attiny202 -f firmware.bin -fs 6:0x04 7:0x00 8:0x00 -t 8000000
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
import re
|
|
49
|
+
import sys
|
|
50
|
+
import time
|
|
51
|
+
import argparse
|
|
52
|
+
import serial
|
|
53
|
+
from serial import Serial
|
|
54
|
+
from serial.tools.list_ports import comports
|
|
55
|
+
|
|
56
|
+
# If the PID/VID of the USB-to-Serial converter is known, it can be defined here,
|
|
57
|
+
# which can make the auto-detection a lot faster.
|
|
58
|
+
#VID = '1A86'
|
|
59
|
+
#PID = '7523'
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ===================================================================================
|
|
63
|
+
# Main Function
|
|
64
|
+
# ===================================================================================
|
|
65
|
+
|
|
66
|
+
def _main():
|
|
67
|
+
# Parse command line arguments
|
|
68
|
+
parser = argparse.ArgumentParser(description='Minimal command line'
|
|
69
|
+
' interface for UPDI programming')
|
|
70
|
+
parser.add_argument('-d', '--device', choices=get_supported_devices(),
|
|
71
|
+
help='target device (if not set, it will be auto-detected)')
|
|
72
|
+
parser.add_argument("-e", "--erase", action="store_true",
|
|
73
|
+
help="perform a chip erase (implied with --flash)")
|
|
74
|
+
parser.add_argument('-f', '--flash', help='BIN file to flash')
|
|
75
|
+
parser.add_argument('-fs', '--fuses', action='append', nargs='*',
|
|
76
|
+
help='fuses to set (syntax: fuse_nr:0xvalue)')
|
|
77
|
+
parser.add_argument('-t', '--trim', help='configure oscillator for given frequency (fuse 2)')
|
|
78
|
+
args = parser.parse_args(sys.argv[1:])
|
|
79
|
+
|
|
80
|
+
# Check arguments
|
|
81
|
+
if not any( (args.fuses, args.flash, args.erase) ):
|
|
82
|
+
print('No arguments - no action!')
|
|
83
|
+
sys.exit(0)
|
|
84
|
+
|
|
85
|
+
if args.fuses is not None:
|
|
86
|
+
fuselist = list()
|
|
87
|
+
for fslist in args.fuses:
|
|
88
|
+
for fsarg in fslist:
|
|
89
|
+
if not re.match('^[0-9]+:0x[0-9a-fA-F]+$', fsarg):
|
|
90
|
+
sys.stderr.write('ERROR: Bad fuses format {}. Expected fuse_nr:0xvalue\n'.format(fsarg))
|
|
91
|
+
sys.exit(1)
|
|
92
|
+
lst = fsarg.split(':0x')
|
|
93
|
+
fuselist.append( (int(lst[0]), int(lst[1], 16)) )
|
|
94
|
+
|
|
95
|
+
# Establish connection via serial to UPDI device
|
|
96
|
+
try:
|
|
97
|
+
print('Connecting to device ...')
|
|
98
|
+
tinyupdi = Programmer()
|
|
99
|
+
except Exception as ex:
|
|
100
|
+
sys.stderr.write('ERROR: ' + str(ex) + '!\n')
|
|
101
|
+
sys.exit(1)
|
|
102
|
+
print('SUCCESS: Found programmer on port %s.' % tinyupdi.port)
|
|
103
|
+
|
|
104
|
+
# Programming the device
|
|
105
|
+
try:
|
|
106
|
+
# Detect target device
|
|
107
|
+
print('Detecting target MCU ...')
|
|
108
|
+
device = tinyupdi.detect()
|
|
109
|
+
print('SUCCESS: Found %s with %d bytes of flash.' % (device, tinyupdi.flash_size))
|
|
110
|
+
if (args.device is not None) and (not args.device == device):
|
|
111
|
+
raise Exception('Found device is not ' + args.device)
|
|
112
|
+
|
|
113
|
+
# Perform chip erase
|
|
114
|
+
if (args.erase) or (args.flash is not None):
|
|
115
|
+
print('Performing chip erase ...')
|
|
116
|
+
tinyupdi.erase()
|
|
117
|
+
|
|
118
|
+
# Flash binary file
|
|
119
|
+
if args.flash is not None:
|
|
120
|
+
print('Flashing %s to %s ...' % (args.flash, device))
|
|
121
|
+
with open(args.flash, 'rb') as f: data = f.read()
|
|
122
|
+
tinyupdi.flash_data(data)
|
|
123
|
+
print('Verifying ...')
|
|
124
|
+
tinyupdi.verify_data(data)
|
|
125
|
+
print('SUCCESS: %d bytes written and verified.' % len(data))
|
|
126
|
+
|
|
127
|
+
# Write fuses
|
|
128
|
+
if args.fuses is not None:
|
|
129
|
+
print('Writing fuses ...')
|
|
130
|
+
for fuse in fuselist:
|
|
131
|
+
tinyupdi.set_fuse(fuse[0], fuse[1])
|
|
132
|
+
print('Verifying fuses ...')
|
|
133
|
+
for fuse in fuselist:
|
|
134
|
+
tinyupdi.verify_fuse(fuse[0], fuse[1])
|
|
135
|
+
print('SUCCESS: Fuses written and verified.')
|
|
136
|
+
|
|
137
|
+
# Write fuse to set oscillator according to frequency
|
|
138
|
+
if args.trim is not None:
|
|
139
|
+
print('Setting oscillator for %s Hz ...' % args.trim)
|
|
140
|
+
osc = tinyupdi.trim(int(args.trim))
|
|
141
|
+
print('SUCCESS: Oscillator runs at %d MHz with corresponding factory calibration.' % osc)
|
|
142
|
+
|
|
143
|
+
except Exception as ex:
|
|
144
|
+
sys.stderr.write('ERROR: ' + str(ex) + '!\n')
|
|
145
|
+
tinyupdi.exit()
|
|
146
|
+
sys.exit(1)
|
|
147
|
+
|
|
148
|
+
# Finish all up
|
|
149
|
+
tinyupdi.exit()
|
|
150
|
+
print('DONE.')
|
|
151
|
+
sys.exit(0)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# ===================================================================================
|
|
155
|
+
# Programmer Class
|
|
156
|
+
# ===================================================================================
|
|
157
|
+
|
|
158
|
+
class Programmer(Serial):
|
|
159
|
+
def __init__(self):
|
|
160
|
+
super().__init__(baudrate = 230400, timeout = 1, parity=serial.PARITY_EVEN, stopbits=serial.STOPBITS_TWO)
|
|
161
|
+
self.syscfg_address = 0x0F00
|
|
162
|
+
self.nvmctrl_address = 0x1000
|
|
163
|
+
self.sigrow_address = 0x1100
|
|
164
|
+
self.fuses_address = 0x1280
|
|
165
|
+
self.userrow_address = 0x1300
|
|
166
|
+
self.flash_pagesize = 64
|
|
167
|
+
self.flash_start = 0x8000
|
|
168
|
+
self.flash_size = 0x0800
|
|
169
|
+
self.identify()
|
|
170
|
+
|
|
171
|
+
# Identify port of programmer and enter programming mode
|
|
172
|
+
def identify(self):
|
|
173
|
+
for p in comports():
|
|
174
|
+
if (('VID' not in globals()) or (VID in p.hwid)) and (('PID' not in globals()) or (PID in p.hwid)):
|
|
175
|
+
self.port = p.device
|
|
176
|
+
try:
|
|
177
|
+
self.open()
|
|
178
|
+
except:
|
|
179
|
+
continue
|
|
180
|
+
self.__send_double_break()
|
|
181
|
+
self.__init()
|
|
182
|
+
if not self.__check():
|
|
183
|
+
self.close()
|
|
184
|
+
continue
|
|
185
|
+
try:
|
|
186
|
+
self.__enter_progmode()
|
|
187
|
+
except:
|
|
188
|
+
self.__unlock()
|
|
189
|
+
return
|
|
190
|
+
raise Exception('Device not found')
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# ------------------------------------------------------------------------------
|
|
194
|
+
# High-Level Functions
|
|
195
|
+
# ------------------------------------------------------------------------------
|
|
196
|
+
|
|
197
|
+
# Detect device
|
|
198
|
+
def detect(self):
|
|
199
|
+
deviceid = self.__get_device_id()
|
|
200
|
+
for dev in UPDI_DEVICES:
|
|
201
|
+
if dev['device_id'] == deviceid:
|
|
202
|
+
self.flash_size = dev['flash_size']
|
|
203
|
+
self.flash_pagesize = dev['flash_pagesize']
|
|
204
|
+
return dev['name']
|
|
205
|
+
raise Exception('Unknown or unsupported device')
|
|
206
|
+
|
|
207
|
+
# Disable UPDI and serial connection
|
|
208
|
+
def exit(self):
|
|
209
|
+
self.__reset(apply_reset=True)
|
|
210
|
+
self.__reset(apply_reset=False)
|
|
211
|
+
self.__stcs(UPDI_CS_CTRLB, (1 << UPDI_CTRLB_UPDIDIS_BIT) | (1 << UPDI_CTRLB_CCDETDIS_BIT))
|
|
212
|
+
self.close()
|
|
213
|
+
|
|
214
|
+
# Does a chip erase using the NVM controller (v0)
|
|
215
|
+
def erase(self):
|
|
216
|
+
if not self.__wait_nvm_ready():
|
|
217
|
+
raise Exception('Timeout waiting for flash ready before erase')
|
|
218
|
+
self.__execute_nvm_command(UPDI_V0_NVMCTRL_CTRLA_CHIP_ERASE)
|
|
219
|
+
if not self.__wait_nvm_ready():
|
|
220
|
+
raise Exception('Timeout waiting for flash ready after erase')
|
|
221
|
+
|
|
222
|
+
# Flash bin file
|
|
223
|
+
def flash_bin(self, filename):
|
|
224
|
+
with open(filename, 'rb') as f: data = f.read()
|
|
225
|
+
self.flash_data(data)
|
|
226
|
+
return len(data)
|
|
227
|
+
|
|
228
|
+
# Verify bin file
|
|
229
|
+
def verify_bin(self, filename):
|
|
230
|
+
with open(filename, 'rb') as f: data = f.read()
|
|
231
|
+
self.verify_data(data)
|
|
232
|
+
return len(data)
|
|
233
|
+
|
|
234
|
+
# Flash data
|
|
235
|
+
def flash_data(self, data):
|
|
236
|
+
if len(data) > self.flash_size:
|
|
237
|
+
raise Exception('Not enough memory')
|
|
238
|
+
self.__write_flash(self.flash_start, data)
|
|
239
|
+
|
|
240
|
+
# Verify data
|
|
241
|
+
def verify_data(self, data):
|
|
242
|
+
if len(data) > self.flash_size:
|
|
243
|
+
raise Exception('Not enough memory')
|
|
244
|
+
readback = self.__read_flash(self.flash_start, len(data))
|
|
245
|
+
if data != readback:
|
|
246
|
+
raise Exception('Flash verification failure')
|
|
247
|
+
|
|
248
|
+
# Set single fuse
|
|
249
|
+
def set_fuse(self, fusenum, value):
|
|
250
|
+
self.__write_fuse(fusenum, value)
|
|
251
|
+
|
|
252
|
+
# Verify single fuse
|
|
253
|
+
def verify_fuse(self, fusenum, value):
|
|
254
|
+
actual_val = self.__read_fuse(fusenum)
|
|
255
|
+
if actual_val != value:
|
|
256
|
+
raise Exception('Fuse ' + str(fusenum) + ' verification failure')
|
|
257
|
+
|
|
258
|
+
# Configure oscillator for given system freqency
|
|
259
|
+
def trim(self, freq):
|
|
260
|
+
for x in [1, 2, 4, 8, 16, 32, 64, 6, 10, 12, 24, 48]:
|
|
261
|
+
if round(20000000 / x) == freq:
|
|
262
|
+
self.set_fuse(0x02, 0x02)
|
|
263
|
+
self.verify_fuse(0x02, 0x02)
|
|
264
|
+
return 20
|
|
265
|
+
if round(16000000 / x) == freq:
|
|
266
|
+
self.set_fuse(0x02, 0x01)
|
|
267
|
+
self.verify_fuse(0x02, 0x01)
|
|
268
|
+
return 16
|
|
269
|
+
raise Exception('Unsupported frequency')
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
# ------------------------------------------------------------------------------
|
|
273
|
+
# Physical Layer for UPDI Stack
|
|
274
|
+
# ------------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
# Send a double break to reset the UPDI port
|
|
277
|
+
def __send_double_break(self):
|
|
278
|
+
self.close()
|
|
279
|
+
temporary_serial = Serial(self.port, 300, stopbits=serial.STOPBITS_ONE, timeout=1)
|
|
280
|
+
temporary_serial.write([UPDI_BREAK, UPDI_BREAK])
|
|
281
|
+
temporary_serial.read(2)
|
|
282
|
+
temporary_serial.close()
|
|
283
|
+
self.open()
|
|
284
|
+
|
|
285
|
+
# Send a char array to UPDI with NO inter-byte delay
|
|
286
|
+
def __send(self, command):
|
|
287
|
+
self.write(command)
|
|
288
|
+
self.read(len(command))
|
|
289
|
+
|
|
290
|
+
# Receive a frame of a known number of chars from UPDI
|
|
291
|
+
def __receive(self, size):
|
|
292
|
+
return self.read(size)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# ------------------------------------------------------------------------------
|
|
296
|
+
# Datalink Layer for UPDI Stack
|
|
297
|
+
# ------------------------------------------------------------------------------
|
|
298
|
+
|
|
299
|
+
# Set the inter-byte delay bit and disable collision detection
|
|
300
|
+
def __init(self):
|
|
301
|
+
self.__stcs(UPDI_CS_CTRLB, 1 << UPDI_CTRLB_CCDETDIS_BIT)
|
|
302
|
+
self.__stcs(UPDI_CS_CTRLA, 1 << UPDI_CTRLA_IBDLY_BIT)
|
|
303
|
+
|
|
304
|
+
# Check UPDI by loading CS STATUSA
|
|
305
|
+
def __check(self):
|
|
306
|
+
if self.__ldcs(UPDI_CS_STATUSA) != 0:
|
|
307
|
+
return True
|
|
308
|
+
return False
|
|
309
|
+
|
|
310
|
+
# Load data from Control/Status space
|
|
311
|
+
def __ldcs(self, address):
|
|
312
|
+
self.__send([UPDI_PHY_SYNC, UPDI_LDCS | (address & 0x0F)])
|
|
313
|
+
response = self.__receive(1)
|
|
314
|
+
if len(response) != 1:
|
|
315
|
+
return 0x00
|
|
316
|
+
return response[0]
|
|
317
|
+
|
|
318
|
+
# Store a value to Control/Status space
|
|
319
|
+
def __stcs(self, address, value):
|
|
320
|
+
self.__send([UPDI_PHY_SYNC, UPDI_STCS | (address & 0x0F), value])
|
|
321
|
+
|
|
322
|
+
# Load a single byte direct from a 16-bit address
|
|
323
|
+
def __ld(self, address):
|
|
324
|
+
self.__send([UPDI_PHY_SYNC, UPDI_LDS | UPDI_ADDRESS_16 | UPDI_DATA_8,
|
|
325
|
+
address & 0xFF, (address >> 8) & 0xFF])
|
|
326
|
+
return self.__receive(1)[0]
|
|
327
|
+
|
|
328
|
+
# Load a 16-bit word directly from a 16-bit address
|
|
329
|
+
def __ld16(self, address):
|
|
330
|
+
self.__send([UPDI_PHY_SYNC, UPDI_LDS | UPDI_ADDRESS_16 | UPDI_DATA_16,
|
|
331
|
+
address & 0xFF, (address >> 8) & 0xFF])
|
|
332
|
+
return self.__receive(2)
|
|
333
|
+
|
|
334
|
+
# Store a single byte value directly to a 16-bit address
|
|
335
|
+
def __st(self, address, value):
|
|
336
|
+
self.__send([UPDI_PHY_SYNC, UPDI_STS | UPDI_ADDRESS_16 | UPDI_DATA_8,
|
|
337
|
+
address & 0xFF, (address >> 8) & 0xFF])
|
|
338
|
+
response = self.__receive(1)
|
|
339
|
+
if len(response) != 1 or response[0] != UPDI_PHY_ACK:
|
|
340
|
+
raise Exception('Failed to communicate with device (st)')
|
|
341
|
+
self.__send([value & 0xFF])
|
|
342
|
+
response = self.__receive(1)
|
|
343
|
+
if len(response) != 1 or response[0] != UPDI_PHY_ACK:
|
|
344
|
+
raise Exception('Failed to communicate with device (st)')
|
|
345
|
+
|
|
346
|
+
# Store a 16-bit word value directly to a 16-bit address
|
|
347
|
+
def __st16(self, address, value):
|
|
348
|
+
self.__send([UPDI_PHY_SYNC, UPDI_STS | UPDI_ADDRESS_16 | UPDI_DATA_16,
|
|
349
|
+
address & 0xFF, (address >> 8) & 0xFF])
|
|
350
|
+
response = self.__receive(1)
|
|
351
|
+
if len(response) != 1 or response[0] != UPDI_PHY_ACK:
|
|
352
|
+
raise Exception('Failed to communicate with device (st)')
|
|
353
|
+
self.__send([value & 0xFF, (value >> 8) & 0xFF])
|
|
354
|
+
response = self.__receive(1)
|
|
355
|
+
if len(response) != 1 or response[0] != UPDI_PHY_ACK:
|
|
356
|
+
raise Exception('Failed to communicate with device (st)')
|
|
357
|
+
|
|
358
|
+
# Load a number of bytes from the pointer location with pointer post-increment
|
|
359
|
+
def __ld_ptr_inc(self, size):
|
|
360
|
+
self.__send([UPDI_PHY_SYNC, UPDI_LD | UPDI_PTR_INC | UPDI_DATA_8])
|
|
361
|
+
return self.__receive(size)
|
|
362
|
+
|
|
363
|
+
# Load a 16-bit word value from the pointer location with pointer post-increment
|
|
364
|
+
def __ld_ptr_inc16(self, words):
|
|
365
|
+
self.__send([UPDI_PHY_SYNC, UPDI_LD | UPDI_PTR_INC | UPDI_DATA_16])
|
|
366
|
+
return self.__receive(words << 1)
|
|
367
|
+
|
|
368
|
+
# Set the pointer location
|
|
369
|
+
def __st_ptr(self, address):
|
|
370
|
+
self.__send([UPDI_PHY_SYNC, UPDI_ST | UPDI_PTR_ADDRESS | UPDI_DATA_16,
|
|
371
|
+
address & 0xFF, (address >> 8) & 0xFF])
|
|
372
|
+
response = self.__receive(1)
|
|
373
|
+
if len(response) != 1 or response[0] != UPDI_PHY_ACK:
|
|
374
|
+
raise Exception('Failed to communicate with device (st_ptr)')
|
|
375
|
+
|
|
376
|
+
# Store data to the pointer location with pointer post-increment
|
|
377
|
+
def __st_ptr_inc(self, data):
|
|
378
|
+
self.__send([UPDI_PHY_SYNC, UPDI_ST | UPDI_PTR_INC | UPDI_DATA_8, data[0]])
|
|
379
|
+
response = self.__receive(1)
|
|
380
|
+
if len(response) != 1 or response[0] != UPDI_PHY_ACK:
|
|
381
|
+
raise Exception('Failed to communicate with device (ACK st_ptr_inc)')
|
|
382
|
+
n = 1
|
|
383
|
+
while n < len(data):
|
|
384
|
+
self.__send([data[n]])
|
|
385
|
+
response = self.__receive(1)
|
|
386
|
+
if len(response) != 1 or response[0] != UPDI_PHY_ACK:
|
|
387
|
+
raise Exception('Failed to communicate with device (st_ptr_inc)')
|
|
388
|
+
n += 1
|
|
389
|
+
|
|
390
|
+
# Store a 16-bit word value to the pointer location with pointer post-increment
|
|
391
|
+
def __st_ptr_inc16(self, data):
|
|
392
|
+
ctrla_ackon = 1 << UPDI_CTRLA_IBDLY_BIT
|
|
393
|
+
ctrla_ackoff = ctrla_ackon | (1 << UPDI_CTRLA_RSD_BIT)
|
|
394
|
+
self.__stcs(UPDI_CS_CTRLA, ctrla_ackoff)
|
|
395
|
+
self.__send([UPDI_PHY_SYNC, UPDI_ST | UPDI_PTR_INC | UPDI_DATA_16] )
|
|
396
|
+
self.__send(data)
|
|
397
|
+
self.__stcs(UPDI_CS_CTRLA, ctrla_ackon)
|
|
398
|
+
|
|
399
|
+
# Store a value to the repeat counter
|
|
400
|
+
def __repeat(self, repeats):
|
|
401
|
+
if (repeats - 1) > UPDI_MAX_REPEAT_SIZE:
|
|
402
|
+
raise Exception('Invalid repeat count')
|
|
403
|
+
repeats -= 1
|
|
404
|
+
self.__send([UPDI_PHY_SYNC, UPDI_REPEAT | UPDI_REPEAT_BYTE, repeats & 0xFF])
|
|
405
|
+
|
|
406
|
+
# Write a key
|
|
407
|
+
def __key(self, size, key):
|
|
408
|
+
if len(key) != 8 << size:
|
|
409
|
+
raise Exception('Invalid Key length')
|
|
410
|
+
self.__send([UPDI_PHY_SYNC, UPDI_KEY | UPDI_KEY_KEY | size])
|
|
411
|
+
self.__send(list(reversed(list(key))))
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
# ------------------------------------------------------------------------------
|
|
415
|
+
# Application Layer for UPDI Stack
|
|
416
|
+
# ------------------------------------------------------------------------------
|
|
417
|
+
|
|
418
|
+
# Enter NVM programming mode
|
|
419
|
+
def __enter_progmode(self):
|
|
420
|
+
if self.__in_prog_mode():
|
|
421
|
+
return
|
|
422
|
+
self.__key(UPDI_KEY_64, UPDI_KEY_NVM)
|
|
423
|
+
key_status = self.__ldcs(UPDI_ASI_KEY_STATUS)
|
|
424
|
+
if not key_status & (1 << UPDI_ASI_KEY_STATUS_NVMPROG):
|
|
425
|
+
raise Exception('Key not accepted')
|
|
426
|
+
self.__reset(apply_reset=True)
|
|
427
|
+
self.__reset(apply_reset=False)
|
|
428
|
+
if not self.__wait_unlocked(100):
|
|
429
|
+
raise Exception('Failed to enter NVM programming mode: device is locked')
|
|
430
|
+
if not self.__in_prog_mode():
|
|
431
|
+
raise Exception('Failed to enter NVM programming mode')
|
|
432
|
+
|
|
433
|
+
# Get device ID
|
|
434
|
+
def __get_device_id(self):
|
|
435
|
+
return int.from_bytes(self.__read_data(self.sigrow_address, 3), byteorder='big')
|
|
436
|
+
|
|
437
|
+
# Check whether the NVM PROG flag is up
|
|
438
|
+
def __in_prog_mode(self):
|
|
439
|
+
if self.__ldcs(UPDI_ASI_SYS_STATUS) & (1 << UPDI_ASI_SYS_STATUS_NVMPROG):
|
|
440
|
+
return True
|
|
441
|
+
return False
|
|
442
|
+
|
|
443
|
+
# Wait for the device to be unlocked
|
|
444
|
+
def __wait_unlocked(self, timeout_ms):
|
|
445
|
+
timeout = Timeout(timeout_ms)
|
|
446
|
+
while not timeout.expired():
|
|
447
|
+
if not self.__ldcs(UPDI_ASI_SYS_STATUS) & (1 << UPDI_ASI_SYS_STATUS_LOCKSTATUS):
|
|
448
|
+
return True
|
|
449
|
+
return False
|
|
450
|
+
|
|
451
|
+
# Unlock and erase
|
|
452
|
+
def __unlock(self):
|
|
453
|
+
self.__key(UPDI_KEY_64, UPDI_KEY_CHIPERASE)
|
|
454
|
+
key_status = self.__ldcs(UPDI_ASI_KEY_STATUS)
|
|
455
|
+
if not key_status & (1 << UPDI_ASI_KEY_STATUS_CHIPERASE):
|
|
456
|
+
raise Exception('Key not accepted')
|
|
457
|
+
self.__reset(apply_reset=True)
|
|
458
|
+
self.__reset(apply_reset=False)
|
|
459
|
+
if not self.__wait_unlocked(500):
|
|
460
|
+
raise Exception('Failed to chip erase using key')
|
|
461
|
+
|
|
462
|
+
# Apply or release UPDI reset condition
|
|
463
|
+
def __reset(self, apply_reset):
|
|
464
|
+
if apply_reset:
|
|
465
|
+
self.__stcs(UPDI_ASI_RESET_REQ, UPDI_RESET_REQ_VALUE)
|
|
466
|
+
else:
|
|
467
|
+
self.__stcs(UPDI_ASI_RESET_REQ, 0x00)
|
|
468
|
+
|
|
469
|
+
# Wait for the NVM controller to be ready
|
|
470
|
+
def __wait_nvm_ready(self):
|
|
471
|
+
timeout = Timeout(10000)
|
|
472
|
+
while not timeout.expired():
|
|
473
|
+
status = self.__ld(self.nvmctrl_address + UPDI_NVMCTRL_STATUS)
|
|
474
|
+
if status & (1 << UPDI_NVM_STATUS_WRITE_ERROR):
|
|
475
|
+
return False
|
|
476
|
+
if not status & ((1 << UPDI_NVM_STATUS_EEPROM_BUSY) | (1 << UPDI_NVM_STATUS_FLASH_BUSY)):
|
|
477
|
+
return True
|
|
478
|
+
return False
|
|
479
|
+
|
|
480
|
+
# Execute an NVM COMMAND on the NVM CTRL
|
|
481
|
+
def __execute_nvm_command(self, command):
|
|
482
|
+
return self.__st(self.nvmctrl_address + UPDI_NVMCTRL_CTRLA, command)
|
|
483
|
+
|
|
484
|
+
# Write a number of words to memory
|
|
485
|
+
def __write_data_words(self, address, data):
|
|
486
|
+
if len(data) == 2:
|
|
487
|
+
value = data[0] + (data[1] << 8)
|
|
488
|
+
return self.__st16(address, value)
|
|
489
|
+
if len(data) > UPDI_MAX_REPEAT_SIZE << 1:
|
|
490
|
+
raise Exception('Invalid data length')
|
|
491
|
+
self.__st_ptr(address)
|
|
492
|
+
self.__repeat(len(data) >> 1)
|
|
493
|
+
return self.__st_ptr_inc16(data)
|
|
494
|
+
|
|
495
|
+
# Writes a number of bytes to memory
|
|
496
|
+
def __write_data(self, address, data):
|
|
497
|
+
if len(data) == 1:
|
|
498
|
+
return self.__st(address, data[0])
|
|
499
|
+
elif len(data) == 2:
|
|
500
|
+
self.__st(address, data[0])
|
|
501
|
+
return self.__st(address + 1, data[1])
|
|
502
|
+
if len(data) > UPDI_MAX_REPEAT_SIZE:
|
|
503
|
+
raise Exception('Invalid data length')
|
|
504
|
+
self.__st_ptr(address)
|
|
505
|
+
self.__repeat(len(data))
|
|
506
|
+
return self.__st_ptr_inc(data)
|
|
507
|
+
|
|
508
|
+
# Write a page of data to NVM (v0)
|
|
509
|
+
def __write_nvm(self, address, data, use_word_access=True):
|
|
510
|
+
if not self.__wait_nvm_ready():
|
|
511
|
+
raise Exception('Timeout waiting for flash ready before page buffer clear')
|
|
512
|
+
self.__execute_nvm_command(UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR)
|
|
513
|
+
if not self.__wait_nvm_ready():
|
|
514
|
+
raise Exception('Timeout waiting for flash ready after page buffer clear')
|
|
515
|
+
if use_word_access:
|
|
516
|
+
self.__write_data_words(address, data)
|
|
517
|
+
else:
|
|
518
|
+
self.__write_data(address, data)
|
|
519
|
+
self.__execute_nvm_command(UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE)
|
|
520
|
+
if not self.__wait_nvm_ready():
|
|
521
|
+
raise Exception('Timeout waiting for flash ready after page write')
|
|
522
|
+
|
|
523
|
+
# Write one fuse value (v0)
|
|
524
|
+
def __write_fuse(self, fusenum, value):
|
|
525
|
+
if not self.__wait_nvm_ready():
|
|
526
|
+
raise Exception('Timeout waiting for NVM controller to be ready before fuse write')
|
|
527
|
+
address = self.fuses_address + fusenum
|
|
528
|
+
self.__st(self.nvmctrl_address + UPDI_NVMCTRL_ADDRL, address & 0xFF)
|
|
529
|
+
self.__st(self.nvmctrl_address + UPDI_NVMCTRL_ADDRH, (address >> 8) & 0xFF)
|
|
530
|
+
self.__st(self.nvmctrl_address + UPDI_NVMCTRL_DATAL, value & 0xFF)
|
|
531
|
+
self.__execute_nvm_command(UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE)
|
|
532
|
+
if not self.__wait_nvm_ready():
|
|
533
|
+
raise Exception('Timeout waiting for NVM controller to be ready after fuse write')
|
|
534
|
+
|
|
535
|
+
# Read a number of bytes of data from UPDI
|
|
536
|
+
def __read_data(self, address, size):
|
|
537
|
+
if size > UPDI_MAX_REPEAT_SIZE:
|
|
538
|
+
raise Exception('Can\'t read that many bytes in one go')
|
|
539
|
+
self.__st_ptr(address)
|
|
540
|
+
if size > 1:
|
|
541
|
+
self.__repeat(size)
|
|
542
|
+
return self.__ld_ptr_inc(size)
|
|
543
|
+
|
|
544
|
+
# Read a number of words of data from UPDI
|
|
545
|
+
def __read_data_words(self, address, words):
|
|
546
|
+
if words > UPDI_MAX_REPEAT_SIZE:
|
|
547
|
+
raise Exception('Can\'t read that many words in one go')
|
|
548
|
+
self.__st_ptr(address)
|
|
549
|
+
if words > 1:
|
|
550
|
+
self.__repeat(words)
|
|
551
|
+
return self.__ld_ptr_inc16(words)
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
# ------------------------------------------------------------------------------
|
|
555
|
+
# NVM programming algorithm
|
|
556
|
+
# ------------------------------------------------------------------------------
|
|
557
|
+
|
|
558
|
+
# Read from flash
|
|
559
|
+
def __read_flash(self, address, size):
|
|
560
|
+
pages = size // self.flash_pagesize
|
|
561
|
+
if size % self.flash_pagesize:
|
|
562
|
+
pages += 1
|
|
563
|
+
data = bytes()
|
|
564
|
+
for _ in range(pages):
|
|
565
|
+
data += (self.__read_data_words(address, self.flash_pagesize >> 1))
|
|
566
|
+
address += self.flash_pagesize
|
|
567
|
+
return data[:size]
|
|
568
|
+
|
|
569
|
+
# Write to flash
|
|
570
|
+
def __write_flash(self, address, data):
|
|
571
|
+
data = self.__pad_data(data, self.flash_pagesize)
|
|
572
|
+
pages = self.__page_data(data, self.flash_pagesize)
|
|
573
|
+
for page in pages:
|
|
574
|
+
self.__write_nvm(address, page, use_word_access=True)
|
|
575
|
+
address += len(page)
|
|
576
|
+
|
|
577
|
+
# Read one fuse value
|
|
578
|
+
def __read_fuse(self, fusenum):
|
|
579
|
+
address = self.fuses_address + fusenum
|
|
580
|
+
data = self.__ld(address)
|
|
581
|
+
return data
|
|
582
|
+
|
|
583
|
+
# Pad data so that there are full pages
|
|
584
|
+
def __pad_data(self, data, blocksize):
|
|
585
|
+
return data + b'\xFF' * (len(data) % blocksize)
|
|
586
|
+
|
|
587
|
+
# Divide data into pages
|
|
588
|
+
def __page_data(self, data, size):
|
|
589
|
+
total_length = len(data)
|
|
590
|
+
result = list()
|
|
591
|
+
while len(result) < total_length / size:
|
|
592
|
+
result.append(data[:size])
|
|
593
|
+
data = data[size:]
|
|
594
|
+
return result
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
# ===================================================================================
|
|
598
|
+
# Timeout Class
|
|
599
|
+
# ===================================================================================
|
|
600
|
+
|
|
601
|
+
# Simple timeout helper in milliseconds.
|
|
602
|
+
class Timeout:
|
|
603
|
+
def __init__(self, timeout_ms):
|
|
604
|
+
self.timeout_ms = timeout_ms
|
|
605
|
+
self.start_time = time.time()
|
|
606
|
+
|
|
607
|
+
def expired(self):
|
|
608
|
+
return time.time() - self.start_time > self.timeout_ms / 1000.0
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
# ===================================================================================
|
|
612
|
+
# Error Class - Raise an Error
|
|
613
|
+
# ===================================================================================
|
|
614
|
+
|
|
615
|
+
class PrgError(Exception):
|
|
616
|
+
def __init__(self, msg='Something went wrong'):
|
|
617
|
+
super(PrgError, self).__init__(msg)
|
|
618
|
+
sys.stderr.write('ERROR: ' + msg + '\n')
|
|
619
|
+
sys.exit(1)
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
# ===================================================================================
|
|
623
|
+
# Device Definitions and Functions
|
|
624
|
+
# ===================================================================================
|
|
625
|
+
|
|
626
|
+
# Get list of supported device names
|
|
627
|
+
def get_supported_devices():
|
|
628
|
+
result = list()
|
|
629
|
+
for d in UPDI_DEVICES:
|
|
630
|
+
result.append(d['name'])
|
|
631
|
+
return result
|
|
632
|
+
|
|
633
|
+
# Device definitions
|
|
634
|
+
UPDI_DEVICES = [
|
|
635
|
+
{'name': 'attiny202', 'device_id': 0x1E9123, 'flash_size': 0x0800, 'flash_pagesize': 0x40},
|
|
636
|
+
{'name': 'attiny402', 'device_id': 0x1E9227, 'flash_size': 0x1000, 'flash_pagesize': 0x40},
|
|
637
|
+
{'name': 'attiny212', 'device_id': 0x1E9121, 'flash_size': 0x0800, 'flash_pagesize': 0x40},
|
|
638
|
+
{'name': 'attiny412', 'device_id': 0x1E9223, 'flash_size': 0x1000, 'flash_pagesize': 0x40},
|
|
639
|
+
{'name': 'attiny204', 'device_id': 0x1E9122, 'flash_size': 0x0800, 'flash_pagesize': 0x40},
|
|
640
|
+
{'name': 'attiny404', 'device_id': 0x1E9226, 'flash_size': 0x1000, 'flash_pagesize': 0x40},
|
|
641
|
+
{'name': 'attiny804', 'device_id': 0x1E9325, 'flash_size': 0x2000, 'flash_pagesize': 0x40},
|
|
642
|
+
{'name': 'attiny1604', 'device_id': 0x1E9425, 'flash_size': 0x4000, 'flash_pagesize': 0x40},
|
|
643
|
+
{'name': 'attiny214', 'device_id': 0x1E9120, 'flash_size': 0x0800, 'flash_pagesize': 0x40},
|
|
644
|
+
{'name': 'attiny414', 'device_id': 0x1E9222, 'flash_size': 0x1000, 'flash_pagesize': 0x40},
|
|
645
|
+
{'name': 'attiny814', 'device_id': 0x1E9322, 'flash_size': 0x2000, 'flash_pagesize': 0x40},
|
|
646
|
+
{'name': 'attiny1614', 'device_id': 0x1E9422, 'flash_size': 0x4000, 'flash_pagesize': 0x40},
|
|
647
|
+
{'name': 'attiny424', 'device_id': 0x1E922C, 'flash_size': 0x1000, 'flash_pagesize': 0x40},
|
|
648
|
+
{'name': 'attiny824', 'device_id': 0x1E9329, 'flash_size': 0x2000, 'flash_pagesize': 0x40},
|
|
649
|
+
{'name': 'attiny1624', 'device_id': 0x1E942A, 'flash_size': 0x4000, 'flash_pagesize': 0x40},
|
|
650
|
+
{'name': 'attiny3224', 'device_id': 0x1E9528, 'flash_size': 0x8000, 'flash_pagesize': 0x80},
|
|
651
|
+
{'name': 'attiny406', 'device_id': 0x1E9225, 'flash_size': 0x1000, 'flash_pagesize': 0x40},
|
|
652
|
+
{'name': 'attiny806', 'device_id': 0x1E9324, 'flash_size': 0x2000, 'flash_pagesize': 0x40},
|
|
653
|
+
{'name': 'attiny1606', 'device_id': 0x1E9424, 'flash_size': 0x4000, 'flash_pagesize': 0x40},
|
|
654
|
+
{'name': 'attiny416', 'device_id': 0x1E9221, 'flash_size': 0x1000, 'flash_pagesize': 0x40},
|
|
655
|
+
{'name': 'attiny816', 'device_id': 0x1E9321, 'flash_size': 0x2000, 'flash_pagesize': 0x40},
|
|
656
|
+
{'name': 'attiny1616', 'device_id': 0x1E9421, 'flash_size': 0x4000, 'flash_pagesize': 0x40},
|
|
657
|
+
{'name': 'attiny3216', 'device_id': 0x1E9521, 'flash_size': 0x8000, 'flash_pagesize': 0x80},
|
|
658
|
+
{'name': 'attiny426', 'device_id': 0x1E922B, 'flash_size': 0x1000, 'flash_pagesize': 0x40},
|
|
659
|
+
{'name': 'attiny826', 'device_id': 0x1E9328, 'flash_size': 0x2000, 'flash_pagesize': 0x40},
|
|
660
|
+
{'name': 'attiny1626', 'device_id': 0x1E9429, 'flash_size': 0x4000, 'flash_pagesize': 0x40},
|
|
661
|
+
{'name': 'attiny3226', 'device_id': 0x1E9527, 'flash_size': 0x8000, 'flash_pagesize': 0x80},
|
|
662
|
+
{'name': 'attiny417', 'device_id': 0x1E9220, 'flash_size': 0x1000, 'flash_pagesize': 0x40},
|
|
663
|
+
{'name': 'attiny427', 'device_id': 0x1E922A, 'flash_size': 0x1000, 'flash_pagesize': 0x40},
|
|
664
|
+
{'name': 'attiny807', 'device_id': 0x1E9323, 'flash_size': 0x2000, 'flash_pagesize': 0x40},
|
|
665
|
+
{'name': 'attiny817', 'device_id': 0x1E9320, 'flash_size': 0x2000, 'flash_pagesize': 0x40},
|
|
666
|
+
{'name': 'attiny827', 'device_id': 0x1E9327, 'flash_size': 0x2000, 'flash_pagesize': 0x40},
|
|
667
|
+
{'name': 'attiny1607', 'device_id': 0x1E9423, 'flash_size': 0x4000, 'flash_pagesize': 0x40},
|
|
668
|
+
{'name': 'attiny1617', 'device_id': 0x1E9420, 'flash_size': 0x4000, 'flash_pagesize': 0x40},
|
|
669
|
+
{'name': 'attiny1627', 'device_id': 0x1E9428, 'flash_size': 0x4000, 'flash_pagesize': 0x40},
|
|
670
|
+
{'name': 'attiny3217', 'device_id': 0x1E9522, 'flash_size': 0x8000, 'flash_pagesize': 0x80},
|
|
671
|
+
{'name': 'attiny3227', 'device_id': 0x1E9526, 'flash_size': 0x8000, 'flash_pagesize': 0x80}
|
|
672
|
+
]
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
# ===================================================================================
|
|
676
|
+
# UPDI Protocol Constants
|
|
677
|
+
# ===================================================================================
|
|
678
|
+
|
|
679
|
+
UPDI_BREAK = 0x00
|
|
680
|
+
UPDI_LDS = 0x00
|
|
681
|
+
UPDI_STS = 0x40
|
|
682
|
+
UPDI_LD = 0x20
|
|
683
|
+
UPDI_ST = 0x60
|
|
684
|
+
UPDI_LDCS = 0x80
|
|
685
|
+
UPDI_STCS = 0xC0
|
|
686
|
+
UPDI_REPEAT = 0xA0
|
|
687
|
+
UPDI_KEY = 0xE0
|
|
688
|
+
UPDI_PTR = 0x00
|
|
689
|
+
UPDI_PTR_INC = 0x04
|
|
690
|
+
UPDI_PTR_ADDRESS = 0x08
|
|
691
|
+
UPDI_ADDRESS_8 = 0x00
|
|
692
|
+
UPDI_ADDRESS_16 = 0x04
|
|
693
|
+
UPDI_ADDRESS_24 = 0x08
|
|
694
|
+
UPDI_DATA_8 = 0x00
|
|
695
|
+
UPDI_DATA_16 = 0x01
|
|
696
|
+
UPDI_DATA_24 = 0x02
|
|
697
|
+
|
|
698
|
+
UPDI_PHY_SYNC = 0x55
|
|
699
|
+
UPDI_PHY_ACK = 0x40
|
|
700
|
+
UPDI_REPEAT_BYTE = 0x00
|
|
701
|
+
UPDI_REPEAT_WORD = 0x01
|
|
702
|
+
UPDI_CS_STATUSA = 0x00
|
|
703
|
+
UPDI_CS_STATUSB = 0x01
|
|
704
|
+
UPDI_CS_CTRLA = 0x02
|
|
705
|
+
UPDI_CS_CTRLB = 0x03
|
|
706
|
+
UPDI_CTRLA_RSD_BIT = 3
|
|
707
|
+
UPDI_CTRLA_IBDLY_BIT = 7
|
|
708
|
+
UPDI_CTRLB_CCDETDIS_BIT = 3
|
|
709
|
+
UPDI_CTRLB_UPDIDIS_BIT = 2
|
|
710
|
+
UPDI_ASI_KEY_STATUS = 0x07
|
|
711
|
+
UPDI_ASI_RESET_REQ = 0x08
|
|
712
|
+
UPDI_ASI_CTRLA = 0x09
|
|
713
|
+
UPDI_ASI_SYS_CTRLA = 0x0A
|
|
714
|
+
UPDI_ASI_SYS_STATUS = 0x0B
|
|
715
|
+
UPDI_ASI_CRC_STATUS = 0x0C
|
|
716
|
+
UPDI_ASI_STATUSA_REVID = 4
|
|
717
|
+
UPDI_ASI_STATUSB_PESIG = 0
|
|
718
|
+
UPDI_ASI_KEY_STATUS_CHIPERASE = 3
|
|
719
|
+
UPDI_ASI_KEY_STATUS_NVMPROG = 4
|
|
720
|
+
UPDI_ASI_KEY_STATUS_UROWWRITE = 5
|
|
721
|
+
UPDI_ASI_SYS_STATUS_RSTSYS = 5
|
|
722
|
+
UPDI_ASI_SYS_STATUS_INSLEEP = 4
|
|
723
|
+
UPDI_ASI_SYS_STATUS_NVMPROG = 3
|
|
724
|
+
UPDI_ASI_SYS_STATUS_UROWPROG = 2
|
|
725
|
+
UPDI_ASI_SYS_STATUS_LOCKSTATUS = 0
|
|
726
|
+
UPDI_ASI_SYS_CTRLA_UROW_FINAL = 1
|
|
727
|
+
UPDI_RESET_REQ_VALUE = 0x59
|
|
728
|
+
UPDI_MAX_REPEAT_SIZE = (0xFF+1)
|
|
729
|
+
|
|
730
|
+
UPDI_KEY_SIB = 0x04
|
|
731
|
+
UPDI_KEY_KEY = 0x00
|
|
732
|
+
UPDI_KEY_64 = 0x00
|
|
733
|
+
UPDI_KEY_128 = 0x01
|
|
734
|
+
UPDI_KEY_256 = 0x02
|
|
735
|
+
UPDI_KEY_NVM = b'NVMProg '
|
|
736
|
+
UPDI_KEY_CHIPERASE = b'NVMErase'
|
|
737
|
+
UPDI_KEY_UROW = b'NVMUs&te'
|
|
738
|
+
UPDI_SIB_8BYTES = UPDI_KEY_64
|
|
739
|
+
UPDI_SIB_16BYTES = UPDI_KEY_128
|
|
740
|
+
UPDI_SIB_32BYTES = UPDI_KEY_256
|
|
741
|
+
|
|
742
|
+
UPDI_NVMCTRL_CTRLA = 0x00
|
|
743
|
+
UPDI_NVMCTRL_CTRLB = 0x01
|
|
744
|
+
UPDI_NVMCTRL_STATUS = 0x02
|
|
745
|
+
UPDI_NVMCTRL_INTCTRL = 0x03
|
|
746
|
+
UPDI_NVMCTRL_INTFLAGS = 0x04
|
|
747
|
+
UPDI_NVMCTRL_DATAL = 0x06
|
|
748
|
+
UPDI_NVMCTRL_DATAH = 0x07
|
|
749
|
+
UPDI_NVMCTRL_ADDRL = 0x08
|
|
750
|
+
UPDI_NVMCTRL_ADDRH = 0x09
|
|
751
|
+
|
|
752
|
+
UPDI_V0_NVMCTRL_CTRLA_NOP = 0x00
|
|
753
|
+
UPDI_V0_NVMCTRL_CTRLA_WRITE_PAGE = 0x01
|
|
754
|
+
UPDI_V0_NVMCTRL_CTRLA_ERASE_PAGE = 0x02
|
|
755
|
+
UPDI_V0_NVMCTRL_CTRLA_ERASE_WRITE_PAGE = 0x03
|
|
756
|
+
UPDI_V0_NVMCTRL_CTRLA_PAGE_BUFFER_CLR = 0x04
|
|
757
|
+
UPDI_V0_NVMCTRL_CTRLA_CHIP_ERASE = 0x05
|
|
758
|
+
UPDI_V0_NVMCTRL_CTRLA_ERASE_EEPROM = 0x06
|
|
759
|
+
UPDI_V0_NVMCTRL_CTRLA_WRITE_FUSE = 0x07
|
|
760
|
+
|
|
761
|
+
UPDI_NVM_STATUS_WRITE_ERROR = 2
|
|
762
|
+
UPDI_NVM_STATUS_EEPROM_BUSY = 1
|
|
763
|
+
UPDI_NVM_STATUS_FLASH_BUSY = 0
|
|
764
|
+
|
|
765
|
+
|
|
766
|
+
# ===================================================================================
|
|
767
|
+
|
|
768
|
+
if __name__ == "__main__":
|
|
769
|
+
_main()
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: tinyupdi
|
|
3
|
+
Version: 1.5.0
|
|
4
|
+
Summary: Minimal UPDI Programming Tool for tinyAVR
|
|
5
|
+
Home-page: https://github.com/wagiminator/MCU-Flash-Tools
|
|
6
|
+
Author: Stefan Wagner
|
|
7
|
+
Author-email: Stefan Wagner <wagiminator@web.de>
|
|
8
|
+
Maintainer-email: Stefan Wagner <wagiminator@web.de>
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2022 Stefan Wagner
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
Project-URL: Homepage, https://wagiminator.github.io/
|
|
31
|
+
Project-URL: Repository, https://github.com/wagiminator/MCU-Flash-Tools
|
|
32
|
+
Project-URL: Issues, https://github.com/wagiminator/MCU-Flash-Tools/issues
|
|
33
|
+
Keywords: AVR,Microchip,ATtiny,tinyAVR,UPDI,microcontroller
|
|
34
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
37
|
+
Classifier: Operating System :: OS Independent
|
|
38
|
+
Classifier: Intended Audience :: Developers
|
|
39
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
40
|
+
Requires-Python: >=3.8
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
License-File: LICENSE
|
|
43
|
+
Requires-Dist: pyserial
|
|
44
|
+
|
|
45
|
+
# tinyUPDI - Minimal UPDI Programming Tool for tinyAVR
|
|
46
|
+
## Description
|
|
47
|
+
This tool allows tinyAVR series 0, 1, and 2 microcontrollers to be programmed using a USB-to-serial converter connected in a special way to the UPDI pin (also called SerialUPDI). More information can be found [here](https://github.com/SpenceKonde/AVR-Guidance/blob/master/UPDI/jtag2updi.md).
|
|
48
|
+
|
|
49
|
+
## Preparations
|
|
50
|
+
If necessary, a driver for the USB-to-serial converter used must be installed.
|
|
51
|
+
|
|
52
|
+
## Installation
|
|
53
|
+
- Install [Python3](https://www.pythontutorial.net/getting-started/install-python/).
|
|
54
|
+
- Install [pip](https://pip.pypa.io/en/stable/installation/).
|
|
55
|
+
- Install rvprog:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
pip install tinyupdi
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Usage
|
|
62
|
+
Connect the USB-to-serial converter via USB to the PC and via the circuit described below to the UPDI pin of the microcontroller.
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
USB2SERIAL tinyAVR
|
|
66
|
+
+--------+ +-----------+
|
|
67
|
+
| RXD| <------------+---> |UPDI (PA0) |
|
|
68
|
+
| | | | |
|
|
69
|
+
| TXD| ---|1kOhm|---+ | |
|
|
70
|
+
| | | |
|
|
71
|
+
| VDD| -----------------> |VDD |
|
|
72
|
+
| GND| -----------------> |GND |
|
|
73
|
+
+--------+ +-----------+
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Alternatively, a pre-assembled [SerialUPDI programmer](https://github.com/wagiminator/AVR-Programmer) can be used.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
Usage: tinyupdi [-h] [-d DEVICE] [-e] [-f FLASH] [-fs [FUSES [FUSES ...]]] [-t TRIM]
|
|
80
|
+
|
|
81
|
+
Optional arguments:
|
|
82
|
+
-h, --help show help message and exit
|
|
83
|
+
-d, --device set target device (if not set, it will be auto-detected)
|
|
84
|
+
-e, --erase perform a chip erase (implied with --flash)
|
|
85
|
+
-f FLASH, --flash FLASH BIN file to flash
|
|
86
|
+
-fs [FUSES [FUSES ...]], --fuses [FUSES [FUSES ...]]
|
|
87
|
+
fuses to set (syntax: fuse_nr:0xvalue)
|
|
88
|
+
-t TRIM, --trim TRIM configure oscillator for given frequency (set fuse 2)
|
|
89
|
+
Example:
|
|
90
|
+
tinyupdi -f firmware.bin -fs 6:0x04 7:0x00 8:0x00 -t 8000000
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Links
|
|
94
|
+
1. [MCU Templates](https://github.com/wagiminator/MCU-Templates)
|
|
95
|
+
2. [MCU Development Boards](https://github.com/wagiminator/Development-Boards)
|
|
96
|
+
3. [AVR Development Boards](https://github.com/wagiminator/AVR-Development-Boards)
|
|
97
|
+
4. [SerialUPDI Programmers](https://github.com/wagiminator/AVR-Programmer)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
setup.py
|
|
5
|
+
tinyupdi/__init__.py
|
|
6
|
+
tinyupdi/tinyupdi.py
|
|
7
|
+
tinyupdi.egg-info/PKG-INFO
|
|
8
|
+
tinyupdi.egg-info/SOURCES.txt
|
|
9
|
+
tinyupdi.egg-info/dependency_links.txt
|
|
10
|
+
tinyupdi.egg-info/entry_points.txt
|
|
11
|
+
tinyupdi.egg-info/requires.txt
|
|
12
|
+
tinyupdi.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pyserial
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
tinyupdi
|