Western Digital WD1770 FDC
Floppy-disc controller chip used on B+ and Master series. Replaces the original Model B’s 8271 (which was expensive and couldn’t do double-density). The 1770/1772 family supports both FM (single-density) and MFM (double-density) at 250 or 500 kbit/s.
Not present on Electron or Model B (Model B can be upgraded with a 1770 board, replacing the 8271).
Register addresses
The 1770 has 4 registers + 1 external drive-control register:
| Register | B+ addr | Master addr | Read | Write |
|---|---|---|---|---|
| Status / Command | &FE84 | &FE28 | Status | Command |
| Track | &FE85 | &FE29 | Track | Track |
| Sector | &FE86 | &FE2A | Sector | Sector |
| Data | &FE87 | &FE2B | Data | Data |
| Drive control | &FE80 | &FE24 | — | Drive ctrl |
The drive-control register is a separate latch outside the 1770 itself. Bit layouts differ between B+ and Master.
Master drive control (&FE24)
| Bit | Field |
|---|---|
| 0 | DS0 — drive 0 select |
| 1 | DS1 — drive 1 select |
| 2 | DS2 — drive 2 select |
| 3 | RES — pulse low ≥50 µs to reset 1770 |
| 4 | SEL — side 0 / side 1 |
| 5 | DDEN — 0=double-density (MFM), 1=single-density (FM) |
| 6-7 | unused |
Set exactly one of DS0/DS1/DS2 at a time. Actual drive selection only takes effect when the motor turns on.
B+ drive control (&FE80)
Different bit assignment — refer to source page naug-ch16-filing §16.4.2 for the B+ layout.
Command types
Commands are written to &FE28 (or &FE84). Four families:
| Type | Commands | Purpose |
|---|---|---|
| I | Restore, Seek, Step, Step-in, Step-out | Head positioning |
| II | Read sector, Write sector | Bulk data transfer |
| III | Read address, Read track, Write track | Format / diagnostic |
| IV | Force interrupt | Abort + state-machine reset |
Type I — head positioning
| Cmd | Hex | Action |
|---|---|---|
| Restore | 0000 hvrr | Seek to track 0 |
| Seek | 0001 hvrr | Seek to track in data register |
| Step | 001u hvrr | Step one track in same direction |
| Step-in | 010u hvrr | Step toward centre |
| Step-out | 011u hvrr | Step toward edge |
Common flags:
- h (bit 3): 0 = enable motor spin-up sequence (6 index pulses ≈ 1.2 s at 300 rpm); 1 = disable spin-up (motor already running).
- v (bit 2): 1 = verify destination by reading next ID field.
- r1, r0 (bits 1-0): stepping rate.
Stepping rates differ between 1770 and 1772:
| r1 r0 | 1770 | 1772 |
|---|---|---|
| 00 | 6 ms | 6 ms |
| 01 | 12 ms | 12 ms |
| 10 | 20 ms | 2 ms |
| 11 | 30 ms | 3 ms |
u (bit 4 on Step variants): update Track register from internal counter.
Type II — sector R/W
| Cmd | Encoding |
|---|---|
| Read sector | 100m he00 |
| Write sector | 101m hepa0 |
- m (bit 4): 0 = single sector, 1 = multi-sector until end-of-track or Type IV abort.
- e (bit 2): 1 = wait 15 ms after head settle.
- p (bit 1, write only): 0 = enable write precompensation.
- a0 (bit 0, write only): 0 = normal data address mark, 1 = deleted.
The 1770 generates an NMI for every byte transferred — your NMI handler must read/write the data register within 32 µs (DD) or 64 µs (SD) per byte or the LD (lost data) status bit sets.
Type III — track-level
- Read address (
1100 he00): reads next 6-byte ID field (track / side / sector / sector-length / 2× CRC). - Read track (
1110 he00): dump entire track including gaps/headers — for diagnostics. - Write track (
1111 hep0): format track — full sector layout written from data stream provided by CPU.
Type IV — force interrupt
1101 i3 i2 i1 i0. Common values:
&D0— terminate current command without generating an NMI.&D4— NMI on next index pulse (i2=1).&D8— immediate NMI (i3=1).
Use &D0 to abort gracefully (e.g. canceling a multi-sector read mid-way). Immediate-NMI form (&D8) must be cleared with &D0 before issuing the next command, and you must wait ≥16 µs (SD) or ≥32 µs (DD) before the next command write.
Status register bits
LDA &FE28 (or &FE84). Bit meanings depend on command type:
| Bit | Type I | Type II/III |
|---|---|---|
| 7 | MON — motor on | MON |
| 6 | WRP — write protect | WRP |
| 5 | SU — spin-up complete | RT — record type (0 = data, 1 = deleted) |
| 4 | RNF — record not found | RNF |
| 3 | CRC — CRC error | CRC |
| 2 | LD — lost data | LD |
| 1 | DRQ — index hole | DRQ — data request |
| 0 | BUSY — command in progress | BUSY |
Critical rule (§16.4.3 p273): don’t poll the status register to check command completion. A read of the status register resets a pending NMI — if you poll right when a byte becomes available, you lose the NMI and the byte. Use the NMI itself as the completion signal.
The NMI handler
The 1770 raises NMI for every byte during a sector read/write. The handler must be tight enough to service it within 32 µs (DD) ≈ 64 CPU cycles at 2 MHz.
Standard pattern (from NAUG §16.4.4 p282-283 example):
; NMI handler at &0D00 (NMI vector at &FFFA points via OS to here)
PHA ; save A — only the regs you use
LDA &FE28 ; read status (or &FE84 on B+)
AND #&1F ; mask top bits
CMP #&03 ; DRQ + BUSY both = byte available
BNE exit ; not our NMI
LDA &FE2B ; read data register (or &FE87 on B+)
.save STA &2000 ; store — address self-modified
INC save+1 ; advance dest LSB
BNE exit
INC save+2 ; carry into MSB
.exit PLA
RTIFiling systems (DFS / ADFS) install handlers like this at NMI time and yank back ownership when finished — see paged-roms service calls &0B (NMI release) and &0C (NMI claim).
Programming sequence — read one sector
; Assumes drive 0, side 0, double-density, motor already running
LDA #&05 : STA &FE24 ; drive ctrl: DS0=1 DDEN=0
LDA #target_track : STA &FE2B ; load track into data register
LDA #&19 : STA &FE28 ; Seek (h=1, v=1, r=00)
.wait_seek
BIT seek_flag ; (set in NMI handler)
BPL wait_seek
LDA #target_track : STA &FE29 ; update Track register (Seek doesn't auto-update reliably)
LDA #target_sector : STA &FE2A ; sector register
LDA #&84 : STA &FE28 ; Read Sector (h=1, m=0)
; Each byte arrives via NMI; handler reads &FE2B and stores at next dest address.
; Final NMI (with BUSY clear): check status for errors.Densities and capacities
| Density | Bits/track | Bytes/sector | Sectors/track | Capacity per side (80-track) |
|---|---|---|---|---|
| Single (FM) | 125 kbit | 256 | 10 | 200 KB |
| Double (MFM) — DFS | 250 kbit | 256 | 18 | 400 KB |
| Double (MFM) — ADFS | 250 kbit | 256 | 16 | 320 KB |
| HD (1772 only, rarely used) | 500 kbit | 512 | 18 | 1.44 MB |
DFS = 18 sectors × 256 bytes per track. ADFS uses different sector layout (16/track, 256 bytes).
Bypass-MOS use cases
- Custom disc image formats — IBM PC discs (NAUG §16.4.4 example), CP/M, Amiga ADF copies.
- Disc repair / sector editor — read raw sectors with CRC errors and patch.
- Format checking — Type III “read track” dumps entire raw track for analysis.
- Faster sequential I/O — DFS’s per-call overhead is significant; for ROM-image work or bulk transfer, direct sector reads can be 2-3× faster.
The trade-off: you take over the NMI vector and risk crashing the machine if your handler is slow or buggy. The 8 KB NMI page (&0D00) only has room for the tight inner loop.
See also
- filing-systems — MOS-level filing-system API (OSFILE/etc).
- naug-ch16-filing — Complete source with worked IBM-disc example.
- disc-formats — DFS / ADFS sector-level layout sitting on top of the WD1770 register interface.
- interrupts — NMI vs IRQ; NMI handler at
&0D00. - os-workspace — NMI area
&0D00-&0D5F.
This wiki is curated by Claude following the LLM-Wiki methodology — a human curates source documents, the LLM compiles structured cross-linked markdown. Content may contain errors, omissions, or stale claims. For authoritative information refer to the original source documents in the bbc-documents GitHub archive.