229 lines
7.2 KiB
Python
229 lines
7.2 KiB
Python
|
|
VERSION = '1.20'
|
|
title ="Arduboy FX data uploader v" + VERSION + " by Mr.Blinky Feb.2022-Mar.2023"
|
|
|
|
import sys
|
|
import os
|
|
import time
|
|
|
|
try:
|
|
toolspath = os.path.dirname(os.path.abspath(sys.argv[0]))
|
|
sys.path.insert(0, toolspath)
|
|
from serial.tools.list_ports import comports
|
|
from serial import Serial
|
|
except:
|
|
sys.stderr.write("pySerial python module not found or wrong version.\n")
|
|
sys.stderr.write("Make sure the correct module is installed or placed at {}\n".format(toolspath))
|
|
sys.exit(-1)
|
|
|
|
compatibledevices = [
|
|
#Arduboy Leonardo
|
|
"VID:PID=2341:0036", "VID:PID=2341:8036",
|
|
"VID:PID=2A03:0036", "VID:PID=2A03:8036",
|
|
#Arduboy Micro
|
|
"VID:PID=2341:0037", "VID:PID=2341:8037",
|
|
"VID:PID=2A03:0037", "VID:PID=2A03:8037",
|
|
#Genuino Micro
|
|
"VID:PID=2341:0237", "VID:PID=2341:8237",
|
|
#Sparkfun Pro Micro 5V
|
|
"VID:PID=1B4F:9205", "VID:PID=1B4F:9206",
|
|
#Adafruit ItsyBitsy 5V
|
|
"VID:PID=239A:000E", "VID:PID=239A:800E",
|
|
]
|
|
|
|
manufacturers = {
|
|
0x01 : "Spansion",
|
|
0x14 : "Cypress",
|
|
0x1C : "EON",
|
|
0x1F : "Adesto(Atmel)",
|
|
0x20 : "Micron",
|
|
0x37 : "AMIC",
|
|
0x9D : "ISSI",
|
|
0xC2 : "General Plus",
|
|
0xC8 : "Giga Device",
|
|
0xBF : "Microchip",
|
|
0xEF : "Winbond"
|
|
}
|
|
|
|
PAGESIZE = 256
|
|
BLOCKSIZE = 65536
|
|
PAGES_PER_BLOCK = BLOCKSIZE // PAGESIZE
|
|
MAX_PAGES = 65536
|
|
bootloader_active = False
|
|
|
|
def print(s):
|
|
sys.stdout.write(s + '\n')
|
|
sys.stdout.flush()
|
|
|
|
def getComPort(verbose):
|
|
global bootloader_active
|
|
devicelist = list(comports())
|
|
for device in devicelist:
|
|
for vidpid in compatibledevices:
|
|
if vidpid in device[2]:
|
|
port=device[0]
|
|
bootloader_active = (compatibledevices.index(vidpid) & 1) == 0
|
|
if verbose : sys.stdout.write("Found {} at port {} ".format(device[1],port))
|
|
return port
|
|
if verbose : print("Arduboy not found.")
|
|
|
|
def bootloaderStart():
|
|
global bootloader
|
|
## find and connect to Arduboy in bootloader mode ##
|
|
port = getComPort(True)
|
|
if port is None : sys.exit(-1)
|
|
if not bootloader_active:
|
|
print("Selecting bootloader mode...")
|
|
try:
|
|
bootloader = Serial(port,1200)
|
|
time.sleep(0.1)
|
|
bootloader.close()
|
|
time.sleep(0.5)
|
|
except:
|
|
sys.stderr.write("COM port not available.\n")
|
|
sys.exit(-1)
|
|
#wait for disconnect and reconnect in bootloader mode
|
|
while getComPort(False) == port :
|
|
time.sleep(0.1)
|
|
if bootloader_active: break
|
|
while getComPort(False) is None : time.sleep(0.1)
|
|
port = getComPort(True)
|
|
|
|
sys.stdout.write("Opening port ...")
|
|
sys.stdout.flush()
|
|
for retries in range(20):
|
|
try:
|
|
time.sleep(0.1)
|
|
bootloader = Serial(port,57600)
|
|
break
|
|
except:
|
|
if retries == 19:
|
|
print(" Failed!")
|
|
sys.exit(-1)
|
|
sys.stdout.write(".")
|
|
sys.stdout.flush()
|
|
time.sleep(0.4)
|
|
print("\r")
|
|
|
|
def getVersion():
|
|
bootloader.write(b"V")
|
|
return int(bootloader.read(2))
|
|
|
|
def getJedecID():
|
|
bootloader.write(b"j")
|
|
jedec_id = bootloader.read(3)
|
|
time.sleep(0.5)
|
|
bootloader.write(b"j")
|
|
jedec_id2 = bootloader.read(3)
|
|
if jedec_id2 != jedec_id or jedec_id == b'\x00\x00\x00' or jedec_id == b'\xFF\xFF\xFF':
|
|
sys.stderr.write("No FX flash chip detected.\n")
|
|
sys.exit(-1)
|
|
return bytearray(jedec_id)
|
|
|
|
def bootloaderExit():
|
|
global bootloader
|
|
bootloader.write(b"E")
|
|
bootloader.read(1)
|
|
|
|
################################################################################
|
|
|
|
def writeFlash(pagenumber, flashdata):
|
|
bootloaderStart()
|
|
|
|
#check version
|
|
if getVersion() < 13:
|
|
sys.stderr.write("Bootloader has no flash cart support\nWrite aborted!\n")
|
|
sys.exit(-1)
|
|
|
|
## detect flash cart ##
|
|
jedec_id = getJedecID()
|
|
if jedec_id[0] in manufacturers.keys():
|
|
manufacturer = manufacturers[jedec_id[0]]
|
|
else:
|
|
manufacturer = "unknown"
|
|
capacity = 1 << jedec_id[2]
|
|
print("Detected FX flash chip with ID {:02X}{:02X}{:02X} size {}KB".format(jedec_id[0],jedec_id[1],jedec_id[2],capacity // 1024))
|
|
|
|
oldtime=time.time()
|
|
# when starting partially in a block, preserve the beginning of old block data
|
|
if pagenumber % PAGES_PER_BLOCK:
|
|
blocklen = pagenumber % PAGES_PER_BLOCK * PAGESIZE
|
|
blockaddr = pagenumber // PAGES_PER_BLOCK * PAGES_PER_BLOCK
|
|
#read partial block data start
|
|
bootloader.write(bytearray([ord("A"), blockaddr >> 8, blockaddr & 0xFF]))
|
|
bootloader.read(1)
|
|
bootloader.write(bytearray([ord("g"), (blocklen >> 8) & 0xFF, blocklen & 0xFF,ord("C")]))
|
|
flashdata = bootloader.read(blocklen) + flashdata
|
|
pagenumber = blockaddr
|
|
|
|
# when ending partially in a block, preserve the ending of old block data
|
|
if len(flashdata) % BLOCKSIZE:
|
|
blocklen = BLOCKSIZE - len(flashdata) % BLOCKSIZE
|
|
blockaddr = pagenumber + len(flashdata) // PAGESIZE
|
|
#read partial block data end
|
|
bootloader.write(bytearray([ord("A"), blockaddr >> 8, blockaddr & 0xFF]))
|
|
bootloader.read(1)
|
|
bootloader.write(bytearray([ord("g"), (blocklen >> 8) & 0xFF, blocklen & 0xFF,ord("C")]))
|
|
flashdata += bootloader.read(blocklen)
|
|
|
|
## write to flash cart ##
|
|
blocks = len(flashdata) // BLOCKSIZE
|
|
for block in range (blocks):
|
|
if (block & 1 == 0) or verifyAfterWrite:
|
|
bootloader.write(b"x\xC2") #RGB LED RED, buttons disabled
|
|
else:
|
|
bootloader.write(b"x\xC0") #RGB LED OFF, buttons disabled
|
|
bootloader.read(1)
|
|
sys.stdout.write("\rWriting block {}/{} ".format(block + 1,blocks))
|
|
sys.stdout.flush()
|
|
blockaddr = pagenumber + block * BLOCKSIZE // PAGESIZE
|
|
blocklen = BLOCKSIZE
|
|
#write block
|
|
bootloader.write(bytearray([ord("A"), blockaddr >> 8, blockaddr & 0xFF]))
|
|
bootloader.read(1)
|
|
bootloader.write(bytearray([ord("B"), (blocklen >> 8) & 0xFF, blocklen & 0xFF,ord("C")]))
|
|
bootloader.write(flashdata[block * BLOCKSIZE : block * BLOCKSIZE + blocklen])
|
|
bootloader.read(1)
|
|
if verifyAfterWrite:
|
|
sys.stdout.write("\rVerifying block {}/{}".format(block + 1,blocks))
|
|
sys.stdout.flush()
|
|
bootloader.write(b"x\xC1") #RGB BLUE RED, buttons disabled
|
|
bootloader.read(1)
|
|
bootloader.write(bytearray([ord("A"), blockaddr >> 8, blockaddr & 0xFF]))
|
|
bootloader.read(1)
|
|
bootloader.write(bytearray([ord("g"), (blocklen >> 8) & 0xFF, blocklen & 0xFF,ord("C")]))
|
|
if bootloader.read(blocklen) != flashdata[block * BLOCKSIZE : block * BLOCKSIZE + blocklen]:
|
|
sys.stderr.write(" verify failed!\n\nWrite aborted.")
|
|
bootloader.write(b"x\x40")#RGB LED off, buttons enabled
|
|
bootloader.read(1)
|
|
sys.exit(-1)
|
|
|
|
#write complete
|
|
bootloader.write(b"x\x44")#RGB LED GREEN, buttons enabled
|
|
bootloader.read(1)
|
|
time.sleep(0.5)
|
|
bootloader.write(b"x\x40")#RGB LED off, buttons enabled
|
|
bootloader.read(1)
|
|
bootloader.close()
|
|
print("\rFX Data uploaded successfully")
|
|
|
|
################################################################################
|
|
|
|
print(title)
|
|
|
|
if (len(sys.argv) != 2) or (os.path.isfile(sys.argv[1]) != True) :
|
|
sys.stderr.write("FX data file not found.\n")
|
|
|
|
filename = os.path.abspath(sys.argv[1])
|
|
|
|
verifyAfterWrite = True
|
|
|
|
print('Uploading FX data from file "{}"'.format(filename))
|
|
f = open(filename,"rb")
|
|
programdata = bytearray(f.read())
|
|
f.close()
|
|
if len(programdata) % PAGESIZE:
|
|
programdata += b'\xFF' * (PAGESIZE - (len(programdata) % PAGESIZE))
|
|
programpage = MAX_PAGES - (len(programdata) // PAGESIZE)
|
|
|
|
writeFlash(programpage, programdata)
|