The Macintosh Floppy Emu works! No, not the flightless Australian bird, but the SD card 800K floppy drive emulator for classic Macintosh computers. I’ve been tinkering with this project for a while now, and wrote about it here several times before. Today I finally got read-only floppy emulation working from an SD card, in a rough approximation of the originally intended design. That makes it possible to download disk images of classic Mac software from the web, copy them to an SD card, and load them onto a Mac Plus or other Macintosh using the Floppy Emu.

Pictured above is the Floppy Emu hardware. Clockwise from the top-left are a custom CPLD board, an Adafruit ATmega32u4 board, and an Adafruit 1.8-inch TFT display with micro-SD card holder underneath. You can just barely see the SD card peeking out under the left edge of the display. In the middle of it all is the big red disk insert button.

The CPLD implements all the timing-sensitive functions and communication with the Mac, but its behavior is simple. The ATmega AVR microcontroller is the brains of the operation. It uses SdFatLib to read 512-byte sectors from a disk image file on the SD card, then passes the bytes one at a time to the CPLD at a speed that mimics a normal external floppy drive. Due to the design of the Macintosh IWM floppy controller, it’s not possible to pass data at a faster bit rate than a real floppy would, although the emulated drive could theoretically be faster overall if its track-to-track step times were faster. In practice I’ve found it difficult to match the performance of a real floppy drive. In its current state the Floppy Emu is actually somewhat slower than the real thing, but still fast enough to keep the floppy controller happy.

Signal Synchronization

Everything was nearly working two days ago, and I had a floppy emulator that worked much of the time, but not 100%. It worked enough so that I could often mount an emulated floppy disk in the Finder, but if I tried to open any of the files on the floppy it would fail with I/O errors. It took an agonizingly long time to isolate the last few bugs, the worst of which proved to be a sort of clock domain synchronization problem when writing to drive registers. The Mac performs a write to the floppy drive’s internal registers by putting the register address and data on the bus, and then asserting the LSTRB signal for a short time. These registers are emulated in the CPLD, but there’s no particular relationship between the timing of the LSTRB signal and the CPLD clock. One of the registers is STEP, and when a zero is written to the register, it moves the drive forward or back one track. My original code looked something like this:

always @(posedge clk) begin // was there a positive edge on lstrb? if (enable == 1 && reg == REG_STEP && lstrb == 1 && lstrbPrev == 0) begin track <= track + 1; end end

The trouble was that the CPLD didn’t always see LSTRB cleanly transition between 0 and 1. Occasionally the CPLD clock would sample LSTRB just as its value was changing, and then funny things would happen. The signal would appear to change from 0 to 1 to 0 to 1 very quickly, causing a double-trigger of the code above, and stepping two tracks when it should only have stepped one. My fix was this:

always @(posedge clk) begin // left shift the current lstrb value into a history buffer lstrbHistory <= { lstrbHistory[4:0], lstrb }; end always @(posedge clk) begin // was there a positive edge on lstrb? if (enable == 1 && reg == REG_STEP && lstrbHistory == 6'b011111) begin track <= track + 1; end end

Looking back on it now, the problem seems fairly clear, but it took me ages to discover what was going wrong.

On-the-Fly Sector Retrieval

I’ve examined the designs of a few other floppy disk emulators, and they all use a sensible technique in which an entire track of data is read into a RAM buffer, and then the sectors in that track are continuously “played” from the RAM buffer, over and over until the computer selects a new track. Since everything is in RAM, there are no sector-to-sector delays needed to fetch new data from from the memory card. The only downside to the technique is that it requires a RAM buffer large enough to hold an entire track’s worth of sectors at once. For the Macintosh that’s 6K, plus about 1.5K more for other buffers and SdFatLib. I wanted to use an 8-bit AVR microcontroller, but few of them have 8K+ of RAM, and nothing that I had handy has more than 2K.

To fit the limited memory available, I used an on-the-fly sector retrieval technique instead of the track-at-a-time technique. This technique only requires a single 512 byte buffer, enough for one sector. After the data bytes from a sector have been sent to the Mac, the AVR loads the next sector from the SD card, which takes about 2 milliseconds. On a real floppy the sector-to-sector padding is only about 0.25 milliseconds, but it turns out that the Mac is tolerant of much longer inter-sector delays as long as you keep sending it $FF sync bytes between sectors.

How much slower does this make Floppy Emu data transfers versus a real floppy? The numbers say about 16%. Assuming 10 sectors per track, 752 bytes per sector after GCR encoding, 2 microseconds per bit, then it takes about 122 milliseconds to transfer all the data in a track from a real floppy. Add an extra 2 ms delay between each sector for SD card access, and the total time increases to 142 ms.

In actual use, however, Floppy Emu appears closer to 3x slower than a real floppy disk. Using Disk Copy 4.2, I was able to read an entire 800K floppy in 41 seconds, and an emulated version of that same floppy in 2 minutes 10 seconds. As best as I can tell, the difference is due to some kind of bug that’s triggering the Mac’s retry mechanism, rather than the 20% SD card access overhead. The TFT display shows the emulated active track and side in real-time, so I can see that after every few tracks read during disk copying, the drive seeks down to track 0, then all the way back up to the track where it left off. This looks like some kind of mechanism for coping with unexpected data: the Mac concludes the drive isn’t where it thought it was, so it resyncs by returning to a known location (track 0) and then continuing. It never reports any errors to the OS or the application, though, so I’m not sure how I can determine what’s causing this behavior.

Further Steps

Encode On-the-Fly: The disk image data that’s stored on the SD card is pre-encoded using the GCR tool that I previously wrote for Plus Too. Now that I’ve got a microcontroller that can run plain old C code, it should be easy to do the GCR encoding on-the-fly in the microcontroller instead. That way the disk images on the SD card would be the exact same disk image files used with popular Mac emulators like Mini vMac.

Disk Image Selection: In its current form, there’s no UI for selecting which disk image file to use from the SD card. It simply looks for “floppy.dsk” and that’s it. It would be nice to have a simple UI for navigating the directories on the card, determining which disk image files are in a supported format, and selecting a file to use.

USB: The ATmega32u4 microcontroller that I’m using is USB-capable. Instead of loading the disk image data from an SD card, maybe it could be loaded from an attached PC over USB? I’m not sure it would be fast enough, and maybe it would be more hassle than it’s worth, but it’s an interesting idea.

Writable Floppy Emulation: The current technique is unsuitable for writes. It’s OK to be slower than a real floppy during reads, because Floppy Emu decides when to send the next sector’s worth of data. But for writes there’s no flow control mechanism– the emulator needs to receive, decode, and write to the SD card fast enough to keep up with the Mac, or else it will fail. That’s not possible with the on-the-fly sector method. To support writing to the emulated floppy, it will require an AVR with a large capacity RAM using the track-at-a-time method. Incoming data will be buffered in RAM, and then a full track of data will be written to the SD card during the period while the emulated drive is stepping to the next track.