mirror of
https://github.com/wagiminator/ATtiny814-USB-PD-Adapter.git
synced 2025-08-07 12:50:28 +03:00
283 lines
10 KiB
Python
283 lines
10 KiB
Python
#!/usr/bin/python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
import sys
|
|
import os
|
|
import argparse
|
|
|
|
# dependencies
|
|
toolspath = os.path.dirname(os.path.realpath(__file__))
|
|
sys.path.insert(0, os.path.join(toolspath, "libs"))
|
|
|
|
import pymcuprog.pymcuprog_main as pymcu
|
|
from pymcuprog.pymcuprog import setup_logging
|
|
|
|
import logging
|
|
|
|
import datetime
|
|
|
|
|
|
class PyMcuException(Exception):
|
|
pass
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
|
|
parser.add_argument("-a", "--action",
|
|
type=str,
|
|
default="",
|
|
help="Action to perform {write, read, erase}.")
|
|
|
|
parser.add_argument("-b", "--baudrate",
|
|
type=str,
|
|
default="115200",
|
|
help="Serial baud rate, if applicable, (default: 115200).")
|
|
|
|
parser.add_argument("-wc", "--write_chunk",
|
|
type=int,
|
|
default=-1,
|
|
help="Max number of bytes of serial data to write per usb packet. -1 (whole page) is recommended. (Default: -1 (no write chunking)) Intended as workaround for specific serial adapters. ")
|
|
|
|
parser.add_argument("-rc", "--read_chunk",
|
|
type=int,
|
|
default=-1,
|
|
help="Max number of bytes to request from the device at a time when reading or verifying. (Default: -1 (maximum - usually 512b)) Intended as a workaround for specific serial adapters.")
|
|
|
|
parser.add_argument("-d", "--device",
|
|
type=str,
|
|
help="Part number, lowercase (e.g. attiny412, ...).")
|
|
|
|
parser.add_argument("--fuses",
|
|
nargs='+',
|
|
type=str,
|
|
default="",
|
|
help="List of offset:value (0x, 0b or decimal).")
|
|
|
|
parser.add_argument("--fuses_print",
|
|
action="store_true",
|
|
help="Print fuse values.")
|
|
|
|
parser.add_argument("-f", "--filename",
|
|
type=str,
|
|
default="",
|
|
help="Hex file to read/write.")
|
|
|
|
parser.add_argument("-wd", "--writedelay",
|
|
type=float,
|
|
default=0,
|
|
help="Page write delay [ms] for tinyAVR and megaAVR. USB latency is usually sufficient without this. (Default: 0 - this severely impacts write performance)")
|
|
|
|
parser.add_argument("-t", "--tool",
|
|
type=str,
|
|
default="uart",
|
|
help="Tool name, defaults to 'uart' (SerialUPDI) mode. The other options are neither tested nor maintained.")
|
|
|
|
parser.add_argument("-u", "--uart",
|
|
type=str,
|
|
default="",
|
|
help="Serial port to use if tool is uart.")
|
|
|
|
parser.add_argument("-s", "--serialnumber",
|
|
type=str,
|
|
default="",
|
|
help="Tool USB serial (optional, for non-Serial UPDI programmers only. This feature is neither tested nor maintained.).")
|
|
|
|
parser.add_argument("-v", "--verbose",
|
|
action="count",
|
|
default=0,
|
|
help="Display more info (can be repeated).")
|
|
|
|
# Parse args
|
|
args = parser.parse_args()
|
|
|
|
if args.action == "" and args.fuses == "" and not args.fuses_print:
|
|
parser.print_help()
|
|
sys.exit(0)
|
|
|
|
if args.action != "" and args.action not in ("read", "write", "erase"):
|
|
print("Error: unknown action '{}'".format(args.action))
|
|
sys.exit(1)
|
|
|
|
if args.action not in ("read", "write") and args.filename != "":
|
|
print("Error: action '{}' takes no filename".format(args.action))
|
|
sys.exit(1)
|
|
|
|
if args.action in ("read", "write") and args.filename == "":
|
|
print("Error: no filename provided")
|
|
sys.exit(1)
|
|
|
|
fuses_dict = {}
|
|
for fuse_str in args.fuses:
|
|
fuse_offset, fuse_val = fuse_str.split(":")
|
|
try:
|
|
fuse_offset = int(fuse_offset, 0)
|
|
fuse_val = int(fuse_val, 0)
|
|
fuses_dict[fuse_offset] = fuse_val
|
|
except ValueError as e:
|
|
print("Error: cannot parse fuse, '{}'".format(e))
|
|
sys.exit(1)
|
|
|
|
print_report(args)
|
|
|
|
logging_level = logging.ERROR
|
|
if args.verbose == 1:
|
|
logging_level = logging.INFO
|
|
elif args.verbose > 1:
|
|
logging_level = logging.DEBUG
|
|
|
|
try:
|
|
setup_logging(user_requested_level=logging_level)
|
|
return_code = pymcuprog_basic(args, fuses_dict)
|
|
sys.exit(return_code)
|
|
except PyMcuException as e:
|
|
print("Error: ".format(e))
|
|
sys.exit(1)
|
|
|
|
|
|
def run_pymcu_action(func, backend, *args, **kwargs):
|
|
args_pymcu = argparse.Namespace(**kwargs)
|
|
time_start = datetime.datetime.now()
|
|
status = func(backend, *args, args_pymcu)
|
|
time_stop = datetime.datetime.now()
|
|
print("Action took {:.2f}s".format((time_stop - time_start).total_seconds()))
|
|
if status != pymcu.STATUS_SUCCESS:
|
|
backend.end_session()
|
|
backend.disconnect_from_tool()
|
|
raise PyMcuException("Call to {} failed".format(func.__name__))
|
|
|
|
|
|
def print_report(args):
|
|
print("SerialUPDI")
|
|
print("UPDI programming for Arduino using a serial adapter")
|
|
print("Based on pymcuprog, with significant modifications")
|
|
print("By Quentin Bolsee and Spence Konde")
|
|
print("Version 1.2.0 - June 2021")
|
|
print("Using serial port {} at {} baud.".format(args.uart, args.baudrate))
|
|
print("Target: {}".format(args.device))
|
|
if args.fuses != "":
|
|
print("Set fuses: {}".format(args.fuses))
|
|
print("Action: {}".format(args.action))
|
|
if args.filename != "":
|
|
print("File: {}".format(args.filename))
|
|
|
|
|
|
def pymcuprog_basic(args, fuses_dict):
|
|
"""
|
|
Main program
|
|
"""
|
|
backend = pymcu.Backend()
|
|
|
|
# connect to tool
|
|
toolconnection = pymcu._setup_tool_connection(argparse.Namespace(tool=args.tool,
|
|
uart=args.uart,
|
|
serialnumber=args.serialnumber))
|
|
try:
|
|
backend.connect_to_tool(toolconnection)
|
|
except pymcu.PymcuprogToolConnectionError as error:
|
|
print("Error: Cannot connect, '{}'".format(error))
|
|
return 1
|
|
|
|
# select device
|
|
device_selected = pymcu._select_target_device(backend, argparse.Namespace(device=args.device, tool=args.tool))
|
|
if device_selected is None:
|
|
backend.disconnect_from_tool()
|
|
print("Error: device selection failed")
|
|
return 1
|
|
|
|
# start session
|
|
args_start = argparse.Namespace(interface="updi",
|
|
clk=args.baudrate,
|
|
high_voltage=False,
|
|
user_row_locked_device=False,
|
|
chip_erase_locked_device=False,
|
|
packpath=False)
|
|
|
|
status = pymcu._start_session(backend,
|
|
device_selected,
|
|
args_start)
|
|
|
|
if status != pymcu.STATUS_SUCCESS:
|
|
if status == pymcu.STATUS_FAILURE_LOCKED and args.action in ("write", "erase"):
|
|
print("Locked state detected, performing chip erase")
|
|
args_start.chip_erase_locked_device = True
|
|
status = pymcu._start_session(backend,
|
|
device_selected,
|
|
args_start)
|
|
if status != pymcu.STATUS_SUCCESS:
|
|
raise PyMcuException("Failed to unlock!")
|
|
else:
|
|
raise PyMcuException("Cannot start session!")
|
|
|
|
try:
|
|
pymcu._action_ping(backend)
|
|
except ValueError:
|
|
print("Device ID mismatch! Stopping.")
|
|
backend.end_session()
|
|
backend.disconnect_from_tool()
|
|
return 1
|
|
|
|
# write fuses
|
|
if fuses_dict is not None and len(fuses_dict) > 0:
|
|
for fuse_offset in sorted(fuses_dict.keys()):
|
|
fuse_value = fuses_dict[fuse_offset]
|
|
print("Setting fuse {}={}".format(hex(fuse_offset), hex(fuse_value)))
|
|
run_pymcu_action(pymcu._action_write, backend,
|
|
offset=fuse_offset,
|
|
literal=[fuse_value],
|
|
memory=pymcu.MemoryNames.FUSES,
|
|
verify=True,
|
|
filename=None)
|
|
print("Finished writing fuses.")
|
|
|
|
# print fuses
|
|
if args.fuses_print:
|
|
run_pymcu_action(pymcu._action_read, backend,
|
|
memory=pymcu.MemoryNames.FUSES,
|
|
offset=0,
|
|
bytes=0,
|
|
filename=None)
|
|
|
|
# actions
|
|
if args.action == "write":
|
|
run_pymcu_action(pymcu._action_erase, backend,
|
|
memory=pymcu.MemoryNameAliases.ALL,
|
|
offset=0)
|
|
|
|
run_pymcu_action(pymcu._action_write, backend,
|
|
memory=pymcu.MemoryNameAliases.ALL,
|
|
offset=0,
|
|
literal=None,
|
|
verify=False,
|
|
filename=args.filename,
|
|
blocksize=None if args.write_chunk <= 0 else args.write_chunk,
|
|
pagewrite_delay=args.writedelay)
|
|
|
|
run_pymcu_action(pymcu._action_verify, backend,
|
|
memory=pymcu.MemoryNameAliases.ALL,
|
|
offset=0,
|
|
literal=None,
|
|
filename=args.filename,
|
|
max_read_chunk=None if args.read_chunk <= 0 else args.read_chunk)
|
|
|
|
elif args.action == "read":
|
|
run_pymcu_action(pymcu._action_read, backend,
|
|
memory=pymcu.MemoryNames.FLASH,
|
|
offset=0,
|
|
bytes=0,
|
|
filename=args.filename,
|
|
max_read_chunk=None if args.read_chunk <= 0 else args.read_chunk)
|
|
elif args.action == "erase":
|
|
run_pymcu_action(pymcu._action_erase, backend,
|
|
memory=pymcu.MemoryNameAliases.ALL,
|
|
offset=0)
|
|
|
|
# close session
|
|
backend.end_session()
|
|
backend.disconnect_from_tool()
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|