mirror of
https://github.com/JorjBauer/aiie.git
synced 2025-02-10 21:30:49 +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;
|
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
|
// return a pointer to the right glyph, and set *invert appropriately
|
||||||
const unsigned char *AppleDisplay::xlateChar(uint8_t c, bool *invert)
|
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 */
|
/* NOTREACHED */
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppleDisplay::Draw80CharacterAt(uint8_t c, uint8_t x, uint8_t y, uint8_t offset)
|
inline void AppleDisplay::Draw14DoubleHiresPixelsAt(uint16_t addr)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
// We will consult 4 bytes (2 in main, 2 in aux) for any single-byte
|
// 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
|
// 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.
|
// 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
|
// FIXME: what happens when the high bit of the left doesn't match the right? Which high bit does
|
||||||
// the overlap bit get?
|
// the overlap bit get?
|
||||||
void AppleDisplay::Draw14HiresPixelsAt(uint16_t addr)
|
inline void AppleDisplay::Draw14HiresPixelsAt(uint16_t addr)
|
||||||
{
|
{
|
||||||
uint8_t row;
|
uint8_t row;
|
||||||
uint16_t col;
|
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;
|
bool invert;
|
||||||
|
|
||||||
@ -476,7 +418,7 @@ void AppleDisplay::redraw40ColumnText()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only draw onscreen locations
|
// 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);
|
const uint8_t *cptr = xlateChar(mmu->read(addr), &invert);
|
||||||
|
|
||||||
for (uint8_t y2 = 0; y2<8; y2++) {
|
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) {
|
uint16_t start = ((*switches) & S_PAGE2) ? 0x4000 : 0x2000;
|
||||||
if ((*switches) & S_80COL) {
|
if ((*switches) & S_80STORE) {
|
||||||
for (uint16_t addr = 0x400; addr <= 0x400 + 0x3FF; addr++) {
|
// Apple IIe, technical nodes #3: 80STORE must be OFF to display Page 2
|
||||||
uint8_t row, col;
|
start = 0x2000;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not text mode - what mode are we in?
|
// FIXME: check MIXED & don't redraw the lower area if it's set
|
||||||
if ((*switches) & S_HIRES) {
|
for (uint16_t addr = start; addr <= start + 0x1FFF; addr+=2) {
|
||||||
// Hires
|
if ((*switches) & S_DHIRES) {
|
||||||
// FIXME: make this draw a row efficiently
|
// FIXME: inline & optimize
|
||||||
// FIXME: can make more efficient by checking S_MIXED for lower bound
|
Draw14DoubleHiresPixelsAt(addr);
|
||||||
uint16_t start = ((*switches) & S_PAGE2) ? 0x4000 : 0x2000;
|
} else {
|
||||||
if ((*switches) & S_80STORE) {
|
// FIXME: inline & optimize
|
||||||
// Apple IIe, technical nodes #3: 80STORE must be OFF to display Page 2
|
Draw14HiresPixelsAt(addr);
|
||||||
start = 0x2000;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (uint16_t addr = start; addr <= start + 0x1FFF; addr+=2) {
|
void AppleDisplay::redrawLores()
|
||||||
if ((*switches) & S_DHIRES) {
|
{
|
||||||
Draw14DoubleHiresPixelsAt(addr);
|
// FIXME: can make more efficient by checking S_MIXED for lower bound
|
||||||
} else {
|
|
||||||
Draw14HiresPixelsAt(addr);
|
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 {
|
} 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;
|
uint16_t start = ((*switches) & S_PAGE2) ? 0x800 : 0x400;
|
||||||
for (uint16_t addr = start; addr <= start + 0x3FF; addr++) {
|
for (uint16_t addr = start; addr <= start + 0x3FF; addr++) {
|
||||||
uint8_t row, col;
|
uint8_t row, col;
|
||||||
deinterlaceAddress(addr, &row, &col);
|
deinterlaceAddress(addr, &row, &col);
|
||||||
if (col <= 39 && row >= 20 && row <= 23) {
|
if (col <= 39 && row <= 23) {
|
||||||
if ((*switches) & S_80COL) {
|
DrawLoresPixelAt(mmu->read(addr), col, row);
|
||||||
Draw80CharacterAt(mmu->read(addr), col, row, ((*switches) & S_PAGE2) ? 0 : 1);
|
|
||||||
} else {
|
|
||||||
DrawCharacterAt(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)
|
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
|
// 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)
|
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;
|
videoBuffer[(y * DISPLAYWIDTH + x) /2] = two4bitColors;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void AppleDisplay::drawPixel(uint8_t color4bit, uint16_t x, uint8_t y)
|
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;
|
uint16_t idx = (y * DISPLAYWIDTH + x) / 2;
|
||||||
if (x & 1) {
|
if (x & 1) {
|
||||||
videoBuffer[idx] = (videoBuffer[idx] & 0xF0) | color4bit;
|
videoBuffer[idx] = (videoBuffer[idx] & 0xF0) | color4bit;
|
||||||
@ -674,6 +569,36 @@ AiieRect AppleDisplay::getDirtyRect()
|
|||||||
|
|
||||||
bool AppleDisplay::needsRedraw()
|
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;
|
return dirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,6 +52,7 @@ class AppleDisplay : public VMDisplay{
|
|||||||
void writeHires(uint16_t address, uint8_t v);
|
void writeHires(uint16_t address, uint8_t v);
|
||||||
|
|
||||||
void displayTypeChanged();
|
void displayTypeChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
bool deinterlaceAddress(uint16_t address, uint8_t *row, uint8_t *col);
|
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);
|
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:
|
private:
|
||||||
volatile bool dirty;
|
volatile bool dirty;
|
||||||
|
@ -112,13 +112,21 @@ void AppleMMU::write(uint16_t address, uint8_t v)
|
|||||||
|
|
||||||
if (address >= 0x400 &&
|
if (address >= 0x400 &&
|
||||||
address <= 0x7FF) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (address >= 0x2000 &&
|
if (address >= 0x2000 &&
|
||||||
address <= 0x5FFF) {
|
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
|
case 0xC051: // SETTEXT
|
||||||
if (!(switches & S_TEXT)) {
|
if (!(switches & S_TEXT)) {
|
||||||
switches |= S_TEXT;
|
switches |= S_TEXT;
|
||||||
|
// also make sure we're *out* of HIRES mode.
|
||||||
|
switches &= ~S_HIRES;
|
||||||
resetDisplay();
|
resetDisplay();
|
||||||
}
|
}
|
||||||
return FLOATING;
|
return FLOATING;
|
||||||
|
@ -63,7 +63,7 @@ class AppleMMU : public MMU {
|
|||||||
private:
|
private:
|
||||||
AppleDisplay *display;
|
AppleDisplay *display;
|
||||||
uint16_t switches;
|
uint16_t switches;
|
||||||
public: // debuggign
|
public: // 'public' for debugging
|
||||||
bool auxRamRead;
|
bool auxRamRead;
|
||||||
bool auxRamWrite;
|
bool auxRamWrite;
|
||||||
bool bank1;
|
bool bank1;
|
||||||
|
@ -23,8 +23,11 @@ AppleVM::AppleVM()
|
|||||||
parallel = new ParallelCard();
|
parallel = new ParallelCard();
|
||||||
((AppleMMU *)mmu)->setSlot(1, parallel);
|
((AppleMMU *)mmu)->setSlot(1, parallel);
|
||||||
|
|
||||||
|
mockingboard = NULL;
|
||||||
|
/*
|
||||||
mockingboard = new Mockingboard();
|
mockingboard = new Mockingboard();
|
||||||
((AppleMMU *)mmu)->setSlot(4, mockingboard);
|
((AppleMMU *)mmu)->setSlot(4, mockingboard);
|
||||||
|
*/
|
||||||
|
|
||||||
#ifdef TEENSYDUINO
|
#ifdef TEENSYDUINO
|
||||||
teensyClock = new TeensyClock((AppleMMU *)mmu);
|
teensyClock = new TeensyClock((AppleMMU *)mmu);
|
||||||
@ -39,7 +42,8 @@ AppleVM::~AppleVM()
|
|||||||
#endif
|
#endif
|
||||||
delete disk6;
|
delete disk6;
|
||||||
delete parallel;
|
delete parallel;
|
||||||
delete mockingboard;
|
if (mockingboard)
|
||||||
|
delete mockingboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fixme: make member vars
|
// fixme: make member vars
|
||||||
@ -60,7 +64,8 @@ void AppleVM::cpuMaintenance(uint32_t cycles)
|
|||||||
}
|
}
|
||||||
|
|
||||||
keyboard->maintainKeyboard(cycles);
|
keyboard->maintainKeyboard(cycles);
|
||||||
mockingboard->update(cycles);
|
if (mockingboard)
|
||||||
|
mockingboard->update(cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppleVM::Reset()
|
void AppleVM::Reset()
|
||||||
@ -83,11 +88,6 @@ void AppleVM::Monitor()
|
|||||||
((AppleMMU *)mmu)->readSwitches(0xC051); // and text mode is on
|
((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)
|
const char *AppleVM::DiskName(uint8_t drivenum)
|
||||||
{
|
{
|
||||||
return disk6->DiskName(drivenum);
|
return disk6->DiskName(drivenum);
|
||||||
|
@ -27,12 +27,11 @@ class AppleVM : public VM {
|
|||||||
const char *DiskName(uint8_t drivenum);
|
const char *DiskName(uint8_t drivenum);
|
||||||
void ejectDisk(uint8_t drivenum);
|
void ejectDisk(uint8_t drivenum);
|
||||||
void insertDisk(uint8_t drivenum, const char *filename, bool drawIt = true);
|
void insertDisk(uint8_t drivenum, const char *filename, bool drawIt = true);
|
||||||
void batteryLevel(uint8_t zeroToOneHundred);
|
|
||||||
|
|
||||||
virtual VMKeyboard *getKeyboard();
|
virtual VMKeyboard *getKeyboard();
|
||||||
|
|
||||||
protected:
|
|
||||||
DiskII *disk6;
|
DiskII *disk6;
|
||||||
|
protected:
|
||||||
VMKeyboard *keyboard;
|
VMKeyboard *keyboard;
|
||||||
ParallelCard *parallel;
|
ParallelCard *parallel;
|
||||||
Mockingboard *mockingboard;
|
Mockingboard *mockingboard;
|
||||||
|
136
apple/diskii.cpp
136
apple/diskii.cpp
@ -25,15 +25,18 @@ DiskII::DiskII(AppleMMU *mmu)
|
|||||||
|
|
||||||
curTrack = 0;
|
curTrack = 0;
|
||||||
trackDirty = false;
|
trackDirty = false;
|
||||||
|
trackToRead = -1;
|
||||||
|
|
||||||
writeMode = false;
|
writeMode = false;
|
||||||
writeProt = false; // FIXME: expose an interface to this
|
writeProt = false; // FIXME: expose an interface to this
|
||||||
writeLatch = 0x00;
|
readWriteLatch = 0x00;
|
||||||
|
|
||||||
disk[0] = disk[1] = -1;
|
disk[0] = disk[1] = -1;
|
||||||
indicatorIsOn[0] = indicatorIsOn[1] = 0;
|
indicatorIsOn[0] = indicatorIsOn[1] = 0;
|
||||||
selectedDisk = 0;
|
selectedDisk = 0;
|
||||||
diskType[0] = diskType[1] = dosDisk;
|
diskType[0] = diskType[1] = dosDisk;
|
||||||
|
|
||||||
|
indicatorNeedsDrawing = true; // set to true whenever the display needs to redraw...
|
||||||
}
|
}
|
||||||
|
|
||||||
DiskII::~DiskII()
|
DiskII::~DiskII()
|
||||||
@ -49,7 +52,7 @@ void DiskII::Reset()
|
|||||||
|
|
||||||
writeMode = false;
|
writeMode = false;
|
||||||
writeProt = false; // FIXME: expose an interface to this
|
writeProt = false; // FIXME: expose an interface to this
|
||||||
writeLatch = 0x00;
|
readWriteLatch = 0x00;
|
||||||
|
|
||||||
ejectDisk(0);
|
ejectDisk(0);
|
||||||
ejectDisk(1);
|
ejectDisk(1);
|
||||||
@ -71,12 +74,12 @@ uint8_t DiskII::readSwitches(uint8_t s)
|
|||||||
|
|
||||||
case 0x08: // drive off
|
case 0x08: // drive off
|
||||||
indicatorIsOn[selectedDisk] = 99;
|
indicatorIsOn[selectedDisk] = 99;
|
||||||
g_display->drawDriveStatus(selectedDisk, false);
|
indicatorNeedsDrawing = true;
|
||||||
flushTrack();
|
flushTrack();
|
||||||
break;
|
break;
|
||||||
case 0x09: // drive on
|
case 0x09: // drive on
|
||||||
indicatorIsOn[selectedDisk] = 100;
|
indicatorIsOn[selectedDisk] = 100;
|
||||||
g_display->drawDriveStatus(selectedDisk, true);
|
indicatorNeedsDrawing = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x0A: // select drive 1
|
case 0x0A: // select drive 1
|
||||||
@ -87,7 +90,7 @@ uint8_t DiskII::readSwitches(uint8_t s)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x0C: // shift one read or write byte
|
case 0x0C: // shift one read or write byte
|
||||||
writeLatch = readOrWriteByte();
|
readWriteLatch = readOrWriteByte();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x0D: // load data register (latch)
|
case 0x0D: // load data register (latch)
|
||||||
@ -95,9 +98,9 @@ uint8_t DiskII::readSwitches(uint8_t s)
|
|||||||
// UTA2E, p. 9-14
|
// UTA2E, p. 9-14
|
||||||
if (!writeMode && indicatorIsOn[selectedDisk]) {
|
if (!writeMode && indicatorIsOn[selectedDisk]) {
|
||||||
if (isWriteProtected())
|
if (isWriteProtected())
|
||||||
writeLatch |= 0x80;
|
readWriteLatch |= 0x80;
|
||||||
else
|
else
|
||||||
writeLatch &= 0x7F;
|
readWriteLatch &= 0x7F;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -120,16 +123,16 @@ uint8_t DiskII::readSwitches(uint8_t s)
|
|||||||
if (!indicatorIsOn[selectedDisk]) {
|
if (!indicatorIsOn[selectedDisk]) {
|
||||||
// printf("Unexpected read while disk isn't on?\n");
|
// printf("Unexpected read while disk isn't on?\n");
|
||||||
indicatorIsOn[selectedDisk] = 100;
|
indicatorIsOn[selectedDisk] = 100;
|
||||||
g_display->drawDriveStatus(selectedDisk, true);
|
indicatorNeedsDrawing = true;
|
||||||
}
|
}
|
||||||
if (indicatorIsOn[selectedDisk] > 0 && indicatorIsOn[selectedDisk] < 100) {
|
if (indicatorIsOn[selectedDisk] > 0 && indicatorIsOn[selectedDisk] < 100) {
|
||||||
indicatorIsOn[selectedDisk]--;
|
indicatorIsOn[selectedDisk]--;
|
||||||
// slowly spin it down...
|
// 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)
|
// p. 9-12, note 2)
|
||||||
return (s & 1) ? FLOATING : writeLatch;
|
return (s & 1) ? FLOATING : readWriteLatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiskII::writeSwitches(uint8_t s, uint8_t v)
|
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
|
// All writes update the latch
|
||||||
if (writeMode) {
|
if (writeMode) {
|
||||||
writeLatch = v;
|
readWriteLatch = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,7 +305,7 @@ void DiskII::select(int8_t which)
|
|||||||
|
|
||||||
if (which != selectedDisk) {
|
if (which != selectedDisk) {
|
||||||
indicatorIsOn[selectedDisk] = 0;
|
indicatorIsOn[selectedDisk] = 0;
|
||||||
g_display->drawDriveStatus(selectedDisk, false);
|
indicatorNeedsDrawing = true;
|
||||||
|
|
||||||
flushTrack(); // in case it's dirty: flush before changing drives
|
flushTrack(); // in case it's dirty: flush before changing drives
|
||||||
trackBuffer->clear();
|
trackBuffer->clear();
|
||||||
@ -322,8 +325,9 @@ uint8_t DiskII::readOrWriteByte()
|
|||||||
if (writeMode && !writeProt) {
|
if (writeMode && !writeProt) {
|
||||||
|
|
||||||
if (!trackBuffer->hasData()) {
|
if (!trackBuffer->hasData()) {
|
||||||
// printf("some sort of error happened - trying to write to uninitialized track?\n");
|
// Error: writing to empty track buffer? That's a raw write w/o
|
||||||
return 0;
|
// knowing where we are on the disk.
|
||||||
|
return GAP;
|
||||||
}
|
}
|
||||||
|
|
||||||
trackDirty = true;
|
trackDirty = true;
|
||||||
@ -350,56 +354,92 @@ uint8_t DiskII::readOrWriteByte()
|
|||||||
// ... so if we get up to the full 1024 we've allocated, there's
|
// ... so if we get up to the full 1024 we've allocated, there's
|
||||||
// something suspicious happening.
|
// something suspicious happening.
|
||||||
|
|
||||||
if (writeLatch < 0x96) {
|
if (readWriteLatch < 0x96) {
|
||||||
// Can't write a de-nibblized byte...
|
// Can't write a de-nibblized byte...
|
||||||
g_display->debugMsg("DII: bad write");
|
g_display->debugMsg("DII: bad write");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
trackBuffer->replaceByte(writeLatch);
|
trackBuffer->replaceByte(readWriteLatch);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're being asked to read a byte from the current track,
|
// trackToRead is -1 when we have a filled buffer, or we have no data at all.
|
||||||
// that's okay - even if it's dirty. As long as we don't change
|
// trackToRead is != -1 when we're flushing our buffer and re-filling it.
|
||||||
// tracks.
|
//
|
||||||
|
// 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()) {
|
// This might update trackToRead with a different track than the
|
||||||
trackDirty = false;
|
// one we're reading. When we finish the read, we'll need to check
|
||||||
trackBuffer->clear();
|
// to be sure that we're still trying to read the same track that
|
||||||
|
// we started with.
|
||||||
|
trackToRead = curTrack;
|
||||||
|
|
||||||
if (diskType[selectedDisk] == nibDisk) {
|
// While we're waiting for the sector to come around, we'll return
|
||||||
// Read one nibblized sector at a time and jam it in trackBuf
|
// GAP bytes.
|
||||||
// directly. We don't read the whole track at once only because
|
return GAP;
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return trackBuffer->peekNext();
|
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)
|
const char *DiskII::DiskName(int8_t num)
|
||||||
{
|
{
|
||||||
if (disk[num] != -1)
|
if (disk[num] != -1)
|
||||||
|
@ -31,6 +31,8 @@ class DiskII : public Slot {
|
|||||||
const char *DiskName(int8_t num);
|
const char *DiskName(int8_t num);
|
||||||
void flushTrack();
|
void flushTrack();
|
||||||
|
|
||||||
|
void fillDiskBuffer(); // called from main loop
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void step(uint8_t phase);
|
void step(uint8_t phase);
|
||||||
|
|
||||||
@ -46,7 +48,7 @@ class DiskII : public Slot {
|
|||||||
private:
|
private:
|
||||||
uint8_t curTrack;
|
uint8_t curTrack;
|
||||||
bool trackDirty; // does this track need flushing to disk?
|
bool trackDirty; // does this track need flushing to disk?
|
||||||
uint8_t writeLatch;
|
uint8_t readWriteLatch;
|
||||||
RingBuffer *trackBuffer; // nibblized data
|
RingBuffer *trackBuffer; // nibblized data
|
||||||
uint8_t *rawTrackBuffer; // not nibblized data
|
uint8_t *rawTrackBuffer; // not nibblized data
|
||||||
|
|
||||||
@ -55,10 +57,12 @@ class DiskII : public Slot {
|
|||||||
AppleMMU *mmu;
|
AppleMMU *mmu;
|
||||||
|
|
||||||
int8_t disk[2];
|
int8_t disk[2];
|
||||||
uint8_t indicatorIsOn[2];
|
volatile uint8_t indicatorIsOn[2];
|
||||||
uint8_t diskType[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
|
#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));
|
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?
|
// Make this a little friendlier, and the expense of some framerate?
|
||||||
// usleep(10000);
|
// usleep(10000);
|
||||||
if (g_vm->vmdisplay->needsRedraw()) {
|
if (g_vm->vmdisplay->needsRedraw()) {
|
||||||
|
@ -473,6 +473,7 @@ void TeensyDisplay::drawDriveDoor(uint8_t which, bool isOpen)
|
|||||||
|
|
||||||
uint16_t xoff = 55;
|
uint16_t xoff = 55;
|
||||||
uint16_t yoff = 216;
|
uint16_t yoff = 216;
|
||||||
|
return; // debugging: disabling this for testing
|
||||||
|
|
||||||
// location for right drive
|
// location for right drive
|
||||||
|
|
||||||
@ -506,12 +507,13 @@ void TeensyDisplay::drawDriveStatus(uint8_t which, bool isRunning)
|
|||||||
// and right drive
|
// and right drive
|
||||||
if (which == 1)
|
if (which == 1)
|
||||||
xoff += 135;
|
xoff += 135;
|
||||||
|
#if 0
|
||||||
for (int y=0; y<2; y++) {
|
for (int y=0; y<2; y++) {
|
||||||
for (int x=0; x<6; x++) {
|
for (int x=0; x<6; x++) {
|
||||||
drawPixel(x + xoff, y + yoff, isRunning ? 0xF800 : 0x8AA9);
|
drawPixel(x + xoff, y + yoff, isRunning ? 0xF800 : 0x8AA9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void TeensyDisplay::drawBatteryStatus(uint8_t percent)
|
void TeensyDisplay::drawBatteryStatus(uint8_t percent)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
#include <SPI.h>
|
#include <SPI.h>
|
||||||
#include <EEPROM.h>
|
#include <EEPROM.h>
|
||||||
#include <TimeLib.h>
|
#include <TimeLib.h>
|
||||||
|
#include <TimerOne.h>
|
||||||
#include "bios.h"
|
#include "bios.h"
|
||||||
#include "cpu.h"
|
#include "cpu.h"
|
||||||
#include "applevm.h"
|
#include "applevm.h"
|
||||||
@ -161,6 +162,10 @@ void setup()
|
|||||||
|
|
||||||
pinMode(56, OUTPUT);
|
pinMode(56, OUTPUT);
|
||||||
pinMode(57, OUTPUT);
|
pinMode(57, OUTPUT);
|
||||||
|
|
||||||
|
Timer1.initialize(3);
|
||||||
|
Timer1.attachInterrupt(runCPU);
|
||||||
|
Timer1.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: move these memory-related functions elsewhere...
|
// FIXME: move these memory-related functions elsewhere...
|
||||||
@ -191,6 +196,8 @@ int heapSize(){
|
|||||||
|
|
||||||
void biosInterrupt()
|
void biosInterrupt()
|
||||||
{
|
{
|
||||||
|
Timer1.stop();
|
||||||
|
|
||||||
// wait for the interrupt button to be released
|
// wait for the interrupt button to be released
|
||||||
while (digitalRead(RESETPIN) == LOW)
|
while (digitalRead(RESETPIN) == LOW)
|
||||||
;
|
;
|
||||||
@ -216,35 +223,57 @@ void biosInterrupt()
|
|||||||
|
|
||||||
// Poll the keyboard before we start, so we can do selftest on startup
|
// Poll the keyboard before we start, so we can do selftest on startup
|
||||||
g_keyboard->maintainKeyboard();
|
g_keyboard->maintainKeyboard();
|
||||||
|
|
||||||
|
Timer1.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool debugState = false;
|
bool debugState = false;
|
||||||
bool debugLCDState = false;
|
bool debugLCDState = false;
|
||||||
|
|
||||||
void loop()
|
void runCPU()
|
||||||
{
|
{
|
||||||
|
|
||||||
if (micros() >= nextInstructionMicros) {
|
if (micros() >= nextInstructionMicros) {
|
||||||
debugState = !debugState;
|
debugState = !debugState;
|
||||||
digitalWrite(56, debugState);
|
digitalWrite(56, debugState);
|
||||||
|
|
||||||
g_cpu->Run(24);
|
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();
|
g_speaker->beginMixing();
|
||||||
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
|
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
|
||||||
g_speaker->maintainSpeaker(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 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
|
// the next instruction should run based on how long the execution
|
||||||
// was ((1000/1023) * numberOfCycles) - which is about 97.8%.
|
// was ((1000/1023) * numberOfCycles) - which is about 97.8%.
|
||||||
|
|
||||||
nextInstructionMicros = startMicros + (float)g_cpu->cycles * 0.978;
|
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 unsigned long nextBattCheck = 0;
|
||||||
static int batteryLevel = 0; // static for debugging code! When done
|
static int batteryLevel = 0; // static for debugging code! When done
|
||||||
@ -270,28 +299,8 @@ void loop()
|
|||||||
batteryLevel = 216;
|
batteryLevel = 216;
|
||||||
|
|
||||||
batteryLevel = map(batteryLevel, 205, 216, 0, 100);
|
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()
|
void doDebugging()
|
||||||
@ -380,6 +389,7 @@ void readPrefs()
|
|||||||
void writePrefs()
|
void writePrefs()
|
||||||
{
|
{
|
||||||
Serial.println("writing prefs");
|
Serial.println("writing prefs");
|
||||||
|
Timer1.stop();
|
||||||
|
|
||||||
prefs p;
|
prefs p;
|
||||||
uint8_t *pp = (uint8_t *)&p;
|
uint8_t *pp = (uint8_t *)&p;
|
||||||
@ -390,4 +400,6 @@ void writePrefs()
|
|||||||
for (uint8_t i=0; i<sizeof(prefs); i++) {
|
for (uint8_t i=0; i<sizeof(prefs); i++) {
|
||||||
EEPROM.write(i, *pp++);
|
EEPROM.write(i, *pp++);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer1.start();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user