I'm currently writing a program for the Apple IIe that requires reading/writing files from disk. In reading the books I've found archived online about assembly language for the Apple II I've come across the $C060
subroutine which is for accessing the cassette port, but I can't seem to find a subroutine that will access the disk drive. Is there such a monitor command? If not, what would I do to read/write a file to/from disk during the program?
1 Answer
It's possible to read and write a floppy disk without loading in DOS. DOS is useful if you want to read/write disks that are usable by other programs, and making things work reliably using DOS is apt to be easier than using raw I/O, but raw I/O can be faster than DOS and allow more information to be stored on a disk, especially if you never need to read or write less than a track at a time.
When using track-at-a-time I/O, writing and reading a disk is conceptually simple:
-
To write a disk track, build a buffer holding about 6K of suitably-formatted data, turn on the motor, move the head to the desired track, turn on the write signal, write the pattern 0x92 $A4 about 500 times [if the buffer is much smaller than 4,000 bytes, it may be necessary to increase that count, so as to write a total of at least ~5,000 bytes], followed by $9F then output the contents of the buffer and turn off the write signal. Bytes must be sent to the drive controller precisely once every 32 clock cycles. Slipping by even one cycle will cause the controller to output garbage.
-
To read a disk track, turn on the motor, move the head to the desired track, and read bytes of data from the disk until one sees a the byte sequence $92 $A4 $9F, and then read the rest of the data. Data will arrive at a rate of about 32 cycles/byte, and each byte must be read within a 7 cycle window.
The data read back should precisely match the data written provided every byte in the buffer upholds three restrictions:
-
Every byte must have the most significant bit set.
-
No byte may contain more than two consecutive 0 bits.
-
Every byte must contain at least one pair of consecutive 1 bits.
There are 64 possible byte values that meet those criteria. Encoding arbitrary data to fit that limitation before storing it, and decoding information that is written in that fashion can be a nuisance, but that's part of the "fun" of writing one's own disk routines. Many disk routines read data into a buffer without decoding it, and then decode it later, but if one chooses a suitable encoding it's possible to decode information in real time as it's received from the disk.
I forgot to mention how to move the turn on the drive, select drive 1 or 2, move head, read, and write bytes from a controller in slot 6:
-
To turn on the drive, access $C0E9. To turn it off, access $C0E8. The effect of turning off the drive will be delayed by about a second.
-
To switch to drive 2, access $C0EB. To switch to drive 1, access $C0EA.
-
To move the head, think of it as being attached to a wheel which is attached to a hand on a clock face. The hand will point at 12:00 when the head is at any even numbered, track, and at 6:00 when it is on any odd numbered track. Reading $C0E1, $C0E3, $C0E5, or $C0E7 will turn on a coil that pulls the hand toward 12:00, 3:00, 6:00, or 9:00. Accessing the next lower address will turn off the coil. Move the head by turning on a coil 90 degrees from the wheel's current position, waiting awhile, turning that coil off and turning on the next one, etc.
-
To see if a drive is attached, read $C0EC a few times and see if the value changes. If not, no drive is attached. If a drive is known to exist, use a two-instruction loop to read $C0EC until the high bit becomes set. If one a four-cycle instruction was used for the read, and a two-cycle branch-not-taken once high bit became set (e.g.Â
wait293: LDX $C0EC / BPL wait293
). To ensure that one reads every byte, ensure that the CPU executes at least 12 and at most 24 cycles before the next time the sequence is executed. Taking less than 12 cycles may yield duplicate reads. Taking more than 24 may cause bytes to be skipped. -
To start writing data, write any value to $C0ED, then write the first byte value to $C0EF and immediately read $C0EC (ignore the value written). One must then execute exactly 24 cycles of other code, a write of the next byte to $C0ED, an immediate read of $C0EC, etc. When done, read $C0EE.