mirror of
https://github.com/JorjBauer/aiie.git
synced 2025-02-05 19:31:56 +00:00
segregating CPU and timing-sensitive sound operations from drawing/disk IO/debugging
This commit is contained in:
parent
ceebb3b1d4
commit
85d81d398a
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
136
apple/diskii.cpp
136
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)
|
||||
|
@ -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
|
||||
|
@ -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()) {
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user