Let's explore some code (mostly psuedocode) for a lot of different functions that you may need to implement when writing a floppy device driver.
Table of Contents
Power On Function
When powering on the device, you should configure the I/O direction for
each pin and immediately pull them all
This will prevent accidental writes and other undefined behavior.
Motor On Function
Spinning up the motor is pretty simple, just pull the
Motor On pin to logic
LOW and wait 500ms. For good
measure, you may
want to also wait for a logic level transition on the Index pin
that one complete revolution has been achieved. This is a good indicator
is working successfully.
In some floppy drives, they'll have a mode called
"Automatic Calibration" which just means
that it will automatically seek track0 when it turns on. I don't believe
this is guaranteed
functionality across drives, so you may want to
manually if the Track00 pin is
Step Track Function
In common 3.5" floppy media, there are 80 tracks (also called
cylinders). To step
along a given track, you first configure the Direction Select pin
and then pulse
the Step pin. 3ms
LOW and then 3ms
This will move
the track stepper motor by a single step along its configured path.
Now here's the fun part: over/under stepping can happen. The timing isn't consistent across devices and sometimes the motor just skips for whatever reason. If you're trying to read data, you should inspect the Sector Metadata to ascertain what track you're actually on and adjust accordingly if it's not what you think it should be.
Seek Track 00 Function
This algorithm will step outwards 100 times, looking for a
on the Track00 pin. If it can't find any pulse, it'll step
inwards a few more
times for good measure.
Read Pulse Function
Reading a pulse from the data line requires knowing exactly how much
time has elapsed between the leading edge of the
to the trailing edge of the
- A 2us pulse represents the binary
- A 3us pulse represents the binary
- A 4us pulse represents the binary
The synchronization function doesn't have to be implemented in assembly, but that's what I did for performance reasons. This code assumes the following functions exist:
- fdd_read_index - A function to sense the INDEX pin
- fdd_read_pulse - A function to parse the next pulse from the DATA pin
If all goes well, this function will return immediately after reading the last pulse which indicates you have found a barrier. Important distinction: this will return for either a sector barrier or a data barrier.
Read Data Function
Assuming you've implemented MFM encoding elsewhere, there's still a bit of work you have to do in order to read data. The most pressing concern is detecting the various error conditions. Mainly:
- The track being wrong
- Completing a full revolution without finding your sector
If you're on the wrong track, you will need to calculate the difference and step the head.
Write Data Function
Assuming you've implemented MFM encoding elsewhere, there's a lot of work you'll still have to do
to get in position. The timing is absolutely critical here.
If you look at the Sector Metadata spec, you'll notice there are 22 bytes of 0x4E
after the metadta and before the userdata. This is basically a time buffer for you do all the verification
in order to make sure you're at the right place. Then you can invoke
mfm_sync() to wait
for the next barrier that indicates you're in the right spot.
NOTE: In my code, I skip the first flux signal. If your prefix byte is 0xFB or 0xFA, the first signal will always be a short pulse and that initial pulse is a byproduct of the barrier code. So you need to effectively skip the first pulse you prepared since it's already accounted for by the barrier.