WD1772 Floppy Disk Controller
A single reference for the Western Digital WD1772 as fitted to the Atari ST, built by cross-checking three source documents against each other and against two independently-written, boot-tested implementations. No single document below is reliable on every point - where they disagree, this page states the resolution and the evidence, rather than picking one silently.
See Reference documents and datasheets for the general note on these source documents' quality, and the Memory Map for Atari ST,STE,TT and Falcon index for the wider hardware reference set.
The WD1772 is a floppy disk formatter/controller: a 28-pin NMOS part with a built-in digital data separator and write precompensation circuit, needing only buffers on the drive side. It supports single (FM) and double (MFM) density, sector lengths of 128/256/512/1024 bytes, up to 240 tracks and up to 240 sectors per track. Clock is 8 MHz +/-0.1%. Data rates are 125 kbit/s (FM) and 250 kbit/s (MFM).
On the Atari ST the chip is not in the 68000 address space directly - every access goes through the DMA controller. See section 15.
| Pin | Name | I/O | Function |
|---|---|---|---|
| 1 | CS* | I | Chip select, active low |
| 2 | R/W* | I | 1 = read, 0 = write |
| 3-4 | A0,A1 | I | Register select (section 3) |
| 5-12 | DAL0-7 | I/O | 8-bit bidirectional data bus |
| 13 | MR* | I | Master reset, active low pulse |
| 14 | GND | - | Ground |
| 15 | VCC | - | +5V +/-5% |
| 16 | STEP | O | One pulse per head step |
| 17 | DIRC | O | High = step in, low = step out |
| 18 | CLK | I | 8MHz +/-0.1%, free-running |
| 19 | RD* | I | Raw read data, active low |
| 20 | MO | O | Motor on, active high |
| 21 | WG | O | Write gate |
| 22 | WD | O | Write data |
| 23 | TR00* | I | Active low, head at track 0 |
| 24 | IP* | I | Active low, index hole |
| 25 | WPRT* | I | Active low, write protected |
| 26 | DDEN* | I | 0=MFM, 1=FM |
| 27 | DRQ | O | Data register full/empty |
| 28 | INTRQ | O | Set at command completion |
Selected by A1/A0. On the Atari these two pins are driven by the DMA chip, not the CPU address bus - see section 15.
| A1 | A0 | Read (R/W*=1) | Write (R/W*=0) |
|---|---|---|---|
| 0 | 0 | Status Register | Command Register |
| 0 | 1 | Track Register | Track Register |
| 1 | 0 | Sector Register | Sector Register |
| 1 | 1 | Data Register | Data Register |
Track Register - current head position. Incremented on step in, decremented on step out. Compared against the ID field track number during read, write and verify. Not loaded while busy.
Sector Register - desired sector, compared against the ID field. Not loaded while busy. Read Address writes the ID field's TRACK address into it (undocumented, relied upon in practice).
Data Register - holding register for read/write data; during a Seek it holds the destination track number.
Command Register - write only, not loaded while busy except for Force Interrupt. Status Register - read only, meaning depends on the last command type.
After writing any register, it cannot be read again until 16us (MFM) or 32us (FM) have elapsed.
Base is the opcode with all flags clear, not the only byte the command can take - e.g. Read Track can appear as $E0, $E4, $E8 or $EC depending on h and e. Match on the top nibble.
Commands may only be written when Busy (status bit 0) is clear. Force Interrupt is the sole exception.
| Flag | Bit | 0 | 1 |
|---|---|---|---|
| u - Update Track Reg | 4 | no update | update |
| m - Multiple Sector | 4 | single sector | multiple |
| h - Motor On | 3 | enable spin-up | disable spin-up |
| v - Verify | 2 | no verify | verify dest. track |
| e - Settling Delay | 2 | no delay | 15ms(1772)/30ms(1770) |
| p - Write Precomp | 1 | enable precomp | disable precomp |
| a0 - Data Addr Mark | 0 | normal ($FB) | deleted ($F8) |
'p' is inverted from the intuitive reading: 0 turns precompensation ON. DrCoolZic's editions print '0 Unable Write Pre-comp', a typo for 'Enable'; Western Digital's original and the Acorn reference both correctly say '0 = Enable'.
'h' is not a motor-speed check. It is a fixed, unconditional wait for a number of index pulses (section 5) before the command executes.
| r1 | r0 | WD1770/1773 | WD1772 |
|---|---|---|---|
| 0 | 0 | 6 ms | 6 ms |
| 0 | 1 | 12 ms | 12 ms |
| 1 | 0 | 20 ms | 2 ms |
| 1 | 1 | 30 ms | 3 ms |
These are the confirmed WD1772 values. Western Digital's own preliminary datasheet gets the WD1772 column WRONG, printing 2/3/5/6ms against the wrong codes - a draft error, since the document is stamped PRELIMINARY and the figures do not match the shipped chip. DrCoolZic's editions and the Acorn reference both give the correct table; EmuTOS's own source comments reject the wrong figures explicitly ('not 2, but 6 msec steprate', and so on); Hatari implements FDC_StepRate_ms[] = { 6, 12, 2, 3 }.
On the Atari, 3ms (r1,r0=1,1) is the practical setting. 2ms works but gives less reliable results. h must always be 0 on the ST, since MO is wired straight to the drive with no separate spin-up handshake to skip.
| Bits | Meaning |
|---|---|
| i0, i1 | not used on the WD1772 (WD1773 only, ready/not-ready) |
| i2 | interrupt on every index pulse ($D4) |
| i3 | immediate interrupt ($D8) |
| $D0 (all 0) | terminate current command, no interrupt |
$D0 is the only command that lets a previous $D8 clear on a later status read or command write. Always follow $D8 with $D0. After any force interrupt, wait 16us (MFM) / 32us (FM) before a new command.
With h=0 and MO low when a command arrives, the WD1772 raises MO and waits 6 index pulses before executing - a one second spindle start-up at 300 RPM. A command arriving while MO is already high executes immediately, defeating the wait, which is what makes consecutive reads/writes fast.
Motor off after idle: 10 index pulses, roughly 2 seconds. DrCoolZic's datasheet edition says 9 revolutions; his Atari guide says 10 index pulses; the Acorn reference gives no figure. 10 is treated as correct here since it is unit-consistent with the 6-pulse spin-up figure.
The drive must stay selected for index pulses to reach the FDC, so the motor can only turn itself off while the drive is still selected.
Direction is valid 24us before the first step pulse. Step pulse width is 4us (MFM) or 8us (FM).
With v=1 an extra 15ms (WD1772) settling time follows the last step, then the track number from the first ID field found is compared against the Track Register. Match plus valid CRC completes the verify; the chip must find a good match within 5 revolutions or Seek Error is set.
Restore - samples TR00*. If already at track 0, Track Register is zeroed and an interrupt generated; otherwise steps until TR00* activates. Not active after 255 pulses -> Seek Error (if v set).
Seek - Track Register holds current position, Data Register the destination; steps until they match. Update the Track Register for the selected drive before seeking, with multiple drives.
Step / Step-in / Step-out - one pulse, direction as named (or as the previous step command for plain Step). u=1 updates the Track Register.
Load the Sector Register first. The chip must find an ID field with matching track, sector and valid CRC within 5 revolutions, or Record Not Found is set. (Western Digital's original is internally inconsistent, saying 'four revolutions' in one place and 'five' elsewhere; DrCoolZic and the Acorn reference both consistently say five - treated as correct, with 'four' a genuine WD original error.)
Read Sector - Data Address Mark must appear within 30 bytes (FM) / 43 bytes (MFM) of the last ID CRC byte, or the ID search restarts. After 5 revolutions without a DAM, RNF is set. A CRC error at the end of the data field terminates the command, even in multiple-sector mode.
| S5 | Address mark | Primary | Alternate |
|---|---|---|---|
| 0 | Data Mark | $FB | $FA |
| 1 | Deleted Data Mark | $F8 | $F9 |
DrCoolZic's status table gives the alternate normal-mark byte as $FC - wrong, it should be $FA. Proof: his own Write Track byte-substitution table shows $F8-$FB written as-is with preset CRC (so $FA is a valid data-mark byte) while $FC uses an entirely different clock pattern used for index-type marks. Hatari confirms in code: normal DAM is 0xFB or 0xFA, deleted DAM is 0xF8 or 0xF9.
Write Sector - once the ID field matches, the chip counts off 11 bytes (FM) / 22 bytes (MFM) from the CRC field, asserts WG if DRQ was serviced, writes 6/12 bytes of zero, the DAM per a0, the data, two CRC bytes, one byte of $FF. Late DRQ mid-sector writes a zero byte and sets Lost Data but does not abort. With m=1 the Sector Register auto-increments until it exceeds the track or Force Interrupt arrives.
Read Address - reads the next ID field: track, side, sector, length, CRC1, CRC2 (6 bytes, one DRQ each). CRC checked, error bit set on failure but bytes still transferred. The track address is written into the Sector Register (undocumented, relied upon in practice; also relied upon: Read Address on a track with no address marks sets RNF).
Read Track - starts at the leading edge of the first index pulse, runs to the next. No CRC checking; gap bytes included; AM detector stays on throughout, so write splices/noise can cause spurious detection and occasionally wrong gap bytes - ID and data fields remain reliable. Known hardware quirk: the WD1772 occasionally returns a wildly wrong byte count from this command (normal is under ~6500 bytes; failures up to ~21000 bytes reported) - retry.
Write Track (formatting) - runs index pulse to index pulse. DRQ raised immediately, but writing does not start until the first byte is in the Data Register; over 3 byte times late aborts with Lost Data. A late byte mid-track is substituted with zero.
Bytes $F5-$FE written during Write Track are interpreted specially rather than literally:
| Byte | FM (DDEN*=1) | MFM (DDEN*=0) |
|---|---|---|
| $00-$F4 | write as-is, CLK=$FF | write as-is |
| $F5 | not allowed | write $A1, missing clock 4/5, preset CRC |
| $F6 | not allowed | write $C2, missing clock 3/4 |
| $F7 | generate 2 CRC bytes | generate 2 CRC bytes |
| $F8-$FB | as-is, CLK=$C7, preset CRC | write as-is |
| $FC | write $FC, CLK=$D7 | write $FC |
| $FD | write $FD, CLK=$FF | write $FD |
| $FE | write $FE, CLK=$C7, preset CRC | write $FE |
| $FF | write $FF, CLK=$FF | write $FF |
In MFM the low bit of marks $F8-$FF is ignored, so $F8=$F9 and $FE=$FF for address-mark recognition.
| Bit | Type I | Type II / III |
|---|---|---|
| S7 | Motor On | Motor On |
| S6 | Write Protect (see note) | Write Protect (write cmds) |
| S5 | Spin-up complete | Record Type (0=Data,1=Del.) |
| S4 | Seek Error | Record Not Found |
| S3 | CRC Error | CRC Error |
| S2 | TR00* state | Lost Data |
| S1 | Index Pulse state | Data Request |
| S0 | Busy | Busy |
If S4 is set, a CRC error (S3) is in an ID field; if clear, in the data field.
| Command | S7 | S6 | S5 | S4 | S3 | S2 | S1 | S0 |
|---|---|---|---|---|---|---|---|---|
| All Type I | MO | X | SU | SE | CRC | T0 | IP | BSY |
| Read Sector | MO | X | RT | RNF | CRC | LD | DRQ | BSY |
| Write Sector | MO | WP | RT | RNF | CRC | LD | DRQ | BSY |
| Read Address | MO | X | X | RNF | CRC | LD | DRQ | BSY |
| Read Track | MO | X | X | X | X | LD | DRQ | BSY |
| Write Track | MO | WP | X | X | X | LD | DRQ | BSY |
| FI (busy) | - | - | - | - | - | - | - | 0 |
| FI (idle) | MO | X | X | X | X | T0 | IP | 0 |
| Idle | MO | WP | X | X | X | T0 | IP | 0 |
X = undefined, - = retains previous value.
Write Protect keeps updating after a Type I command. Undocumented by Western Digital: following any Type I command the chip sits in 'Type I idle' and continuously refreshes S6 alongside MO and TR00 - NOT true after Type II/III. To poll write-protect (e.g. detect a disk swap), follow the Type II/III command with a Type I command such as a seek to the current track.
| After | Before | FM | MFM |
|---|---|---|---|
| Write Command Reg | Read Busy bit (S0) | 48us | 24us |
| Write Command Reg | Read status bits 1-7 | 64us | 32us |
| Write any register | Read same register | 32us | 16us |
MFM double density, 512 bytes/sector, 9 sectors - the standard Atari ST format:
| Count | Value | Field |
|---|---|---|
| 60 | $4E | Gap 1, post-index |
| 12 | $00 | Gap 2, PLL lockup |
| 3 | $F5 (writes $A1) | Gap 2, sync |
| 1 | $FE | ID address mark |
| 1 | track number | ID record |
| 1 | side number | ID record |
| 1 | sector number | ID record |
| 1 | $02 (see note) | sector length, 512 bytes |
| 1 | $F7 | writes 2 CRC bytes |
| 22 | $4E | Gap 3a, post-ID |
| 12 | $00 | Gap 3b, write splice |
| 3 | $F5 (writes $A1) | Gap 3b, sync |
| 1 | $FB | data address mark |
| 512 | data | data record |
| 1 | $F7 | writes 2 CRC bytes |
| 40 | $4E | Gap 4, post-data |
| ~668 | $4E | Gap 5, run-out |
Gap 5 is written until the chip interrupts out at the index pulse. The sector-length field is $02 for 512 bytes. Both DrCoolZic's table and the Acorn reference's 'recommended format' column print $01 here (the 256-byte code) - apparently inherited from an earlier 256-byte version of this table and never updated in either. Use the sector length table below, which every source agrees on.
FM single density, 128 bytes/sector, 10 sectors:
| Count | Value | Field |
|---|---|---|
| 40 | $FF (or $00) | Gap 1 |
| 6 | $00 | Gap 2 |
| 1 | $FE | ID address mark |
| 1 | track number | ID record |
| 1 | side number | ID record |
| 1 | sector number | ID record |
| 1 | $00 | sector length (128) |
| 1 | $F7 | writes 2 CRC bytes |
| 11 | $FF (or $00) | Gap 3 |
| 6 | $00 | Gap 3 |
| 1 | $FB | data address mark |
| 128 | data | data record |
| 1 | $F7 | writes 2 CRC bytes |
| 10 | $FF (or $00) | Gap 4 |
| ~369 | $FF (or $00) | Gap 5 |
| Gap | FM | MFM |
|---|---|---|
| Gap 1 | 16 x $FF | 32 x $4E |
| Gap 2 | 6 x $00 (exact) | 12 x $00 (exact) + 3 x $A1 (exact) |
| Gap 3 | 10 x $FF, 4 x $00 (min) | 24 x $4E, 8 x $00 (min) + 3 x $A1 |
| Gap 4 | 27 x $FF | 38 x $4E |
Constraints on any custom format: sector size must be 128, 256, 512 or 1024 bytes; Gap 2 cannot be varied at all; 3 bytes of $A1 are mandatory in MFM. The Index Address Mark is not required for WD1772 operation. Gaps 1, 3 and 4 can technically go as short as 2 bytes for the chip's own purposes, but PLL lock-up time, motor speed variation and write-splice area all demand more in practice.
| Value | Bytes |
|---|---|
| $00 | 128 |
| $01 | 256 |
| $02 | 512 |
| $03 | 1024 |
Polynomial: G(x) = x^16 + x^12 + x^5 + 1. The CRC register is preset to all ones before data is shifted through it, and covers everything from the address mark up to (not including) the CRC bytes themselves.
A byte is assembled every 32us (8 bits at 4us each) in MFM, so a 16-byte DMA FIFO burst on the Atari happens roughly every 512us. Useful when measuring or reverse-engineering copy-protection schemes that manipulate bit width.
INTRQ is generated at the completion of every command, reset by reading the Status Register or writing the Command Register. Also generated on a Force Interrupt condition (section 4). Reading (or writing) the Data Register automatically clears both the DRQ status bit and the DRQ line.
When using INTRQ, do not also poll Busy - reading the Status Register to check Busy also resets INTRQ, which can miss a pending interrupt.
The WD1772 is not memory-mapped on the Atari ST. A1 and A0 on the chip are driven by the DMA controller, not the 68000 address bus, and every access goes through it.
1. Write a mode value to the DMA mode register at $FF8606 to select which FDC register (or the DMA sector counter) the data port will reach. 2. Read or write the data port at $FF8604.
| FDC register | DMA read | DMA write |
|---|---|---|
| Command(W)/Status(R) | $0080 | $0180 |
| Track Register | $0082 | $0182 |
| Sector Register | $0084 | $0184 |
| Data Register | $0086 | $0186 |
| DMA sector count (W) | $0090 | $0190 |
Only the low 8 bits of the $FF8604 word are used; the upper byte is ignored on write, reads back all ones.
DMA status ($FF8606 on read) bit 0 is active-low: 1 = no error, 0 = error. This is the single most miscopied bit in Atari documentation. EmuTOS's own DMA_OK define is 0x0001, confirming this polarity; Atari's own TOS floppy driver relies on the same reading.
DMA FIFOs move data only in 16-byte bursts. A single Read Address command only transfers 6 bytes total, less than one burst - it does not reach memory via DMA at all.
| Bit(s) | Meaning |
|---|---|
| Bit 0 | Side select: 1 = side 0, 0 = side 1 |
| Bit 1 | Drive 0 select, active low |
| Bit 2 | Drive 1 select, active low |
| Bits 3-7 | Other functions - PRESERVE these, do not clobber |
FDC/HDC interrupt arrives on MFP GPIP bit 5. Bit 5 LOW ($FFFA01 masked with $20 giving zero) means the FDC has raised an interrupt.
On the ST, MO is wired straight to the drive with no separate handshake to skip, so: h must always be 0; e should be 0 (no extra settling delay); p should be 0 (write precompensation enabled), always; step rate normally 3ms (r1,r0=1,1).
None of the three source datasheets say anything about running the WD1772 at 16 MHz - 8 MHz is the datasheet clock throughout. But 16 MHz operation is common in practice (it is how HD/1.44MB support is added to the ST family), so this section collects what is actually known, keeping official documentation and community belief separate.
AJAX is confirmed in an official Atari document, cited directly in EmuTOS source (bios/machine.c), which quotes the 'ATARI FALCON030 Service Guide', dated 1 October 1992: DIP switch bit 6 off means 'AJAX FDC chip installed (support for 1.44MB floppy)'. EmuTOS sets its _FDC cookie accordingly (see The Cookie Jar). AJAX was Atari's own HD/ED-capable FDC redesign, manufactured by Toshiba (part number C302096) after Western Digital could not sustain production of a suitable part.
By contrast, the claim that a specific WD part number ('WD1772PH02-02') officially supported 500 kbit/s high-density operation appears to be unsourced. It is on Wikipedia's 'Western Digital FD1771' article and every mirror of it, but Wikipedia's own article marks this exact sentence [citation needed] - the only uncited claim in that whole list of chip variants. Neither Western Digital's original datasheet nor DrCoolZic's edition mentions a 'PH02-02' designation, a 500 kbit/s mode, or any HD capability at all. Treat it as unverified community lore until a primary source turns up.
This is drawn from direct commercial testing of WD1772/VL1772 chips by exxos over 2016-2026, corroborated independently by a second person's hardware failure and a third community member's stated understanding. None of this is official manufacturer guidance - it is the best evidence available, since the datasheets do not address 16 MHz operation at all.
The '-0202' date/batch code does not reliably indicate 16 MHz capability. The commonly repeated claim that all -0202 chips can do 16MHz was directly tested and found false: a -0202-equipped Mega STE wrote a bit more than a full 1.44MB disk before failing, needing to cool down before another attempt worked; a substitute VL1772 chip showed the identical symptom, ruling out a one-off bad chip. Full account, from the person who found this: exxosforum.co.uk - 'WD1772-0202 - overheats at 16MHz'
| Part | Sustained 16MHz | Notes |
|---|---|---|
| VL1772 | ~0.5 h | Runs hotter than WD; pot luck if 16MHz at all |
| WD1772 (any date code) | commonly a few h; some fail almost immediately | unpredictable, no part-number guarantee |
| AJAX | designed for continuous | officially documented - see above |
exxos's own site documents this finding, revised as testing accumulated over ten years - current text, with the update history, at The LaST Upgrade - ATARI 1.44 floppy mod. Current position, stated plainly there: no ordinary WD1772 or VL1772, regardless of date code, should be assumed safe for continuous 16 MHz operation. Any can typically run 16 MHz for short bursts (several consecutive HD formats observed); most eventually overheat if left at 16 MHz continuously.
The fix is architectural, not chip selection: only run the clock at 16 MHz while actually accessing the drive, dropping back to 8 MHz the rest of the time. exxos's own HD upgrade kits (HD4 through HD7, 1990s onward, see The LaST Upgrade - ATARI 1.44 floppy mod) work this way - almost zero 1772s have been found to fail in 16MHz mode under this design, even after consecutively formatting several 1.44 floppies, with any WD or VL part fitted.
This is a real contrast with Atari's own factory HD implementation. Independent research into the Mega STE and TT found they do not switch density per-access either - TOS sets the FDC clock via a PAL-driven register reactively, on error and retry, rather than switching back after every single transaction, which is consistent with the factory-chip failure account above. Write-up: troed.se - 'The tale of high density Atari mystery'
Practical recommendation: design or choose an HD interface that drops the FDC clock to 8 MHz whenever the drive is not actively being accessed. Under that discipline, any standard WD1772 or VL1772 has been found reliable regardless of date code. Only a design that holds the chip at 16 MHz for extended continuous periods needs a chip genuinely rated for that duty cycle - in practice, AJAX.
Full detail and evidence for each of these is inline above; this is the index.
| # | Item | Wrong in | Right per |
|---|---|---|---|
| 1 | Read Track opcode | DrCoolZic (1101) | WD orig./Acorn/EmuTOS: 1110 |
| 2 | p flag wording | DrCoolZic ('Unable') | WD orig./Acorn: 'Enable' |
| 3 | WD1772 step rates | WD original (2/3/5/6ms) | DrCoolZic/Acorn/EmuTOS/Hatari |
| 4 | Settling delay | WD original (30ms) | DrCoolZic(15ms)/Acorn/Hatari |
| 5 | Type II revolutions | WD original ('four') | DrCoolZic/Acorn: five |
| 6 | S5 alt. DAM byte | DrCoolZic ($FC) | Hatari ($FA) + DrCoolZic's own table |
| 7 | 512B sector length | DrCoolZic/Acorn ($01) | sector length table: $02 |
| 8 | Motor-off idle | ambiguous (9 rev/10 pulse) | 10 pulses, unit-consistent |
| 9 | 'PH02-02 = 500kbit HD' | Wikipedia (uncited) | no datasheet documents this; AJAX is real |
Two of the three source documents contain genuine errors on points where the third gets it right. Western Digital's own preliminary datasheet is not a safe fallback authority - it is stamped PRELIMINARY and is wrong about the WD1772-specific step rate table and settling time, both significant enough to break real code if trusted blindly. Always check a disputed figure against EmuTOS or Hatari before relying on any one printed source.
A PDF version of this page, with the same diagrams, is available for offline reference. See Reference documents and datasheets.
Back to Reference documents and datasheets · Memory Map for Atari ST,STE,TT and Falcon