MFM Encoding
There have been many encoding methods devised over the years, some can be applied to floppy disks and help reduce the magnetic flux transitions within this type of media. Common methods include FM, MFM, and MMFM. Each with its own pros and cons. Here we'll talk specifically about MFM encoding and how it pertains to your average 3.5" Floppy Disk.
Some facts:
- Every even bit is a true bit of original data.
- Every odd bit is a clock signal. Although the odd bits are optimized (more on this later).
- The resulting signal doubles in size (a u8 byte will need a u16 to encode).
- Although the signal may be double the size, the pulses it generates are fewer.
Table of Contents
Pulses
When we talk about pulses, we're referring specifically to the timing of a flux transition. Floppy disks work by magnetizing a section of a physically spinning disk and the time between magnetic transitions indicate how many zeros there are in the signal. For example
- A short transition (S) will nominally take 2us, and represents 0b10
- A medium transition (M) will nominally take 3us, and represents 0b100
- A long transition (L) will nominally take 4us, and represents 0b1000
# 2.5 microseconds (in clock cycles) T_25 = 2.5 * F_CPU_HZ / 1000000 # 3.5 microseconds (in clock cycles) T_35 = 3.5 * F_CPU_HZ / 1000000 def read_symbol: counter = 0 while data_pin is low: counter++ while data_pin is high: counter++ if counter < T_25: return 0 else if counter < T_35: return 1 else return 2
In this example, we are counting clock cycles. The timing needs to be very precise and you'll have to be very familiar with the clock speed of your particular device.
A cautionary tale: check the generated assembly (yeah, I know that's a bit intense) and make sure the compiler didn't inject 10,000 lines of boundary checks. Simple things like that can be enough to ruin your timing. When I wrote my original controller, I had to bust out some basic assembly to get it perfect.
The Algorithm
At it's core, the algorithm to produce an MFM encoded signal
works as follows:
to encode x, y, z
the resulting signal would be generated
like (x, x nor y, y, y nor z, z, ...)
Implementing this may seem easy, but there's a catch! The last bit of the signal will depend on the subsequent bit of the next signal. What this means in practice is that you can't really encode one byte at a time. Instead, you must stream the bits in a sequence, letting all of them flow together for your entire payload.
I think this is why most (all?) floppy disk controllers require you to write an entire sector at once. You can't just change the 3rd byte in place and call it good because that might have a cascading effect, ruining the timing of downstream bits. Instead, you must rewrite the whole sector at once to keep the timing information consistent.
The Clock Signal
Here's a fun observation! If we we are encoding, say, 0x1
the resulting signal will be
0101010101010010
. The first bit is a 0! This can't translate nicely into a S/M/L pulse and so
the resulting data will be shifted by 1 bit, ultimately offsetting your clock signal and ruining the entire
payload.
The leading bit is important and I think this is why the floppy disk format require your first byte to be
either 0xFA
or 0xFB
because it will ensure your clock signal doesn't get messed up.
If you find the data is not being parsed properly, try leading with either 0xFA
or 0xFB
(or really anything that causes the signal to begin with a 1) and see if that fixes your payload.
MFM Pulse Encoder
Enter a comma delimmited list of hexadecimal values. For example: 0xA1, 0x13, 0x37. The encoded signal and accompanying pulses will be rendered below.
MFM Pulse Decoder
Enter a sequence of pulses in the form of S, M, or L. For example: LMSSMMSMMMSMMSLSSS. The value parsed from the signal will be rendered below.
Further Reading
Next, let's check out some FUNCTIONS that can be implemented for your floppy drive.