Self EEPROM programmer in a retro computer

Photo by Kumpan Electric / Unsplash

In all OMEN designs I suggested a jumper/switch, usually called WREN - WRite ENable, in the EEPROM wiring, and I also wrote that it should be wired so that the /WE input is always a logic 1.

So why is it there? Why didn't I connect it directly to +5 volts?

When the /WE input of the EEPROM is connected to logic 1, it is write disabled. And that's good, that's the way it's supposed to be and that's the way we want it. Most of the time we just want to read from the EEP ROM. If some code writes to it, it is usually by mistake, and if it could write, it would bring more annoyance than benefit.

But there are also situations where it is a good idea to allow registration. For example, when you want to update firmware, a service program, or add an extension to the EEPROM. Which are things that can be useful from time to time, so it's good to enable them in some way, but at the same time you don't use them daily and routinely, so it's okay if enabling them is a bit non-trivial. Preferably so complicated that it can't happen by accident. The ideal is therefore to use what is called a pin header, or three metal pins, and a thing called a "shunt" or also a "jumper", which conductively connects pins 1-2 or 2-3.

In this example from the OMEN Alpha schematic, the aforementioned switch is drawn with the JP5 designation. When pins 1 and 2 are connected, a supply voltage is applied to the /WE input. If pins 2 and 3 are connected, the /WR signal from the processor is applied to this input. The wiring is similar for Bravo and Echo, because the principle is still the same.

You probably suspect that there will be a catch in the EEPROM writing, and you are right. Probably the main hook, the actual hook, is that something will go wrong. The data will be written wrong, the program will have errors, you'll overwrite the underlying software, and when you reboot, you'll have a device in a "brick" state, the functional equivalent of a brick.

The second catch comes from the very principle of writing data to EEPROM. In terms of connection, such a memory is no different from RAM: you bring data to the data bus, address to the address bus, activate the /CS and /WE signals, and that's it! However, EEPROM is primarily ROM, i.e. read-only memory, and although we have the ability to overwrite the data, it has its tricky bits. For example, it takes quite a long time to write, and during that time the memory should not be disturbed, and certainly should not be asked for data.

Most EEPROMs, and this is true for both types we use in our designs, the AT28C64 and AT28C256, can write data either byte by byte or in whole blocks, but after we write the data or the whole block, we have to wait. For these memories, the wait time is up to 10 milliseconds; for their faster variants, such as the 28C256F, it is 3 milliseconds.

That's really not super fast. If we were to write byte by byte to the OMEN Alpha memory, which is 32 kB, it would take 32768 x 10 ms = 327.68 seconds, which is just over five minutes. If we use block-by-block writing of 64 bytes, we can do it in five seconds.

The consequence of this limitation is that a program that writes to the EEPROM cannot be running in that EEPROM at the same time. After writing a byte, the processor would access the same memory. The memory would take this as a sign to start writing, it would pause for 10 milliseconds, but the processor would read instructions from the same memory during those 10 milliseconds. But during the write, the memory sends information to the data bus that it is writing, or that it has finished writing, so it would read nonsense instead of instructions, the program would crash immediately, and the brick would occur before you could say "RESET!"

So here are two golden rules:

  1. The program that writes to the EEPROM must run completely in RAM and must ensure that no address that would activate the EEPROM appears on the bus during the write cycle.
  2. A program that overwrites EEPROM must be three times as careful as a regular program. Check all data before declaring it written, preferably slowly but safely, remembering to disable all interrupts and praying that an unmaskable interrupt does not come from somewhere.

How to write to EEPROM?

Writing is exactly the same as writing to RAM. Use any instruction that writes to memory, and if the address matches the EEPROM and you have previously enabled writing to the EEPROM (with a hardware switch, see above), the memory will start storing the written byte at the specified address. You cannot read data from the memory while writing, instead you read status information.

Block write allows up to 64 consecutive bytes to be written at a time. No special instructions are needed to write a block, you simply write to address a, a+1, a+2, … Just follow two simple rules:

  • No more than 150 microseconds must elapse between writes, which is usually not a problem; for Alpha this works out to 276 clock cycles, during which time we can reliably prepare a new byte and write it.
  • All addresses in a block must have the same value in bits A6 - A14 (for 28C256; for 28C64 memory it is bits A6 - A12). When writing blocks, you can only change bits A0 - A5, the lowest 6 bits of the address.

If you do not follow these rules, i.e., there is a large time delay or the next address exceeds the block limit of 64 bytes, the memory discards the additional data and starts an internal write cycle.

After the internal write cycle is started, the memory ignores further write requests. This cycle can take up to 10 milliseconds, as we have already discussed. During this time, the memory informs that data is being written through two mechanisms, /DATA Polling and the Toggle bit.

/DATA Polling consists of the program reading data from the same address where it last wrote. While the write is in progress, the value being written is inverted in the highest bit of D7. Thus, if you wrote, for example, 0x55 (which is binary 01010101) to address 0xAAA as the last value, when you read from the same address, you will read a value that has a 1 in the high bit. Once the write cycle is complete, the value read will match what you wrote to memory.

The second method of checking is bit toggling - during the write cycle, the value of bit D6 will change periodically as it is read 0 - 1 - 0 - 1, and so on. When the write cycle ends, the value of D6 stops changing.

You can use both of these methods to determine if it is safe to write more data. An alternative approach is to simply wait the declared 10 milliseconds. But a real status check using /DATA polling or toggle bit techniques is the safest.

You can take inspiration from a small sample program (for OMEN Alpha) where the /DATA polling technique is used:

Comments powered by Talkyard.

Martin Maly

Martin Maly

Programmer, journalist, writer and electronic hobbyist. Vintage CPU lover. Creating new computers with the spirit of 80's.