segregating CPU and timing-sensitive sound operations from drawing/disk IO/debugging

This commit is contained in:
Jorj Bauer 2017-02-26 19:59:51 -05:00
parent ceebb3b1d4
commit 85d81d398a
11 changed files with 310 additions and 311 deletions

View File

@ -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<<x2)) | (d & (1<<(x2+1))) );
if (pixelOn) {
uint8_t val = (invert ? c_black : textColor);
drawPixel(val, (basex+x2)/2, y*8+y2);
} else {
uint8_t val = (invert ? textColor : c_black);
drawPixel(val, (basex+x2)/2, y*8+y2);
}
}
}
}
void AppleDisplay::DrawCharacterAt(uint8_t c, uint8_t x, uint8_t y)
{
bool invert;
const uint8_t *cptr = xlateChar(c, &invert);
for (uint8_t y2 = 0; y2<8; y2++) {
uint8_t d = *(cptr + y2);
for (uint8_t x2 = 0; x2 < 7; x2++) {
if (d & 1) {
uint8_t val = (invert ? c_black : textColor);
drawPixel(val, x*7+x2, y*8+y2);
} else {
uint8_t val = (invert ? textColor : c_black);
drawPixel(val, x*7+x2, y*8+y2);
}
d >>= 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<<x2)) | (d & (1<<(x2+1))) );
if (pixelOn) {
uint8_t val = (invert ? c_black : textColor);
drawPixel(val, (basex+x2)/2, row*8+y2);
} else {
uint8_t val = (invert ? textColor : c_black);
drawPixel(val, (basex+x2)/2, row*8+y2);
}
}
}
// Draw the second of two characters
cptr = xlateChar(mmu->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<<x2)) | (d & (1<<(x2+1))) );
if (pixelOn) {
uint8_t val = (invert ? c_black : textColor);
drawPixel(val, (basex+x2)/2, row*8+y2);
} else {
uint8_t val = (invert ? textColor : c_black);
drawPixel(val, (basex+x2)/2, row*8+y2);
}
}
}
}
}
}
void AppleDisplay::redraw40ColumnText(uint8_t startingY)
{
bool invert;
@ -476,7 +418,7 @@ void AppleDisplay::redraw40ColumnText()
}
// Only draw onscreen locations
if (col <= 39 && row <= 23) {
if (row >= 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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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)

View File

@ -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

View File

@ -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()) {

View File

@ -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)

View File

@ -3,6 +3,7 @@
#include <SPI.h>
#include <EEPROM.h>
#include <TimeLib.h>
#include <TimerOne.h>
#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<sizeof(prefs); i++) {
EEPROM.write(i, *pp++);
}
Timer1.start();
}