© James R. Strickland 2016

James R. Strickland, Junk Box Arduino, 10.1007/978-1-4842-1425-1_8

8. EPROM/Flash Explorer

James R. Strickland

(1)Highlands Ranch, Colorado, USA

Every time you load a sketch to the Cestino, the software on your host computer compiles the sketch into instructions and uploads it into the Cestino’s flash memory. You know all this. We dealt with it in Chapter 2. If you’ve watched your Cestino, you know that any sketch you upload remains in flash between reboots and power cycles. Persistent memory is a useful thing.

You’ve probably heard of firmware as midway between software—the code that runs on a device, and hardware—the device itself. Firmware is a kind of software, but it comes with the device and is available at power up, no external storage required, just like the flash in the Cestino’s ATmega1284P. Firmware can be simple, a few kilobytes of instructions for an 8-bit microprocessor, or it can megabytes in size.

Here’s an example. I have an old Ampro Littleboard computer from the mid 1980s. Its firmware fits in a 4 kilobyte EPROM, which gives it just enough smarts to boot from a floppy. By contrast, the 128k Macintosh, of approximately the same vintage, had 64kilobytes of ROM containing boot-up procedures, desktop components, and a wide variety of other routines so that this software didn’t have to be stored in the machine’s RAM. The Mac I’m writing this on routinely gets firmware updates in the 4-5 megabyte range, and I can’t find documentation for how big its firmware space really is.

Firmware is ubiquitous. Most of the devices in your junk box will have some kind of persistent memory in them, somewhere, and what’s in it will be firmware.

How interesting is that?

The answer is, it depends. For really tiny firmware like my Ampro’s, what’s in there will be mostly machine code, unreadable if you don’t happen to be a Z80 microprocessor or dedicated enough to decode each byte and figure out what it told the micrprocessor to do. For others, there’s lots of that, but also all the text strings the firmware can display. These can make for interesting poking around, even without disassembling the machine-readable parts of the firmware. They can give you a peek into how the device worked, and if there were any “Easter eggs” in it—little jokes or references included by the original programmers when a little space was left over in the firmware device.

Note

The firmware of a device is nearly always copyrighted under very restrictive terms. If you read the license information back when you bought your device, it may have included agreeing not to disassemble the firmware. We’re peeking where we weren’t invited, and if binaries of that ancient firmware wind up online, there are companies who will contact their lawyers.

The EPROM and Flash we’re going to dig into here, some of which are shown in Figure 8-1, are vintage parts, but I burned the ones shown in this book with open source firmware licensed under the GPL, so that I can quote parts of them in this book and not get in trouble. It’s your equipment. It’s unlikely anyone will care if you look into its guts, but use caution about sharing that data with others, and do so at your own risk.

A340964_1_En_8_Fig1_HTML.jpg
Figure 8-1. EPROMs and Flash

The Stuff You Need

This project has a tiny little shopping list. We’re moving up in the world. Individual components do a lot more.

New Parts

None!

Used Parts

A PROM/EPROM. Nearly any old 5v 27xx PROM or EPROM will work, although really old ICs may have very different wiring pinouts than the early 1980s vintage 27128 that I used. Truly ancient EPROMS like the Intel 1702 (dating to 1971 or so, before the JEDEC standard emerged) will often want a second (and third) power supply voltage, usually negative relative to ground, and we can’t power them easily with the Cestino. 2716s should probably be avoided unless you can find the datasheet for your exact part from your part’s manufacturer, as they were manufactured just as the JEDEC standard was being put into place and there are several different pinouts for the same IC number, some of which require more than the single 5-volt power supply.

Parallel EEPROMs and Flash ICs will also work (there’s a slightly different schematic and sketch for them) and, as long as they don’t require a Vpp (programming voltage) above 5v, we can reprogram them as well. (EEPROMS will require the sketch to be modified to erase the bytes singly rather than with a single erase command, and the method will likely be different.)

If you’re buying new, I recommend the SST39SF020A-70-4C-PHE, available at Mouser. It’s what I used, and should work with the circuit and the sketch provided. Other types may require modifications to the sketch. You can get new 27Cxx EPROMS from the likes of Futurelec, but they’ll be blank, and thus, not much fun to read. As always, make sure you get the DIP, CDIP, or PDIP version, as the others won’t work in your breadboard.

A Quick Introduction to Hexedecimal

Up until now, we’ve been dealing with single bytes, and it’s been convenient to represent them in binary notation: 0b00000000 to 0b11111111. As our numbers get larger than 255, however, binary notation starts to get painful. When faced with 0b1111111100000000, which is only a 16-bit number, or its decimal equivalent, 65280 it gets very difficult to sort out which bits go where.

As we’ve already talked about back in Chapter 4, base 10 and base 2 are arbitrary choices. There’s another numbering system that’s much more convenient to use when dealing with large binary values. It’s called hexadecimal, usually shortened to “hex.” Hexadecimal is a base 16 number system (hex—six, deci—ten.) It counts 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. Hex is very, very convenient to use with binary numbers, as each digit can exactly represent the contents of four bits. No more, no less. In the C, C++ and Arduino worlds, we represent these with 0x in front of them, so low values aren’t confused with decimal values, just as we used the 0b in front of binary values for the same reason. 0b1111 is 0xF, 0b0000 is 0x0, 0b0001 is 0x1 and our ugly 16-bit binary value 0b1111111100000000 aka decimal 65280 becomes 0xFF00. If we need to know which bytes have what values (which we will in this chapter) we just take pairs of digits from right to left. 0x00 is 0, 0xFF is 255, and 0x1FF is 256.

Often times, we’re taught math as though it is a great capital-T Truth. It’s not, really. Math is a language, a symbolic system to represent quantity. How that quantity represented, among other things, while rigorously defined and internally consistent, is essentially arbitrary. In this chapter we’ll be using hex to represent our values because it’s convenient. Make math work for you, not the other way around.

ROM, EPROM, EEPROM, and Flash: A Recognition Guide

In the beginning (or at least prior to 1971, which is close enough) there was ROM. ROM stands for read-only memory. These were ICs where the data was simply encoded in their masks, when the ICs were manufactured. Mask ROMs were great, if you were making millions of a given piece of electronics, and the firmware never needed to change. The cost to set them up was enormous, and it took weeks to make them, so you had to get your firmware right the first time.

So things remained until 1970, when Intel’s Dov Frohman was troubleshooting another IC entirely, dealing with transistors whose gate connections had broken. He noticed that these transistors had very different properties, and exploited those properties to develop the Intel 1702 EPROM, or Erasable Programmable Read-Only Memory.

Essentially, a memory cell in an EPROM is a single FET transistor with an extra, unconnected gate layer. When the cell is programmed, high voltages (47v on the 1702) between the gate and the drain force electrons to migrate across the insulating layer between the control gate and the floating gate. Since FETs draw essentially no gate current, there they stay. This is called hot electron injection . The charge between the gate and the floating gate is the electronic representation of the data.

When we go to read the data, we apply a (much lower) voltage to the control gate. If there is no charge stuck between the floating gate and the gate, the FET acts normally, and switches on. If there is a charge between the floating gate and the gate, it raises the voltage required to make the FET conduct. We apply our usual low voltage to the gate and... nothing happens. Programming an EPROM is not about programming where the ones should be. It’s about programming where the zeros should be.

To erase an EPROM, you shine ultraviolet light in the quartz window in the top of the package (if you look, you can see the actual die of the integrated circuit) for several minutes (depending on the intensity of your UV light source, but 15-20 minutes is the norm) until the insulating layer ionizes and gives the electrons a path back out, turning all the bits back to 1.

When you look in a really old piece of electronics, something from the 1980s or 1990s at the latest, and you find an IC with a sticker of some kind (often silver) over the middle, you’ve probably got an EPROM. You can take that sticker off if you need to read the numbers on the chip. It won’t erase right away. It takes weeks of full sunlight to erase an EPROM. Properly protected (covered with a sticker inside a dark computer case) EPROMs are rated to store data for decades. The truth is, I’ve never found a used one that was blank, even after 30 years, although I can’t vouch for the data’s validity.

Most parallel EPROMS will have id numbers that begin with 27, like 2716 or 27128, where the remaining digits will tell you the size in kilobits. So a 27128 has 128 kilobits, which if you divide by 8, gives you 16 kilobytes. That’s how the standard says they should be. However, when you’re dealing with stone-age electronics like this, you have to remember that the JEDEC standard for EPROMs came out after the first couple generations of them were manufactured, so there are lots of other numbers out there.

Generally speaking, if it’s the same width as the ATmega1284P (0.6 inches or a bit over 15mm) and it has a sticker or a quartz window, it’s probably an EPROM. If it has a C after the 27, it’s a CMOS EPROM, which tells you about its manufacturing technology, and that it will probably take less power and lower voltage to program it. But read the datasheet.

Note

Some EPROMs have different word sizes—particularly 16-bit words—but they’re few and far between and easy to recognize by having more pins than usual.

PROMs are a type of ROM. PROM stands for Programmable Read Only Memory . In reality, they’re EPROMS without the quartz window. If you could get UV inside them, you could erase them, but the die is sealed up inside the package. If you happened to have an X-ray machine you could possibly erase them, but PROMs were cheaper than EPROMs because they didn’t have the erase function tested, in addition to lacking the quartz window.

PROMS have the same IDs as their erasable counterparts and are pin compatible. Presumably the manufacturers and JEDEC felt it was obvious: if your 2764 has a big quartz window in it, it’s an EPROM, and if it doesn’t, it’s a PROM. Still, it’s something to watch out for if you’re ordering these parts.

EEPROMs work like EPROMs do, save that more recent types can be erased one bit at a time electrically. They do this by field electron emission, that is, electrons quantum-tunnel through very, very thin oxide layers, thus clearing the cell. Quantum mechanics at work. Their numbers always start with 28. Some can be programmed and erased at 5v (or lower) due to the addition of charge pumps (combinations of diodes and capacitors that allow voltage to be multiplied electronically) to produce the higher voltages needed for programming and erasing.

Flash is a specialized type of EEPROM, distinct from EEPROMs mostly by the fact that they are NAND devices, whereas EPROMs and EEPROMs are NOR devices. The difference? NOR types can access only single words of data (bytes, most of the time) like their EPROM and EEPROM ancestors, whereas NANDs can go down to the bit level, which is important if your flash is pretending to be a hard drive. NAND also allows more cells to be in the same area of silicon, as does the ability to erase only in blocks (sometimes called sectors). Modern flash, of the type found in flash drives, replaces the floating gate with charge trapping. An insulating oxide layer without a metal floating gate is injected with electrons via hot carrier injection, similar to hot electron injection, causing electron charges to migrate into the insulating oxide layer, programming the bit. The bit is erased by quantum tunneling, exactly as it’s done in EEPROM. The big advantage to this, besides fewer layers in the manufacturing process, is that multiple cells can share the same trap, and the number of write/erase cycles the flash can endure before it wears out is hugely improved.

Parallel flash ICs will have numbers that begin with 29 or 39, followed by an F (for flash.)

There are lots of other types of programmable ICs. The ATmega1284P is one, obviously, but there are also various logic arrays (GALs, FPGAs, and so forth), serial EEPROM and Flash, and so on. (Serial EEPROMs and Serial Flash will have different ID numbers beginning with 24 or 25, respectively. These can be viewed with the Cestino too, but the sketch would be quite different.)

The devices we’re playing with in this chapter are strictly the parallel memory types. What all these devices have in common is this: when you feed one an address on its address lines, it sets its data lines to the data it’s storing at that address. So if I have an an Intel 1702 flash, and put 0x0F (turn on the lowest four bits of its address lines), I will get the sixteenth byte (remember, addresses start with 0) that was stored in that EPROM.

Build the EPROM Explorer

I have in my breadboard an Intel 27128 EPROM dating to 1982 (the package is large and they just stamped the date on it directly), courtesy of a friend who emptied his junk box of ancient EPROMs to the benefit of mine. You can see it in Figure 8-2, buried in the nest of wires we’re about to build. According to the datasheet, it’s a 128 kilobit (16 kilobyte by 8) EPROM with a 250ns access time. That was fast in 1982, according to the datasheet. My 27128 has a 22v programming voltage , which there’s no easy way to generate on the Cestino. Oh well. We can read it with 5 volts, so let’s see if there’s anything interesting in it.

A340964_1_En_8_Fig2_HTML.jpg
Figure 8-2. EPROM Explorer

I know what you’re thinking. The schematic in Figure 8-3 looks pretty strange. A bunch of lines are all tied together? All the pins of several of the Cestino’s ports are tied together? What? That can’t be how it works. Well, you’re right.

A340964_1_En_8_Fig3_HTML.jpg
Figure 8-3. EPROM Explorer Schematic

Getting On the Bus

If you remember all the way back to Chapter 2, when we built the Cestino, we called the power and ground lines that span both breadboards and the lines up and down the sides of the boards “busses,” like the + and - bus. You may have also heard of the PCI-E bus, Universal Serial Bus (USB), ISA Bus, Unibus, Q-Bus. (okay, probably not the last two. They’re long-extinct proprietary minicomputer bus standards.) It’s time we come to grips with what a bus is because we’re about to build a couple of them.

A bus is a bundle of wires or traces on a circuit board. It comes from the same Latin root, omnibus (literally ‘for all’) that omnibus spending bills Congress so loves, and the big yellow vehicle that some of us had to ride to school—school bus—does. The word probably comes to us by way of the bus bar (sometimes spelled buss bar) in electrical work, where multiple circuits might tie to a really thick, hollow bar acting as a conductor. It’s a good description of what a digital bus does. It connects multiple devices together so they can communicate with each other. On more complex busses, it also allows for signaling so that devices don’t clobber each other’s communications on the bus. Sounds like the bus is a standard, as much as electronics, doesn’t it? You’ve got a good ear. Busses are standards, defined in terms of signals. If your device has the same signals in the right order at the right speed, it should be able to communicate on the bus.

Busses can have slots for circuitry to be added in, but they don’t have to.

A computer bus standard might have eight data lines and 16 address lines and a bunch of other signals for arbitration for who gets to use the bus when. It might define the interface electronics—voltages, currents, what the sockets are like (if there are any), what speed and order the signals have to get there, and so on. Even more complex, modern busses, like PCI-E and USB are serial busses. They have multiple lines for bandwidth, but each line is its own communication, and does not depend on the simultaneous arrival of any of its peers.

The busses we’re using to talk to the EPROM are simple parallel busses. One for data, one for addresses. For us, it’s important that all the lines on our parallel bus be sent simultaneously. Here is where using ports as 8 bit units pays off. The faster we go, the less practical turning signals on and off individually becomes.

The three busses in the schematic are indicated by one line with a lot of lines connected to it. These lines aren’t connected together electrically. Instead, they’re tied into a single bus, in this case eight wires, corresponding to a single port. In order to maintain sanity, each signal on the bus is labeled at both ends, so A0, A1, A2, and so forth on the IC are connected to A0, A1, A2, and so forth on the port we’re using for the low order address byte on the ATmega1284P. The bus line in the schematic corresponds to all eight lines. It makes for a neater (readable) schematic. Now you know how to read it.

In order to make a more photogenic project, I’ve hanked (tied) the lines for each 8-bit bus together, and color coded them by their function. The two yellow busses are ADDR_MSB and ADDR_LSB, for example. Technically they’re one bus, but I split them apart for clarity and hanked them that way. You don’t have to do it this way, but you might want to consider it. The prototype was like an explosion in a technicolor spaghetti factory. When I found the method I used in the photos I started liking breadboarding a whole lot more. It does make a logic probe more necessary, since it’s harder to see exactly what wire goes from one pin to the other. Fortunately, we have one.

Power, Ground, and Unused Signals

Let’s do the easy connections first. Vcc on pin 28 to the + bus. Vss on pin 14 to the - bus. Vpp we don’t care about. The datasheet says it should be at Vcc for read. I left mine floating. It doesn’t seem to make a difference. If we were actually planning to use the code in the EPROM, it’d be better to wire that to the + bus, but if a little noise creeps in while we’re just looking around, it probably won’t matter.

/CE (or /E on some datasheets) on pin 20 stands for chip enable. We’d normally use that to switch which EPROM we were talking to, but we only have the one. It’s active low, so wire it to the - bus. We always want the EPROM enabled. Likewise, /OE, Output Enable (also known as /G for Gate) on pin 22. It’s also active low, and it controls when the EPROM can talk to the data bus. Our answer to that is “always,” so wire that to the – bus, too. /PGM is for Program. It’s active low, so if we were programming this EPROM, we’d pull it low, with Vpp at 22.5v, and go hot-electron-injecting to write zeros to our EPROM. We won’t be doing that, so wire that to the + bus to hold it high.

If you’re starting to suspect that the control bus I mentioned previously might involve signals like that, you’re exactly right. File that away, you’ll see that again before the end of this chapter.

Data Bus

DQ0 to DQ7 are the data bus. They may be called D0-D7 on your datasheet. It means the same thing. This is the 8-bit data port of the EPROM. Not surprisingly, we’ll tie it in to an 8-bit port on the ATmega1284P, in this case port B. As you can see in the schematic, connect PB0 (ATmega1284P pin 1) to DQ0 (EPROM pin 11), PB1 (ATmega pin 2) to DQ2 (EPROM pin 12), and so on for all 8 data pins. I’d really encourage you to make all these connections with the same color jumpers if you can. If you want to get fancy and hank the wires like I did in Figure 8-2, I’d suggest waiting until we’ve debugged this wiring.

If you look at the actual IC (the pins in the schematic are not in order) you’ll see that DQ0-3 is on the left side of the IC, right above the ground pin, and the remaining 5 pins go up the other side. If you peek ahead to Figure 8-4, you might notice that while the 39SF020A has more pins, the data pins are in exactly the same place relative to the bottom of the IC. Remember I mentioned the JEDEC standards for EPROMs and flash? Pin placement is one of those standards. Not all EPROM/flash makers adhere to the standards, but when they do it makes wiring a lot easier.

A340964_1_En_8_Fig4_HTML.jpg
Figure 8-4. Flash Explorer

From now on, we’ll call this bus DATA_READ.

Address Bus(es)

The 27128 EPROM has 14 address pins, 0-13, which you’d expect for 16kB of address space. In order to span the full range of addresses the EPROM is capable of, we’ll need two Cestino ports, although two pins of one port won’t be connected to anything.

Here is where endianness matters. If you know your EPROM came out of a big endian machine like an Amiga or an old Motorola 68000 series Mac, you could wire the IC’s low order port (A0-A7) to PORT A and the high order port (A8-A14) to PORT C and change the sketch a little, but you’ll have to change the sketch anyway, so it’d be easier to change which ports are used for which there.

Remember back in Chapter 2, where we talked about the address bus size, register sizes, and all that? Is any of that coming to mind now? Here’s how it all ties in. In a microprocessor or microcontroller with an external memory interface, there’s one register, a bit of memory inside the processor, called the address register. When a program tells the processor to write a given value to a given spot in memory, it sets the address register to the the address it was given, sets some control bus bits, then sets the data bus to the value, sets or clears some more control bits, then moves on to the next instruction, which itself sets the address register, reads the instruction in the data port, and so on. We’ll get into this in much more detail in chapter 11 when we’ll be looking at a microprocessor whose only memory interface is external.

If the ATmega1284P had an external memory interface, it would be little-endian, and you’d have to change the wiring. Sadly, there don’t appear to be any ATmegas with external memory interfaces in DIP packaging. Instead, we’re doing what’s called “bit banging,” that is, generating bit patterns with software to trigger hardware. It’s slower, but the bottle neck is still communication with the console and our ability to read what comes out. We’re not in that big a hurry.

When I talk about an external memory interface, I’m talking about pins the microprocessor controls when selecting memory addresses, and a data bus for reading data and instructions into the microprocessor. The ATmega1284P obviously has both these components, but they’re internal, and we can’t touch them. If you want to play with external memory and Arduino, the Arduino Mega’s 1280 or 2560 ATmega microcontroller does have an external memory interface for up to 64kB of ram, or for reading EPROMs. It’d be a very different sketch.

So let’s wire our address bus. It connects two ATmega1284P ports to the 14 address pins on the EPROM. In the photo in Figure 8-2, all the address lines are yellow, and each 8-bit port’s wires are hanked together. We’ll start with the least significant bit, that is, bits 0 to 7. These are wired to port C of the ATMega1284P. As with the data port, PC0 (ATmega pin 21) connects to A0 (EPROM pin 10), PC1 connects to A1 (ATmega 22 to EPROM 9) and so on, until you get to PC7 and A7.

From now on, we’ll call this the ADDR_LSB bus, which is part of the larger address bus.

At this point, you might want to skip ahead to the sketch and debugging, as you can, theoretically, access the first 255 bytes of the EPROM. It’s easier to debug things when you don’t have 24 wires running across your breadboard.

Back so soon? Okay, let’s go ahead and wire up the most significant byte (or part of a byte, in this case). We’re using Port A for the ADDR_MSB port. The wiring is a tiny bit more complicated, as PA0 connects to A8, but the indicators in the schematic should make it clear what we’re using the ATmega pins for and which pin to connect them to on the EPROM. It’s also a little more complicated because the MSB address pins are scattered all over the top end of the EPROM. Again, the pins shown on the schematic are not in the order they are on the IC.

PA0 connects to A8 (ATmega 40 to EPROM 25), PA1 to A9 (ATmega39 to EPROM 24), PA2 to A10 (ATmega38 to EPROM 21), PA3 to A11 (ATmega37 to EPROM 23) PA4 to A12 (ATmega36 to EPROM 2) PA5 to A13 (ATmega 35 to EPROM 26). If your EPROM is bigger, connect the additional address lines to PA6-PA8 and set MAX_ADDRESS in the sketch accordingly.

That should cover the wiring. Let’s plug the Cestino in (you did unplug it first, right?) and see what we get.

The EPROM_Explorer Sketch

This sketch isn’t hugely complicated. It’s really a derivative of Binary Numbers on Display, but there are a bunch of functions and some C/C++ wrinkles I haven’t shown before, so it does bear a close look.

What this sketch needs to do is: generate 14 bit addresses , put them on the ADDR_LSB and ADDR_MSB pins to feed the EPROM, then read the DATA_READ lines and display whatever printable information is in the EPROM at that address to the console. Sounds like two functions to you? It did to me. Before we cover those functions, though, we need to cover the preprocessor definitions.

#define ADDR_LSB PORTC
#define ADDR_MSB PORTA
#define DATA_READ PINB
#define MAX_ADDRESS 0x3FFF

Remember that #define runs at compile time, and thus consumes none of the ATmega’s memory at run time. The preprocessor substitutes the second value for the first value before GCC goes to work on our code. So we substitute ADDR_LSB with PORTC, ADDR_MSB with PORTA, and DATA_READ with PINB. We’re giving aliases to our ports, in case you want to modify the code to use different ports, or make this sketch work on a different Arduino entirely. It also makes the code more readable.

The last define is MAX_ADDRESS , the maximum address in the EPROM, in hexadecimal. Its decimal equivalent is 16383. Since this is the number of bytes this EPROM can store, and since the EPROM is governed by JEDEC standards, we can say the EPROM holds 16 kilobytes. (I’ll rant more about this subject in Chapter 9.) If you prefer the international standard names for such things, you can go ahead and call them 16 kibibytes. Which is a mouthful.

The functions come next, and in this sketch they do nearly all the heavy lifting. We’ll start with select_EPROM_address , as it’s the most complicated.

void select_EPROM_address(uint16_t EPROM_address) {
  union {
    uint16_t address_uint;
    byte byte_array[2];
  } address_union;


  address_union.address_uint = EPROM_address;

  ADDR_LSB = address_union.byte_array[0];
  ADDR_MSB = address_union.byte_array[1];
}

Remember the new C/C++ code wrinkle I mentioned? It’s the union at the top of this function. Up until now, we’ve been dealing with single byte values, thrown out a single port. This is all well and good, but the 27128 EPROM has 14 bits of address space. You already know from Chapter 4 that as numbers in the Cestino get bigger than 255, the number of bits goes above eight. In this case, we’re using a two-byte unsigned integer datatype called a uint16_t. We could subdivide this two-byte variable to get our least significant bits (bits 0 to 7) and most significant bits (8-13) with bitwise ands and rotations, but in C, and by extension C++, there is another way.

In C, every variable is really a pointer to an area of memory. It’s an address. What happens if you give two different variables the same address? They both access the same area in memory. And if those two variables don’t happen to be the same type? You can access the data in that area of memory in different ways. And that’s exactly what the union is doing. A C union is two or more representations of the same value in memory. Two pointers to the same area in memory.

The union we’re creating is called address_union . It has two variables: a uint16_t called address_uint, and a two byte array called byte_array[] . Always bear in mind that these two variables are two different representations of the exact same data in memory. So when we set address_union.address_uint to the address that’s passed in to the select_EPROM_address() function , we are also setting both bytes of address_union.byte_array[]. What happens next is pretty straightforward. We set the ADDR_LSB port to address_union.byte_array[0] and the ADDR_MSB port to address_union.byte_array[1].

Note

The address_union.byte_array only produces the correct byte order on little-endian systems like ATmega, intel, and so on. If you know your EPROM came out of a big-endian system: say, an old Macintosh, or a Radio Shack Color Computer with a Motorola processor in it, your address byte order will be big endian. To fix this, swap ADDR_LSB and ADDR_MSB in this function. If you know your EPROM came from a 16-bit system like a Macintosh, the data bytes will also be in big_endian, probably spread across two or more different EPROMs.

The next function, like most of the long functions in this book, is a pretty-printer. You could just dump the values from the EPROM straight to the serial console, but you’d quickly find your console full of gibberish if you did. Also, the Arduino serial monitor may be programmed to react in certain ways to certain normally unprintable characters. It knows tabs and linefeeds at the very least. So it’s probably a good idea to filter the output so it doesn’t make a horrendous mess on the serial monitor. If you really want to see the exact contents of every byte, you could change this function to output the values in hex.

void dump_EPROM(uint16_t start_addr, uint16_t end_addr ) {
  String data_line = "";
  for (uint16_t c = start_addr; c <= end_addr; c++) {
    select_EPROM_address(c);


    if ((!(c % 0x40)) && c > 0) {
      Serial.print("0x");
      Serial.print((c - 0x40), HEX);
      Serial.print(" ");
      Serial.println(data_line);
      data_line = "";
    }


    if (isprint(DATA_READ)) {
      data_line += (char)DATA_READ;
    } else {
      data_line += ".";
    }
  }
}

The dump_EPROM() function takes two uint16_t parameters: start_addr and end_addr. These are the start address and end address, respectively, and they go straight into a for loop definition just like you’d expect, right after we define a String object called data_line and set it empty.

For each address the for loop generates, we go to that address in the EPROM using select_EPROM_address() . Before we read anything, however, we do some housekeeping.

We’re going to accumulate every 64 bytes (or markers for unprintable bytes) we gather from the EPROM in data_line, and then print them en masse. It’s faster, and less prone to noise related errors, particularly if we happened to be using pins on port D. (Which we will before the end of this chapter.) The easiest way to make data_line print when it should is to take the modulus (remainder) of the address we’re at by 64. That’s the next thing we do. If there is no remainder of the address divided by 64 (0x40), and if c is not zero, we print the start address of the line in hex, then a tab, then the contents of data_line. Then we clear data_line.

This is our first trip through the loop, and c is 0, so we fail the second test. Nothing happens.

Next, we pass the data on DATA_READ (remember, this is PINB, or the read register of PORT B, which is connected to our data pins on the EPROM) to the C isprint function. If that function tells us this is a printable character, we add it to data_line. If it’s not printable, we add a period to the data line instead, so everything will continue to line up in the serial console.

Then we go back around the loop. When c hits 64, we Serial.print an address, then Serial.println data_line , then clear data_line to accumulate the next line.

void setup() {
  Serial.begin(115200);


  DDRC = 0b11111111;
  DDRA = 0b11111111;
  DDRB = 0b00000000;
  Serial.println(" Running");
}

The next function is setup() . Very little happens here, but what does happen is critical. We set up the serial console, set ports C and A to output on all pins, as they’re our ADDR_LSB and ADDR_MSB ports , respectively, and port B to input on all pins because it’s DATA_READ, and tied to the data pins of the EPROM. (Note that this is 0b for binary instead of 0x for hex.) After that, we print a message to the serial console that tells us when the program starts.

void loop() {
  dump_EPROM(0, MAX_ADDRESS);
  Serial.println(" Done.");
  while (0 == 0) {};
}

Loop is even less interesting. It calls dump_EPROM from 0 to max_address, prints a message to tell us when we’re done, and then drops into a while loop that runs forever to keep loop() from being called again.

Here’s the complete sketch, comments and wiring guide intact.

//EPROM_Explorer
//----------------------------------------------------------
// Dump the readable characters of a 27128 EPROM to the
// Console.
// ---------------------------------------------------------
//Hardware:
//For a 27128 or similar EPROM:
//ATmega Pin Pin Name Port name 27128 Pin  Pin Name Function  
//         1 PB0      DATA_READ        11  DQ0      DATA    
//         2 PB1      DATA_READ        12  DQ1      DATA    
//         3 PB2      DATA_READ        13  DQ2      DATA    
//         4 PB3      DATA_READ        15  DQ3      DATA    
//         5 PB4      DATA_READ        16  DQ4      DATA    
//         6 PB5      DATA_READ        17  DQ5      DATA    
//         7 PB6      DATA_READ        19  DQ6      DATA    
//         8 PB7      DATA_READ        19  DQ7      DATA    
//                              


//        22 PC0      ADDR_LSB         10  A0      ADDRESS  
//        23 PC1      ADDR_LSB         09  A1      ADDRESS  
//        24 PC2      ADDR_LSB         08  A2      ADDRESS  
//        25 PC3      ADDR_LSB         07  A3      ADDRESS  
//        26 PC4      ADDR_LSB         06  A4      ADDRESS  
//        27 PC5      ADDR_LSB         05  A5      ADDRESS  
//        28 PC6      ADDR_LSB         04  A6      ADDRESS  
//        29 PC7      ADDR_LSB         03  A7      ADDRESS  
//                              
//        35 PA5      ADDR_MSB         26  A13     ADDRESS  
//        36 PA4      ADDR_MSB         02  A12     ADDRESS  
//        37 PA3      ADDR_MSB         23  A11     ADDRESS  
//        38 PA2      ADDR_MSB         21  A10     ADDRESS  
//        39 PA1      ADDR_MSB         24  A09     ADDRESS  
//        40 PA0      ADDR_MSB         25  A08     ADDRESS  


// ----------------------------------------------------------
// James R. Strickland
// ----------------------------------------------------------


//preprocessor definitions.
#define ADDR_LSB PORTC
#define ADDR_MSB PORTA
#define DATA_READ PINB
#define MAX_ADDRESS 0x3FFF


// ----------------------------------------------------------
// select_EPROM_address(address)
// ----------------------------------------------------------
// This function sets ADDR_LSB, ADDR_MSB, and the first two
// bits of ADDR_BANK_CTRL to the uint16_t (four byte unsigned
// int) address passed to it. These ports are wired to the
// EPROM IC's address pins.
//
// To break the address down into single byte chunks, we use
// a union, which essentially maps two variables (a 2 byte
// unsigned int and an array of 2 bytes, in this case) to
// one area of memory. Then we can peel off the bytes as we
// want them. Note that this code is endian-sensitive. If
// you run it on some future big-endian Arduino, you'll need
// to change the byte order.
// ----------------------------------------------------------


void select_EPROM_address(uint16_t EPROM_address) {

  //Declare the union we're going to use for address processing.
  union {
    uint16_t address_uint;
    byte byte_array[2];
  } address_union;


  address_union.address_uint = EPROM_address;

  ADDR_LSB = address_union.byte_array[0];
  ADDR_MSB = address_union.byte_array[1];
}


// ----------------------------------------------------------
//dump_EPROM(start_addr,end_addr)
// ----------------------------------------------------------
// This function dumps the contents of the EPROM to the console.
// data_line is an Arduino String object. We initialize it empty.
// Iterate through all the addresses, start_addr to end_addr,
// inclusive.
// If we've gotten to 64 characters gathered, print the start
// address of the line, then print the data_line and clear it.
// If the data on DATA_READ is printable, add it to the current
// data_line. Otherwise add a period.
// ----------------------------------------------------------
void dump_EPROM(uint16_t start_addr, uint16_t end_addr ) {
  String data_line = "";
  for (uint16_t c = start_addr; c <= end_addr; c++) {
    select_EPROM_address(c);//iterate through all the other addresses


    if ((!(c % 0x40)) && c > 0) { //if we're at 64 characters
      Serial.print("0x");
      Serial.print((c - 0x40), HEX);
      Serial.print(" ");
      Serial.println(data_line);
      data_line = ""; //clear the data line
    }


    if (isprint(DATA_READ)) { //if the data on DATA_READ is printable
      data_line += (char)DATA_READ; //add it.
    } else {
      data_line += "."; //otherwise add a period.
    }
  }
}


// ----------------------------------------------------------
// setup()
// ----------------------------------------------------------
// Set up serial and port data directions. Initalize ADDR_BANK_CTRL
// so that the EPROM is ready to be read. Tell the user that we're
// running. Runs once.
// ----------------------------------------------------------


void setup() {
  Serial.begin(115200);


  DDRC = 0b11111111; //ADDR_LSB - Address LSB Port
  DDRA = 0b11111111; //ADDR_MSB - Address MSB Port
  DDRB = 0b00000000; //DATA_READ - Data port
  Serial.println(" Running");
}


// ----------------------------------------------------------
// loop()
// ----------------------------------------------------------
// Call dump_EPROM, then do nothing forever.
// ----------------------------------------------------------


void loop() {
  dump_EPROM(0, MAX_ADDRESS); //Dump the entire EPROM.


  Serial.println(" Done.");
  while (0 == 0) {}; //do nothing forever
}

Output

What kinds of things can you see inside an EPROM? Well, here’s what was in mine.

Running
0x0  U.......11/28/10XTIDE110-=XTIDE Universal BIOS (XT)=..v1.1.5 (1
0x40 1/28/10)........>...............................................
0x80 ................................................................

Hey, it’s an IDE BIOS extention for IBM PC/XTs. It dates to 2010? Well, yes. Remember how I said the contents of EPROMs tended to be copyrighted and I loaded these myself? This BIOS is the open source XT-IDE BIOS by Tomi Tilli. You can read all about it in Credit where Credit’s due.

0xC0  ..................................%s@ %x...Master.Slave .IDE %s
0x100 at %x: .not found...Floppy Drive. Hard Drive .Booting from %s %
0x140 x.%x ... .Boot Sector.found.%s %s!...Error %x!...Boot menu callb
0x180 ack via INT 18h.FDD.HDD.%c to %c boots from %s with %s mappings.
0x1C0 ..Copyright 2009-2010 by Tomi Tilli.%s %c.Foreign Hard Disk.ROM
0x200 Boot.Capacity : .%s%u.%u %ciB.%s%u.%u %ciB/ %u.%u %ciB.Addr..Bl
0x240 ock.Bus.IRQ.Reset.%s.%5u.%c%2u. %c%c.%5x.L-CHS.P-CHS.LBA28.LBA48
0x280 .%sUnknown.%s5." or 3." DD.%s%c%c", %ukiB.....1.........QP.....

Lots of strings. They tell us the age of the EPROM’s code, many of the error messages, and so on.

0x2C0 [email protected]...[].UVS..1.1...<.t...
0x300 ..B..[^]..UWQR.........1.<.t.<%t#........).∼.Z.....R..ZY_]......
0x340 .....<.t.....0...v.Q........Yu.............F.EE.g....F.EE..u....
0x380 ........V.EE.`.....V..^.....Q.....F.EE.x..V.EE.1....`.j.t.......
0x3C0 ..PR..........ZX.........W1.SQ......1...R..1...Z......0...G..u..
0x400 .Y[_.S0.......R.h...ZB[.S1.K...R.h...ZB[.UWVQ....1..............
0x440 ...t..............s...0.Y^_]....u.......0.........u....Selection
0x480 Timeout %us..1.................. ....Q....N.0..N....(.. ...YQ..
0x4C0 ..V........Y..∼..tG......:.∼..t:.F..u4......'.∼..t'.}..N..V.0.;V
0x500 .r..V...R...ZA9.r.............Q.F..u...;N.t.. .... .........V..|
0x540 .Y.....V....s.1..k..o.....N.0...Q.i....Y...r.1..<..N.0..F..u...t
0x580 [email protected]...'..Q.∼..t.1.....N.0.Q.....
0x5C0 .Y.............. .....f.... ..............N.0.II................
0x600 ..∼..t..V...u..7..f.Q....YP......^....*V.B.v.(...0..............

Readable strings are getting thinner. What’s in here are instructions for an 8088 in binary. Some of them don’t pass the isprint() test, so they’re not shown at all. That’s pretty much it for human-readable data.

0x9C0 ..1.....l........P.1....l..:...Xt.....RP1.......XZ...r...P1.....
0xA00 l..;.l.u.X.....PQRSUVW...r..).P...P..u..&_^][ZYX.....1..........
0xA40 ...1............c.......1...................+..U&.L.&..N........
[Lots of Machine Readable and Empty Lines Cut]
Done.

Debugging

Got gibberish? Well, you’re in the right place. The contents of your EPROM were meant to communicate with a computer, not a human being. It’s possible there is nothing readable by humans in there.

I haven’t seen any like that so far. Almost all EPROMs you find have some code that was meant to interact with a human being, and that means that there are words in there, even if they’re as banal as “Please insert system disk” or “Boot device not found,” or messages like that. You should get something. If you don’t, before you give up on that EPROM, try some debugging. There are a lot of circuits connected in this project, and transposed wires and bad connections are easy to do. (I looked at a lot of gibberish, and I knew there were readable strings on my EPROM.)

If you got no readable characters at all, or certain characters are always wrong, this is usually a data bus problem. Make sure the data lines are all connected, and in the right order. Use the logic probe to check the data pins on the EPROM, then the data pins on the ATmega and make sure you get the same results. If you don’t, there’s a wiring problem.

If you’re sure your data lines are right, go through the ADDR_LSB lines next. If these are wrong, especially if the first two or three bits are wrong, all your characters will be in the wrong order. Put a delay() statement in dump_EPROM’s loop, maybe a hundred milliseconds or so. Hook the logic probe up to A0 and run the sketch. A1 should pulse half as fast, A2 half of that, and so on. If that doesn’t give you answers, increase the delay to a couple thousand milliseconds to give yourself enough time to test both ends of the address line, from the EPROM end to the ATmega end. If all these lines check out, you should get something readable at some point.

The most puzzling bugs are when you get readable strings and then what are obviously words, but some characters are wrong. These are usually address bus problems. Note the start address of the line the first messed up word occurs on. That will give you some idea what address line changed between the last completely good line and the first mangled one. Check both ends of those address lines while the sketch is running (put delay() calls in if you have to).

If everything clears up when you put delay() into the loop, you’ve got a loose wire or some other kind of bad connection. Adding resistance and/or capacitance to a circuit introduces a time delay. It could be, if your EPROM is old enough, that even bit-banging the EPROM is too fast for it, but it’s not likely. Our 20MHz Cestino does nothing at all in less than 50ns, and there are a lot of software steps going on for each attempt to read the EPROM. Still, if you get values that don’t change from one address to the next and they’re in words so they look like they should, add a 1ms delay inside the read loop. The earliest EPROMs I’ve read about were still 250ns devices. 1000ns (1ms) should give them plenty of time.

Build the Flash Explorer

For those who couldn’t find a programmed 27xx series EPROM to read, or whose EPROM turned out to be blank, I offer this version of the project: Flash Explorer, shown in Figure 8-4.

I’m not going to go into as much depth, as reading a flash is exactly the same as reading an EPROM , except that the device is bigger and needs more address lines. We will, however, write to this flash IC, so even if you got a new, blank one from Mouser or Digikey, you can still have some fun with your device. I built mine with a 39SF020A , a common 256k8 device that’s still available at the usual suppliers. If you use a different IC, your pinout may be slightly different, although the JEDEC standard was well in place by the time flash chips hit the scene. The command sequence and particularly the registers for writing may be quite different, so you may have to modify the sketch.

Another important difference: the flash device I used has a primitive controller in it. Although you can ignore the controller to read data from the flash just like an EPROM, (the datasheet even says so) erasing and programming the flash device requires telling the controller what you want. This adds to what’s in the sketch. It’s also an indicator of how much later flash ICs like my 39SF020A are than the 27128. It was reasonable for IC designers in 2001 to assume that any device trying to program this flash would have a microprocessor, rather than simple TTL circuits . Twenty years makes a difference.

Power, Ground, and Unused Signals

As with any IC, the power and ground lines are easiest to connect first. If you look at Figure 8-5, you can see that Vcc (power) and Vss (ground) are on pins 32 and 16, respectively.

A340964_1_En_8_Fig5_HTML.jpg
Figure 8-5. Flash Explorer Schematic

It doesn’t show in the diagram because the pins aren’t in order, but this is the equivilant position they’re in on the EPROM, relative to the top and bottom of the IC. With the pin-1 end of the package facing north, Vcc is on the northern most pin on the east side, and Vss is on the southern most pin on the west side of the package, though the flash chip has 32 pins instead of the EPROM’s 28. Your JEDEC standard at work. Wire these to the + and – bus, respectively.

Data Bus

Just as with the EPROM explorer, the data pins are D0-D7 (possibly called DQ0-DQ7 on your datasheet), and like on the EPROM, they’ll be the three pins north of Vss on the west side of the IC and the southernmost five pins on the east side, with D0 being the lowest pin number and D7 being the highest. This is the JEDEC standard again. Wire these to PB0-PB7 on the ATmega1284P, just like you did with the EPROM.

Address Bus(es)

If you now expect the ADDR_LSB (lowest significant byte of the address bus) to be one pin north of DQ0 and going backward up the west side of the chip, you’re wise to the JEDEC standard. That’s exactly where they are, in this case from pins 12 to 5 for A0 to A7, respectively. Wire these to PC0-PC7 on your ATmega. That’s PORT C, from pin 22 to pin 29.

The JEDEC standard gets more complicated for the ADDR_MSB pins, the most significant byte of the address bus, but even their semirandom scattering is very similar to that of the 27128 we looked at in the EPROM explorer. In order of the ATmega1284’s pins (which are PORT A once again,) connect PA0 to Pin 27, PA1 to pin 26, PA2 to pin 23, PA3 to pin 25, PA4 to pin 4, PA5 to pin28, PA6 to pin 29, and PA7 to pin 3.

We now have enough address lines to talk to the first 64k of this 256k flash. If you want to test here, like we did with the EPROM, I won’t blame you.

We do have two more address lines we need to find some way of switching, or we won’t ever get past 64k in this flash. We’re out of completely unused ports, but if we’re careful with the sketch, we can go ahead and use the pins in PORT D that aren’t part of RS232 communications with the console. We’ll call these the ADDR_BANK lines. Wire PD2 and PD3 to flash pins 2 and 30, respectively, for A16 and A17.

A Brief Diversion: Bank Switching

Microprocessors today have dozens of memory control lines, and their 64 bit registers can address all those lines at a time. The flip side of all this luxury is that they’re all SMD—Surface Mount Devices—usually in hobbiest-unfriendly packages that assume the whole board will be baked in order to solder their pins to the board. In the 1970s and 1980s, it wasn’t so. Most 8-bit microprocessors, as we’ll see, had 16 address lines, and 16-bit memory registers to address them, which, if you count it out in binary on your fingers, you’ll find is 64k. You may, if you’re old enough, remember that some computers could address more than that, despite being 8-bit microprocessors. The secret was memory bank switching.

In a nutshell, because we’re not really using this technique in this book, memory bank switching was a hardware technique that generated some of the address line signals, and allowed a microprocessor to map different parts of the real memory map in different places of the memory the processor could see. Popular Z80 operating systems broke memory into 16kb banks, and so long as the code controlling the bank switch was not in the bank being switched, you could tell the memory controller to switch another bank in and have more memory, albeit with the same addresses as the bank you switched out. The cost, however, was high. Your program, or at least the operating system, had to keep track of what bank had what data (or program code) in it, and switching banks slowed execution of the program down. Most programmers breathed a sigh of relief when flat memory maps (memory maps without segmentation or banking) became all the rage in microcomputers.

We’re not really using normal memory address lines with the ATmega. As I mentioned previously, it hasn’t got any on the outside where we can get at them, and we’re bit-banging our address signals. But because we’ve treated ports A and C as our address bus in this chapter, I named the extra two pins ADDR_BANK as an homage to the old days.

Control Bus

In the EPROM, you will recall, we pinned all the control lines to their useful settings and left them there. Because communications with the flash chip are a lot more complicated, we need to make those lines switchable. Because we haven’t got any other unused pins on the ATmega1284P, we’ll use more of port D’s pins. Yes, it will get a little complicated switching port D to do our bidding with all these not-really-related signals tied to it, but it’s manageable.

The first signal we’ll deal with is /E, which may also be labelled /CE on your datasheet. This is the chip enable. It determines whether the flash pays attention to address information coming in. Wire that to PD4 on the ATmega1284P. We had this signal on the EPROM too, but we just pinned it to the - bus to keep the IC enabled.

The next signal is /G for Gate, sometimes called /OE for Output Enable. Wire it to PD5 on the ATMega1284P. The EPROM had one of these too, and again, we pinned it to the - bus to keep the output enabled. Not so here.

The last signal is /W, for Write, which may be labelled /WE. Once again, the EPROM had this too, but it was useless to us so we pinned it to the + bus. We need that pin in this project, so wire it to pin PD6 on the ATmega1284P.

If your Flash chip is a 512k8 model, you may need to move the signal down one pin, so you can keep your A18 line with A16 and A17.

Congratulations. We’ve now used all but one GPIO pin on the ATmega1284P.

The Flash_Explorer Sketch

Flash is ... different. Although we can read with the same combination of /CS and /G being low, flash isn’t protected from stray data by virtue of requiring up to 48v on its programming pin. Flash is smart. When we want to write to it, we have to send a specific pattern of bytes, chosen by JEDEC as the least likely pattern to occur randomly, to specific locations on the flash (these are registers, like the Cestino’s PORT registers ), and we have to have our control signals set properly too.

In order to send any bytes to the flash, we first have to tell the flash to listen for them, by setting the /CS signal low, the /G signal high, setting the address and data lines how we want them, and pulsing the /W signal low. The flash will latch (store) our address signals on the falling edge of the /W pulse (as /W goes from +5v to 0v), and it will latch the data signals on the following rising edge, as /W goes from low to high. When we want to read data from the flash again, we have to raise /W and lower /G. We can’t get away with just pinning signals low anymore. We have to use them.

We’ll use this mechanism to send a command to the flash by sending a specific series of bytes, then send data to write, and we have to do this for each byte we want to write to the flash.

We send a different sequence of bytes to those registers if we want to erase one sector of the flash (which we won’t), or we want to erase the whole IC at once (which we will.) It does more, so it’s more complicated. Let’s get started.

Note

If it works right, this sketch ERASES THE FLASH. If you intend to use the device the flash IC came out of, you may want to disable the set_flash_signals, send_to_flash, program_flash, and erase_flash functions. Use at your own risk.

//preprocessor definitions.
#define ADDR_LSB PORTC
#define ADDR_MSB PORTA
#define DATA_READ PINB
#define DATA_WRITE PORTB
#define DATA_DDR DDRB
#define ADDR_BANK_CTRL PORTD
#define MAX_ADDRESS 0x03FFFF
#define FLASH_READ 0b0100
#define FLASH_WRITE 0b0010
#define FLASH_WAIT_WRITE 0b0110

The preprocessor definitions are pretty much the same, except for the addition of ADDR_BANK_CTRL, to represent both functions of port D. The max address is much higher, 3FFFF. There are also control bit settings define for FLASH_READ, FLASH_WRITE, and FLASH_WAIT_WRITE .

These are bit patterns to set to the /E, /G, and /W pins to read from the flash, write to the flash’s registers (this does not write data to the flash itself, only sends messages to the flash’s controller), and a safe state that won’t interrupt write sequences between pulses of the /W signal. What’s important about these three words is that they correspond to /W, /G, and /E, in that order, left to right, and that a 1 means the signal is high, or disabled, as these signals are active low. So FLASH_READ is /E enabled, /G enabled, /W disabled. FLASH_WRITE is /E enabled, /G disabled, and /W enabled, and FLASH_WAIT_WRITE is /E enabled, /G disabled, and /W disabled. This will make more sense when we use it in set_flash_signals .

String message = "Over hill, over dale, 
Thorough bush, thorough brier,Over park, over pale,
Thorough flood, thorough fire, I do wander everywhere.
- Shakespeare";

This piece of code defines the message we’ll write to the flash, when the time comes. It could be anything. I chose Shakespeare. Note that the backslashes () tell C++ that this line continues past the linefeeds.

 void set_flash_signals(uint8_t bits) {
  uint8_t temp = ADDR_BANK_CTRL & 0b00001111;
  temp |= (bits << 4);
  ADDR_BANK_CTRL = temp;
}

As I mentioned earlier, the 39SF020A requires that the /E and /W signals be enabled (low) and that /G be disabled (high) to send bytes and addresses to the flash. Furthermore, it requires that /G never go low during the write, or the write will abort. This is to protect the flash while the system it’s attached to is powered up. If a random control signal pattern happened to hit the write settings during powerup, a random message could be sent to the flash controller, and the flash could wind up in the wrong mode to be read from. Instead, the 39SF020A will abort the write if the signals aren’t set right through the entire process. Much different from our rather relaxed EPROMs. To handle this, we have the set_flash_signals() function .

First, we store ADDR_BANK_CTRL, masked with 0b00001111, into temp. This stores all the address settings and protects the RS232 lines from our code, but clears all the control signals. Placing this value in temp rather than ADDR_BANK_CTRL directly ensures that /G is not enabled during the write, even for a few dozen nanoseconds.

Next, we rotate the bits argument left by four spaces, and OR temp with them. This means that the three bit setting that was passed in the bits parameter now occupies bits 4-6, where our control signals are.

Finally, we set ADDR_BANK_CTRL to this new computed value, and we’re done.

void select_flash_address(uint32_t flash_address) {
  uint8_t temp;


  ADDR_LSB = 0;
  ADDR_MSB = 0;
  ADDR_BANK_CTRL &= 0b11110011;
  // Initialize address registers for safety.


  union {
    uint32_t address_uint;
    byte byte_array[4];
  } address_union;


  address_union.address_uint = flash_address;
  ADDR_LSB = address_union.byte_array[0];
  ADDR_MSB = address_union.byte_array[1];
  temp = ADDR_BANK_CTRL;
  temp &= 0b11110011;
  temp |= (address_union.byte_array[2] << 2);
  ADDR_BANK_CTRL = temp;
}

Select_flash_address works in much the same way select_EPROM_address did, save that it takes a uint32_t, an unsigned 32 bit integer as its argument, and the union breaks it into an array of four bytes instead of two. The fourth byte is unused, as are all but two bits of the third byte. Select_flash_address is also a lot more paranoid about initializing address registers for safety.

To set A16 and A17, we do the same kind of thing we did in set_flash_signals() . Set temp to ADDR_BANK_CTRL, and temp with 0b11110011 to preserve all the bits of ADDR_BANK_CTRL except our two address bits, which we zero out, rotate address_union.byte_array[2] left by two bits and or the result with temp, then set ADDR_BANK_CTRL to temp. This ensures that ADDR_BANK_CTRL does not change until we’ve computed its new value completely.

void send_to_flash(uint32_t address, byte data) {

  select_flash_address(address);
  DATA_WRITE = data;


  set_flash_signals(FLASH_WRITE);
  set_flash_signals(FLASH_WAIT_WRITE);
}

The send_to_flash() function sends bytes and addresses from the sketch to the flash IC’s controller. It doesn’t write data to the flash’s memory. That’s the job of program_flash() . It’s a communication routine. It’s simple, and fast.

The first thing we do is set the address lines using the select_flash_address function . We set the data lines directly to the DATA_WRITE register, and then we do a curious thing. We call set_flash_signals with FLASH_WRITE, setting /W low, and then set it right back to FLASH_WAIT_WRITE , which sets /W high again without changing /G. If you look at the datasheet for the SST 39SF020A, you’ll see that the /W (/WE, really. It means the same thing) pulse width has to be a minimum of 40ns (nano-seconds) long. Because our Cestino runs at 20MHz, each of its clock pulses is 50ns long, so even with these two commands back to back, there’s no way for the Cestino to generate a pulse that is too short. If you use a different, older, slower flash here, you may need to insert a delay.

You may have noticed that this function doesn’t set the flash signals to a known state on the way in. This needs to be handled by the calling function .

void program_flash(uint32_t address, byte data) {
  DATA_DDR = 0xFFFF;
  set_flash_signals(FLASH_WAIT_WRITE);


  send_to_flash(0x5555, 0xAA);
  send_to_flash(0x2AAA, 0x55);
  send_to_flash(0x5555, 0xA0);


  send_to_flash(address, data);

  DATA_WRITE = 0;
  DATA_DDR = 0x0;
  delay(1);


  set_flash_signals(FLASH_READ);
  //Set the flash signals back to FLASH_READ mode.
}

Program_flash is not a complicated function, but it has to be exactly right, or we won’t successfully program data to the flash IC. This particular version works with the 39SF020A, but read your datasheet and get the commands for your flash IC.

Program_flash sets the DATA_DDR to write mode, then sets the flash signals to FLASH_WAIT_WRITE. This turns /G off (high), turns /CE on. It then sends three values to three specific registers on the flash. These aren’t stored. They’re intercepted by the flash controller, and interpreted to mean “program the next data you get to the next address you get. So we send that data to that address. Programming flash takes some time. 30μs, to be exact, so I put a 1ms delay (1000μs) to take care of it.

void erase_flash() {
  DATA_DDR = 0xFFFF;
  set_flash_signals(FLASH_WAIT_WRITE);


  // for 39SF020A
  send_to_flash(0x5555, 0xAA);
  send_to_flash(0x2AAA, 0x55);
  send_to_flash(0x5555, 0x80);
  send_to_flash(0x5555, 0xAA);
  send_to_flash(0x2AAA, 0x55);
  send_to_flash(0x5555, 0x10);
  select_flash_address(0);


  DATA_WRITE = 0;
  DATA_DDR = 0x0;
  set_flash_signals(FLASH_READ);
}

Erase_flash works a lot like program_flash. It sets the DDR on the DATA port to write, sets the flash signals to FLASH_WAIT_WRITE , then sends a command sequence to the flash IC’s controller, in this case one that’s six bytes long to six specific registers. Again, check your data sheet for the right commands for your IC.

Erasing the flash takes time, about 20ms, according to the datasheet. This function doesn’t do that, so we have to make sure we wait after calling it. Your wait may be longer if you use a different flash IC .

void dump_flash(uint32_t start_addr, uint32_t end_addr ) {
  char address[12];
  uint32_t c;
  DATA_DDR = 0x0;
  set_flash_signals(FLASH_READ);


  sprintf(address, "0x%06lX :", start_addr);
  String data_line = String(address);


  for (c = start_addr; c <= end_addr; c++) {
    select_flash_address(c);//iterate through all the addresses


    if ((!(c % 0x40)) && c > 0) { //if we're at 64 characters
      Serial.println(data_line);
      sprintf(address, "0x%06lX :", c);
      data_line = String(address);
    }


    if (isprint(DATA_READ)) { //if the data on DATA_READ is printable
      data_line += (char)DATA_READ; //add it.
    } else {
      data_line += "."; //otherwise add a period.
    }
  }


  Serial.println(data_line);

You may notice that I’ve changed dump_flash somewhat from its ancestor, dump_eprom. Formatting lines so they line up is often an exercise in frustration with Arduino, because Arduino and/or the Arduino serial console can’t handle tabs correctly. To that end, I’ve used sprintf() , an old school C formatted output tool, to get the addresses all the same length. Sprintf doesn’t know anything about the String object, so the first thing we do is set it up an array of 12 chars for it to put its output in so we can get at it. After that, we create a uint32_t (32 bit unsigned integer) called c, which will be the index of our big loop and hold the address we want to read, set the DATA_DDR so the DATA port is in read mode, and call set_flash_signals with FLASH_READ to put the flash in read mode. Reading is just like it is with EPROMs. We can just set the signals and go.

Next, we generate the beginning of each line by calling sprintf() with a pattern 0x%06lX, followed by a space and a colon. There’s a mixture of stuff in here. 0x is literal, so our addresses are properly denoted as hex addresses. You’ll get the 0x in your output. %06yadda tells sprintf to make every address six digits long, and to prepend zeros as required. The l tells sprintf to expect a long integer, and X tells it to output the address in hex. The final space and colon are also literal, and will be in the output. Short version: sprintf formats start_addr into numbers that look like this: 0x000001: and puts that value into its 12 character array. We promptly put that character array into a String object called data_line . There. We have the first line of the dump.

Next, we iterate through all the addresses from start_addr to end_addr, incrementing c each time. We call select_start_address with c as its argument.

Then we check to see if our data line is full, at 0x40 or 64 items of data. If it is, we Serial.println data_line, then go through the sprintf process again, this time with c as the argument, and set data_line to it, starting a new data line.

No matter what’s happened prior, either the creation of a new data line, or nothing, the next thing we do is see if the DATA_READ register is a printable character with the c isprint function. If it is, we append the value of DATA_READ, cast as a char (character) to the data_line string. If it’s not, we append a period to hold the space.

When we reach the end of our loop we print data_line, to ensure that even if our end_addr was not on a 64-bit boundary, we get our data.

void setup() {
  Serial.begin(115200);


  DDRC = 0b11111111;
  DDRA = 0b11111111;
  DDRD = DDRD | 0b11111100;
  ADDR_BANK_CTRL = FLASH_BUS_READ;
  Serial.println(" Running");
}

Setup is very similar to the one in EPROM_Explorer. It does configure port D for its role as ADDR_BANK_CTRL , leaving the last two bits set however they were, and it sets ADDR_BANK_CTRL to FLASH_BUS_READ . Yes, this clobbers RS232 communications. Briefly. Once. We can live with it. It then sends us a message to tell us we’re running.

void loop() {
  Serial.println("Dumping Flash.");
  dump_flash(0x00, MAX_ADDRESS);
  Serial.println("Erasing Flash.");
  erase_flash();
  delay(100);
  Serial.println("Done Erasing Flash.");


  Serial.println("Programming Flash.");

  for (int c = 0; c <= message.length(); c++) {
    program_flash(c, message.charAt(c));
  }
  Serial.println("Dumping Flash Again.");
  dump_flash(0x00, 0xff);
  Serial.println(" Done.");
  while (0 == 0) {};
}

As with EPROM_Explorer, loop()exists to call the functions we've built. It dumps the entire flash from 0 to MAX_ADDRESS, then wipes the flash. It then waits 100ms to allow the flash to complete its erasure, then programs the first few dozen bytes of the flash with the Shakespeare quote we set in message, above, then dumps the first 64 bytes of the flash to show that we actually got a successful write.

As always, the entire sketch is here, for your convenience.

// Flash_Explorer v2.0
// Revised to work properly with 39SF020A.
//----------------------------------------------------------
// This sketch dumps the contents of the flash (an old BIOS
// flash in this case), then erases it and programs a bit of
// Shakespeare at the beginning, then dumps the first 255
// bytes of the flash again to show that it's been erased
// and reprogrammed.
// ---------------------------------------------------------
//Hardware:
//For a 39SF020A or similar flash IC:
//ATmega Pin    Name   Port name Flash Pin  Name   Func
//         1    PB0    DATA_READ        13  D0     DATA
//         2    PB1    DATA_READ        14  D1     DATA
//         3    PB2    DATA_READ        15  D2     DATA
//         4    PB3    DATA_READ        17  D3     DATA
//         5    PB4    DATA_READ        18  D4     DATA
//         6    PB5    DATA_READ        19  D5     DATA
//         7    PB6    DATA_READ        20  D6     DATA
//         8    PB7    DATA_READ        21  D7     DATA
//
//        16    PD2    BANK_ADDR        02  A16    ADDRESS
//        17    PD3    BANK_ADDR        30  A17    ADDRESS
//        18    PD4    CTRL             22  /E     Chip Enable
//        19    PD5    CTRL             24  /G     Gate
//        20    PD6    CTRL             31  /W     Write Enable
//
//        22    PC0    ADDR_LSB         12  A0     ADDRESS
//        23    PC1    ADDR_LSB         11  A1     ADDRESS
//        24    PC2    ADDR_LSB         10  A2     ADDRESS
//        25    PC3    ADDR_LSB         09  A3     ADDRESS
//        26    PC4    ADDR_LSB         08  A4     ADDRESS
//        27    PC5    ADDR_LSB         07  A5     ADDRESS
//        28    PC6    ADDR_LSB         06  A6     ADDRESS
//        29    PC7    ADDR_LSB         05  A7     ADDRESS
//
//        33    PA7    ADDR_MSB         03  A15    ADDRESS
//        34    PA6    ADDR_MSB         29  A14    ADDRESS
//        35    PA5    ADDR_MSB         28  A13    ADDRESS
//        36    PA4    ADDR_MSB         04  A12    ADDRESS
//        37    PA3    ADDR_MSB         25  A11    ADDRESS
//        38    PA2    ADDR_MSB         23  A10    ADDRESS
//        39    PA1    ADDR_MSB         26  A09    ADDRESS
//        40    PA0    ADDR_MSB         27  A08    ADDRESS


// ----------------------------------------------------------
// James R. Strickland
// ----------------------------------------------------------


//preprocessor definitions.
#define ADDR_LSB PORTC
#define ADDR_MSB PORTA
#define DATA_READ PINB
#define DATA_WRITE PORTB
#define DATA_DDR DDRB
#define ADDR_BANK_CTRL PORTD
#define MAX_ADDRESS 0x03FFFF
#define FLASH_READ 0b0100
#define FLASH_WRITE 0b0010
#define FLASH_WAIT_WRITE 0b0110


// This is the message we'll be programming into the flash.
// You can change it if you want. The marks mean "line
// continues after the line break."
String message = "Over hill, over dale,
Thorough bush, thorough brier,Over park, over pale,
Thorough flood, thorough fire, I do wander everywhere.
- Shakespeare";


// ----------------------------------------------------------
// set_flash_signals(uint8_t bits)
// ----------------------------------------------------------
// This function sets the signal bits of ADDR_BANK_CTRL (Bits
// 4-6) to the value of the first three bits passed in the
// bits field. ADDR_BANK_CTRL is a port register and the state
// of the signals on it needs to not have unknown transitional
// states, so we compute the new byte with temp and set the
// final value to ADDR_BANK_CTRL.
// ----------------------------------------------------------
void set_flash_signals(uint8_t bits) {
  uint8_t temp = ADDR_BANK_CTRL & 0b00001111;
  //mask the control bits off of ADDR_BANK_CTRL
  //and store the result in temp.
  
  temp |= (bits << 4);
  // OR temp with bits, rotated left by 4 places.


  ADDR_BANK_CTRL = temp;
  //set ADDR_BANK_CONTROL to the computed value.
}
// ----------------------------------------------------------
// select_flash_address(address)
// ----------------------------------------------------------
// This function sets ADDR_LSB, ADDR_MSB, and the first two
// bits of ADDR_BANK_CTRL to the uint32_t (four byte unsigned
// int) address passed to it. These registers are wired to the
// flash IC's address pins.
//
// To break the address down into single byte chunks, we use
// a union, which essentially maps two variables (a 4 byte
// unsigned int and an array of 4 bytes, in this case) to
// one area of memory. Then we can peel off the bytes as we
// want them. Note that this code is endian-sensitive. If
// you run it on some future big-endian Arduino, you'll need
// to change the byte order.
//
// We have to extract the lowest two bits of the third byte
// and rotate them two spaces to the left, then AND them in
// to the ADDR_BANK_CTRL register, since we don't want to
// disturb the signal lines or the RS232 lines.
// ----------------------------------------------------------


void select_flash_address(uint32_t flash_address) {
  uint8_t temp;


  ADDR_LSB = 0;
  ADDR_MSB = 0;
  ADDR_BANK_CTRL &= 0b11110011;
  // Initialize address registers for safety.


  union {
    uint32_t address_uint;
    byte byte_array[4];
  } address_union;
  //Declare the union we're going to use for address processing.


  address_union.address_uint = flash_address;
  //Set address_union.address_uint to the address we were given.


  ADDR_LSB = address_union.byte_array[0];
  //set the ADDR_LSB register to the first byte of the union.


  ADDR_MSB = address_union.byte_array[1];
  //set the ADDR_MSB register to the second byte of the union.


  temp = ADDR_BANK_CTRL;
  temp &= 0b11110011;
  temp |= (address_union.byte_array[2] << 2);
  ADDR_BANK_CTRL = temp;
  // set bits 3 and 4 of ADDR_BANK_CTRL to the first two bits
  // of the third byte in the union.
}


// ----------------------------------------------------------
// send_to_flash(address,data)
// ----------------------------------------------------------
// This function sets the ATmega's data port to write mode selects
// the address pin settings for the flash IC, and writes (data) to
// the data port. We use it to pass commands to the flash IC.
//
// Note that this does not directly write to the flash's storage.
// This function actually talks to the flash's built-in controller,
// but no data is written (programmed) to the flash without passing
// the flash a specific sequence of commands.
//
// NOTE BENE: This function assumes that the flash signals will be
// set to FLASH_WAIT_WRITE when it enters. This must be handled by
// the calling function, since changing /OE (gate) between
// transmitted bytes aborts command sequences.
//
// First we set the address lines to the address by calling
// select_flash_address().
// Then we set the data lines to the data we're given.
// Then we pulse the /W signal low by calling set_flash_signals with
// FLASH_WRITE, and then with FLASH_WAIT_WRITE.
// And then we're done.
// ----------------------------------------------------------
void send_to_flash(uint32_t address, byte data) {


  select_flash_address(address); //Set the address lines
  DATA_WRITE = data; //Set the data lines


  set_flash_signals(FLASH_WRITE);
  set_flash_signals(FLASH_WAIT_WRITE);
  //pulse /W low. It only has to go low a few nanoseconds.
  // Nothing on the cestino happens in less than 50, so we're
  // not going too fast for the 85ns 39SF020A. If your flash
  // is slower, you may have to add a delay.
}


// ----------------------------------------------------------
// program_flash(address,data)
// ----------------------------------------------------------
// To actually store (program) data onto the flash, we have to
// send it a specific pattern of addresses and data bytes before
// we send it the address we want and the byte of data we want
// stored there.
// To that end, we set DATA_DDR to output so we can write data,
// then call set_flash_signals to set it to FLASH_WAIT_WRITE
// mode. This disables /OE (gate) but does not enable /W (write).
// We call send_to_flash four times. The first three tell the
// flash controller what we want to do, and the fourth call
// gives it our address and data.
// Then clear the DATA_WRITE pins and set DATA_DDR back to
// read mode. Delay 1ms to because flash is slow and we're
// not polling its status line to tell when it's done. Set the
// flash's control signals to FLASH_READ mode and we're done.
// ----------------------------------------------------------
void program_flash(uint32_t address, byte data) {
  DATA_DDR = 0xFFFF;
  set_flash_signals(FLASH_WAIT_WRITE);


  //for 39F0020. These may be different for other flash ICs.
  send_to_flash(0x5555, 0xAA); //Tell flash to store data
  send_to_flash(0x2AAA, 0x55);
  send_to_flash(0x5555, 0xA0);


  send_to_flash(address, data); //Give flash our data and address.

  DATA_WRITE = 0; //clear the DATA_WRITE pins.
  DATA_DDR = 0x0; //set DATA_DDR back to read mode.
  delay(1); //Delay one ms because flash is slow.
  //We could probably save a few microseconds monitoring the
  //status bit, but that'd be a lot more code.


  set_flash_signals(FLASH_READ);
  //Set the flash signals back to FLASH_READ mode.
}


// ----------------------------------------------------------
// erase_flash()
// ----------------------------------------------------------
// If you guessed that erasing the entire flash would be another
// sequence of commands sent to specific addresses, you guessed
// right. That's what this function does.
//
// Set DATA_DDR to write mode, and set the flash's signals to
// FLASH_WAIT_WRTTE - which is /W disabled, /OE disabled, and
// /CE enabled. Call send_to_flash 6 times to give it the
// command sequence to erase the entire flash.
// Then clear DATA_WRITE, set DATA_DDR to read mode, and
// set the flash's signals back to FLASH_READ.
//
// NOTE BENE: Erasing the flash takes a few ms, and this
// function doesn't include the wait, so the calling function
// needs to do it. The flash returns gibberish if you try to
// read it while it's erasing. It goes without saying that
// writes fail, too.
// ----------------------------------------------------------
void erase_flash() {
  DATA_DDR = 0xFFFF;
  set_flash_signals(FLASH_WAIT_WRITE);


  // for 39SF020A
  send_to_flash(0x5555, 0xAA);
  send_to_flash(0x2AAA, 0x55);
  send_to_flash(0x5555, 0x80);
  send_to_flash(0x5555, 0xAA);
  send_to_flash(0x2AAA, 0x55);
  send_to_flash(0x5555, 0x10);
  select_flash_address(0);
  //These are specific to the 39SF020A. Other flash chips have
  //different registers that are similarly unlikely patterns
  //to occur by accident.


  DATA_WRITE = 0;
  DATA_DDR = 0x0;
  set_flash_signals(FLASH_READ);
  //Clear data_write and set DATA_DDR back to read mode. Then
  //set the flash signals back to FLASH_READ mode.
}


// ----------------------------------------------------------
// dump_flash(start_addr,end_addr)
// ----------------------------------------------------------
// This function dumps the contents of the flash to the console.
// data_line is an Arduino String object. We initialize it empty.
// Iterate through all the addresses, start_addr to end_addr,
// inclusive.
//
// Start by creating a char array for the line's address, a
// uint32_t called c to iterate through the addresses with,
// then set DATA_DDR to read mode, and set the flash's signals
// to FLASH_READ mode.
//
// We're going to use the sprintf function to properly format our
// addresses, since Arduino's tabs don't work properly. Sprintf
// takes a pattern of text and variable display information,
// and fills the variables in based on that pattern. In this case,
// the pattern is 0x%06lx. The first two characters, 0x, are
// literal. You'll see them in the output. %06lx tells sprintf
// "this is a 6 digit number. Fill in preceding zeros if you
// need to. The variable will be a long integer (32 bits in
// the Arduino implimentation we're using), and it should be
// displayed in hexidecimal.
//
// The normal C printf would send that to the console, but we'd
// like to put it in a String object. To do that, we use sprintf,
// pass it the char array address[12] Once it's there, we pull
// it into the String object data_line, where it makes up the
// beginning of the line.
//
// After that, we iterate through the address range we're given,
// and add the character value of DATA_READ to the string if it's
// printable, and a . if it's not. When data_line is 64 characters
// long, we serial.println it, clear it, and set its beginning
// the same way we did before, then get back to work.
// When we're done, we serial.println data_line once more to
// get any data that might have been in an incomplete data line.
// ----------------------------------------------------------
void dump_flash(uint32_t start_addr, uint32_t end_addr ) {
  char address[12];
  uint32_t c;
  DATA_DDR = 0x0;
  set_flash_signals(FLASH_READ);


  sprintf(address, "0x%06lX :", start_addr);
  String data_line = String(address);


  for (c = start_addr; c <= end_addr; c++) {
    select_flash_address(c);//iterate through all the addresses


    if ((!(c % 0x40)) && c > 0) { //if we're at 64 characters
      Serial.println(data_line);
      sprintf(address, "0x%06lX :", c);
      data_line = String(address);
    }


    if (isprint(DATA_READ)) { //if the data on DATA_READ is printable
      data_line += (char)DATA_READ; //add it.
    } else {
      data_line += "."; //otherwise add a period.
    }
  }


  Serial.println(data_line);

}

// ----------------------------------------------------------
// setup()
// ----------------------------------------------------------
// Set up serial and port data directions. Tell the user we're
// running. Runs once.
// ----------------------------------------------------------


void setup() {
  Serial.begin(115200);


  DDRC = 0b11111111; //ADDR_LSB - Address LSB Port
  DDRA = 0b11111111; //ADDR_MSB - Address MSB Port
  DDRD |= 0b11111100; //ADDR_BANK_CTRL Bank address and control port.
  Serial.println(" Running");
}


// ----------------------------------------------------------
// loop()
// ----------------------------------------------------------
// Mostly we just call functions here.
// Dump the entire flash
// Erase the flash
// Program the flash.
// Dump the first 64 characters of the flash again so we can
// see what we programmed. Then do nothing forever.
// ----------------------------------------------------------
void loop() {
  Serial.println("Dumping Flash.");
  dump_flash(0x00, MAX_ADDRESS); //Dump the entire flash.
  Serial.println("Erasing Flash.");
  erase_flash(); //wipe the flash.
  delay(100);
  Serial.println("Done Erasing Flash.");


  Serial.println("Programming Flash.");

  for (int c = 0; c <= message.length(); c++) {
    program_flash(c, message.charAt(c));
  }
  Serial.println("Dumping Flash Again.");
  dump_flash(0x00, 0xff);
  Serial.println(" Done.");
  while (0 == 0) {};
}

Output

I used a 39SF020A BIOS flash I made. Here’s what’s on it.

Running
Dumping Flash.
0x000000 :..Xi 8088 BIOS, Version 0.8. Copyright (C) 2010 - 2012 Sergey Ki
0x000040 :selev..Distributed under the terms of the GNU General Public Lic
0x000080 :ense.....none.: .; .Main Processor:       .Mathematics Co-
0x0000C0 :processor:  .Intel 8088 '78..WARNING: This CPU does not disable
0x000100 : interrupts after loading segment registers!...Intel 8088 '81 or
0x000140 : later, or older Intel 80C88...Harris / Intersil / newer Intel 8
0x000180 :0C88...NEC V20...Intel 8087...Display Adapter Type:    .EGA/V
0x0001C0 :GA (Video BIOS Present)...CGA...MDA or Hercules...Floppy disk dr
0x000200 :ives:     Drive 0: .; Drive 1: .360 KB, 5.25".1.2 MB, 5.25".
0x000240 :720 KB, 3.5".1.44 MB, 3.5".2.88 MB, 3.5".PS/2 Aux Device (Mouse)
0x000280 ::  .Present...Absent...Serial Ports:        .COM.Parall
0x0002C0 :el Ports:       .LPT.Testing RAM (ESC to skip): ...ERROR:
0x000300 : Faulty memory detected at ..Total Conventional RAM:   .Reserv
0x000340 :ed for EBDA:     .Available Conventional RAM: . KiB...Boot
0x000380 :failed, press any key to try again......No ROM BASIC...Found BIO
0x0003C0 :S extension ROM at .0, initializing......Booting OS......ERROR:
0x000400 :RTC battery is bad...ERROR: NVRAM checksum is invalid, loading d
0x000440 :efault values to NVRAM...Press F1 to run NVRAM setup..........NV
0x000480 :RAM Setup Menu:..f - Change first floppy type..g - Change second
0x0004C0 : floppy type..p - Print current settings..w - Save changes and e
0x000500 :xit..q - Exit without saving changes....Enter your selection: ..
0x000540 :.Floppy Setup Menu:..0 - No floppy..1 - 360 KB, 5.25"..2 - 1.2 M
0x000580 :B, 5.24"..3 - 720 KB, 3.5"..4 - 1.44 MB, 3.5"..6 - 2.88 MB, 3.5"
0x0005C0 :..q - Return to the main menu....Enter your selection: ...$.2.@.
0x000600 :M...[....6ff∼ff.|``|ff|.|ff|ff|.∼``````.8lllll..∼``|``∼...∼<∼...
0x000640 :<f...f<.ffn∼vff.<fn∼vff.flxpxlf..6fffff.........fff∼fff.<fffff<.

[Lots of machine readable code and empty lines cut]

This is what a PC BIOS looks like. Once again, it’s an open source BIOS that I burned there with my EPROM/flash burner. Yours will probably have much sterner copyright warnings.

0x0062C0 :[email protected].$V....0.^....<it.<It.<ru..e.<Ru..^....
0x006300 :....X.IOCHK NMI detected. Type 'i' to ignore IOCHK NMIs, or 'r'
0x006340 :to reboot.......................................................
0x006380 :................................................................

[Lots of machine readable lines cut]

0x007F00 :...................................P..@...... .. ....u......!...
0x007F40 :!. . .&k..X..........PSQR..@....>...tj.................t.....<v.
0x007F80 :......R...Q.uH...J.uA............< s.. .3.u*..8.r....&.u......u.
0x007FC0 :..8.r......Z.....ZY[X........R1.....Z..%.........[...12/26/12 ..

[Many, many empty lines cut]

Erasing Flash.
Done Erasing Flash

We've erased the flash.

0x03FFC0 :................................................................
Erasing Flash.
Done Erasing Flash.
Programming Flash.
Dumping Flash Again.
0x000000 :Over hill, over dale, Thorough bush, thorough brier,Over park, o
0x000040 :ver pale, Thorough flood, thorough fire, I do wander everywhere.
0x000080 :- Shakespeare...................................................
0x0000C0 :................................................................


Done.

The Shakespeare quote got written. We can see it in the dump here. It's from A Midsummer Night's Dream.

Credit Where Credit Is Due

I’m certainly not the first person to read EPROMs and read or write flash from an Arduino. It’s one of those standard projects that pops up every couple years when someone (like me) does it from scratch again. I saw those projects. I knew it could be done.

The hanked breadboard wiring technique is something I cribbed just recently from here: http://forum.6502.org/viewtopic.php?f=4&t=3329 This guy’s doing amazing (and fast) projects on massive breadboards, and he’s getting them to work, despite the naysayers.

The XT-IDE Universal Bios, which I burned onto the 27128 and then showed in the Output section of the EPROM explorer is here: https://www.lo-tech.co.uk/wiki/XTIDE_Universal_BIOS

Sergey Malinov’s Xi (PC XT clone) BIOS is here, at http://www.malinov.com/Home/sergeys-projects/sergey-s-xt#TOC-What-is-required-to-build-a-functioning-computer - all the way at the bottom in the files marked “bios-0.x.x.tar.gz”. It’s a complete PC XT BIOS for Sergey's Xi PC-on-a-board project, and it’s open source. Awesome.

Further

The obvious place to go with this project would be a general purpose programmer for EPROMs, EEPROMs, and flash. There are a few problems that you’d need to solve to get there.

First: we have not in any way guaranteed the integrity of the data we’re reading and writing to the device. For looking at strings, that’s not a big problem. For machine readable data (software), it’s critical. Fortunately, this is a software problem. My thought here is to reach back to 1980 and dig up Kermit. Not the one Disney owns. The other one. Kermit is a data transfer protocol written by Frank DaCruise in 1980 to transfer information from microcomputers to the minicomputers most colleges had by that point. It is designed for noisy phone modem/RS232 connections not unlike the Cestino’s RS232 to USB connection, it is well documented. It is simple enough to implement on even the smallest microcomputer platforms of the day, which were far less capable than the Cestino. It’s been ported to a huge variety of platforms including most modern desktop OSs and is part of many other terminal emulation packages. Best of all, it’s open source.

The next problem would be a place to store the binary to be burned onto the device in question, be it EPROM, flash, or whatever. You could add an I2C flash to the Cestino fairly easily, but it would take two pins out of PORT C, which would complicate the design. You could add an SD card interface using SPI, which would take three pins out of port B. None of these solutions are unworkable. The sketch would simply have to be careful about when it reads, when it writes, and to return the pins to a known state, just as we did with PD0 and PD1 in this project. You could add an IO expander, like the MPC23S18 that could give you 16 IO lines in exchange for the same three SPI lines plus a chip select line for each SPI device. It would complicate the sketch further, but not that badly. It might be slower, but we’re not in that big a hurry.

The most complicated part, for me at least, would be the power supply needed for the various programming voltages. I would probably start with a standard ATX power supply. These are plentiful in my junk box, and people keep giving me more. They produce some, but not all, of the needed voltages, and plenty of current to convert to the rest. It wouldn’t need to be the most modern or largest to have plenty of resources for an EPROM/flash programmer.

Connecting the correct programming voltages to the correct pins is another complicated problem. The solution in my head is to bring all the board’s lines out to a band of pin headers, and then have a separate configuration board that connects the pin headers together, one for each type of IC you intend to program, and here the JEDEC standard helps a lot. You could have a single configuration board for all JEDEC 28 pin EPROMs, one for all 32 pin flashes, and so on. Old fashioned? Sure. My Ebay EPROM/Flash programmer does all that in software. Will it work even after the platforms it was designed to work with are themselves junkbox refugees? Yes.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.226.181.57