Added 2.0rc2

Fixes major bug where chunks of "00h" data were treated as "no data".
This commit is contained in:
Kris Sekula 2020-11-23 07:16:50 -08:00 committed by GitHub
parent 261b430cea
commit 44e0207b5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 490 additions and 0 deletions

View File

@ -0,0 +1,490 @@
# kris@mygeekyhobby.com
#
# ver 1.0 - may 2020 - initial release
# ver 1.2 - june 2020 - added save to SPI EEPROM functionality
# - fixed the args parsing
# - added auto load wait option
# - added save SPI option
# ver 1.3 - july 2020 - removed some of the debug stuff
# - removed address scrolling when loading
# ver 1.6g - Aug 2020 - Lunches GUI when no parameters specified (code contributed by John Gerrard)... requires PySimpleGUI module "pip install PySimpleGUI"
# - small fixes to the output
# ver 1.6e - fixed bug with SPI==y
#
# ver 2.0rc1 - Oct 2020 - save parameters in GUI
# - .bin and .hex file support
# - binary mode upload with packets of 128-512 bytes at a time
# - Save to SPI works with any type of data range (any source file less than 128 bytes gets "padded" with 0xFF)
# ver 2.0rc2 - Oct 2020 - support other extenstions like .rom .img etc... everything not .hex is treated as binary (thanks to Hubert for the feedback and contribution)
# - fixed bug where a 128Byte of "00h" was treated as "no data" (thanks to Hubert for the feedback and contribution)
version = "2.0rc2"
import argparse
import serial,os
import time
import PySimpleGUI as sg
import sys
# All routines here
# Print int list in Hex format (for debugging only)
def PrintHex(intList):
row = 0
for val in intList:
print("%0.2X"%val, end=' ', flush=True)
row += 1
if row == 64:
print("")
row=0
# -----------------------------------------------------------------------
# -----------------------------------------------------------------------
def chunkData(bfr):
"""
Analyze the 64k buffer, find all 128Byte chunks that are in use (have data != to -1)
Will return a list of 512 entries with "1" meaning block of 128Bytes had data chaned and "0" no data changed
"""
mapped=[]
for i in range(0,len(bfr),128):
chunk = bfr[i:i+128] # split buffer into 128 Bytes chunks
used = False
for cell in chunk: # if any byte changed, mark chunk for upload
if cell >= 0:
used = True
if used:
mapped.append(1)
else:
mapped.append(0)
return mapped
# -----------------------------------------------------------------------
def plotData(mapped):
"""
Display map of data usage in the buffer, "*" data used and "_" not used
"""
col=0
for idx in range(len(mapped)):
if idx == 0: # first address special case
print("0x%0.4X"%idx, end=' ', flush=True)
if mapped[idx] == 1:
print("*", end=' ', flush=True)
else:
print("_", end=' ', flush=True)
col+=1
if col == 32:
if (idx*128+128) < 65535:
print("")
print("0x%0.4X"%int(idx*128+128),end=' ', flush=True)
col=0
return
# -----------------------------------------------------------------------
def packetData(mapped):
data=[]
"""
create a list of tuples [(start_address,size),(start_address,size),...,(start_address,size)] ... size can be 128,256,384,512
in other words find continues ranges of 128Byte data that has changed, group into packets of 128-512 bytes, create a list for later processing
"""
n=0
col=0
start=-2
for idx in range(len(mapped)):
if mapped[idx] == 1:
if n == 0:
start = idx*128
n+=1
if n == 4:
data.append((start,512))
n=0
else:
if n > 0:
start = (idx-n)*128
data.append((start,n*128))
n=0
start = -2
return data
# -----------------------------------------------------------------------
def Decode(len, sum, addr, data):
"""
Borrowed this from www.sbprojects.net, credits to San Bergmans
Decode an Intel string.
Returns a list of N bytes.
Returns an empty list if an error has occurred.
"""
global Errors
checksum = len + (addr & 0xFF) + (addr >> 8)
index = 0
bytes = []
if len <= 0 or len > 32:
# Illegal length
Eprint("Illegal line length found.")
Errors += 1
return bytes
while len > 0:
# Iterate over all data bytes
decimal = int(data[index:index+2], 16)
bytes.append(decimal)
checksum = checksum + decimal
index += 2
len -= 1
checksum = (checksum + int(sum, 16)) & 0xFF
if checksum != 0:
# Checksum didn't add up
Eprint("Checksum error.")
bytes = []
Errors += 1
return bytes
return bytes
# -----------------------------------------------------------------------
def ParseIntelLine(line):
"""
Borrowed this from www.sbprojects.net, credits to San Bergmans
Parse an IntelHex line.
Record types 02, 03, 04 and 05 are ignored.
Maximum payload length is 32 bytes.
Everything after the checksum is ignored.
Returns True if EOF record found, or when an error is found.
"""
global Errors
# Remove EOL character and leading and trailing white space
line = line.strip()
if len(line) == 0:
# Ignore empty lines
return False
# Pad the line with plenty of spaces to make parsing easier
# We don't have to worry about the line being too short
line = line + ' '*74
if line[0] in ('#', ';'):
# Ignore lines starting with a comment symbol
return False
if line[0] != ":":
# Line must begin with a colon
Eprint("Illegal start of formatted line.")
Errors += 1
return True
if line[7:9] in ('02', '03', '04', '05'):
# These record types are simply ignored
return False
if line[0:11] == ":00000001FF":
# End of file marker found
return True
datalength = int(line[1:3], 16)
checksum_index = (datalength + 4) * 2 + 1
checksum = line[checksum_index:checksum_index + 2]
address = int(line[3:7], 16)
data = line[9:checksum_index]
# Decode the dataline
bytes = Decode(datalength, checksum, address, data)
if len(bytes) == 0:
# Decoding the line resulted in an error
return True
#print(address, bytes)
for x in range (0,len(bytes)):
buff64k[x+address] = bytes[x]
sg.theme('DarkAmber') # Add a touch of color
if not sg.user_settings_get_entry('startaddress',''): # this is a hack for default settings not working for some reason in sg.user_settings module
sg.user_settings_set_entry('startaddress', "0x0000")
form = sg.FlexForm('Eprom emulator Uploader 2.0')
from serial.tools.list_ports import comports
com = [p.device for p in comports()]
layout = [ [sg.Text('Eprom Type'), sg.InputCombo(('2716', '2732','2764', '27128','27256', '27512'),sg.user_settings_get_entry('mem',''), key='mem', size=(10, 6))],
[sg.Text('COM port '), sg.InputCombo(com,sg.user_settings_get_entry('port',''),key='port',size=(10, 1))],
[sg.Checkbox('Save to SPI',sg.user_settings_get_entry('spi',''),key='spi')],
[sg.Checkbox('Auto Start',sg.user_settings_get_entry('auto',''),key='auto')],
[sg.Text('Start Address (for binary files)'),sg.Input(sg.user_settings_get_entry('startaddress',''), key='startaddress', size=(10,1))], # 4
[sg.Text('Choose A File', size=(35, 1))],
[sg.In(sg.user_settings_get_entry('filename', ''),key='filename'),sg.FileBrowse(file_types=(("iHex files", "*.hex"),("Binary","*.bin"),("Image","*.img"),("All files","*.*")))], # 6
[sg.Submit(), sg.Cancel()],
]
if len(sys.argv) >= 3:
parser = argparse.ArgumentParser(description='This script sends data to EPROM Emulator over serial port, requires python 3.8 and pyserial "pip install pyserial"')
parser.add_argument('file_arg', metavar='<file>', type=str, help='Data file in 8 bit InteHEX format (*.hex) or binary format (*.*, all other extensions except *.hex) ')
parser.add_argument('port_arg', metavar='<port>', type=str, help='Serial port to use, eg COM1 in Windows or "/dev/ttyUSB0" in Linux')
parser.add_argument('-mem', metavar='<type>', type=str, choices=['2716', '2732','2764', '27128', '27256', '27512'], default='27512', help='Emulated EPROM memory type, eg.: 2716..27512. Defaults to 27512')
parser.add_argument('-spi', metavar='y/n', choices=['y','n'], default=False, help='Enable/Disable emulator saves data to SPI EEPROM. This only works on "full images" with data in blocks of 128 bytes, starting from 0000h')
parser.add_argument('-auto', metavar='y/n', choices=['y','n'], default=False, help='Enable/Disable emulator automatically load last saved image and EPROM configuraiton on start.')
parser.add_argument('-start', metavar='<start>', type=str, default="0x0000", help='start address for loading binarly files, defaults to 0x0000 if none provided')
args = parser.parse_args()
mem = args.mem
port = args.port_arg
spi = args.spi
auto = args.auto
startaddrstr = args.start
file = args.file_arg
gui = False
else:
window = sg.Window('EPROM EMU NG GUI Uploader '+version, layout)
gui = True
# Create the Window
# Event Loop to process "events" and get the "values" of the inputs
while True:
event, values = window.read()
mem = values['mem']
port = values['port']
spi = values['spi']
auto = values['auto']
startaddrstr = values['startaddress']
if values['spi']: spi="y"
else: spi= "n"
if values['auto']: auto="y"
else: auto = "n"
file = values['filename']
if event == sg.WIN_CLOSED or event == 'Cancel': # if user closes window or clicks cancel
exit()
break
if event == 'Submit': # if user closes window or clicks cancel
sg.user_settings_set_entry('filename', values['filename'])
sg.user_settings_set_entry('startaddress', values['startaddress'])
sg.user_settings_set_entry('mem', values['mem'])
sg.user_settings_set_entry('spi', values['spi'])
sg.user_settings_set_entry('auto', values['auto'])
sg.user_settings_set_entry('port', values['port'])
break
window.close()
mem_type = ""
if mem == "2716":
mem_type = ':ini16'
max_size = 2048
elif mem == "2732":
mem_type = ':ini32'
max_size = 4096
elif mem == "2764":
mem_type = ':ini64'
max_size = 8192
elif mem == "27128":
mem_type = ':ini128'
max_size = 16384
elif mem == "27256":
mem_type = ':ini256'
max_size = 32768
elif mem == "27512":
mem_type = ':ini512'
max_size = 65536
try:
ser = serial.Serial(port=port,baudrate=115200,timeout=0.5,writeTimeout=0)
except:
print("Failed to open port, verify port name")
exit()
# ------------------------------------------------------------------------
buff64k = [-1]*65536
# -------------------------------------------------------------------------
filename, file_extension = os.path.splitext(file)
if file_extension.lower() == ".hex":
isbin = False
else:
isbin = True
if isbin: # process bin
startaddr = int(startaddrstr, 16)
binfile = open(file,"rb").read()
file_size = len(binfile)
# load the binary file into the buffer at start location
for x in range (0,file_size):
buff64k[x+startaddr] = binfile[x]
print("Done processing bin\n")
else: # process hex file
Bytes_Written = 0
Errors = 0
with open(file) as fp:
while True:
line = fp.readline()
if len(line) > 0:
if ParseIntelLine(line):
break
else:
break
if Errors == 0:
print("Done processing hex\n")
fp.close()
# generate data map (list of 128Byte chunks that has data)
datamap = chunkData(buff64k)
# display map of data in buffer
print('Each symbol represents 128Bytes "*" with data, "-" with no data\n')
plotData(datamap)
# process data map into a list of 128Byte chunks that can be sent in 1,2,3 or 4 chunks of 128Bytes to the emulator
data = packetData(datamap)
# ----------------------------------------------------------------------------------
print ("\n\nRunning EPROM EMU NG python script version",version)
print("")
print("File used: {}".format(os.path.basename(file)))
print("")
print("Using serial port {}, emulating: {} EPROM".format(port,mem))
time.sleep(2) # on nano, opening the port will trigger reset of the arduino, so need to wait
try:
for x in range(4):
data_tx = ("\r\n").encode()
ser.write(data_tx)
ser.flushInput() # ignore anything waiting in the input buffer.
print("\n-- attempting to get sync --\n")
data_tx = (":dml\r\n").encode()
ser.write(data_tx)
response = ser.readline()
if "HW: " in response.decode(): # Emulator will respond with version number like "HW: v1.0"
print(response.decode())
elif "...." in response.decode():
print("Waiting for autoupload to finish... Note: you can disable autoupload by long pressing pushbutton")
while "..." in ser.readline().decode():
print(".", end=' ', flush=True)
print("")
time.sleep(2)
ser.flushInput() # ignore anything waiting in the input buffer.
data_tx = (":dml\r\n").encode()
ser.write(data_tx)
response = ser.readline()
if "HW: " in response.decode():
print(response.decode())
else:
print("Failed to connect after autoload - exiting")
exit()
else:
print("Failed to connect - exiting")
exit()
#
# when sending entire "image" of and EPROM, for example 32k of data to emulate 27256 EPROM, you can ask the EMULATOR to save the data to SPI EEPROM
# this will only work if you send full blocks of 128 bytes, in consecutive range starting from address 0000h up to the size of emulated EPROM
# the emulator can reload this data when the button is pressed, can take up to 1min for 27512 - LED will quickly flash during loading.
#
if spi == "y":
print('-- Setting "Save to SPI EEPROM" option to enable --')
data_tx = (":iniSPI1\r\n").encode()
ser.write(data_tx)
response = ser.readline()
print(response.decode())
if spi == "n":
print('-- Setting "Save to SPI EEPROM" option to disable --')
data_tx = (":iniSPI0\r\n").encode()
ser.write(data_tx)
response = ser.readline()
print(response.decode())
#
# Set the Auto load from SPI EEPROM option
#
if auto == "y":
print('-- Setting "Auto load from SPI EEPROM" option to enable --')
data_tx = (":iniAuto1\r\n").encode()
ser.write(data_tx)
response = ser.readline()
print(response.decode())
if auto == "n":
print('-- Setting "Auto load from SPI EEPROM" option to disable --')
data_tx = (":iniAuto0\r\n").encode()
ser.write(data_tx)
response = ser.readline()
print(response.decode())
print("-- prcessing file --\n")
start = time.time()
for StartAddr,FrameSize in data:
# request byte frame processing
data_tx = (":SBN\r\n").encode()
ser.write(data_tx)
# Wait for ACK
response = ser.readline() # this is a form of sync before the array gets sent
# Send address and no of bytes incomming
ser.write((str(StartAddr)+"\n").encode())
ser.write((str(FrameSize)+"\n").encode())
# set un-used bytes "-1" to "0xffh"
for x in range (StartAddr,StartAddr+FrameSize):
if buff64k[x] < 0:
buff64k[x] = 0xff
#print("\nBuffer: ", len(buff64k[StartAddr:StartAddr+FrameSize])," Start Address:",str(hex(StartAddr)))
#PrintHex(buff64k[StartAddr:StartAddr+FrameSize])
# send to emulator
ser.write(buff64k[StartAddr:StartAddr+FrameSize])
# Wait for ACK
response = ser.readline()
print("0x%0.4X"%StartAddr,response.decode().strip(), end='\r', flush=True)
taken = time.time() - start
print("\n\nTime taken to transfer: {} \n".format(str(round(taken,2))))
print("-- Setting EPROM Type --")
data_tx = (mem_type + "\r\n").encode()
ser.write(data_tx)
response = ser.readline()
print(response.decode())
print("-- Finished -- ")
# enable emulation
data_tx = (":EMUON\r\n").encode()
ser.write(data_tx)
response = ser.readline()
print(response.decode())
except Exception as e:
print("Failed to send",e)
ser.close()
if gui:
input("Press Enter to continue...")
sys.exit(0)