SENI
KECANTIKAN & KESEHATAN
KERAJINAN
BUDAYA & SEJARAH
HIBURAN
LINGKUNGAN
MAKANAN & MINUMAN
REKAYASA BALIK
ILMU PENGETAHUAN
OLAHRAGA
TEKNOLOGI
PERANGKAT PAKAI
FM and MFM: Making the Data Clock Itself
Ed

Dibuat oleh

Ed

30. Agustus 2026FI
16
0
0
0
0

FM and MFM: Making the Data Clock Itself

A read head is a differentiator. It produces a voltage proportional to the RATE OF CHANGE of flux, which means a stretch of uniform magnetisation produces exactly nothing no matter how long it is. Record a thousand zeros and the head is silent; record a million and it is equally silent. The drive cannot tell them apart. It could count them with a clock, but it has no clock worth trusting. The spindle wanders by a percent or two with temperature and supply voltage, the disc was probably written in a different drive on a different day, and a percent of error accumulated over a few hundred bits puts everything in the wrong place. So the clock is built into the data. Frequency Modulation recording puts a transition at every cell boundary as a clock, plus one in the middle of the cell for a 1 — self-clocking, completely reliable, and it spends half of every transition on saying nothing. Modified FM noticed that the clock transition is only NEEDED between two consecutive zeros, dropped it everywhere else, and doubled the density of every floppy disk in the world without touching the head or the medium. That is what the labels single density and double density meant. FULLY BUILDABLE, and the exercise is the point: encode, decode, then walk the clock error up until the decoder fails. The error at which it breaks IS the detection window, and that number governs everything about how dense a drive can be.
Menengah
4 hours

Instruksi

1

Do it once by hand

Take the byte 10100011 and write out its FM cell stream on paper: for each data bit, a clock cell that is always 1, then a data cell equal to the bit. Sixteen cells. Now MFM the same byte. Same layout, but the clock cell is 1 only when the PREVIOUS data bit and this one are both 0. Count the 1s in each stream — those are flux reversals, and they are what actually gets written. Then draw both as waveforms, flipping the line up or down at every 1. Two pictures of the same byte. The MFM one has fewer corners in it, and that difference is a doubling of the capacity of every floppy disk ever sold.

Alat yang dibutuhkan:

Notebook and PencilNotebook and Pencil
2

Encode, decode, and break it

Flash the sketch and open the serial monitor at 115200. It encodes five bytes to MFM, turns the cell stream into transition TIMES, applies a deliberate clock error, and recovers the data with a software phase-locked loop. It then walks the error from 0 to 30 percent and prints where the decode fails. Watch what happens around the failure point: it does not degrade gently, it goes from perfect to garbage within a percent or two. A run of zeros is where it breaks first, which is exactly why the maximum run length matters. Put an LED and a 330 ohm resistor on GPIO19 so each recovered transition blinks, and a second on GPIO18 for the raw flux waveform. At a slow data rate you can watch the code working before you ever reach for the scope. Change the message to all 0x00 and re-run. MFM's clock cells save it. Then disable the clock cells in `encodeMFM` and re-run: it fails immediately, at zero error.
mfm_codec.inocpp
// MFM encoder and decoder on an ESP32, with a deliberately wobbly clock so you can
// watch the detection window close.
//
// The point of the exercise: encode a byte, transmit it as flux TRANSITIONS at a data
// rate you control, recover it with a software PLL, then walk the clock error up until
// the decoder fails. The error at which it fails is the detection window, and it is the
// number that decides how dense a drive can be.
//
// Wiring (optional, for the scope): GPIO18 outputs the flux waveform, GPIO19 pulses on
// each recovered transition. With no wiring at all the sketch still runs the loopback.

const int PIN_FLUX = 18;
const int PIN_TICK = 19;

// ---- MFM encode: clock bit is 1 only when previous and current data bits are both 0
int encodeMFM(const uint8_t *data, int nBytes, uint8_t *cells) {
  int n = 0, prev = 0;
  for (int i = 0; i < nBytes; i++) {
    for (int b = 7; b >= 0; b--) {
      int bit = (data[i] >> b) & 1;
      cells[n++] = (prev == 0 && bit == 0) ? 1 : 0;   // clock cell
      cells[n++] = bit;                                // data cell
      prev = bit;
    }
  }
  return n;
}

// ---- turn the cell stream into transition TIMES, with a clock error applied
int cellsToTimes(const uint8_t *cells, int nCells, float cellNs, float err, uint32_t *t) {
  int n = 0;
  float now = 0.0;
  for (int i = 0; i < nCells; i++) {
    now += cellNs * (1.0 + err);
    if (cells[i]) t[n++] = (uint32_t)now;
  }
  return n;
}

// ---- software PLL: nudge the window to each arriving transition, then bin it
int decodeMFM(const uint32_t *t, int nT, float cellNs, uint8_t *cells) {
  int n = 0;
  float window = cellNs, phase = 0.0;
  uint32_t last = 0;
  for (int i = 0; i < nT; i++) {
    float gap = (float)(t[i] - last);
    int nCells = (int)(gap / window + 0.5);            // how many cells since the last one
    if (nCells < 1) nCells = 1;
    for (int j = 1; j < nCells; j++) cells[n++] = 0;   // the silent cells
    cells[n++] = 1;                                    // this transition
    // PLL: pull the window towards the observed spacing, gently
    float measured = gap / nCells;
    window += 0.05 * (measured - window);
    last = t[i];
  }
  return n;
}

int cellsToBytes(const uint8_t *cells, int nCells, uint8_t *out) {
  int n = 0; uint8_t acc = 0; int nb = 0;
  for (int i = 1; i < nCells; i += 2) {                // data cells are the odd ones
    acc = (acc << 1) | cells[i];
    if (++nb == 8) { out[n++] = acc; acc = 0; nb = 0; }
  }
  return n;
}

void setup() {
  Serial.begin(115200);
  delay(300);
  pinMode(PIN_FLUX, OUTPUT);
  pinMode(PIN_TICK, OUTPUT);

  const uint8_t msg[] = {0xA3, 0x00, 0x00, 0xFF, 0x5A};
  const int nMsg = sizeof(msg);
  static uint8_t cells[512], back[512], outBytes[64];
  static uint32_t times[512];

  int nCells = encodeMFM(msg, nMsg, cells);
  int nTrans = 0;
  for (int i = 0; i < nCells; i++) nTrans += cells[i];
  Serial.printf("# %d data bits -> %d channel cells, %d flux transitions\n",
                nMsg * 8, nCells, nTrans);
  Serial.println("clock_error_pct,bytes_recovered,ok");

  // Walk the clock error up until the decoder gives up.
  for (int e = 0; e <= 30; e++) {
    float err = e / 100.0;
    int nT = cellsToTimes(cells, nCells, 1000.0, err, times);
    int nB = decodeMFM(times, nT, 1000.0, back);
    int nOut = cellsToBytes(back, nB, outBytes);

    bool ok = (nOut >= nMsg);
    for (int i = 0; ok && i < nMsg; i++) ok = (outBytes[i] == msg[i]);
    Serial.printf("%d,%d,%s\n", e, nOut, ok ? "OK" : "FAIL");
    if (!ok) {
      Serial.printf("# detection window closed at about %d %% clock error\n", e);
      break;
    }
  }
}

void loop() {}

Material untuk langkah ini:

LED AssortmentLED Assortment1 buah
Resistor Kit (1/4W, E12 Series)Resistor Kit (1/4W, E12 Series)1 buah

Alat yang dibutuhkan:

ESP32 Development BoardESP32 Development Board
Desktop ComputerDesktop Computer
Digital OscilloscopeDigital Oscilloscope
Jumper Wire SetJumper Wire Set
Breadboard - ClassicBreadboard - Classic
3

Density ratio, and the drift budget

Loading Jupyter Notebook...

Alat yang dibutuhkan:

Desktop ComputerDesktop Computer
4

Compendium: three constraints, one code

DC BALANCE, WHICH MFM IGNORES. A read channel is AC-coupled, because the head makes no DC. A code that allows long stretches at one polarity makes the baseline wander and the detector's threshold with it. EFM on the compact disc spends three merging bits per symbol almost entirely on this; MFM does not bother, and floppy drives paid for it with finicky read amplifiers. THE SIBLING, ANNOUNCED. RLL(2,7) is blueprint 7: same rate 1/2, minimum run raised from 1 to 2, density ratio 1.0 to 1.5. The whole difference is what the decoder must remember. AGAINST PARITY. The parity blueprint adds redundancy to DETECT an error. A channel code adds redundancy so the signal can be READ at all. Different jobs, and they stack — real drives do both.

Alat yang dibutuhkan:

Notebook and PencilNotebook and Pencil

Bahan

2

Alat yang Diperlukan

6

CC0 Domain Publik

Cetak biru ini dirilis di bawah CC0. Anda bebas untuk menyalin, memodifikasi, mendistribusikan, dan menggunakan karya ini untuk tujuan apa pun, termasuk komersial, tanpa meminta izin.

Dukung Maker dengan membeli produk melalui Cetak Biru mereka di mana mereka mendapatkan Komisi Pembuat yang ditetapkan oleh Penjual, atau buat iterasi baru dari Cetak Biru ini dan masukkan sebagai koneksi di milik Anda sendiri.

Diskusi

(0)

Masuk untuk bergabung dengan diskusi

Memuat komentar...