HEX ( Floppy controller is memory mapped ) DC14 CONSTANT FDATA \ Floppy data port DC08 CONSTANT FREG \ Floppy DOR 0000 CONSTANT FSEL0 \ Floppy 0 select mask 0100 CONSTANT FSEL1 \ Floppy 1 select mask 0400 CONSTANT FRST- \ Controller reset not 1000 CONSTANT FMOT0 \ Motor 0 enable 2000 CONSTANT FMOT1 \ Motor 1 enable F000 CONSTANT FMOT \ Motor mask DC10 CONSTANT FSTAT \ Floppy status port 4000 CONSTANT FDIR \ T=RD F=WR 8000 CONSTANT FREQ \ Floppy REQUEST bit 1000 CONSTANT FBUSY \ Floppy busy bit C000 CONSTANT BUSYMSK \ busy and request FDIR FREQ OR CONSTANT FREQRD \ Request read : BSWAP ( N - N' ) \ special hardware to swap bytes. -1 0 !+ @ ; \ This only cost 4 cycles on my machine. \ I but the byte swap at address $FFFF for simplisty. : WFRQ ( - FStat ) \ Wait for FRequest. BEGIN FSTAT @ \ Check Status DUP FREQ AND 0= \ for request. WHILE DROP REPEAT ; : FSTAT& ( Mask - Result ) \ Masking for status FSTAT @ AND ; : FRDAT ( - DATA ) \ Read Floppy Data port. WFRQ \ Wait for request. FDIR AND \ Should be a read request. IF FDATA @ \ Read floppy data BSWAP 0FF AND \ Make byte ELSE ABORT" RD? " \ Was expecting to read? THEN ; : FWDAT ( Data - ) BSWAP \ Move byte to D8-D15. WFRQ \ Wait for request. FDIR AND \ Check for write? IF ABORT" WR? " \ Was expecting to write? ELSE FDATA ! \ Write floppy data. THEN ; DECIMAL : FSPECIFY ( - ) \ Specify command \ These values are different for AT controllers at different speeds. \ I am not using DMA in my simple system -- I am using polled \ I/O instead. This means that I have to dedicate the NC4000 \ to disk-only during transfers. If one wanted to, one could \ use a DMA controller and use DMA. For 360K drives, one needs \ to be able to supply a byte every 32 µs, and 16 µs for a 1.2 Mb. \ This is no problem for a 4 MHz NC4000, but one should analyze \ time used to do reads and writes. 3 FWDAT \ Command Byte Specify [ 24 ( STEP) 2 / NEGATE 16 * 192 ( HUT) 32 / + ] LITERAL FWDAT \ 24 ms step and 192 ms head unload [ 40 ( HLT) 4 / 2 * 1 ( NonDMA) OR ] LITERAL FWDAT ; \ 40 ms head load time with no DMA \ Most commands are built with 9 bytes \ Command 1st byte. VARIABLE FSEL \ Selects, 2nd byte of a command. VARIABLE FCYL \ Cylinder, 3rd byte of a command. VARIABLE FHD \ Head, 4th byte of a command. VARIABLE FSEC \ Sector, 5th byte of a command. \ Sector size, 6th byte of a command. \ Max Sectors, 7th byte of a command. 1B CONSTANT GPL \ Gap Length, 8th byte of a command. \ Data Length, Always 0FF for non zero Sector size above. \ Result returns 7 bytes, first 3 are status. VARIABLE FST0 VARIABLE FST1 VARIABLE FST2 \ last 4 are cyl,head,sect#,sect size. DECIMAL : CTSH ( DskAddr - ) \ Break a logical address into Cyl,HD,SEC. 18 /MOD \ 18 sectors per track. DUP 39 > \ 40 track per disk. IF ABORT" " THEN \ to many tracks? FCYL ! \ track = cylinder. 9 /MOD FLAG FHD ! \ determine head. 1 + FSEC ! ; \ Sector numbers start at 1 not zero. HEX : WNBSY ( - ) BEGIN 1F00 FSTAT& 0= \ Wait for no activity of disk. UNTIL ; : FWHD/SEL ( - ) \ Writes standard second command byte. FHD @ 4 AND \ Head bit. FSEL @ OR FWDAT ; \ With drive bits. : FCMD ( Command - ) \ Write a standard 9 byte command sequence. WNBSY \ Wait not busy 40 OR FWDAT \ Mask in MFM bit to command. FWHD/SEL \ Head drive select. FCYL @ FWDAT \ Cylinder number. FHD @ 1 AND FWDAT \ Head number. FSEC @ DUP FWDAT \ Sector #. 2 FWDAT \ 2 = 512 Sector size. FWDAT \ sector # or sectors per track. GPL FWDAT \ Gap length. -1 FWDAT ; \ Always 0FF. : FRSLT ( - ) \ Read Result Phase data. FRDAT FST0 ! \ FRDAT FST1 ! \ FRDAT FST2 ! \ Read 3 status bytes. 3 FOR FRDAT DROP \ discard other 4 bytes. NEXT 100 CYCLES ; \ a little delay for about 25 us. : FCLR ( - ) \ Clear any activity. BEGIN 8 FWDAT \ Status command. FRDAT 80 - \ No other status. WHILE FRDAT DROP \ discard cyclinder. REPEAT ; : WTEND ( - ) \ Wait for end of operation. 0 BEGIN DROP 8 FWDAT \ Status. FRDAT DUP 80 - \ no activity. UNTIL FST0 ! \ save status. FRDAT FCYL ! ; \ and current cylinder. : SELDRV ( DRV# - ) DUP IF FSEL1 FMOT1 \ controller mask and motor for drive1. ELSE FSEL0 FMOT0 \ controller mask and motor for drive2. THEN OR FRST- OR \ clear reset and start motor. FREG ! FSEL ! ; \ and drive select for controller. : ?FSEL ( DRV# -) \ Make sure drive is selected and running FSEL @ OVER - \ check selected IF SELDRV \ if not then select drive. ELSE DROP \ else do nothing. THEN ; : FRECAL ( Drv# -) \ Recal drive. DUP SELDRV \ Start drive. FSPECIFY \ Load step, hlt and hut FCLR \ force a clear of computers 7 FWDAT \ recal command FWDAT \ write drive number WTEND ; \ wait for the end : FOFF ( - ) \ motor off \ This should be called when it is desired to turn motor off. \ This may be connected with some timer interrupt. I use \ the KEY wait loop with a timer. FRST- FREG ! \ turn motors off -1 FSEL ! ; \ invalid drive for ?FSEL : FSEEK ( -) \ Seek cylinder. WNBSY \ wait not busy. FCLR \ clear controller. 0F FWDAT \ seek cylinder command. FWHD/SEL \ second command byte FCYL @ FWDAT \ cylinder. WTEND ; \ wait until done. \ Addressing is 16-bit words : FSEC! ( Addr -) \ Write a sector from address. 5 FCMD \ Write sector. 1FF FOR 1 @+ \ fetch 512 bytes. SWAP FWDAT \ write to floppy. NEXT DROP FRSLT ; \ read result. : FSEC@ ( Addr - ) \ Read a sector to address. 6 FCMD \ Read a sector command. 1FF FOR FRDAT \ get 512 bytes. SWAP 1 !+ \ put them into buffer. NEXT DROP FRSLT ; \ read result. DECIMAL : WRBUF ( Addr Blk# - ) \ Write a 1K buffer to disk block number. 360 /MOD ?FSEL \ start drive. 2* \ disk physical address( 512/SEC ). DUP CTSH \ calc cylinder, sector and head. FSEEK \ move there. OVER FSEC! \ write first half. 1 + \ next logical sector. CTSH FSEEK \ move there. 512 + FSEC! ; \ write next bytes. : RDBUF ( Addr Blk# - ) \ Read a 1K buffer from disk block number. 360 /MOD ?FSEL \ start drive. 2* \ disk physical address( 512/SEC ). DUP CTSH \ calc cylinder, sector and head. FSEEK \ move there. OVER FSEC@ \ Read first sector. 1 + \ next logical sector. CTSH FSEEK \ move there. DUP 512 + FSEC@ \ Read second sector. 1023 FOR \ Mask $4000 into each byte for cmForth. DUP @ 16384 OR SWAP 1 !+ NEXT DROP ;