diff --git a/apple/appledisplay.cpp b/apple/appledisplay.cpp index 94fd574..7562786 100644 --- a/apple/appledisplay.cpp +++ b/apple/appledisplay.cpp @@ -102,84 +102,6 @@ bool AppleDisplay::deinterlaceHiresAddress(uint16_t address, uint8_t *row, uint1 return true; } - -void AppleDisplay::writeLores(uint16_t address, uint8_t v) -{ - if (address >= 0x800 && !((*switches) & S_80COL)) { - if (!((*switches) & S_PAGE2)) { - // writing to page2 text/lores, but that's not displayed right now, so nothing to do - return; - } - } - - uint8_t row, col; - deinterlaceAddress(address, &row, &col); - - if (col <= 39) { - if ((*switches) & S_TEXT || - (((*switches) & S_MIXED) && row >= 20)) { - if ((*switches) & S_80COL) { - Draw80CharacterAt(v, col, row, ((*switches) & S_PAGE2) ? 0 : 1); - } else { - DrawCharacterAt(v, col, row); - } - } - } - - if (!((*switches) & S_TEXT) && - !((*switches) & S_HIRES)) { - if (col <= 39) { - if (row < 20 || - (! ((*switches) & S_MIXED))) { - // low-res graphics mode. Each character has two 4-bit - // values in it: first half is the "top" pixel, and second "bottom". - if (((*switches) & S_80COL) && ((*switches) & S_DHIRES)) { - Draw80LoresPixelAt(v, col, row, ((*switches) & S_PAGE2) ? 0 : 1); - } else { - DrawLoresPixelAt(v, col, row); - } - } - } - } - dirty = true; -} - -void AppleDisplay::writeHires(uint16_t address, uint8_t v) -{ - if ((*switches) & S_HIRES) { - if ((*switches) & S_DHIRES) { - // Double hires: make sure we're drawing to the page that's visible. - // If S_80STORE is on, then it's $2000 (and S_PAGE2 controls main/aux bank r/w). - // If S_80STORE is off, then it's $4000 (and RAMRD/RAMWT are used). - - if (((*switches) & S_80STORE) && address >= 0x4000 && address <= 0x5FFF) - return; - if ( !((*switches) & S_80STORE) && address >= 0x2000 && address <= 0x3FFF) - return; - - Draw14DoubleHiresPixelsAt(address); - dirty = true; - - return; - } - - // If it's a write to single hires but the 80store byte is on, then what do we do? - if ((*switches) & S_80STORE) { - // FIXME - return; - } - - // Make sure we're writing to the page that's visible before we draw - if (address >= 0x2000 && address <= 0x3FFF && ((*switches) & S_PAGE2)) - return; - if (address >= 0x4000 && address <= 0x5FFF && !((*switches) & S_PAGE2)) - return; - - Draw14HiresPixelsAt(address & 0xFFFE); - dirty = true; - } -} - // return a pointer to the right glyph, and set *invert appropriately const unsigned char *AppleDisplay::xlateChar(uint8_t c, bool *invert) { @@ -225,60 +147,7 @@ const unsigned char *AppleDisplay::xlateChar(uint8_t c, bool *invert) /* NOTREACHED */ } -void AppleDisplay::Draw80CharacterAt(uint8_t c, uint8_t x, uint8_t y, uint8_t offset) -{ - bool invert; - const uint8_t *cptr = xlateChar(c, &invert); - - // Even characters are in bank 0 ram. Odd characters are in bank 1 ram. - // Technically, this would need 560 columns to work correctly - and I - // don't have that, so it's going to be a bit wonky. - // - // First pass: draw two pixels on top of each other, clearing only - // if both are black. This would be blocky but probably passable if - // it weren't for the fact that characters are 7 pixels wide, so we - // wind up sharing a half-pixel between two characters. So we'll - // render these as 3-pixel-wide characters and make sure they always - // even-align the drawing on the left side so we don't overwrite - // every other one on the left or right side. - - for (uint8_t y2 = 0; y2<8; y2++) { - uint8_t d = *(cptr + y2); - for (uint8_t x2 = 0; x2 <= 7; x2+=2) { - uint16_t basex = ((x * 2 + offset) * 7) & 0xFFFE; // even aligned - bool pixelOn = ( (d & (1<>= 1; - } - } -} - -void AppleDisplay::Draw14DoubleHiresPixelsAt(uint16_t addr) +inline void AppleDisplay::Draw14DoubleHiresPixelsAt(uint16_t addr) { // We will consult 4 bytes (2 in main, 2 in aux) for any single-byte // write. Align to the first byte in that series based on what @@ -333,7 +202,7 @@ void AppleDisplay::Draw14DoubleHiresPixelsAt(uint16_t addr) // because between two bytes there is a shared bit. // FIXME: what happens when the high bit of the left doesn't match the right? Which high bit does // the overlap bit get? -void AppleDisplay::Draw14HiresPixelsAt(uint16_t addr) +inline void AppleDisplay::Draw14HiresPixelsAt(uint16_t addr) { uint8_t row; uint16_t col; @@ -457,7 +326,80 @@ void AppleDisplay::Draw14HiresPixelsAt(uint16_t addr) } } -void AppleDisplay::redraw40ColumnText() +void AppleDisplay::redraw80ColumnText(uint8_t startingY) +{ + uint8_t row, col; + col = -1; // will force us to deinterlaceAddress() + bool invert; + const uint8_t *cptr; + + // FIXME: is there ever a case for 0x800, like in redraw40ColumnText? + uint16_t start = 0x400; + + // Every time through this loop, we increment the column. That's going to be correct most of the time. + // Sometimes we'll get beyond the end (40 columns), and wind up on another line 8 rows down. + // Sometimes we'll get beyond the end, and we'll wind up in unused RAM. + // But this is an optimization (for speed) over just calling DrawCharacter() for every one. + for (uint16_t addr = start; addr <= start + 0x3FF; addr++,col++) { + if (col > 39 || row > 23) { + // Could be blanking space; we'll try to re-confirm... + deinterlaceAddress(addr, &row, &col); + } + + // Only draw onscreen locations + if (row >= startingY && col <= 39 && row <= 23) { + // Even characters are in bank 0 ram. Odd characters are in bank + // 1 ram. Technically, this would need 560 columns to work + // correctly - and I don't have that, so it's going to be a bit + // wonky. + // + // First pass: draw two pixels on top of each other, clearing + // only if both are black. This would be blocky but probably + // passable if it weren't for the fact that characters are 7 + // pixels wide, so we wind up sharing a half-pixel between two + // characters. So we'll render these as 3-pixel-wide characters + // and make sure they always even-align the drawing on the left + // side so we don't overwrite every other one on the left or + // right side. + + // Draw the first of two characters + cptr = xlateChar(mmu->readDirect(addr, 1), &invert); + for (uint8_t y2 = 0; y2<8; y2++) { + uint8_t d = *(cptr + y2); + for (uint8_t x2 = 0; x2 <= 7; x2+=2) { + uint16_t basex = ((col * 2) * 7) & 0xFFFE; // even aligned + bool pixelOn = ( (d & (1<readDirect(addr, 0), &invert); + for (uint8_t y2 = 0; y2<8; y2++) { + uint8_t d = *(cptr + y2); + for (uint8_t x2 = 0; x2 <= 7; x2+=2) { + uint16_t basex = ((col * 2 + 1) * 7) & 0xFFFE; // even aligned -- +1 for the second character + bool pixelOn = ( (d & (1<= startingY && col <= 39 && row <= 23) { const uint8_t *cptr = xlateChar(mmu->read(addr), &invert); for (uint8_t y2 = 0; y2<8; y2++) { @@ -496,86 +438,56 @@ void AppleDisplay::redraw40ColumnText() } } -void AppleDisplay::modeChange() +void AppleDisplay::redrawHires() { - if ((*switches) & S_TEXT) { - if ((*switches) & S_80COL) { - for (uint16_t addr = 0x400; addr <= 0x400 + 0x3FF; addr++) { - uint8_t row, col; - deinterlaceAddress(addr, &row, &col); - if (col <= 39 && row <= 23) { - Draw80CharacterAt(mmu->readDirect(addr, 0), col, row, 1); - Draw80CharacterAt(mmu->readDirect(addr, 1), col, row, 0); - } - } - } else { - redraw40ColumnText(); - } - - return; + uint16_t start = ((*switches) & S_PAGE2) ? 0x4000 : 0x2000; + if ((*switches) & S_80STORE) { + // Apple IIe, technical nodes #3: 80STORE must be OFF to display Page 2 + start = 0x2000; } - // Not text mode - what mode are we in? - if ((*switches) & S_HIRES) { - // Hires - // FIXME: make this draw a row efficiently - // FIXME: can make more efficient by checking S_MIXED for lower bound - uint16_t start = ((*switches) & S_PAGE2) ? 0x4000 : 0x2000; - if ((*switches) & S_80STORE) { - // Apple IIe, technical nodes #3: 80STORE must be OFF to display Page 2 - start = 0x2000; + // FIXME: check MIXED & don't redraw the lower area if it's set + for (uint16_t addr = start; addr <= start + 0x1FFF; addr+=2) { + if ((*switches) & S_DHIRES) { + // FIXME: inline & optimize + Draw14DoubleHiresPixelsAt(addr); + } else { + // FIXME: inline & optimize + Draw14HiresPixelsAt(addr); } + } +} - for (uint16_t addr = start; addr <= start + 0x1FFF; addr+=2) { - if ((*switches) & S_DHIRES) { - Draw14DoubleHiresPixelsAt(addr); - } else { - Draw14HiresPixelsAt(addr); +void AppleDisplay::redrawLores() +{ + // FIXME: can make more efficient by checking S_MIXED for lower bound + + if (((*switches) & S_80COL) && ((*switches) & S_DHIRES)) { + for (uint16_t addr = 0x400; addr <= 0x400 + 0x3ff; addr++) { + uint8_t row, col; + deinterlaceAddress(addr, &row, &col); + if (col <= 39 && row <= 23) { + Draw80LoresPixelAt(mmu->readDirect(addr, 0), col, row, 1); + Draw80LoresPixelAt(mmu->readDirect(addr, 1), col, row, 0); } } } else { - // Lores - // FIXME: can make more efficient by checking S_MIXED for lower bound - - if (((*switches) & S_80COL) && ((*switches) & S_DHIRES)) { - for (uint16_t addr = 0x400; addr <= 0x400 + 0x3ff; addr++) { - uint8_t row, col; - deinterlaceAddress(addr, &row, &col); - if (col <= 39 && row <= 23) { - Draw80LoresPixelAt(mmu->readDirect(addr, 0), col, row, 1); - Draw80LoresPixelAt(mmu->readDirect(addr, 1), col, row, 0); - } - } - } else { - uint16_t start = ((*switches) & S_PAGE2) ? 0x800 : 0x400; - for (uint16_t addr = start; addr <= start + 0x3FF; addr++) { - uint8_t row, col; - deinterlaceAddress(addr, &row, &col); - if (col <= 39 && row <= 23) { - DrawLoresPixelAt(mmu->read(addr), col, row); - } - } - } - } - - if ((*switches) & S_MIXED) { - // Text at the bottom of the screen... - // FIXME: deal with 80-char properly uint16_t start = ((*switches) & S_PAGE2) ? 0x800 : 0x400; for (uint16_t addr = start; addr <= start + 0x3FF; addr++) { uint8_t row, col; deinterlaceAddress(addr, &row, &col); - if (col <= 39 && row >= 20 && row <= 23) { - if ((*switches) & S_80COL) { - Draw80CharacterAt(mmu->read(addr), col, row, ((*switches) & S_PAGE2) ? 0 : 1); - } else { - DrawCharacterAt(mmu->read(addr), col, row); - } + if (col <= 39 && row <= 23) { + DrawLoresPixelAt(mmu->read(addr), col, row); } } } } +void AppleDisplay::modeChange() +{ + dirty = true; +} + void AppleDisplay::Draw80LoresPixelAt(uint8_t c, uint8_t x, uint8_t y, uint8_t offset) { // Just like 80-column text, this has a minor problem; we're talimg @@ -626,28 +538,11 @@ void AppleDisplay::DrawLoresPixelAt(uint8_t c, uint8_t x, uint8_t y) void AppleDisplay::draw2Pixels(uint16_t two4bitColors, uint16_t x, uint8_t y) { - if (!dirty) { - dirty = true; - dirtyRect.left = x; dirtyRect.right = x + 1; - dirtyRect.top = dirtyRect.bottom = y; - } else { - extendDirtyRect(x, y); - extendDirtyRect(x+1, y); - } - videoBuffer[(y * DISPLAYWIDTH + x) /2] = two4bitColors; } inline void AppleDisplay::drawPixel(uint8_t color4bit, uint16_t x, uint8_t y) { - if (!dirty) { - dirty = true; - dirtyRect.left = dirtyRect.right = x; - dirtyRect.top = dirtyRect.bottom = y; - } else { - extendDirtyRect(x, y); - } - uint16_t idx = (y * DISPLAYWIDTH + x) / 2; if (x & 1) { videoBuffer[idx] = (videoBuffer[idx] & 0xF0) | color4bit; @@ -674,6 +569,36 @@ AiieRect AppleDisplay::getDirtyRect() bool AppleDisplay::needsRedraw() { + if (dirty) { + // Figure out what graphics mode we're in and redraw it in its entirety. + + if ((*switches) & S_TEXT) { + if ((*switches) & S_80COL) { + redraw80ColumnText(0); + } else { + redraw40ColumnText(0); + } + + return true; + } + + // Not text mode - what mode are we in? + if ((*switches) & S_HIRES) { + redrawHires(); + } else { + redrawLores(); + } + + // Mixed graphics modes: draw text @ bottom + if ((*switches) & S_MIXED) { + if ((*switches) & S_80COL) { + redraw80ColumnText(20); + } else { + redraw40ColumnText(20); + } + } + } + return dirty; } diff --git a/apple/appledisplay.h b/apple/appledisplay.h index f6bbed4..44cd484 100644 --- a/apple/appledisplay.h +++ b/apple/appledisplay.h @@ -52,6 +52,7 @@ class AppleDisplay : public VMDisplay{ void writeHires(uint16_t address, uint8_t v); void displayTypeChanged(); + private: bool deinterlaceAddress(uint16_t address, uint8_t *row, uint8_t *col); @@ -68,7 +69,10 @@ class AppleDisplay : public VMDisplay{ const unsigned char *xlateChar(uint8_t c, bool *invert); - void redraw40ColumnText(); + void redraw40ColumnText(uint8_t startingY); + void redraw80ColumnText(uint8_t startingY); + void redrawHires(); + void redrawLores(); private: volatile bool dirty; diff --git a/apple/applemmu.cpp b/apple/applemmu.cpp index 6887ba5..fae52da 100644 --- a/apple/applemmu.cpp +++ b/apple/applemmu.cpp @@ -112,13 +112,21 @@ void AppleMMU::write(uint16_t address, uint8_t v) if (address >= 0x400 && address <= 0x7FF) { - display->writeLores(address, v); + + // If it's mixed mode, or if it's not HIRES mode, then force a redraw + if (!(switches & S_HIRES) || (switches & S_MIXED)) { + // Force a redraw + display->modeChange(); + } return; } if (address >= 0x2000 && address <= 0x5FFF) { - display->writeHires(address, v); + if (switches & S_HIRES) { + // Force a redraw + display->modeChange(); + } } } @@ -385,6 +393,8 @@ uint8_t AppleMMU::readSwitches(uint16_t address) case 0xC051: // SETTEXT if (!(switches & S_TEXT)) { switches |= S_TEXT; + // also make sure we're *out* of HIRES mode. + switches &= ~S_HIRES; resetDisplay(); } return FLOATING; diff --git a/apple/applemmu.h b/apple/applemmu.h index 47f8527..852ac43 100644 --- a/apple/applemmu.h +++ b/apple/applemmu.h @@ -63,7 +63,7 @@ class AppleMMU : public MMU { private: AppleDisplay *display; uint16_t switches; - public: // debuggign + public: // 'public' for debugging bool auxRamRead; bool auxRamWrite; bool bank1; diff --git a/apple/applevm.cpp b/apple/applevm.cpp index cab87a6..63902f6 100644 --- a/apple/applevm.cpp +++ b/apple/applevm.cpp @@ -23,8 +23,11 @@ AppleVM::AppleVM() parallel = new ParallelCard(); ((AppleMMU *)mmu)->setSlot(1, parallel); + mockingboard = NULL; + /* mockingboard = new Mockingboard(); ((AppleMMU *)mmu)->setSlot(4, mockingboard); + */ #ifdef TEENSYDUINO teensyClock = new TeensyClock((AppleMMU *)mmu); @@ -39,7 +42,8 @@ AppleVM::~AppleVM() #endif delete disk6; delete parallel; - delete mockingboard; + if (mockingboard) + delete mockingboard; } // fixme: make member vars @@ -60,7 +64,8 @@ void AppleVM::cpuMaintenance(uint32_t cycles) } keyboard->maintainKeyboard(cycles); - mockingboard->update(cycles); + if (mockingboard) + mockingboard->update(cycles); } void AppleVM::Reset() @@ -83,11 +88,6 @@ void AppleVM::Monitor() ((AppleMMU *)mmu)->readSwitches(0xC051); // and text mode is on } -void AppleVM::batteryLevel(uint8_t zeroToOneHundred) -{ - g_display->drawBatteryStatus(zeroToOneHundred); -} - const char *AppleVM::DiskName(uint8_t drivenum) { return disk6->DiskName(drivenum); diff --git a/apple/applevm.h b/apple/applevm.h index 0f4b4c8..9aa801c 100644 --- a/apple/applevm.h +++ b/apple/applevm.h @@ -27,12 +27,11 @@ class AppleVM : public VM { const char *DiskName(uint8_t drivenum); void ejectDisk(uint8_t drivenum); void insertDisk(uint8_t drivenum, const char *filename, bool drawIt = true); - void batteryLevel(uint8_t zeroToOneHundred); virtual VMKeyboard *getKeyboard(); - protected: DiskII *disk6; + protected: VMKeyboard *keyboard; ParallelCard *parallel; Mockingboard *mockingboard; diff --git a/apple/diskii.cpp b/apple/diskii.cpp index a270cf9..2c1a4e2 100644 --- a/apple/diskii.cpp +++ b/apple/diskii.cpp @@ -25,15 +25,18 @@ DiskII::DiskII(AppleMMU *mmu) curTrack = 0; trackDirty = false; + trackToRead = -1; writeMode = false; writeProt = false; // FIXME: expose an interface to this - writeLatch = 0x00; + readWriteLatch = 0x00; disk[0] = disk[1] = -1; indicatorIsOn[0] = indicatorIsOn[1] = 0; selectedDisk = 0; diskType[0] = diskType[1] = dosDisk; + + indicatorNeedsDrawing = true; // set to true whenever the display needs to redraw... } DiskII::~DiskII() @@ -49,7 +52,7 @@ void DiskII::Reset() writeMode = false; writeProt = false; // FIXME: expose an interface to this - writeLatch = 0x00; + readWriteLatch = 0x00; ejectDisk(0); ejectDisk(1); @@ -71,12 +74,12 @@ uint8_t DiskII::readSwitches(uint8_t s) case 0x08: // drive off indicatorIsOn[selectedDisk] = 99; - g_display->drawDriveStatus(selectedDisk, false); + indicatorNeedsDrawing = true; flushTrack(); break; case 0x09: // drive on indicatorIsOn[selectedDisk] = 100; - g_display->drawDriveStatus(selectedDisk, true); + indicatorNeedsDrawing = true; break; case 0x0A: // select drive 1 @@ -87,7 +90,7 @@ uint8_t DiskII::readSwitches(uint8_t s) break; case 0x0C: // shift one read or write byte - writeLatch = readOrWriteByte(); + readWriteLatch = readOrWriteByte(); break; case 0x0D: // load data register (latch) @@ -95,9 +98,9 @@ uint8_t DiskII::readSwitches(uint8_t s) // UTA2E, p. 9-14 if (!writeMode && indicatorIsOn[selectedDisk]) { if (isWriteProtected()) - writeLatch |= 0x80; + readWriteLatch |= 0x80; else - writeLatch &= 0x7F; + readWriteLatch &= 0x7F; } break; @@ -120,16 +123,16 @@ uint8_t DiskII::readSwitches(uint8_t s) if (!indicatorIsOn[selectedDisk]) { // printf("Unexpected read while disk isn't on?\n"); indicatorIsOn[selectedDisk] = 100; - g_display->drawDriveStatus(selectedDisk, true); + indicatorNeedsDrawing = true; } if (indicatorIsOn[selectedDisk] > 0 && indicatorIsOn[selectedDisk] < 100) { indicatorIsOn[selectedDisk]--; // slowly spin it down... } - // Any even address read returns the writeLatch (UTA2E Table 9.1, + // Any even address read returns the readWriteLatch (UTA2E Table 9.1, // p. 9-12, note 2) - return (s & 1) ? FLOATING : writeLatch; + return (s & 1) ? FLOATING : readWriteLatch; } void DiskII::writeSwitches(uint8_t s, uint8_t v) @@ -176,7 +179,7 @@ void DiskII::writeSwitches(uint8_t s, uint8_t v) // All writes update the latch if (writeMode) { - writeLatch = v; + readWriteLatch = v; } } @@ -302,7 +305,7 @@ void DiskII::select(int8_t which) if (which != selectedDisk) { indicatorIsOn[selectedDisk] = 0; - g_display->drawDriveStatus(selectedDisk, false); + indicatorNeedsDrawing = true; flushTrack(); // in case it's dirty: flush before changing drives trackBuffer->clear(); @@ -322,8 +325,9 @@ uint8_t DiskII::readOrWriteByte() if (writeMode && !writeProt) { if (!trackBuffer->hasData()) { - // printf("some sort of error happened - trying to write to uninitialized track?\n"); - return 0; + // Error: writing to empty track buffer? That's a raw write w/o + // knowing where we are on the disk. + return GAP; } trackDirty = true; @@ -350,56 +354,92 @@ uint8_t DiskII::readOrWriteByte() // ... so if we get up to the full 1024 we've allocated, there's // something suspicious happening. - if (writeLatch < 0x96) { + if (readWriteLatch < 0x96) { // Can't write a de-nibblized byte... g_display->debugMsg("DII: bad write"); return 0; } - trackBuffer->replaceByte(writeLatch); + trackBuffer->replaceByte(readWriteLatch); return 0; } - // If we're being asked to read a byte from the current track, - // that's okay - even if it's dirty. As long as we don't change - // tracks. + // trackToRead is -1 when we have a filled buffer, or we have no data at all. + // trackToRead is != -1 when we're flushing our buffer and re-filling it. + // + // Don't fill it right here, b/c we don't want to bog down the CPU + // thread/ISR. + if ((trackToRead != -1) || !trackBuffer->hasData()) { + // Need to read in a track of data and nibblize it. We'll return 0xFF + // until that completes. + trackDirty = false; // effectively flush; forget that we had any data :) - if (!trackBuffer->hasData()) { - trackDirty = false; - trackBuffer->clear(); + // This might update trackToRead with a different track than the + // 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; - if (diskType[selectedDisk] == nibDisk) { - // Read one nibblized sector at a time and jam it in trackBuf - // directly. We don't read the whole track at once only because - // of RAM constraints on the Teensy. There's no reason we - // couldn't, though, if RAM weren't at a premium. - - for (int i=0; i<16; i++) { - g_filemanager->seekBlock(disk[selectedDisk], curTrack * 16 + i, diskType[selectedDisk] == nibDisk); - if (!g_filemanager->readBlock(disk[selectedDisk], rawTrackBuffer, diskType[selectedDisk] == nibDisk)) { - // FIXME: error handling? - return 0; - } - trackBuffer->addBytes(rawTrackBuffer, 416); - } - } else { - // It's a .dsk / .po disk image. Read the whole track in to rawTrackBuffer and nibblize it. - g_filemanager->seekBlock(disk[selectedDisk], curTrack * 16, diskType[selectedDisk] == nibDisk); - if (!g_filemanager->readTrack(disk[selectedDisk], rawTrackBuffer, diskType[selectedDisk] == nibDisk)) { - // FIXME: error handling? - return 0; - } - - nibblizeTrack(trackBuffer, rawTrackBuffer, diskType[selectedDisk], curTrack); - } - - trackBuffer->setPeekCursor(0); + // While we're waiting for the sector to come around, we'll return + // GAP bytes. + return GAP; } return trackBuffer->peekNext(); } +void DiskII::fillDiskBuffer() +{ + // No work to do if trackToRead is -1 + if (trackToRead == -1) + return; + + int8_t trackWeAreReading = trackToRead; + int8_t diskWeAreUsing = selectedDisk; + + trackBuffer->clear(); + trackBuffer->setPeekCursor(0); + + if (diskType[diskWeAreUsing] == nibDisk) { + // Read one nibblized sector at a time and jam it in trackBuf + // directly. We don't read the whole track at once only because + // of RAM constraints on the Teensy. There's no reason we + // 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)) { + // FIXME: error handling? + trackToRead = -1; + return; + } + trackBuffer->addBytes(rawTrackBuffer, 416); + } + } 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)) { + // FIXME: error handling? + trackToRead = -1; + return; + } + + nibblizeTrack(trackBuffer, rawTrackBuffer, diskType[diskWeAreUsing], curTrack); + } + + // Make sure we're still intending to read the track we just read + if (trackWeAreReading != trackToRead || + diskWeAreUsing != selectedDisk) { + // Abort and let it start over next time + return; + } + + // Buffer is full, we're done - reset trackToRead and that will let the reads reach the CPU! + trackToRead = -1; +} + const char *DiskII::DiskName(int8_t num) { if (disk[num] != -1) diff --git a/apple/diskii.h b/apple/diskii.h index 75ad157..9f661bc 100644 --- a/apple/diskii.h +++ b/apple/diskii.h @@ -31,6 +31,8 @@ class DiskII : public Slot { const char *DiskName(int8_t num); void flushTrack(); + void fillDiskBuffer(); // called from main loop + private: void step(uint8_t phase); @@ -46,7 +48,7 @@ class DiskII : public Slot { private: uint8_t curTrack; bool trackDirty; // does this track need flushing to disk? - uint8_t writeLatch; + uint8_t readWriteLatch; RingBuffer *trackBuffer; // nibblized data uint8_t *rawTrackBuffer; // not nibblized data @@ -55,10 +57,12 @@ class DiskII : public Slot { AppleMMU *mmu; int8_t disk[2]; - uint8_t indicatorIsOn[2]; + volatile uint8_t indicatorIsOn[2]; uint8_t diskType[2]; - int8_t selectedDisk; + volatile int8_t selectedDisk; + volatile bool indicatorNeedsDrawing; + volatile int8_t trackToRead; // -1 when we're idle; not -1 when we need to read a track. }; #endif diff --git a/sdl/aiie.cpp b/sdl/aiie.cpp index 52a471f..a427fd4 100644 --- a/sdl/aiie.cpp +++ b/sdl/aiie.cpp @@ -237,6 +237,9 @@ int main(int argc, char *argv[]) printf("hit: %llu; miss: %llu; pct: %f\n", hitcount, misscount, (double)misscount / (double)(misscount + hitcount)); } + // fill disk buffer when needed + ((AppleVM*)g_vm)->disk6->fillDiskBuffer(); + // Make this a little friendlier, and the expense of some framerate? // usleep(10000); if (g_vm->vmdisplay->needsRedraw()) { diff --git a/teensy/teensy-display.cpp b/teensy/teensy-display.cpp index f097df0..aca0086 100644 --- a/teensy/teensy-display.cpp +++ b/teensy/teensy-display.cpp @@ -473,6 +473,7 @@ void TeensyDisplay::drawDriveDoor(uint8_t which, bool isOpen) uint16_t xoff = 55; uint16_t yoff = 216; + return; // debugging: disabling this for testing // location for right drive @@ -506,12 +507,13 @@ void TeensyDisplay::drawDriveStatus(uint8_t which, bool isRunning) // and right drive if (which == 1) xoff += 135; - +#if 0 for (int y=0; y<2; y++) { for (int x=0; x<6; x++) { drawPixel(x + xoff, y + yoff, isRunning ? 0xF800 : 0x8AA9); } } +#endif } void TeensyDisplay::drawBatteryStatus(uint8_t percent) diff --git a/teensy/teensy.ino b/teensy/teensy.ino index 716f0aa..d87f98e 100644 --- a/teensy/teensy.ino +++ b/teensy/teensy.ino @@ -3,6 +3,7 @@ #include #include #include +#include #include "bios.h" #include "cpu.h" #include "applevm.h" @@ -161,6 +162,10 @@ void setup() pinMode(56, OUTPUT); pinMode(57, OUTPUT); + + Timer1.initialize(3); + Timer1.attachInterrupt(runCPU); + Timer1.start(); } // FIXME: move these memory-related functions elsewhere... @@ -191,6 +196,8 @@ int heapSize(){ void biosInterrupt() { + Timer1.stop(); + // wait for the interrupt button to be released while (digitalRead(RESETPIN) == LOW) ; @@ -216,35 +223,57 @@ void biosInterrupt() // Poll the keyboard before we start, so we can do selftest on startup g_keyboard->maintainKeyboard(); + + Timer1.start(); } bool debugState = false; bool debugLCDState = false; -void loop() +void runCPU() { - if (micros() >= nextInstructionMicros) { debugState = !debugState; digitalWrite(56, debugState); - + g_cpu->Run(24); - - // Only update the speaker and CPU when we've moved the CPU forward (or there's nothing to do). + + // These are timing-critical, for the audio and paddles. + // There's also a keyboard repeat in here that hopefully is + // minimal overhead... g_speaker->beginMixing(); ((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles); g_speaker->maintainSpeaker(g_cpu->cycles); + // The CPU of the Apple //e ran at 1.023 MHz. Adjust when we think // the next instruction should run based on how long the execution // was ((1000/1023) * numberOfCycles) - which is about 97.8%. - nextInstructionMicros = startMicros + (float)g_cpu->cycles * 0.978; - - // Don't update the LCD if we had to run the CPU: if we need - // multiple concurrent runs to catch up, then we want to catch up. - return; } +} + +void loop() +{ + if (digitalRead(RESETPIN) == LOW) { + // This is the BIOS interrupt. We immediately act on it. + biosInterrupt(); + } + + ((AppleVM*)g_vm)->disk6->fillDiskBuffer(); + + g_keyboard->maintainKeyboard(); + + debugLCDState = !debugLCDState; + digitalWrite(57, debugLCDState); + + doDebugging(); + + + g_vm->vmdisplay->needsRedraw(); + AiieRect what = g_vm->vmdisplay->getDirtyRect(); + g_vm->vmdisplay->didRedraw(); + g_display->blit(what); static unsigned long nextBattCheck = 0; static int batteryLevel = 0; // static for debugging code! When done @@ -270,28 +299,8 @@ void loop() batteryLevel = 216; batteryLevel = map(batteryLevel, 205, 216, 0, 100); - ((AppleVM *)g_vm)->batteryLevel( batteryLevel ); + g_display->drawBatteryStatus(batteryLevel); } - - if (digitalRead(RESETPIN) == LOW) { - // This is the BIOS interrupt. We immediately act on it. - biosInterrupt(); - } - - if (g_vm->vmdisplay->needsRedraw()) { - digitalWrite(57, HIGH); - AiieRect what = g_vm->vmdisplay->getDirtyRect(); - // Clear the flag before redrawing. Not specifically required - // any longer now that we're not running out of an interrupt, - // but still safe. - g_vm->vmdisplay->didRedraw(); - g_display->blit(what); - digitalWrite(57, LOW); - } - - g_keyboard->maintainKeyboard(); - - doDebugging(); } void doDebugging() @@ -380,6 +389,7 @@ void readPrefs() void writePrefs() { Serial.println("writing prefs"); + Timer1.stop(); prefs p; uint8_t *pp = (uint8_t *)&p; @@ -390,4 +400,6 @@ void writePrefs() for (uint8_t i=0; i