Retrocomputing: EEPROM programming

Created: 14 Apr 2025
Updated: 25 Jan 2026
sst29.jpg

Background

I have been wanting to try experiments with digital electronics. In the same vein Ben Eater has been doing with his "8-bit breadboard computers" serie. And for that I am going to need some sort of EEPROM where to put my program in machine code.

Hardware

Arduino MEGA

An Arduino UNO was first entertained as a possible solution for this. But later timing and pin count requirements (more than 16 pins) made it a bit convoluted. Since I would need to do Arduino Port programming with bitshifters. And hoping that after all that it will still comply with write timings.

So I ended up choosing an Arduino Mega, a board with more than enough GPIO pins for this task.

arduino-mega.jpg

The EEPROM

I found a SST29EE010 on an old motherboard.

My initial happiness vanished as soon as I discovered that Ben Eater's EEPROM programmer script won't work right out of the box.

Turns out that it has some sort of software write protection (SDP). Which complicates writting to it.

A W29C020 can also be used in the same way.

sst29-pinout.jpg
Figure 1: eeprom pinout

Theory

I found this video useful in explaining what this EEPROM memory protection is. And how to practically disable it with custom software and an Arduino.

EEPROM Read Protocol

  • Controlled by CE# and OE# (# means active low)
    • both have to be low/active
  • Seems pretty normal, at least compared to 28C256 which Ben Eater used.
T_RC read cycle 90
T_AA addr access - 90
T_CE chip enable - 90
T_OE output enable - 40
sst29-read.png
Figure 2: reading timings

EEPROM Write Protocol

  • Writes have to follow a handshake part of SDP protocol.
  • JEDEC standard Sofware Data Protection (SDP) protocol.
    1. 3-byte load sequence for SDP

      eeprom[0x5555] = 0xAA;
      eeprom[0x2AAA] = 0x55;
      eeprom[0x5555] = 0xA0;
      
    2. 1-byte load cycle to a page buffer
    3. internally controlled write cycle
  • A0-A6 are used for page writes
  • A15-A16 do not matter at all on SDP commands
  • Any byte not loaded with user data will be written to FF
sst29-write.png
Figure 3: write timings
  • address is latched at falling edge of #WE
  • data is latched at raising edge of #WE
  • Said protocol can be disabled. Enabled on EEPROMs found.

Detour: borring an Arduino.

While I started working on this I did so with an Arduino UNO using bitshifters. After failing miserable. And worst of all not knowing if it was my code or the physical limitations of my setup the cause of failure.

I opted for sacrifice a 3d printer to get an Arduino Mega from it to try without the register shifters.

In the process I learned how to dump the binary flash content of an arduino to a file with avrdude.

$ avrdude -D -p atmega2560 -c wiring -P /dev/ttyACM0 -U flash:r:flashdump.bin:r -v

Real world challenges

  • Electronic Interference: turns out that if I cradle in my hand the Breadboard, including the Arduino, in my hand it will create some sort of noise that will affect readings.
  • Bad Wiring: We are going to be using +24 GPIO pins from the Arduino to the EEPROM. Plus some that should be hardcoded to LOW, like CE. Double check wiring positioning and cable health.
  • Ground all INPUTS not being used: As mentioned before, this is particularilly important for address inputs not used.
  • Write code only for testing:
    • code to read serially (without using port programming) ensure the sanity of my port programmed code

It works!

So, after some weird lectures and even weirder succesful-ish writes. I fixed some wirings and…it works!

000:  41 42 43 44 45 46 47 48   01 02 04 08 10 20 40 80
010:  7f bf df ef f7 fb fd fe   00 ff 55 aa 30 31 32 33
020:  ea ea ea ea ea ea ea ea   ea ea ea ea ea ea ea ea
030:  ea ea ea ea ea ea ea ea   ea ea ea ea ea ea ea ea
040:  ea ea ea ea ea ea ea ea   ea ea ea ea ea ea ea ea

Which is exactly the test pattern given by TommyPROM. Meant to be a non random pattern easily discerned from noise.

byte data[] = {
    'A',  'B',  'C',  'D',  'E',  'F',  'G',  'H',  0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
    0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe, 0x00, 0xff, 0x55, 0xaa, '0',  '1',  '2',  '3'
};

Source

This is the final code (for now) that I use to write the EEPROM using an arduino Mega heavily based on TommyPROM version of Ben Eater's code. You might find a more recent recent version here.

#define NBLOCKS 5
#define WE 4
#define OE 3

// Port Registers:
// <- A = data
// -> C = addr lower
// -> L = addr higher

byte data[] = {
    'A',  'B',  'C',  'D',  'E',  'F',  'G',  'H',
    0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
    0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe,
    0x00, 0xff, 0x55, 0xaa, '0',  '1',  '2',  '3'
};

void setAddress(word addr) {
  DDRC = 0xff; // 1 = OUTPUT
  DDRL = 0xff; // 1 = OUTPUT
  PORTC = (addr >> 0) & 0x00ff;
  PORTL = (addr >> 8) & 0x00ff;
}

void writeEEPROM(int addr, byte wdata) {
  digitalWrite(OE, HIGH);
  setAddress(addr);
  digitalWrite(WE, LOW);
  DDRA = 0xff; // 1 = pinMode = OUTPUT
  PORTA = wdata;
  delayMicroseconds(1);
  digitalWrite(WE, HIGH);
}

byte readEEPROM(int addr) {
  byte rdata = 0;
  DDRA = 0x00; // 0 = pinMode = INPUT
  setAddress(addr);
  digitalWrite(WE, HIGH);
  digitalWrite(OE, LOW);
  rdata = PINA;
  digitalWrite(OE, HIGH);
  return rdata;
}

void setup() {
  digitalWrite(WE, HIGH);
  pinMode(WE, OUTPUT); digitalWrite(WE, HIGH);
  pinMode(OE, OUTPUT); digitalWrite(OE, HIGH);

  DDRC = 0xff; // 1 = OUTPUT
  DDRL = 0xff; // 1 = OUTPUT

  Serial.begin(57600);
  while(!Serial);

  /* Serial.print("\nDisabling SDP..."); */
  /* disable_sdp(); */
  /* Serial.println(" done"); */

  // Dump
  printContents();
  // Programming
  Serial.print("Programming ... ");
  for (word address = 0; (address < sizeof(data)); address++) {
    // Wait after each TBLCO (Byte Load Cycle Time)
    if (address != 0 && address % 128 == 0) {
      while (readEEPROM(address-1) != readEEPROM(address-1)) {
        delayMicroseconds(100);
      };
    }
    writeEEPROM(address, data[address]);
  }
  // Padding
  for (word address = sizeof(data); address % 16 != 0; address++) {
    writeEEPROM(address, 0x00); // NOP = 0x00
  }
  Serial.println("done");
  delay(500);
  // Validation
  Serial.print("Validating...");
  byte okvalidation = 0;
  for (word address = 0; address < sizeof(data); address++) {
    if (readEEPROM(address) != data[address]) {
      okvalidation = 1;
      break;
    }
  }
  if (okvalidation != 0) {
    Serial.println("FAIL");
  } else {
    Serial.println("OK");
  }
  // Dump
  printContents();
}

void loop() {
}

void printContents() {
  for (int base = 0; base <= (NBLOCKS * 127)+1; base += 16) {
    byte bdata[16];
    for (int offset = 0; offset <= 15; offset += 1) {
      bdata[offset] = readEEPROM(base + offset);
    }
    char sbuf[80];
    sprintf(sbuf, "%03x:  %02x %02x %02x %02x %02x %02x %02x %02x   %02x %02x %02x %02x %02x %02x %02x %02x",
            base, bdata[0], bdata[1], bdata[2], bdata[3], bdata[4], bdata[5], bdata[6], bdata[7],
            bdata[8], bdata[9], bdata[10], bdata[11], bdata[12], bdata[13], bdata[14], bdata[15]);
    Serial.println(sbuf);
  }
}

void disable_sdp() {
  digitalWrite(OE, HIGH);
  DDRA = 0xff;
  setByte(0x5555, 0xaa);
  setByte(0x2aaa, 0x55);
  setByte(0x5555, 0x80); // 0x80
  setByte(0x5555, 0xaa);
  setByte(0x2aaa, 0x55);
  setByte(0x5555, 0x20); // 0x20
  DDRA = 0x00;
  delay(10);
}

void setByte(int addr, byte data) {
  setAddress(addr); // digitalWrite(OE, HIGH);
  PORTA = data;
  delayMicroseconds(1);
  digitalWrite(WE, LOW);
  delayMicroseconds(1);
  digitalWrite(WE, HIGH);
}

Resources

New Feature: Multi page writting.

Today trying to write data to a ROM. Ended up hitting a bug that I missed. That is that it didn't support writting to multiple pages. Since each page is of 128 bytes. I added a pause (TBCLO) after each page write.

Seems to work :)