#!/bin/env python3 from sys import exit from time import sleep from RPi.GPIO import IN, OUT import RPi.GPIO as GPIO from re import fullmatch import argparse import atexit # EEPROM AT28C256 Pin names and RPi GPIO Pin Numbers # b means bar = inverted gpio_l = [2, 3, 4, 17, 27, 22, 10, 9, 11, 5, 6, 13, 19, 26] gpio_r = [14, 15, 18, 23, 24, 25, 8, 7, 12, 16, 20, 21] # # Defining which 6502 pin goes to which GPIO pin # A = [ # 27, 22, 10, 9, 11, 5, 6, 13, 13, 6, 5, 11, 9, 10, 22, 27, # A0 - A7 3, 4, 18, 17, # A8 - A11 23, 2, 15 # A12 - A14 ] IO = [ 16, 20, 21, 12, 7, 8, 25, 24] OEb = 26 # Output Enable WEb = 19 # Write Enable CEb = 14 # Chip Enable is hooked up to A15 on the processor controls = [CEb, WEb, OEb] # TIMES # Read: t_read_ACC = 150 * 1e-9 # Address to Output Delay t_read_CE = 70 * 1e-9 # CE to output delay t_read_OE = 150 * 1e-9 # OE to output delay t_read_DF = 50 * 1e-9 # data float # Write: t_write_AS = 0 # Address Setup time t_write_OES = 0 # OEb Setup time t_write_AH = 50 * 1e-9 # Address Hold Time t_write_CS = 0 # Chip Select Setup Time t_write_CH = 0 # Chip Select Hold Time t_write_WP = 100 * 1e-9 # Write Pulse Width t_write_DS = 50 * 1e-9 # Data Setup Time t_write_DH = 10 * 1e-9 # Data Hold Time t_write_OEH = 0 # OEb Hold Time t_write_WPH = 50 * 1e-9 # Write Puls High t_write_WC = 10 * 1e-3 # Write Cycle Time # t_write_WP = 100 * 1e-6 # Write Pulse Width # t_write_WPH = 50 * 1e-6 # Write Pulse High !!!2*e5 longer than in Datasheet, since shorter high caused Problems with my Chip!!! # t_read_ACC = 150 * 1e-6 # Address to Output Delay # t_write_DH = 1 * 1e-5 # Data Hold Time # Toggle t_toggle_OE = 0 # OE to output delay t_toggle_OEH = 10 * 1e-9 # OE hold time t_toggle_OEHP = 150 * 1e-9 # OE high pulse # t_toggle_OEH = 500 * 1e-7 # OE hold time # t_toggle_OEHP = 500 * 1e-7 # OE high pulse PRINT_HEX = True PRINT_BIN = False PRINT_DEC = False REP_CHAR = '\n' # # UTILITY # def print_pins(): for i in range(len(A)): print(f" A{i:02} - {A[i]}") for i in range(len(IO)): print(f"IO{i:02} - {IO[i]}") print(f" CEb - {CEb}") print(f" WEb - {WEb}") print(f" OEb - {OEb}") def format_number(n, hex_digits=2, bin_digits=8, dec_digits=3, sep=" "): s = "" if not PRINT_HEX and not PRINT_BIN and not PRINT_DEC: print("ERROR: echo: at least one of hex, bin, dec must be set to True") exit(1) if PRINT_HEX: s += "0x" + format(n, f'0{hex_digits}x') + sep if PRINT_BIN: s += "0b" + format(n, f'0{bin_digits}b') + sep if PRINT_DEC: s += format(n, f'0{dec_digits}') + sep return s[:-len(sep)] def echo(address: int, value: int, message: str="", mode: str="-", end=None): """ print a message for a given address and value """ s = mode + " " "[" + format_number(address, hex_digits=4, bin_digits=15, dec_digits=5) \ + "] {" + format_number(value, hex_digits=2, bin_digits=8, dec_digits=3) + "} " \ + message print(s, end=REP_CHAR if end is None else end) def clear_line(): print(" " * 80, end='\r') def convert_to_int(v): """ convert 0xABC and 0b0101 to int """ if type(v) == str and fullmatch(f"0x[0-9,a-f,A-F]+", v): return int(v.replace("0x", ""), 16) elif type(v) == str and fullmatch(f"0b[0]+", v): return int(v.replace("0b", ""), 2) return int(v) def get_n_bits(i: int): """ return how many bits are needed to express the number in binary """ return len(bin(i)) - 2 # -2 for the "0x" def get_bit(value: int, n: int): """ get the n-th bit (0-based) """ return value & (1 << n) def read_binary_file(filepath, from_ad=0, to_ad=-1) -> list[int]: with open(filepath, "rb") as file: bindata = [] for byte in file.read(): bindata.append(byte) if to_ad < 0: to_ad = len(bindata) - 1 return bindata[from_ad:to_ad+1] # # GPIO functions # def setup_io_pins(IOdirection=OUT): # OUT when writing and IN when reading for pin in IO: GPIO.setup(pin, IOdirection) def setup_pins(): # setup the pins GPIO.setmode(GPIO.BCM) for pin in controls: GPIO.setup(pin, OUT, initial=1) # inverted, is 1 means disable for pin in A: GPIO.setup(pin, OUT, initial=0) setup_io_pins(IN) def cleanup(): GPIO.cleanup() def set_address(address: int, bits=15): """ set the address pins to the given value """ for j in range(bits): GPIO.output(A[j], get_bit(address, j)) def write_byte(byte: int, address: int, verbose=True, debug=False): if verbose: echo(address, byte, mode="w") GPIO.output(OEb, 1) # setup the address set_address(address, bits=15) GPIO.output(CEb, 0) # wait setup time sleep(max(t_write_CS, t_write_AS, t_write_OES)) # Start the write pulse -> enable WEb GPIO.output(WEb, 0) # Setup Data setup_io_pins(OUT) for j in range(8): GPIO.output(IO[j], get_bit(byte, j)) sleep(t_write_DS) # wait until minimum write pulse width sleep(t_write_WP) if debug: clear_line() echo(address, byte, mode="w", message="DEBUG: Press enter to continue") input() # End Write Pulse -> disable WEb GPIO.output(WEb, 1) # wait "Data Hold" sleep(max(t_write_DH, t_write_WPH)) GPIO.cleanup(IO) setup_io_pins(IN) wait_write_finished() GPIO.output(CEb, 1) def write_page(address: int, page: list[int], verbose=True) -> int: GPIO.output(OEb, 1) # setup the address set_address(address, bits=15) GPIO.output(CEb, 0) echo(address, 0, mode="p") assert 64 - (address % 64) >= len(page), f"64 - (address % 64) ={64 - (address % 64)}!>={len(page)}=len(page)" # wait setup time sleep(max(t_write_CS, t_write_AS, t_write_OES)) # Setup Data setup_io_pins(OUT) for i in range(len(page)): set_address(address + i, bits=6) # Start the write pulse -> enable WEb GPIO.output(WEb, 0) for j in range(8): GPIO.output(IO[j], get_bit(page[i], j)) sleep(max(t_write_DS, t_write_WP)) GPIO.output(WEb, 1) sleep(t_write_WPH) GPIO.cleanup(IO) setup_io_pins(IN) # wait_write_finished() sleep(t_write_WC) # print(f"Toggled {timeout} times") GPIO.output(CEb, 1) def wait_write_finished(): # check the toggle bit IO6, if it stops toggling the write is done sleep(t_toggle_OEH) timeout = 0 while timeout < 1e3: GPIO.output(OEb, 0) sleep(t_toggle_OE) bit1 = GPIO.input(IO[6]) sleep(t_toggle_OEH) GPIO.output(OEb, 1) sleep(t_toggle_OEHP) GPIO.output(OEb, 0) sleep(t_toggle_OE) bit2 = GPIO.input(IO[6]) sleep(t_toggle_OEH) GPIO.output(OEb, 1) sleep(t_toggle_OEHP) if bit1 == bit2: break timeout += 1 def read_byte(address: int, verbose=True, debug=False) -> int: GPIO.output(WEb, 1) setup_io_pins(IN) # set the address valid ad_bin = set_address(address, bits=15) # low in chip/output enable -> enable GPIO.output(CEb, 0) GPIO.output(OEb, 0) # wait the "Address to Output Delay" until the output is valid sleep(max(t_read_ACC, t_read_CE, t_read_OE)) byte = 0 for j in range(8): byte |= (GPIO.input(IO[j]) << j) if debug: clear_line() echo(address, byte, mode="r", message="DEBUG: Press enter to continue") input() # high in OEb and CEb -> disable GPIO.output(OEb, 1) GPIO.output(CEb, 1) sleep(t_read_DF) if verbose: echo(address, byte, mode="r") return byte # # FUNCTIONS # def read(from_ad=0, to_ad=255, delay=1e-3, ignore=[0xff], debug=False, compare:list[int]=[], verbose=True) -> list[int]: """ from_ad: start address from where to read to_ad: end address to read to delay: delay between readings in s verbose: wether to print the reading ignore: list of values which are not printed """ content = [] unequal = [] if compare: assert len(compare) == to_ad - from_ad + 1, f"{len(compare)} vs = {to_ad - from_ad + 1} {to_ad} - {from_ad} + 1" for i in range(from_ad, to_ad + 1): byte = read_byte(i, verbose=verbose, debug=debug) content.append(byte) if compare: if not compare[i-from_ad] == content[i-from_ad]: unequal.append(i) echo(i, byte, "vs {" + format_number(compare[i-from_ad], hex_digits=2, bin_digits=8, dec_digits=3) + "} in file", mode="c", end="\n") # wait artifical delay sleep(delay) clear_line() if compare: return unequal return content def write(content: list[int], from_ad=0, delay=0, debug=False, only_diff=True, verbose=True, check_written=True, use_page_write=False): """ Write a list if bytes to the eeprom. WEb controlled """ to_ad = from_ad+len(content)-1 current_content = [] if only_diff: print(f"Reading EEPROM and only writing deviating values") current_content = read(from_ad, to_ad=to_ad, delay=delay, debug=debug, verbose=True) assert len(content) == len(current_content) failed = [] print(f"Writing to EEPROM: {len(content)} bytes from address {format(from_ad, '04x')} to {format(to_ad, '04x')}.") n_written = 0 n_written_pages = 0 if use_page_write: # assert from_ad == 0, "from_ad != 0 not supported yet" # assert (to_ad+1) % 64 == 0, f"to_ad-1 % 64 != 0 not supported yet. to_ad={to_ad}, to_ad+1% 64 = {(to_ad+1) % 64}" for i in range(from_ad, to_ad, 64): # print(i, i+64, i+64 - (i%64)) page_end = i + 64 - (i % 64) page = content[i:page_end] # print(page, current_content[i:page_end]) if only_diff and page == current_content[i:page_end]: continue write_page(i, page) n_written_pages += 1 n_written += len(page) sleep(delay) else: for i in range(from_ad, to_ad + 1): if only_diff and (only_diff and content[i-from_ad] != current_content[i-from_ad]): continue write_byte(content[i-from_ad], i, verbose=True, debug=debug) n_written += 1 # wait artifical delay sleep(delay) clear_line() print(f"Written " + (f"{n_written_pages} pages / " if use_page_write else "") + f"{n_written} bytes") if check_written: print("Comparing EEPROM to file...") failed = read(from_ad=from_ad, to_ad=to_ad, delay=delay, debug=debug, verbose=True, compare=content) retries = 2 while len(failed) > 0 and retries > 0: print(f"Found {len(failed)} bytes deviating from the file - retrying: {retries} times") for ad in failed: write_byte(content[ad-from_ad], ad, verbose=verbose) failed = read(from_ad=from_ad, to_ad=to_ad, delay=delay, debug=debug, verbose=False, compare=content) retries -= 1 clear_line() print("Comparing complete") if len(failed) > 0: print(f"Errors occured: {len(failed)} values could not be written:") print("[", end="") for ad in failed: print(format(ad, '04x'), end=",") print("]") return def erase(from_ad=0, to_ad=32767, **keys): """ Write all 1 to the EEPROM WEb controlled """ data = [0xff for i in range(from_ad, to_ad)] write(data, from_ad=from_ad, **keys) print("Erased EEPROM - Done!") return def main(): global REP_CHAR, PRINT_BIN, PRINT_DEC, PRINT_HEX parser = argparse.ArgumentParser( prog="AT28C256-EEPROM Writer", description="Read and write from/to an AT28C256 EEPROM via the Raspberry Pi 4's GPIO pins\n2023 Matthias Quintern", epilog="Numbers can be passed as int, hex prefixed with '0x' and binary prefixed with '0b'", ) parser.add_argument("--write", "-w", dest="write", action="store_true", default=False, help="write binary file onto the EEPROM") parser.add_argument("--page", "-p", dest="use_page_write", action="store_true", default=False, help="use page write mode") parser.add_argument("--no-validate", dest="validate_write", action="store_false", default=True, help="dont validate write operations") parser.add_argument("--compare", "-c", dest="compare", action="store_true", default=False, help="compare EEPROM content to binary file") parser.add_argument("--read", "-r", dest="read", action="store_true", default=False, help="read the contents of the EEPROM") parser.add_argument("--erase", "-e", dest="erase", action="store_true", default=False, help="erase the contents of the EEPROM") parser.add_argument("--file", "-f", metavar="file", dest="file", action="store", default="", help="file for --write and --compare") parser.add_argument("--from", metavar="start-address", dest="from_ad", action="store", default=0, type=convert_to_int, help="start at address x") parser.add_argument("--to", metavar="end-address", dest="to_ad", action="store", default=0x7fff, type=convert_to_int, help="end at address y (inclusive)") parser.add_argument("--debug", dest="debug", action="store_true", default=False, help="stop the program after setting up the address") parser.add_argument("--delay", dest="delay", metavar="delay", action="store", type=float, default=0.0, help="extra delay between cycles") parser.add_argument("--ignore", dest="ignore", metavar="numbers", action="store", nargs="*", type=convert_to_int, default=[0x100], help="ignore the numbers a b ... Default=[0xff]") parser.add_argument("--verbose", "-v", dest="verbose", action="store_true", default=False, help="increase verbosity") parser.add_argument("--print-pins", dest="print_pins", action="store_true", default=False, help="print the pin layout and exit") parser.add_argument("--no-hex", dest="no_hex", action="store_true", default=False, help="Dont print values hex") parser.add_argument("--bin", dest="bin", action="store_true", default=False, help="Print values in binary") args = parser.parse_args() if (args.write or args.compare) and not args.file: parser.error('--file is required with --write and --compare') if not args.verbose: REP_CHAR = '\r' if args.no_hex: PRINT_HEX = False if args.bin: PRINT_BIN = True if args.file: file = read_binary_file(args.file, from_ad=args.from_ad, to_ad=args.to_ad) if args.to_ad != args.from_ad + len(file) - 1: args.to_ad = args.from_ad + len(file) - 1 else: file = None if args.print_pins: print_pins() exit(0) if args.erase: erase(from_ad=args.from_ad, to_ad=args.to_ad, delay=args.delay, debug=args.debug, verbose=True) if args.write: write(file, from_ad=args.from_ad, delay=args.delay, debug=args.debug, verbose=True, check_written=args.validate_write, use_page_write=args.use_page_write) if args.read: read(from_ad=args.from_ad, to_ad=args.to_ad, delay=args.delay, debug=args.debug, verbose=True, ignore=args.ignore) if args.compare: unequal = read(from_ad=args.from_ad, to_ad=args.to_ad, delay=args.delay, debug=args.debug, verbose=args.verbose, ignore=args.ignore, compare=file) print(f"Found {len(unequal)} deviating values") if __name__ == '__main__': atexit.register(cleanup) setup_pins() main() # PRINT_BIN = True # for i in range(15): # address = (1 << i) # PRINT_BIN = True # set_address(address=address) # input(f"DEBUG: {format_number(address, hex_digits=4, bin_digits=15)}")