For my Apple ][ projects, I needed an EEPROM programmer. After Googling a while, I found one DIY project on 6502.org. It is the MEEPROMMER, by Mario Keller.

IMG_3418

My build, the empty socket is for the EEPROM

A few nights searching for parts and soldering, I had one built and I added my Arduino Pro micro (5V/16MHz). It all seemed working fine until I tried to write my first bytes. The commandline tool I found (by Zack Nelson, with some tweaks by Paul Annesley), written in Python didn’t receive the ‘%’ prompt when writing more than $180 (384) bytes. After searching the internet (and various forums), I got some reply from someone, who said that maybe the Pro micro was/is the culprit. He tested a test sketch, which failed with the Pro micro, and ran it on his Arduino UNO. This worked for him.

IMG_3417

Pro micro removed, UNO attached through jumper wires

I asked my uncle if he could lend me one of his UNO boards. I uploaded an unmodified sketch and ran the python script by Paul. With success I can now write an EEPROM (I use the AT28C64B for my project).

As a reference I will mention the source(s) of the Arduino sketch and python script below, with which I had success:

Arduino Sketch (original here)

/**
 * ** project Arduino EEPROM programmer **
 * 
 * This sketch can be used to read and write data to a
 * AT28C64 or AT28C256 parallel EEPROM
 *
 * $Author: mario, edited by Zack $
 * $Date: 2013/05/05 11:01:54 $
 * $Revision: 1.3 $
 *
 * This software is freeware and can be modified, reused or thrown away without any restrictions.
 *
 * Use this code at your own risk. I'm not responsible for any bad effect or damages caused by this software!!!
 *
 **/

#define VERSIONSTRING "MEEPROMMER $Revision: 1.3 $ $Date: 12/18/2013 15:56:00 $, CMD:R,r,w,W,V, u"

// shiftOut part
#define PORTC_DS      0
#define PORTC_LATCH   1
#define PORTC_CLOCK   2

// eeprom IO lines:D0-D7 = pins2-9

// IO lines for the eeprom control
#define PORTC_CE   3
#define PORTC_OE   4
#define PORTC_WE   5

//a buffer for bytes to burn
#define BUFFERSIZE 1024 
byte buffer[BUFFERSIZE];
//command buffer for parsing commands
#define COMMANDSIZE 16
char cmdbuf[COMMANDSIZE];

unsigned int startAddress,endAddress;
unsigned int lineLength,dataLength;

//define COMMANDS
#define NOCOMMAND    0
#define VERSION      1

#define READ_HEX    10
#define READ_BIN    11

#define WRITE_PAGE  20
#define WRITE_BIN   21

#define UNLOCK      30

/*****************************************************************
 *
 *  CONTROL and DATA functions
 *
 ****************************************************************/

void data_bus_input(){
  DDRD &= 0b00000011;
  DDRB &= 0b11111100;
}

void data_bus_output(){
  DDRD |= 0b11111100;
  DDRB |= 0b00000011;
}

byte read_data_bus(){
  return ((PINB<<6)|(PIND>>2));
}

void write_data_bus(byte data){
  PORTB &= 0b11111100;  
  PORTB |= (data>>6);
  PORTD &= 0b00000011;
  PORTD |= (data<<2);
}

//shift out the given address to the 74hc595 registers
void set_address_bus(unsigned int address){
  bitClear(PORTC,PORTC_LATCH);		//disable latch line
  fastShiftOut(highByte(address));	//shift out highbyte
  fastShiftOut(lowByte(address));	//shift out lowbyte
  bitSet(PORTC,PORTC_LATCH);		//enable latch and set address
}

void fastShiftOut(byte data){
  for(int i=7; i>=0; i--){//shift MSB first
    //clear data pin after shift to prevent bleed through
    bitClear(PORTC,PORTC_DS);
    bitClear(PORTC,PORTC_CLOCK);
    if(bitRead(data,i))bitSet(PORTC,PORTC_DS);
    //register shifts bits on rising clock
    bitSet(PORTC,PORTC_CLOCK);
  }
  bitClear(PORTC,PORTC_CLOCK);
}

byte read_byte(unsigned int address){
  data_bus_input();			//set databus for reading
  bitClear(PORTC,PORTC_CE);	//enable chip select
  bitSet(PORTC,PORTC_WE);	//disable write
  set_address_bus(address);	//set address bus
  bitClear(PORTC,PORTC_OE);	//enable output
  //delay 312.5ns @ 16MHz
  __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t");
  byte data = read_data_bus();
  bitSet(PORTC,PORTC_OE);	//disable output
  bitSet(PORTC,PORTC_CE);	//enable chip select
  return data;
}

void fast_write(unsigned int address, byte data){
  bitSet(PORTC,PORTC_OE);	//first disable output
  bitSet(PORTC,PORTC_WE);	//disable write
  set_address_bus(address);	//set address bus
  data_bus_output();		//set databus to output
  write_data_bus(data);		//set data bus
  bitClear(PORTC,PORTC_CE);	//enable chip select
  bitClear(PORTC,PORTC_WE);	//enable write
  __asm__("nop\n\t""nop\n\t");  //delay 125ns @ 16MHz
  bitSet(PORTC,PORTC_WE);	//disable write
  data_bus_input();
  bitClear(PORTC,PORTC_OE);
  //delay 312.5ns @ 16MHz
  __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t");
  while(data != read_data_bus()); //poll data
  bitSet(PORTC,PORTC_OE);
  bitSet(PORTC,PORTC_CE);
}

void write_block_page(unsigned int address, byte* buffer, int len, int page){
  address &= ~(page-1);	//address must not break page boundaries
  bitSet(PORTC,PORTC_WE);	//disable write
  bitClear(PORTC,PORTC_CE);	//enable chip
  for(int n=0; n<len/page; n++){
    bitSet(PORTC,PORTC_OE);
    data_bus_output();		//set databus to output
    for(unsigned int i=n*page; i<(n+1)*page; i++){
      bitSet(PORTC,PORTC_WE);		//disable write
      set_address_bus(address+i);	//set address bus
      write_data_bus(buffer[i]);	//set data bus
      bitClear(PORTC,PORTC_WE);		//enable write
      //100ns+ tWP delay caused by for looping
    }
    bitSet(PORTC,PORTC_WE);		//write must be high for tBLC(100us)
    data_bus_input();
    bitClear(PORTC,PORTC_OE);	//enable output
    //delay 312.5ns @ 16MHz
    __asm__("nop\n\t""nop\n\t""nop\n\t""nop\n\t""nop\n\t");
    while(buffer[(n+1)*page-1] != read_data_bus()); //poll data
  }
  bitSet(PORTC,PORTC_CE);
}

/************************************************
 *
 * COMMAND and PARSING functions
 *
 *************************************************/

void readCommand(){
  int idx = 0;
  do {//read 'til linebreak or buffer is full
    if(Serial.available())cmdbuf[idx++] = Serial.read();
  } while (cmdbuf[idx-1] != '\n' && idx < COMMANDSIZE);
  for(;idx<COMMANDSIZE;idx++)cmdbuf[idx] = 0;//clear the rest of command buffer 
}

byte parseCommand(){
  //command format:C AAAA DDDD LL
  startAddress=hexWord(cmdbuf+2);	//A
  dataLength=hexWord(cmdbuf+7);	//D
  lineLength=hexByte(cmdbuf+12);	//L
  switch(cmdbuf[0]){
  case 'R':
    return(READ_HEX);
  case 'r':
    return(READ_BIN);
  case 'W':
    return(WRITE_PAGE);
  case 'w': 
    return(WRITE_BIN);
  case 'V':
    return(VERSION);
  case 'u':
    return(UNLOCK);
  default:
    return(NOCOMMAND);
  }
}

byte hexDigit(char c){ //ascii char to value
  if(c >= '0' && c <= '9')return c - '0';
  if(c >= 'a' && c <= 'f')return c - 'a' + 10;
  if(c >= 'A' && c <= 'F')return c - 'A' + 10;
  return 0;// getting here is bad: it means the character was invalid
}

byte hexByte(char* a){ //ascii byte to value
  return ((hexDigit(a[0])<<4) | hexDigit(a[1]));
}

//ascii word to value
unsigned int hexWord(char* data){
  return ((hexDigit(data[0])<<12)|
    (hexDigit(data[1])<<8)|
    (hexDigit(data[2])<<4)|
    (hexDigit(data[3]))); 
}


/************************************************
 *
 * INPUT / OUTPUT Functions
 *
 *************************************************/

void read_block(unsigned int from, unsigned int to, int linelength){
  int outcount = 0;
  //loop from "from address" to "to address" (included)
  for(unsigned int address = from; address <= to; address++){
    if(outcount == 0){
      //print out the address at the beginning of the line
      Serial.println();
      Serial.print("0x");
      printAddress(address);
      Serial.print(" : ");
    }
    //print data, separated by a space
    printByte(read_byte(address));
    Serial.print(" ");
    outcount = (++outcount % linelength);
  }
  Serial.println();
}

void read_binblock(unsigned int from, unsigned int to){
  for(unsigned int address = from; address <= to; address++){
    Serial.write(read_byte(address));
  }
  Serial.print('\0');//success return code
}  

void write_block(unsigned int address, byte* buffer, int len){
  for(unsigned int i = 0; i < len; i++){
    fast_write(address+i,buffer[i]);
  }   
}

void printAddress(unsigned int address){
  if(!(address & 0xfff0)) Serial.print("0");
  if(!(address & 0xff00)) Serial.print("0");
  if(!(address & 0xf000)) Serial.print("0");
  Serial.print(address, HEX);
}

void printByte(byte data){
  if(!(data & 0xf0)) Serial.print("0");
  Serial.print(data, HEX);  
}


/************************************************
 *
 * MAIN
 *
 *************************************************/
void setup(){
  DDRC = 0b00111111;	//set EEPROM & shiftOut Pins as output
  PORTC = 0b00111000;	//set EEPROM Pins high
  //Serial.begin(57600);	//set speed of serial connection
  Serial.begin(115200);
  while(!Serial) {
    // We might need this!
  }
}

void loop(){
  int bytes = 0;
  readCommand();
  switch(parseCommand()){
  case READ_HEX:
    if(lineLength==0) lineLength=32; //default
    endAddress = startAddress + dataLength -1;
    read_block(startAddress,endAddress,lineLength);
    Serial.println('%');//success return code
    break;
  case READ_BIN:
    endAddress = startAddress + dataLength -1;
    read_binblock(startAddress,endAddress);
    break;
  case WRITE_PAGE:
    if(dataLength > 1024) dataLength = 1024;
    while(bytes < dataLength)if(Serial.available())buffer[bytes++] = Serial.read();
    if(lineLength==0) lineLength=32; //page size
    write_block_page(startAddress,buffer,dataLength,lineLength);
    Serial.println('%');
    break;
  case WRITE_BIN:
    if(dataLength > 1024) dataLength = 1024; //1024 is max
    while(bytes < dataLength)if(Serial.available())buffer[bytes++] = Serial.read();
    write_block(startAddress,buffer,dataLength);
    Serial.println('%');
    break;
  case VERSION:
    Serial.println(VERSIONSTRING);
    break;
  case UNLOCK:
    fast_write(0x5555,0xAA);
    fast_write(0x2AAA,0x55);
    fast_write(0x5555,0x80);
    fast_write(0x5555,0xAA);
    fast_write(0x2AAA,0x55);
    fast_write(0x5555,0x20);
    Serial.print('%');//success return code
    break;
  }
}

Python script (original here)

#!/usr/bin/env python3

#Meeprommer commandline interface
#By Zack Nelson
#Project Home:
#https://github.com/mkeller0815/MEEPROMMER
#http://www.ichbinzustaendig.de/dev/meeprommer-en

# Some tweaks by @pda

import serial, sys, argparse

# Parse command line arguments
parser = argparse.ArgumentParser(
        description='Meepromer Command Line Interface',
        epilog='Read source for further information')

task = parser.add_mutually_exclusive_group()
task.add_argument('-V', '--version', dest="cmd", action="store_const",
        const="version", help='Request and display device version')
task.add_argument('-w', '--write', dest="cmd", action="store_const",
        const="write", help='Write to EEPROM')
task.add_argument('-W', '--write_paged', dest="cmd", action="store_const",
        const="write_paged", help='Fast paged write to EEPROM')
task.add_argument('-r', '--read', dest="cmd", action="store_const",
        const="read", help='Read from EEPROM as ascii')
task.add_argument('-d', '--dump', dest="cmd", action="store_const",
        const="dump", help='Dump EEPROM to binary file')
task.add_argument('-v', '--verify', dest="cmd", action="store_const",
        const="verify", help='Compare EEPROM with file')
task.add_argument('-u', '--unlock', dest="cmd", action="store_const",
        const="unlock", help='Unlock EEPROM')
task.add_argument('-l', '--list', dest="cmd", action="store_const",
        const="list", help='List serial ports')

parser.add_argument('-a', '--address', action='store', default='0',
        help='Starting eeprom address (as hex), default 0')
parser.add_argument('-o', '--offset', action='store', default='0',
        help='Input file offset (as hex), default 0')
parser.add_argument('-b', '--bytes', action='store', default='8',
        type=int, help='Number of kBytes to r/w, default 8')
parser.add_argument('-p', '--page_size', action='store', default='32',
        type=int, help='Number of bytes per EEPROM page e.g.:'+
            'CAT28C*=32, AT28C*=64, X28C*=64, default 32')
parser.add_argument('-f', '--file', action='store',
        help='Name of data file')
parser.add_argument('-c', '--com', action='store', 
        default='COM5', help='Com port address')
parser.add_argument('-s', '--speed', action='store', 
        type=int, default='115200', help='Com port baud, default 115200')

def list_ports():
    from serial.tools import list_ports
    for x in list_ports.comports():
        print(x[0], x[1])

def dump_file():
    ser.flushInput()
    ser.write(bytes("r "+format(args.address,'04x')+" "+
        format(args.address+args.bytes*1024,'04x')+
        " 10\n", 'ascii'))
    eeprom = ser.read(args.bytes*1024)
    if(ser.read(1) != b'\0'):
        print("Error: no Ack")
        sys.exit(1)
    try:
        fo = open(args.file,'wb+')
    except OSError:
        print("Error: File cannot be opened, verify it is not in use")
        sys.exit(1)
    fo.write(eeprom)
    fo.close()

def verify():
    print("Verifying...")
    ser.flushInput()
    ser.write(bytes("r "+format(args.address,'04x')+" "+
        format(args.bytes*1024,'04x')+" 10\n", 'ascii'))
    try:
        fi = open(args.file,'rb')
    except FileNotFoundError:
        print("Error: ",args.file," not found, please select a valid file")
        sys.exit(1)
    except TypeError:
        print("Error: No file specified")
        sys.exit(1)
        
    fi.seek(args.offset)
    file = fi.read(args.bytes*1024)
    eeprom = ser.read(args.bytes*1024)
    if ser.read(1) != b'\0':
        print("Error: no EOF received")
        
    if file != eeprom:
        print("Not equal")
        n = 0
        for i in range(args.bytes*1024):
            if file[i] != eeprom[i]:
                n+=1
        print(n,"differences found")
        sys.exit(1)
    else:
        print("Ok")

def read_eeprom():
    ser.flushInput()
    ser.write(bytes("R "+format(args.address,'04x')+" "+
        format(args.address+args.bytes*1024,'04x')+
        " 10\n", 'ascii'))
    ser.readline()#remove blank starting line
    for i in range(round(args.bytes*1024/16)):
        print(ser.readline().decode('ascii').rstrip())

def write_eeprom(paged):
    import time
    
    fi = open(args.file,'rb')
    fi.seek(args.offset)
    now = time.time() #start our stopwatch
    for i in range(args.bytes): #write n blocks of 1024 bytes
        output = fi.read(1024)
        print("Writing from",format(args.address+i*1024,'04x'),
              "to",format(args.address+i*1024+1023,'04x'))
        if paged:
            ser.write(bytes("W "+format(args.address+i*1024,'04x')+
                " 0400 "+format(args.page_size,'02x')+"\n", 'ascii'))
        else:
            ser.write(bytes("w "+format(args.address+i*1024,'04x')+
                " 0400 00\n", 'ascii'))

        ser.flushInput()
        ser.write(output)
        if(ser.read(1) != b'%'):
            print("Error: no Ack")
            sys.exit(1)
    print("Wrote",args.bytes*1024,"bytes in","%.2f"%(time.time()-now),"seconds")
    
def unlock():
    print("Unlocking...")
    ser.flushInput()
    ser.write(bytes("u 0000 0000 00\n", 'ascii'))
    if ser.read(1) != b'%':
        print("Error: no ack")
        sys.exit(1)

def version():
    ser.write(b'V\n')
    print(ser.readline().decode('ascii').rstrip())

args = parser.parse_args()
#convert our hex strings to ints
args.address = int(args.address,16)
args.offset = int(args.offset,16)

class DebugSerial(serial.Serial):
    def write(self, data):
        #print("write: %s" % (data,))
        super().write(data)

SERIAL_TIMEOUT = 20 #seconds
try:
    ser = DebugSerial(args.com, args.speed, timeout=SERIAL_TIMEOUT)
except serial.serialutil.SerialException:
    print("Error: Serial port is not valid, please select a valid port")
    sys.exit(1)

# It seems necessary to wait >= ~1.8 seconds for the device to respond.
import time
time.sleep(2)

if args.cmd == 'version':
    version()
elif args.cmd == 'write':
    write_eeprom(False)
elif args.cmd == 'write_paged':
    write_eeprom(True)
elif args.cmd == 'read':
    read_eeprom()
elif args.cmd == 'dump':
    dump_file()
elif args.cmd == 'verify':
    verify()
elif args.cmd == 'unlock':
    unlock();
elif args.cmd == 'list':
    list_ports()
    
ser.close()
sys.exit(0)