Arduino: How to write an EEPROM

Created: 14 Apr 2025
Updated: 24 Apr 2025

I found on of these 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 write protection. Which complicates the writting (and reading?).

CAUTION

This is probably just interesting to me. But It kind of helped me keep my sanity in the process. Regardless of the results….

Background

The Hardware

  • An arduino UNO: I am a bit limited in the number of digital pins. And i am not sure if i could use the analog ones. So at the very least I need to keep using the bitshifters for the address stuff.

The EEPROM

sst29.jpg
Figure 1: is a PDIP-32
sst29-pinout.jpg
Figure 2: eeprom pinout
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 3: reading timings
Write Protocol
  • 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
  • JEDEC standard Sofware Data Protection (SDP)
    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
sst29-write.png
Figure 4: write timings
  • address is latched at falling edge of #WE
  • data is lateched at raising edge of #WE

The bitshifter (x2)

sst29-4094pinout.png
Figure 5: bitshifter - 4094 pinout

Journal

Take #0

Luckly I found some code on the internet that someone with seemly the same EEPROM. Using the arduino ide to write on it. Unfortunely it used a different board. Not the arduino UNO that I own. One with more IO pins that not needed the bit shifters.

So I adapted the code to work with.

Problem is that the handshake seems to require CE. So I am going to have to use one of those analogue pins.

000:  2c 2c 6b d9 08 7f d2 f5   1d 33 9c 5f 08 20 77 8b
010:  b0 71 0e e7 0c 29 16 3e   71 52 57 08 e5 9c e2 ec
020:  88 16 5d c6 49 2b 4d 3c   e2 a6 e5 f0 42 09 e8 fc
030:  2e 71 4a 20 6c d3 8f 78   fb 44 82 ef 21 36 0e ed
040:  91 54 fc 92 04 b4 6a 2c   34 f1 fc d6 69 91 1f d7
050:  52 ae d2 f4 5c 19 5d f9   17 1f d4 67 76 3a 9c 43
060:  fe 41 fa 78 80 d9 d6 da   5f 99 bd d3 62 16 ef 2d
070:  0d 2c 21 57 2c aa 47 ab   59 4e b0 ff 15 59 f2 63

Take #1 - Mission failure

It didn't work :(

Maybe I am doing something wrong.

Dealing with the frustuations of hardware hits different. Sure my code may be the problem, but the looming ghost that my faulty wiring is the issue.

Redid the wiring. Still no luck.

I need to develop a better workflow. Maybe think in smaller. Use leds or a separate board to constantly check stuff.

Take #2

Seems like some of the random 00 reading i was getting may be related to magnetic fields of my monitor…may need to consider purchase a new one. the problem seems to happen when i run the erasememory() aka mess with CE

Still, the problem of not being able to write persists.

Decided to browse on the internet for more code. I remembered also the code i saw that works to write flash memories.

  • "5555" and "2AAA" seem good enough strings to put on github search
  • seems like the flash writing code also uses this handshake for writing

Since I have already CE on analog, maybe I can put OE there too and unburden the shiftregister of it.

Take #3

I think I succeded in erasing it…or I fried it.

I see all 0's. I was checking what was causing those random zeros. And I unplugged the power of the board live and plug it again….

nvm, i am back to getting zeroes at random, with some bits I recognize from before, so the data is still there. To illustrate:

000:  2c 2c 6b d9 08 7f d2 f5   1d 33 9c 5f 08 20 00 00
010:  00 00 00 e7 0c 29 16 3e   71 52 57 08 e5 9c e2 ec
020:  88 16 5d c6 49 2b 4d 3c   e2 a6 00 00 00 00 00 00
030:  2e 71 4a 20 6c d3 8f 78   fb 44 82 ef 21 36 0e ed
040:  91 54 fc 92 04 b4 00 00   00 00 00 00 69 91 1f d7
050:  52 ae d2 f4 5c 19 5d f9   17 1f d4 67 76 3a 9c 43
060:  fe 41 00 00 00 00 00 00   5f 99 bd d3 62 16 ef 2d
070:  0d 2c 21 57 2c aa 47 ab   59 4e b0 ff 15 59 f2 00

This happened after finally using a separate pin for OE. But i dunno…

Take #4

I changed the Serial.begin() velocity from 57600 to 9600. A noticeable change seems to be that the random zeros now happens in clusters (?. Always in different places.

000:  00 00 6b d9 08 7f d2 f5   1d 33 9c 5f 08 20 77 8b
010:  b0 71 0e e7 0c 29 00 00   00 00 00 00 00 00 00 00
020:  88 16 5d c6 49 2b 4d 3c   00 00 00 00 00 00 00 00
030:  2e 71 4a 20 6c d3 8f 78   fb 00 00 00 00 00 00 00
040:  91 54 fc 92 04 b4 6a 2c   34 f1 00 00 00 00 00 00
050:  52 ae d2 f4 5c 19 5d f9   17 1f d4 00 00 00 00 00
060:  fe 41 fa 78 80 d9 d6 da   5f 99 bd d3 00 00 00 00
070:  0d 2c 21 57 2c aa 47 ab   59 4e b0 ff 15 00 00 00

Also when I started fresh after being unplugged for hours. It started showing no zeros. Only after trying to write something i saw this.

Take #5 - ✡️

I sacrificed a 3d printer to get an Arduino Mega 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

Now, I am getting weird inconsistent readings…redoing the wiring…

Take #6 - 💀

I think I fried the chip…all returns 0…

000:  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00
010:  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00
020:  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00
030:  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00
040:  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00
050:  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00
060:  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00
070:  00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00

Now that I have a board with enough pins, I tried directly with the script the used the teensy to program this memory.

Same reading. So my code is NOT the issue in this case.

Wiring?

Take #7 - Adios

Alright, rewired but using the Arduino UNO again with the bitshifters.

I would like to say that I know the pins by hearth now, but I also fried the chip so…

Nope, also zeroes, chip is gonezo, caput, se fini, au revoir, adios.

Take #8 - 🧟‍♂️

I wanted to see if my reading make sense so I intended to tie the reading of D2 to V+. But…

Another misswiring.

I wired A16 to D2 on the EEPROM and the reading of D2 to V+.

Resulted into this reading.

000:  2c 2c 04 04 04 7f 04 f5   1d 04 9c 5f 04 04 77 04
010:  04 04 0e e7 0c 04 16 3e   04 04 57 04 e5 9c 04 ec
020:  04 16 5d c6 04 04 4d 3c   04 a6 e5 04 04 04 04 fc
030:  2e 04 04 04 6c 04 8f 04   04 44 04 ef 04 36 0e ed
040:  04 54 fc 04 04 b4 04 2c   34 04 fc d6 04 04 1f d7
050:  04 ae 04 f4 5c 04 5d 04   17 1f d4 67 76 04 9c 04
060:  fe 04 04 04 04 04 d6 04   5f 04 bd 04 04 16 ef 2d
070:  0d 2c 04 57 2c 04 47 04   04 4e 04 ff 15 04 04 04

Notice that, the "2c 2c" are back. And zeroes are filled with "04".

So…I fixed the wiring and…

000:  2c 2c 6b d9 08 7f d2 f5   1d 33 9c 5f 08 20 77 8b
010:  b0 71 0e e7 0c 29 16 3e   71 52 57 08 e5 9c e2 ec
020:  88 16 5d c6 49 2b 4d 3c   e2 a6 e5 f0 42 09 e8 fc
030:  2e 71 4a 20 6c d3 8f 78   fb 44 82 ef 21 36 0e ed
040:  91 54 fc 92 04 b4 6a 2c   34 f1 fc d6 69 91 1f d7
050:  52 ae d2 f4 5c 19 5d f9   17 1f d4 67 76 3a 9c 43
060:  fe 41 fa 78 80 d9 d6 da   5f 99 bd d3 62 16 ef 2d
070:  0d 2c 21 57 2c aa 47 ab   59 4e b0 ff 15 59 f2 63

No random "00" anymore.

This is annoying.

I guess the most problematic part is that this chip has memory. So the old turn it off and turning it on might not work all the time.

In this case maybe it remembered some bad command I sent until I shook ⚡ it off…

Interlude

After repeated failures trying to write to the EEPROM. I am back to square one. But this time armed with better a better sense of what I should avoid (i hope).

Journal (again)

Take #1

So…I looked around and I found a video that talks about EEPROM memory protection. In particular it mentions problems that might arise with the timings of the SDP commands.

It says that there is a tight window to issue the commands. And Arduino might struggle using DigitalWrite().

Looking back at the code that I mentioned before that disables the SDP. The Teensy 3.5 they used has a clock speed of 120MHz, while my Arduino's have 16MHz.

Video offers 2 solutions. 1) write your arduino code to use ports with some bit-math 2) or use a project that already uses it

Uploader didn't try either and used a commercial EEPROM programmer.

Take #2

Even better, project author has some alternative code that uses Ben Eater's code as base.

And it doesn't work, i get reads of all "0xFF" but memory looks like usual when using regular code.

Take #3

Let's try understanding what it is doing.

Looking at some other code mentioned before, the one that is used on FLASH memories. It seems it uses Arduino ports too. So, even if want to give up on this EEPROM due timings, looks like I would need port's speed to work with FLASH memories.

Take #4 - about port registers

  • Someone wrote an abstraction library for them using macros.

Port registers

  • Arduino docs
  • 8 bit wide, each one
  • 3 ports, some bits/pins are not usable

    D D0-D7 0,1*
    B D8-D13 6,7
    C A0-5 6,7
    • D0: aka RX set as 0=input
    • D1: aka TX set as 1=output
  • 3 registers for each port

    DDR rw configure direction 0=INPUT 1=OUTPUT
    PORT rw controls LOW/HIGH
    PIN r reads INPUT ins

Take #5 - 🎉 Success! (kinda)

After fixing more wiring and realizing that in order to make code that handles port work correctly i need to respect the original pin position (duh!). I got to write somethin.

000:  ff ff ee ff ff ff ff ff   ff ff ff ff ff ff ff ff
010:  ff ff ff ff ff ff ff ff   ff ff ff ff ff ff ff ff
...
0e0:  ff ff ff ff ff ff ff ff   ff ff ff ff ff ff ff ff
0f0:  ff ff ff ff ff ff ff ff   ff ff ff ff ff ff ff bb

Those ff were written (i think but i am not 100% sure, such is my mess with wiring) by the script i found, but only once. Then It stopped working.

It did however:

  • disabled the software protection.
  • still works to write a byte, bb in this case. It is suppose to write a lot more but I can only get it to write one

Similarly, that ee is written by my dumb slow digitalWrite() code. But also with this code I can only write 1 byte per run.

Timings timings timings….

Take #5.5

Just some thoughts. Moving outside the IDE helped. In particular I like the current setup switching between uploading my dumb code and the fast port based code for the same pinouts. That way I blame of why something is not working is less abstract.

Take #6 - The Good Ending

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.

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'
};

Only that I am using an Arduino Mega for this. While their code, for me, it seems to only been able to unlock the writes. Whatever the case. It is done!. Now to try that 8048.

Source

This is the final code that I use to write the EEPROM using an arduino Mega heavily based on TommyPROM version of Ben Eater's code.

#define WE 14
#define OE 15

// 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 = DDRL = 0xff; // 1 = OUTPUT
  PORTC = addr & 0x00ff;
  PORTL = addr >> 8;
}

void writeEEPROM(int addr, byte data) {
  digitalWrite(OE, HIGH);
  setAddress(addr);
  digitalWrite(WE, LOW);
  DDRA = 0xff; // 1 = pinMode = OUTPUT
  PORTA = data;
  delayMicroseconds(1);
  digitalWrite(WE, HIGH);
  /* delay(10); */ // FIX: Sometimes needed?
}

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

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

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

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

  printContents();
  Serial.print("Programming...");
  int s = 127;
  for (word address = 0; (address < sizeof(data)); address++) {
    writeEEPROM(address, data[address]);
  }
  for (word address = sizeof(data); (address <= s); address++) {
    writeEEPROM(address, 0xea);
  }
  while (readEEPROM(s) != readEEPROM(s)) {
    Serial.print(".");
    delayMicroseconds(100);
  };
  Serial.println(" done");

  delay(100);
  printContents();
}

void loop() {
}

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