From 23b1e12861f8c1d752c642871d7181816a5dfc90 Mon Sep 17 00:00:00 2001 From: Kris Sekula Date: Mon, 23 Nov 2020 14:22:06 -0800 Subject: [PATCH] missed some code in RC2 from RC1 ... fixed now --- Software/EPROM_NG_v2.0rc3.py | 496 +++++++++++++++++++++++++++++++++++ 1 file changed, 496 insertions(+) create mode 100644 Software/EPROM_NG_v2.0rc3.py diff --git a/Software/EPROM_NG_v2.0rc3.py b/Software/EPROM_NG_v2.0rc3.py new file mode 100644 index 0000000..851d3e4 --- /dev/null +++ b/Software/EPROM_NG_v2.0rc3.py @@ -0,0 +1,496 @@ +# 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) +# ver 2.0rc3 - Oct 2020 - code from rc1 that was missing in rc2 was added back, one typo fixed (thx again Hubert) + +version = "2.0rc3" + +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 + + + +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',False),key='spi'),sg.Checkbox('Show Data Map',sg.user_settings_get_entry('map',True),key='map')], + [sg.Checkbox('Auto Start',sg.user_settings_get_entry('auto',False),key='auto')], + [sg.Text('Start Address (for binary files)'),sg.Input(sg.user_settings_get_entry('startaddress',"0x0000"), 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) >= 2: + parser = argparse.ArgumentParser(description='This script sends data to EPROM Emulator over serial port, requires python 3.8, pyserial and pysimplegui (use "pip3 install"') + parser.add_argument('file_arg', metavar='', type=str, help='Data file in 8 bit iHEX (*.hex) or binary format (*.*, all other extensions except *.hex)') + parser.add_argument('port_arg', metavar='', type=str, help='Serial port to use, eg COM1 in Windows or "/dev/ttyUSB0" in Linux') + parser.add_argument('-mem', metavar='', 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') + 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='', type=str, default="0x0000", help='start address for loading binary files, defaults to 0x0000 if none provided') + parser.add_argument('-map', metavar='y/n', choices=['y','n'], default=True, help='Show a map of memory usage after loading files') + args = parser.parse_args() + mem = args.mem + port = args.port_arg + spi = args.spi + auto = args.auto + startaddrstr = args.start + mapmem = args.map + 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'] + mapmem = values['map'] + + if values['spi']: spi="y" + else: spi= "n" + + if values['auto']: auto="y" + else: auto = "n" + + if values['map']: mapmem="y" + else: mapmem = "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']) + sg.user_settings_set_entry('map', values['map']) + 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 +# ------------------------------------------------------------------------- +print ("\nRunning EPROM EMU NG python script version",version) +print("") +print("File used: {}\n".format(os.path.basename(file))) + +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 +if mapmem == "y": + print('Each symbol represents 128Bytes "*" with data, "-" with no data\n') + plotData(datamap) +else: + print("Memory map disabled") +# 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\nUsing 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() + + # + + 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("-- processing 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: {}s \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) \ No newline at end of file