Computing History · Microprocessor Revolution
MFM Encoding and the Fixed Geometry of a Floppy Sector
Why magnetic media store clock bits alongside data, how a sector is framed by address marks and gaps, and how FAT12 packs a cluster chain into twelve-bit entries.
A floppy drive has no address lines. The controller cannot ask for block 1,203; it can only ask the drive to step to a track and read whatever passes under the head. Everything that a filesystem thinks of as a numbered block must therefore be found by reading the stream and looking for a marker that says a block begins here. That requirement, plus the physics of magnetic recording, determines almost the entire layout of a formatted diskette. It is also the reason a controller is a distinct chip with its own registers and status bits rather than a handful of latches, and why the firmware’s disk service is a translation layer rather than a thin wrapper.
Why raw bits cannot be written directly
A disk surface stores flux transitions, not levels. Reading the head produces a pulse each time the recorded magnetisation flips direction, and the controller must turn those pulses into bits. If the data were written as raw bits with a one represented by a transition and a zero by no transition, a long run of zeros would produce no pulses at all, and the controller’s phase-locked loop would drift, losing track of where bit boundaries are. A long run of ones would produce pulses so closely spaced that they might merge.
Encoding schemes exist to guarantee that transitions occur often enough for the clock to stay locked, and far enough apart that they are distinguishable. Frequency modulation, used on single-density disks, writes a clock transition at the start of every bit cell and a data transition in the middle of a cell for a one. That guarantees a transition at least once per cell, but it also pulls the closest possible spacing between transitions down to half a cell, so every cell must be at least twice as long as the media’s resolution would otherwise allow.
Modified frequency modulation removes the redundant clock transitions. A data bit of one is written as a transition in the middle of the cell and no transition at the boundary. A data bit of zero is written with a boundary transition only if the previous data bit was also zero.
Count possible transition positions on a grid of half-cells, which is the granularity both schemes write at. FM uses every grid position, so the closest two transitions can be is one grid step and the furthest is two. MFM never writes in adjacent grid positions, so its minimum spacing is two steps and its maximum is three. Same grid, same head, same surface, and the closest spacing has doubled, which is what allows MFM cells to be half as long at the same resolution limit.
That shift is the whole point. A read channel’s resolution limit is set by how closely spaced two flux transitions can be and still be told apart, so FM’s one-step minimum forces its cells to be at least as long as the media’s minimum resolvable spacing. MFM’s two-step minimum allows cells half that length at the same media density, which doubles the data rate for the same head and surface. This is why a 500 kilobit-per-second MFM drive and a 250 kilobit-per-second FM drive can use identical media: the MFM drive writes cells half as long but never places two transitions in adjacent grid positions.
The encoding explains the track capacity. A 3.5-inch high-density diskette rotates at 300 revolutions per minute, so one revolution takes 200 milliseconds. The controller transfers data at 500 kilobits per second, which over one revolution is 100,000 bits, or 12,500 bytes. Eighteen sectors of 512 bytes hold 9,216 bytes, so the sync fields, address marks, headers, CRCs, and gaps of eighteen sectors must fit inside the remaining 3,284 bytes, about 180 bytes per sector. Framing therefore consumes a little over a quarter of the track, and the end-of-track gap absorbs whatever is left over. Adding a nineteenth sector is impossible not because the data will not fit but because the per-sector overhead will not.
The address mark as an out-of-band token
Because the stream is a continuous sequence of encoded bits with no inherent framing, the controller needs a pattern that cannot occur in ordinary data. Each sector begins with an address mark, a byte value that the encoder deliberately writes with a missing clock transition so that it cannot appear as encoded data. The controller watches for that violation of the encoding rules and treats it as a synchronisation point.
After the mark come four bytes: the cylinder number, the head number, the sector number, and a size code. The size code names the sector length by power of two, with the value 2 meaning 512 bytes. A CRC follows. The controller compares the CRC against what it computes from the four bytes and rejects the sector if they differ, which is how a misread header is detected before any data is transferred.
The data field begins after a gap and a sync field. The sync field is a run of zero bytes, whose encoded form produces a long sequence of evenly spaced transitions that lets the controller’s loop settle its phase before the first real byte arrives. The gap between the header and the data exists because the controller needs time to decide that the header it just read is the one it wanted, and to switch from reading headers to reading data. A gap that is too short causes the controller to miss the start of the data field; a gap that is too long wastes track space that could have held data.
/* Parse the four-byte ID field of a floppy sector header.
The values come from the address mark; the CRC check is separate. */
struct sector_id {
unsigned char cylinder;
unsigned char head;
unsigned char sector; /* 1-based within the track */
unsigned char size; /* log2(bytes) - 7; 2 means 512 */
};
int id_matches(const struct sector_id *id, int cyl, int head, int sec)
{
return id->cylinder == cyl
&& id->head == head
&& id->sector == sec
&& id->size == 2; /* only 512-byte sectors are accepted */
}
Gaps as a scheduling budget
The bytes between sectors are not padding in the sense of being arbitrary. They are the time the controller needs to finish one operation and begin the next, expressed in bytes at a known data rate. After the data field and its CRC, a gap gives the controller room to complete the transfer, raise an interrupt, and prepare for the next sector’s header. At the end of the track, a longer gap absorbs the difference between the total of all sectors and the actual track length, which varies because the drive’s speed is not exact and the media’s capacity is not perfectly uniform.
That last gap is why formats are specified with a nominal sector count rather than a guaranteed one. A track that nominally holds eighteen sectors may, on a particular drive, have slightly more or less room, and the format’s inter-sector gaps are chosen so that the eighteen sectors fit with margin. A drive whose speed is slightly fast writes the same bytes into a slightly shorter track, and the final gap shrinks. A drive whose speed is slow leaves a larger gap. Neither is an error as long as the sectors can be located.
The interleave factor is a separate scheduling decision. Sectors are physically numbered in the order they are written, but a filesystem may choose to number them so that consecutively numbered sectors are not adjacent on the track. If the controller cannot begin reading the next sector immediately after finishing the current one, then adjacent numbering forces the drive to wait a full revolution for the next sector to come around. Numbering with a factor of two, so that sector 2 follows sector 1 after one intervening sector, gives the controller time to be ready. The cost is that a long sequential read visits sectors in a different physical order, and the optimal interleave depends on the controller’s speed, so a disk formatted on one machine may perform poorly on another.
FAT12 and the twelve-bit entry
Once sectors can be located, a filesystem must decide which of them belong to which file. FAT12 does this with a table of twelve-bit entries, one per cluster, where a cluster is a fixed number of consecutive sectors. The choice of twelve bits is an economy measure: with 512-byte sectors and a cluster size of one sector, a 1.44 MB diskette holds 2,880 clusters, which needs twelve bits to name. A sixteen-bit table would waste a third of its space for a disk of this size.
Twelve-bit entries do not align to bytes, so the table packs two entries into every three bytes. The entry for an even-numbered cluster occupies the low twelve bits of the first byte and a half; the entry for the odd-numbered cluster occupies the high half of the second byte and all of the third. Reading one entry requires loading two bytes or three depending on parity, and shifting.
/* Read FAT12 entry n from a table held in memory.
Even entries: low 12 bits of bytes[1.5n]; odd: high 12 bits. */
unsigned int fat12_get(const unsigned char *fat, unsigned int n)
{
unsigned int offset = n + (n >> 1); /* n * 1.5, without a divide */
unsigned int value = fat[offset] | (fat[offset + 1] << 8);
if (n & 1)
return value >> 4; /* odd entry: high 12 bits */
return value & 0x0FFF; /* even entry: low 12 bits */
}
The first two entries of the table are not clusters. The first holds a media descriptor byte, which identifies the disk’s format, and the second holds an end-of-chain marker. Cluster numbering therefore begins at two, and the entry for cluster 2 is the third twelve-bit value in the table. This offset is a persistent source of off-by-one errors in code that treats the table as a simple array.
A cluster’s entry either names the next cluster in the chain or holds a value in a reserved range. Values from 0xFF8 through 0xFFF mark the end of the chain, 0xFF7 marks a cluster that has been found defective, 0xFF0 through 0xFF6 are reserved, and zero marks a free cluster. Values from 0x002 through 0xFEF name the next cluster, and because the table is indexed by cluster number, a chain is a linked list stored in an array. Walking a file means following the links, and a corrupt link that points backward creates a loop that a naive walker will follow forever. Filesystem checkers detect this by counting steps against the total cluster count.
Where the format’s assumptions fail
The twelve-bit design caps the cluster count at 4,084, since the values above that are reserved as end-of-chain and bad-cluster markers; a volume with 4,085 or more clusters is formatted as FAT16 instead. A diskette is far below that limit, but a hard disk partitioned with the same scheme and a small cluster size would exceed it, which is why the same driver has to decide which table width it is looking at by counting clusters rather than by reading a version number.
The geometry is also an assumption that can be wrong. A drive’s actual number of tracks and sectors is a property of the hardware, and the values in the boot sector describe what the filesystem believes. A diskette written with a geometry the drive does not support will read as garbage even though every sector’s CRC is valid, because the controller is stepping to the wrong place. Nothing in the sector header prevents this: the header names a cylinder and head, and if the drive is at a different physical position than the controller believes, the header it reads will not match what it asked for, and the read fails. The failure is detectable, which is the point of putting the address in the header at all. Firmware that hides the controller’s geometry from programs does so through the disk service vectors, which translate a cylinder-head-sector request into whatever command sequence the installed controller requires; the controller’s own command and status registers are reached through dedicated I/O ports rather than through memory.
References
- IBM Diskette General Information Manual — IBM Corporation
- Microsoft FAT Specification — Microsoft
- SA400 Minifloppy Disk Drive OEM Manual — Shugart Associates