simplified head phase/track handling; don't overrun RWTS logic sequencer

This commit is contained in:
Jorj Bauer 2017-12-29 22:16:09 -05:00
parent 69348f1b64
commit a24406870e
3 changed files with 136 additions and 84 deletions

View File

@ -23,11 +23,8 @@ DiskII::DiskII(AppleMMU *mmu)
this->mmu = mmu;
trackPos[0] = trackPos[1] = 0;
prevTrack[0] = prevTrack[1] = 0;
stepperPhases[0] = stepperPhases[1] = 0;
curPhase[0] = curPhase[1] = 0;
curTrack[0] = curTrack[1] = 0;
curHalfTrack[0] = curHalfTrack[1] = 0;
trackDirty = false;
trackToRead = -1;
@ -51,11 +48,8 @@ DiskII::~DiskII()
void DiskII::Reset()
{
trackPos[0] = trackPos[1] = 0;
prevTrack[0] = prevTrack[1] = 0;
stepperPhases[0] = stepperPhases[1] = 0;
curPhase[0] = curPhase[1] = 0;
curTrack[0] = curTrack[1] = 0;
curHalfTrack[0] = curHalfTrack[1] = 0;
trackDirty = false;
trackToRead = -1;
@ -82,20 +76,30 @@ uint8_t DiskII::readSwitches(uint8_t s)
{
switch (s) {
case 0x00: // change stepper motor phase
break;
case 0x01:
setPhase(0);
break;
case 0x02:
break;
case 0x03:
setPhase(1);
break;
case 0x04:
break;
case 0x05:
case 0x06:
case 0x07:
step(s);
setPhase(2);
break;
case 0x06: // 3 off
break;
case 0x07: // 3 on
setPhase(3);
break;
case 0x08: // drive off
indicatorIsOn[selectedDisk] = 99;
g_display->setDriveIndicator(selectedDisk, false); // FIXME: after a spell...
checkFlush(curTrack[selectedDisk]);
checkFlush(curHalfTrack[selectedDisk]>>1);
break;
case 0x09: // drive on
indicatorIsOn[selectedDisk] = 100;
@ -116,7 +120,7 @@ uint8_t DiskII::readSwitches(uint8_t s)
case 0x0D: // load data register (latch)
// This is complex and incomplete. cf. Logic State Sequencer,
// UTA2E, p. 9-14
if (!writeMode && indicatorIsOn[selectedDisk]) {
if (!writeMode) {
if (isWriteProtected())
readWriteLatch |= 0x80;
else
@ -129,9 +133,9 @@ uint8_t DiskII::readSwitches(uint8_t s)
// FIXME: with this shortcut here, disk access speeds up ridiculously.
// Is this breaking anything?
return ( (readOrWriteByte() & 0x7F) |
(isWriteProtected() ? 0x80 : 0x00) );
/*return ( (readOrWriteByte() & 0x7F) |
(isWriteProtected() ? 0x80 : 0x00) );
*/
break;
case 0x0F: // set write mode
setWriteMode(true);
@ -155,6 +159,11 @@ uint8_t DiskII::readSwitches(uint8_t s)
// Any even address read returns the readWriteLatch (UTA2E Table 9.1,
// p. 9-12, note 2)
// if ((s & 1) == 0 && curHalfTrack[selectedDisk] <= 3) {
// printf("Read: %X\n", readWriteLatch);
// fflush(stdout);
// }
return (s & 1) ? FLOATING : readWriteLatch;
}
@ -162,14 +171,24 @@ void DiskII::writeSwitches(uint8_t s, uint8_t v)
{
switch (s) {
case 0x00: // change stepper motor phase
break;
case 0x01:
setPhase(0);
break;
case 0x02:
break;
case 0x03:
setPhase(1);
break;
case 0x04:
break;
case 0x05:
case 0x06:
case 0x07:
step(s);
setPhase(2);
break;
case 0x06: // 3 off
break;
case 0x07: // 3 on
setPhase(3);
break;
case 0x08: // drive off
@ -206,54 +225,74 @@ void DiskII::writeSwitches(uint8_t s, uint8_t v)
}
}
// where a is the address poked & 0x07 (e.g. "0xC0E0 & 0x07")
void DiskII::step(uint8_t a)
/* The Disk ][ has a stepper motor that moves the head across the tracks.
* Switches 0-7 turn off and on the four different magnet phases; pulsing
* from (e.g.) phase 0 to phase 1 makes the motor move up a track, and
* (e.g.) phase 1 to phase 0 makes the motor move down a track.
*
* Except that's not quite true: the stepper actually moves the head a
* _half_ track.
*
* This is a very simplified version of the stepper motor code. In theory,
* we should keep track of all 4 phase magnets; and then only move up or down
* a half track when two adjacent motors are on (not three adjacent motors;
* and not two opposite motors). But that physical characteristic isn't
* important for most diskettes, and our image formats aren't likely to
* be able to provide appropriate half-track data to the programs that played
* tricks with these half-tracks (for copy protection or whatever).
*
* This setPhase is only called when turning *on* a phase. It's assumed that
* something is turning *off* the phases correctly; and that the combination
* of the previous phase that was on and the current phase that's being turned
* on are reliable enough to determine direction.
*
* The _phase_delta array is four sets of offsets - one for each
* current phase, detailing what the step will be given the next
* phase. This kind of emulates the messiness of going from phase 0
* to 2 -- it's going to move forward two half-steps -- but then doing
* the same thing again is just going to move you back two half-steps...
*
*/
void DiskII::setPhase(uint8_t phase)
{
int phase = (a >> 1) & 3;
int phase_bit = (1 << phase);
const int8_t _phase_delta[16] = { 0, 1, 2, -1, // prev phase 0 -> 0/1/2/3
-1, 0, 1, 2, // prev phase 1 -> 0/1/2/3
-2, -1, 0, 1, // prev phase 2 -> 0/1/2/3
1, -2, -1, 0 // prev phase 3 -> 0/1/2/3
};
if (a & 1) {
stepperPhases[selectedDisk] |= phase_bit;
} else {
stepperPhases[selectedDisk] &= ~phase_bit;
int8_t prevPhase = curPhase[selectedDisk];
int8_t prevHalfTrack = curHalfTrack[selectedDisk];
curHalfTrack[selectedDisk] += _phase_delta[(prevPhase * 4) + phase];
curPhase[selectedDisk] = phase;
// Cap at 35 tracks (a normal disk size). Some drives let you go farther,
// and we could support that by increasing this limit - but the images
// would be different too, so there would be more work to abstract out...
if (curHalfTrack[selectedDisk] > 35 * 2 - 1) {
curHalfTrack[selectedDisk] = 35 * 2 - 1;
}
// If the magnet opposite the cog is off, then try to move. Move in
// the direction of adjacent magent - but if both adjacent magents
// are on, then don't move at all.
int direction = 0;
int cur_phase = curPhase[selectedDisk];
if (stepperPhases[selectedDisk] & (1 << ((cur_phase + 1) & 3))) {
direction++;
}
if (stepperPhases[selectedDisk] & (1 << ((cur_phase+3) & 3))) {
direction--;
// Don't go past the innermost track, of course.
if (curHalfTrack[selectedDisk] < 0) {
curHalfTrack[selectedDisk] = 0;
// recalibrate! This is where the fun noise goes DaDaDaDaDaDaDaDaDa
}
if (direction) {
int next_phase = cur_phase + direction;
if (next_phase < 0) {
next_phase = 0;
// recalibrate
}
// FIXME: don't go beyond the end of the media. For a 35-track disk, that's 68 == ((35-1) * 2).
if (next_phase > 69) {
next_phase = 69;
// ... Or go to 79? or 68, which we stopped at before? Not sure
// what the right behavior is here.
}
if ((cur_phase >> 1) != (next_phase >> 1)) {
// We're about to change tracks - be sure to flush the track if we've written to it
checkFlush(prevTrack[selectedDisk]);
prevTrack[selectedDisk] = curTrack[selectedDisk];
curTrack[selectedDisk] = next_phase>>1;
// mark it to be read
trackToRead = curTrack[selectedDisk];
}
curPhase[selectedDisk] = next_phase;
/*
printf("phase %d => %d; curHalfTrack %d => %d\n",
prevPhase, curPhase[selectedDisk],
prevHalfTrack, curHalfTrack[selectedDisk]);
*/
if (curHalfTrack[selectedDisk]>>1 != prevHalfTrack>>1) {
// We're changing track - flush the old track back to disk
checkFlush(prevHalfTrack>>1);
// mark the new track to be read
trackToRead = curHalfTrack[selectedDisk]>>1;
}
}
@ -329,13 +368,17 @@ void DiskII::select(int8_t which)
indicatorIsOn[selectedDisk] = 0;
g_display->setDriveIndicator(selectedDisk, false);
checkFlush(curTrack[selectedDisk]);
checkFlush(curHalfTrack[selectedDisk]>>1);
// set the selected disk drive
selectedDisk = which;
// trackToRead = curHalfTrack[selectedDisk]>>1;
trackToRead = -1; // Assume we don't have to read anything on the
// newly selected drive. When we get the first
// read, we'll notice the track buffer is empty...
trackBuffer->clear();
}
// set the selected disk drive
selectedDisk = which;
trackToRead = curTrack[selectedDisk];
}
uint8_t DiskII::readOrWriteByte()
@ -392,12 +435,24 @@ uint8_t DiskII::readOrWriteByte()
//
// Don't fill it right here, b/c we don't want to bog down the CPU
// thread/ISR.
if (trackToRead == curTrack[selectedDisk]) {// waiting for a read to complete
if (trackToRead == curHalfTrack[selectedDisk]>>1) {// waiting for a read to complete for the current track
return GAP;
}
// return 0x00 every other byte. Helps the logic sequencer stay in sync.
// Otherwise we wind up waiting long periods of time for it to sync up,
// presumably because we're overrunning it (returning data faster than
// the actual drive would be able to)?
static bool whitespace = false;
if (whitespace) {
whitespace = false;
return 0x00;
}
whitespace = !whitespace;
if ((trackToRead != -1) || !trackBuffer->hasData()) {
checkFlush(curTrack[selectedDisk]);
checkFlush(curHalfTrack[selectedDisk]>>1);
// Need to read in a track of data and nibblize it. We'll return 0xFF
// until that completes.
@ -406,7 +461,7 @@ uint8_t DiskII::readOrWriteByte()
// one we're reading. When we finish the read, we'll need to check
// to be sure that we're still trying to read the same track that
// we started with.
trackToRead = curTrack[selectedDisk];
trackToRead = curHalfTrack[selectedDisk]>>1;
// While we're waiting for the sector to come around, we'll return
// GAP bytes.
@ -430,7 +485,6 @@ void DiskII::fillDiskBuffer()
return;
trackDirty = false;
trackBuffer->clear();
int8_t trackWeAreReading = trackToRead;
int8_t diskWeAreUsing = selectedDisk;
@ -445,8 +499,8 @@ void DiskII::fillDiskBuffer()
// couldn't, though, if RAM weren't at a premium.
for (int i=0; i<16; i++) {
g_filemanager->seekBlock(disk[diskWeAreUsing], trackWeAreReading * 16 + i, diskType[diskWeAreUsing] == nibDisk);
if (!g_filemanager->readBlock(disk[diskWeAreUsing], rawTrackBuffer, diskType[diskWeAreUsing] == nibDisk)) {
g_filemanager->seekBlock(disk[diskWeAreUsing], trackWeAreReading * 16 + i, true);
if (!g_filemanager->readBlock(disk[diskWeAreUsing], rawTrackBuffer, true)) {
// FIXME: error handling?
trackToRead = -1;
return;
@ -456,14 +510,14 @@ void DiskII::fillDiskBuffer()
} else {
// It's a .dsk / .po disk image. Read the whole track in to
// rawTrackBuffer and nibblize it.
g_filemanager->seekBlock(disk[diskWeAreUsing], trackWeAreReading * 16, diskType[diskWeAreUsing] == nibDisk);
if (!g_filemanager->readTrack(disk[diskWeAreUsing], rawTrackBuffer, diskType[diskWeAreUsing] == nibDisk)) {
g_filemanager->seekBlock(disk[diskWeAreUsing], trackWeAreReading * 16, false);
if (!g_filemanager->readTrack(disk[diskWeAreUsing], rawTrackBuffer, false)) {
// FIXME: error handling?
trackToRead = -1;
return;
}
nibblizeTrack(trackBuffer, rawTrackBuffer, diskType[diskWeAreUsing], curTrack[selectedDisk]);
nibblizeTrack(trackBuffer, rawTrackBuffer, diskType[diskWeAreUsing], curHalfTrack[selectedDisk]>>1);
}
// Make sure we're still intending to read the track we just read
@ -518,7 +572,7 @@ void DiskII::flushTrack(int8_t track, int8_t sel)
return;
}
nibErr e = denibblizeTrack(trackBuffer, rawTrackBuffer, diskType[sel], curTrack[selectedDisk]);
nibErr e = denibblizeTrack(trackBuffer, rawTrackBuffer, diskType[sel], curHalfTrack[selectedDisk]>>1);
switch (e) {
case errorShortTrack:
g_display->debugMsg("DII: short track");

View File

@ -34,7 +34,7 @@ class DiskII : public Slot {
void fillDiskBuffer(); // called from main loop
private:
void step(uint8_t phase);
void setPhase(uint8_t phase);
bool isWriteProtected();
void setWriteMode(bool enable);
@ -48,7 +48,8 @@ class DiskII : public Slot {
#endif
private:
volatile uint8_t curTrack[2];
volatile int8_t curHalfTrack[2];
volatile int8_t curPhase[2];
volatile bool trackDirty; // does this track need flushing to disk?
uint8_t readWriteLatch;
RingBuffer *trackBuffer; // nibblized data
@ -58,11 +59,6 @@ class DiskII : public Slot {
bool writeProt;
AppleMMU *mmu;
int trackPos[2];
int prevTrack[2];
int stepperPhases[2];
int curPhase[2];
int8_t disk[2];
volatile uint8_t indicatorIsOn[2];
uint8_t diskType[2];

View File

@ -110,7 +110,9 @@ void nibblizeTrack(RingBuffer *trackBuffer, uint8_t *rawTrackBuffer,
trackBuffer->addByte(0xAA);
trackBuffer->addByte(0xEB);
// trackBuffer->addByte(GAP);
#ifdef LONGGAPS
trackBuffer->addByte(GAP);
#endif
}
}