general VM structure and a test OpenCV target

This commit is contained in:
Jorj Bauer 2017-02-19 18:55:54 -05:00
parent 680e57013f
commit 218b3b77a0
44 changed files with 34708 additions and 5 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
apple2e.rom
disk.rom
*~
*.o

View File

@ -1,16 +1,30 @@
LDFLAGS=-L/usr/local/lib
CXXFLAGS=-Wall -I .. -I . -O3
LDFLAGS=-L/usr/local/lib -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_features2d -lopencv_calib3d
CXXFLAGS=-Wall -I .. -I . -I apple -O3
TSRC=cpu.cpp util/testharness.cpp
all:
@echo There is no 'all' target. Yet.
OPENCVOBJS=cpu.o opencv/dummy-speaker.o opencv/opencv-display.o opencv/opencv-keyboard.o opencv/opencv-paddles.o opencv/opencv-filemanager.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o apple/slot.o RingBuffer.o globals.o opencv/aiie.o
ROMS=apple/applemmu-rom.h apple/diskii-rom.h
all: opencv
opencv: roms $(OPENCVOBJS)
g++ $(LDFLAGS) -o aiie-opencv $(OPENCVOBJS)
clean:
rm -f *.o *~ */*.o */*~ testharness.basic testharness.verbose testharness.extended
rm -f *.o *~ */*.o */*~ testharness.basic testharness.verbose testharness.extended aiie-opencv apple/diskii-rom.h apple/applemmu-rom.h
test: $(TSRC)
g++ $(CXXFLAGS) -DBASICTEST $(TSRC) -o testharness.basic
g++ $(CXXFLAGS) -DVERBOSETEST $(TSRC) -o testharness.verbose
g++ $(CXXFLAGS) -DEXTENDEDTEST $(TSRC) -o testharness.extended
roms: apple2e.rom disk.rom
./util/genrom.pl apple2e.rom disk.rom
apple/applemmu-rom.h: roms
apple/diskii-rom.h: roms

View File

@ -19,6 +19,54 @@ Build log:
https://hackaday.io/project/19925-aiie-an-embedded-apple-e-emulator
Building (on a Mac)
===================
While this isn't the purpose of the emulator, it is functional, and is
my first test target for most of the work. With MacOS 10.11.6 and
Homebrew, you can build and run it like this:
$ make opencv
$ ./aiie-opencv /path/to/disk.dsk
As the name implies, this requires that OpenCV is installed and in
/usr/local/lib. I've done that with Homebrew like this
$ brew install opencv
"Why OpenCV?" you might ask. Well, it's just because I had code from
another project lying around that directly manipulated OpenCV bitmap
data. It's functional, and the Mac build is only about functional
testing (for me).
VM
==
The virtual machine architecture is broken in half - the virtual and
physical pieces. There's the root VM object (vm.h), which ties
together the MMU, virtual keyboard, and virtual display.
Then there are the physical interfaces, which aren't as well
organized. They exist as globals in globals.cpp:
FileManager *g_filemanager = NULL;
PhysicalDisplay *g_display = NULL;
PhysicalKeyboard *g_keyboard = NULL;
PhysicalSpeaker *g_speaker = NULL;
PhysicalPaddles *g_paddles = NULL;
There are the two globals that point to the VM and the virtual CPU:
Cpu *g_cpu = NULL;
VM *g_vm = NULL;
And there are two global configuration values that probably belong in
some sort of Prefs class:
int16_t g_volume;
uint8_t g_displayType;
CPU
===

113
RingBuffer.cpp Normal file
View File

@ -0,0 +1,113 @@
#include "RingBuffer.h"
#include <stdlib.h>
RingBuffer::RingBuffer(int16_t length)
{
this->buffer = (uint8_t *)malloc(length);
this->max = length;
this->fill = 0;
this->ptr = 0;
this->cursor = 0;
}
RingBuffer::~RingBuffer()
{
free (this->buffer);
}
void RingBuffer::clear()
{
this->fill = 0;
}
bool RingBuffer::isFull()
{
return (this->max == this->fill);
}
bool RingBuffer::hasData()
{
return (this->fill != 0);
}
bool RingBuffer::addByte(uint8_t b)
{
if (this->max == this->fill)
return false;
int idx = (this->ptr + this->fill) % this->max;
this->buffer[idx] = b;
this->fill++;
return true;
}
bool RingBuffer::replaceByte(uint8_t b)
{
if (cursor < fill) {
buffer[cursor] = b;
cursor++;
if (cursor >= fill) {
cursor = 0;
}
return true;
}
return false;
}
bool RingBuffer::addBytes(uint8_t *b, int count)
{
for (int i=0; i<count; i++) {
if (!addByte(b[i]))
return false;
}
return true;
}
uint8_t RingBuffer::consumeByte()
{
if (this->fill == 0)
return 0;
uint8_t ret = this->buffer[this->ptr];
this->fill--;
this->ptr++;
this->ptr %= this->max;
return ret;
}
uint8_t RingBuffer::peek(int16_t idx)
{
uint16_t p = (this->ptr + idx) % this->max;
return this->buffer[p];
}
int16_t RingBuffer::count()
{
return this->fill;
}
uint16_t RingBuffer::getPeekCursor()
{
return this->cursor;
}
void RingBuffer::setPeekCursor(int16_t idx)
{
this->cursor = idx;
}
void RingBuffer::resetPeekCursor()
{
this->cursor = 0;
}
uint8_t RingBuffer::peekNext()
{
uint8_t ret = peek(cursor);
cursor++;
if (cursor >= fill) {
cursor = 0;
}
return ret;
}

34
RingBuffer.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef __RINGBUFFER_H
#define __RINGBUFFER_H
#include <stdint.h>
class RingBuffer {
public:
RingBuffer(int16_t length);
~RingBuffer();
void clear();
bool isFull();
bool hasData();
bool addByte(uint8_t b);
bool addBytes(uint8_t *b, int count);
bool replaceByte(uint8_t b);
uint8_t consumeByte();
uint8_t peek(int16_t idx);
uint16_t getPeekCursor();
void setPeekCursor(int16_t idx);
void resetPeekCursor();
uint8_t peekNext();
int16_t count();
private:
uint8_t *buffer;
int16_t max;
int16_t ptr;
int16_t fill;
int16_t cursor;
};
#endif

610
apple/appledisplay.cpp Normal file
View File

@ -0,0 +1,610 @@
#include <ctype.h> // isgraph
#include <stdlib.h> // calloc
#include <string.h> // strlen
#include "appledisplay.h"
#include "applemmu.h" // for switch constants
#include "font.h"
/* Fourpossible Hi-Res color-drawing modes..
MONOCHROME: show all the pixels, but only in green;
BLACKANDWHITE: monochrome, but use B&W instead of B&G;
NTSCLIKE: reduce the resolution to 140 pixels wide, similar to how an NTSC monitor would blend it
PERFECTCOLOR: as the Apple RGB monitor shows it, which means you can't have a solid color field
*/
#include "globals.h"
AppleDisplay::AppleDisplay(uint8_t *vb) : VMDisplay(vb)
{
this->switches = NULL;
this->dirty = true;
}
AppleDisplay::~AppleDisplay()
{
}
bool AppleDisplay::deinterlaceAddress(uint16_t address, uint8_t *row, uint8_t *col)
{
if (address >= 0x800 && address < 0xC00) {
address -= 0x400;
}
uint8_t block = (address >> 7) - 0x08;
uint8_t blockOffset = (address & 0x00FF) - ((block & 0x01) ? 0x80 : 0x00);
if (blockOffset < 0x28) {
*row = block;
*col = blockOffset;
} else if (blockOffset < 0x50) {
*row = block + 8;
*col = blockOffset - 0x28;
} else {
*row = block + 16;
*col = blockOffset - 0x50;
}
return true;
}
// calculate x/y pixel offsets from a memory address.
// Note that this is the first of 7 pixels that will be affected by this write;
// we'll need to update all 7 starting at this x.
bool AppleDisplay::deinterlaceHiresAddress(uint16_t address, uint8_t *row, uint16_t *col)
{
// each row is 40 bytes, for 7 pixels each, totalling 128
// pixels wide.
// They are grouped in to 3 "runs" of 40-byte blocks, where
// each group is 64 lines after the one before.
// Then repeat at +400, +800, +c00, +1000, +1400, +1800, +1c00 for
// the other 7 pixels tall.
// Repeat the whole shebang at +0x80, +0x100, +0x180, ... to +280
// for each 8-pixel tall group.
// There are 8 bytes at the end of each run that we ignore. Skip them.
if ((address & 0x07f) >= 0x78 &&
(address & 0x7f) <= 0x7f) {
*row = 255;
*col = 65535;
return false;
}
*row = ((address & 0x380) >> 4) +
((address & 0x1c00)>>10) +
64 * ((address & 0x7f) / 40);
*col = ((address & 0x7f) % 40) * 7;
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)
{
if (c <= 0x3F) {
// 0-3f: inverted @ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ !"#$%&'()*+,-./0123456789:;<=>?
// (same w/o mousetext, actually)
*invert = true;
return &ucase_glyphs[c * 8];
} else if (c <= 0x5F) {
// 40-5f: normal mousetext
// (these are flashing @ABCDEFG..[\]^_ when not in mousetext mode)
if ((*switches) & S_ALTCH) {
*invert = false;
return &mousetext_glyphs[(c - 0x40) * 8];
} else {
*invert = true;
return &ucase_glyphs[(c - 0x40) * 8];
}
} else if (c <= 0x7F) {
// 60-7f: inverted `abcdefghijklmnopqrstuvwxyz{|}~*
// (these are flashing (sp)!"#$%...<=>? when not in mousetext)
if ((*switches) & S_ALTCH) {
*invert = true;
return &lcase_glyphs[(c - 0x60) * 8];
} else {
*invert = true;
return &ucase_glyphs[((c-0x60) + 0x20) * 8];
}
} else if (c <= 0xBF) {
// 80-BF: normal @ABCD... <=>? in both character sets
*invert = false;
return &ucase_glyphs[(c - 0x80) * 8];
} else if (c <= 0xDF) {
// C0-DF: normal @ABCD...Z[\]^_ in both character sets
*invert = false;
return &ucase_glyphs[(c - 0xC0) * 8];
} else {
// E0- : normal `abcdef... in both character sets
*invert = false;
return &lcase_glyphs[(c - 0xE0) * 8];
}
/* 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.
uint16_t fgColor = g_displayType == m_monochrome?c_green:c_white;
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 : fgColor);
drawPixel(val, (basex+x2)/2, y*8+y2);
} else {
uint8_t val = (invert ? fgColor : 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);
uint16_t fgColor = g_displayType == m_monochrome?c_green:c_white;
for (uint8_t y2 = 0; y2<8; y2++) {
uint8_t d = *(cptr + y2);
for (uint8_t x2 = 0; x2 < 7; x2++) {
if (d & (1 << x2)) {
uint8_t val = (invert ? c_black : fgColor);
drawPixel(val, x*7+x2, y*8+y2);
} else {
uint8_t val = (invert ? fgColor : c_black);
drawPixel(val, x*7+x2, y*8+y2);
}
}
}
}
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
// address we were given...
addr &= ~0x01;
// Figure out the position of that address on the "normal" hires screen
uint8_t row;
uint16_t col;
deinterlaceHiresAddress(addr, &row, &col);
if (row >= 160 &&
((*switches) & S_MIXED)) {
// displaying text, so don't have to draw this line
return;
}
// Make sure it's a valid graphics area, not a dead hole
if (col <= 280 && row <= 192) {
// Grab the 4 bytes we care about
uint8_t b1A = mmu->readDirect(addr, 0);
uint8_t b2A = mmu->readDirect(addr+1, 0);
uint8_t b1B = mmu->readDirect(addr, 1);
uint8_t b2B = mmu->readDirect(addr+1, 1);
// Construct the 28 bit wide bitstream, like we do for the simpler 14 Hires pixel draw
uint32_t bitTrain = b2A & 0x7F;
bitTrain <<= 7;
bitTrain |= (b2B & 0x7F);
bitTrain <<= 7;
bitTrain |= (b1A & 0x7F);
bitTrain <<= 7;
bitTrain |= (b1B & 0x7F);
// Now we pop groups of 4 bits off the bottom and draw our
// NTSC-style-only color. The display for this project only has
// 320 columns, so it's silly to try to do 560 columns of
// monochrome; and likewise, we can't do "perfect" representation
// of shifted color pixels. So NTSC it is, and we'll draw two screen
// pixels for every color.
for (int8_t xoff = 0; xoff < 14; xoff += 2) {
drawPixel(bitTrain & 0x0F, col+xoff, row);
drawPixel(bitTrain & 0x0F, col+xoff+1, row);
bitTrain >>= 4;
}
}
}
// Whenever we change a byte, it's possible that it will have an affect on the byte next to it -
// 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)
{
uint8_t row;
uint16_t col;
deinterlaceHiresAddress(addr, &row, &col);
if (row >= 160 &&
((*switches) & S_MIXED)) {
return;
}
if (col <= 280 && row <= 192) {
/*
The high bit only selects the color palette.
There are only really two bits here, and they can be one of six colors.
color highbit even odd restriction
black x 0x80,0x00
green 0 0x2A 0x55 odd only
violet 0 0x55 0x2A even only
white x 0xFF,0x7F
orange 1 0xAA 0xD5 odd only
blue 1 0xD5 0xAA even only
in other words, we can look at the pixels in pairs and we get
00 black
01 green/orange
10 violet/blue
11 white
When the horizontal byte number is even, we ignore the last
bit. When the horizontal byte number is odd, we use that dropped
bit.
So each even byte turns in to 3 bits; and each odd byte turns in
to 4. Our effective output is therefore 140 pixels (half the
actual B&W resolution).
(Note that I swap 0x02 and 0x01 below, because we're running the
bit train backward, so the bits are reversed.)
*/
uint8_t b1 = mmu->read(addr);
uint8_t b2 = mmu->read(addr+1);
// Used for color modes...
bool highBitOne = (b1 & 0x80);
bool highBitTwo = (b2 & 0x80);
uint16_t bitTrain = (b1 & 0x7F) | ((b2 & 0x7F) << 7);
for (int8_t xoff = 0; xoff < 14; xoff += 2) {
if (g_displayType == m_monochrome) {
draw2Pixels(((bitTrain & 0x01 ? c_green : c_black) << 4) |
(bitTrain & 0x02 ? c_green : c_black),
col+xoff, row);
} else if (g_displayType == m_blackAndWhite) {
draw2Pixels(((bitTrain & 0x01 ? c_white : c_black) << 4) |
(bitTrain & 0x02 ? c_white : c_black),
col+xoff, row);
} else if (g_displayType == m_ntsclike) {
// Use the NTSC-like color mode, where we're only 140 pixels wide.
bool highBitSet = (xoff >= 7 ? highBitTwo : highBitOne);
uint8_t color;
switch (bitTrain & 0x03) {
case 0x00:
color = c_black;
break;
case 0x02:
color = (highBitSet ? c_orange : c_green);
break;
case 0x01:
color = (highBitSet ? c_medblue : c_purple);
break;
case 0x03:
color = c_white;
break;
}
draw2Pixels( (color << 4) | color, col+xoff, row );
} else {
// Use the "perfect" color mode, like the Apple RGB monitor showed.
bool highBitSet = (xoff >= 7 ? highBitTwo : highBitOne);
uint8_t color;
switch (bitTrain & 0x03) {
case 0x00:
color = c_black;
break;
case 0x02:
color = (highBitSet ? c_orange : c_green);
break;
case 0x01:
color = (highBitSet ? c_medblue : c_purple);
break;
case 0x03:
color = c_white;
break;
}
uint16_t twoColors;
if (color == c_black || color == c_white || bitTrain & 0x01) {
twoColors = color;
} else {
twoColors = c_black;
}
twoColors <<= 4;
if (color == c_black || color == c_white || bitTrain & 0x02) {
twoColors |= color;
} else {
twoColors |= c_black;
}
draw2Pixels(twoColors, col+xoff, row);
}
bitTrain >>= 2;
}
}
}
void AppleDisplay::modeChange()
{
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 {
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) {
DrawCharacterAt(mmu->read(addr), col, row);
}
}
}
dirty = true;
return;
}
// 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;
}
for (uint16_t addr = start; addr <= start + 0x1FFF; addr+=2) {
if ((*switches) & S_DHIRES) {
Draw14DoubleHiresPixelsAt(addr);
} else {
Draw14HiresPixelsAt(addr);
}
}
} 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);
}
}
}
}
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
// a 7-pixel-wide space and dividing it in half. Here I'm drawing
// every other column 1 pixel narrower (the ">= offset" in the for
// loop condition).
//
// Make those ">= 0" and change the "*7" to "*8" and you've got
// 320-pixel-wide slightly distorted but cleaner double-lores...
if (!offset) {
// The colors in every other column are swizzled. Un-swizzle.
c = ((c & 0x77) << 1) | ((c & 0x88) >> 3);
}
uint8_t pixel = c & 0x0F;
for (uint8_t y2 = 0; y2<4; y2++) {
for (int8_t x2 = 3; x2>=offset; x2--) {
drawPixel(pixel, x*7+x2+offset*3, y*8+y2);
}
}
pixel = (c >> 4);
for (uint8_t y2 = 4; y2<8; y2++) {
for (int8_t x2 = 3; x2>=offset; x2--) {
drawPixel(pixel, x*7+x2+offset*3, y*8+y2);
}
}
}
// col, row are still character-like positions, as in DrawCharacterAt.
// Each holds two pixels (one on top of the other).
void AppleDisplay::DrawLoresPixelAt(uint8_t c, uint8_t x, uint8_t y)
{
uint8_t pixel = c & 0x0F;
for (uint8_t y2 = 0; y2<4; y2++) {
for (int8_t x2 = 6; x2>=0; x2--) {
drawPixel(pixel, x*7+x2, y*8+y2);
}
}
pixel = (c >> 4);
for (uint8_t y2 = 4; y2<8; y2++) {
for (int8_t x2 = 6; x2>=0; x2--) {
drawPixel(pixel, x*7+x2, y*8+y2);
}
}
}
void AppleDisplay::draw2Pixels(uint16_t two4bitColors, uint16_t x, uint8_t y)
{
videoBuffer[(y * 320 + x) /2] = two4bitColors;
}
void AppleDisplay::drawPixel(uint8_t color4bit, uint16_t x, uint8_t y)
{
uint16_t idx = (y * 320 + x) / 2;
if (x & 1) {
videoBuffer[idx] = (videoBuffer[idx] & 0xF0) | color4bit;
} else {
videoBuffer[idx] = (videoBuffer[idx] & 0x0F) | (color4bit << 4);
}
}
void AppleDisplay::setSwitches(uint16_t *switches)
{
dirty = true;
this->switches = switches;
}
bool AppleDisplay::needsRedraw()
{
return dirty;
}
void AppleDisplay::didRedraw()
{
// FIXME: there's a drawing bug somewhere that's not getting the dirty flag set right.
// dirty = false;
}

75
apple/appledisplay.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef __APPLEDISPLAY_H
#define __APPLEDISPLAY_H
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <stdlib.h>
#endif
#include "vmdisplay.h"
enum {
c_black = 0,
c_magenta = 1,
c_darkblue = 2,
c_purple = 3,
c_darkgreen = 4,
c_darkgrey = 5,
c_medblue = 6,
c_lightblue = 7,
c_brown = 8,
c_orange = 9,
c_lightgray = 10,
c_pink = 11,
c_green = 12,
c_yellow = 13,
c_aqua = 14,
c_white = 15
};
enum {
m_blackAndWhite = 0,
m_monochrome = 1,
m_ntsclike = 2,
m_perfectcolor = 3
};
class AppleMMU;
class AppleDisplay : public VMDisplay{
public:
AppleDisplay(uint8_t *vb);
virtual ~AppleDisplay();
virtual bool needsRedraw();
virtual void didRedraw();
void modeChange(); // FIXME: rename 'redraw'?
void setSwitches(uint16_t *switches);
void writeLores(uint16_t address, uint8_t v);
void writeHires(uint16_t address, uint8_t v);
private:
bool deinterlaceAddress(uint16_t address, uint8_t *row, uint8_t *col);
bool deinterlaceHiresAddress(uint16_t address, uint8_t *row, uint16_t *col);
void Draw80CharacterAt(uint8_t c, uint8_t x, uint8_t y, uint8_t offset);
void DrawCharacterAt(uint8_t c, uint8_t x, uint8_t y);
void Draw14DoubleHiresPixelsAt(uint16_t address);
void Draw14HiresPixelsAt(uint16_t addr);
void Draw80LoresPixelAt(uint8_t c, uint8_t x, uint8_t y, uint8_t offset);
void DrawLoresPixelAt(uint8_t c, uint8_t x, uint8_t y);
void draw2Pixels(uint16_t two4bitColors, uint16_t x, uint8_t y);
void drawPixel(uint8_t cidx, uint16_t x, uint8_t y);
const unsigned char *xlateChar(uint8_t c, bool *invert);
private:
volatile bool dirty;
uint16_t *switches; // pointer to the MMU's switches
};
#endif

196
apple/applekeyboard.cpp Normal file
View File

@ -0,0 +1,196 @@
#include "applekeyboard.h"
#include "physicalkeyboard.h" // for LA/RA constants
#include "applemmu.h"
#include "globals.h"
// How many CPU cycles before we begin repeating a key?
#define STARTREPEAT 700000
// How many CPU cycles between repeats of a key?
#define REPEATAGAIN 66667
AppleKeyboard::AppleKeyboard(AppleMMU *m)
{
this->mmu = m;
for (uint16_t i=0; i<sizeof(keysDown); i++) {
keysDown[i] = false;
}
anyKeyIsDown = false;
startRepeatTimer = 0;
repeatTimer = 0;
capsLockEnabled = true;
}
AppleKeyboard::~AppleKeyboard()
{
}
bool AppleKeyboard::isVirtualKey(uint8_t kc)
{
if (kc >= 0x81 && kc <= 0x86) {
return true;
}
return false;
}
// apply the apple keymap.
// FIXME: easier with an array, but is that better?
uint8_t AppleKeyboard::translateKeyWithModifiers(uint8_t k)
{
// tolower, so we know what we're working with...
if (k >= 'A' && k <= 'Z') {
k = k - 'A' + 'a';
}
if (keysDown[_CTRL]) {
if (k >= 'a' && k <= 'z') {
return k - 'a' + 1;
}
// FIXME: any other control keys honored on the //e keyboard?
}
if (capsLockEnabled && k >= 'a' && k <= 'z') {
return k - 'a' + 'A';
}
if (keysDown[LSHFT] || keysDown[RSHFT]) {
if (k >= 'a' && k <= 'z') {
return k - 'a' + 'A';
}
switch (k) {
case '1':
return '!';
case '2':
return '@';
case '3':
return '#';
case '4':
return '$';
case '5':
return '%';
case '6':
return '^';
case '7':
return '&';
case '8':
return '*';
case '9':
return '(';
case '0':
return ')';
case '-':
return '_';
case '=':
return '+';
case '[':
return '{';
case ']':
return '}';
case '\\':
return '|';
case '`':
return '~';
case ';':
return ':';
case '\'':
return '"';
case ',':
return '<';
case '.':
return '>';
case '/':
return '?';
}
// FIXME: what the heck is it? I guess we don't need to shift it?
}
// And if we fall through, then just return it as-is
return k;
}
void AppleKeyboard::keyDepressed(uint8_t k)
{
keysDown[k] = true;
// If it's not a virtual key, then set the anyKeyDown flag
// (the VM will see this as a keyboard key)
if (!isVirtualKey(k)) {
if (!anyKeyIsDown) {
mmu->setKeyDown(true);
anyKeyIsDown = true;
}
keyThatIsRepeating = translateKeyWithModifiers(k);
startRepeatTimer = g_cpu->cycles + STARTREPEAT;
mmu->keyboardInput(keyThatIsRepeating);
} else if (k == LA) {
// Special handling: apple keys
mmu->isOpenApplePressed = true;
return;
} else if (k == RA) {
// Special handling: apple keys
mmu->isClosedApplePressed = true;
return;
} else if (k == LOCK) {
// Special handling: caps lock
capsLockEnabled = !capsLockEnabled;
return;
}
}
void AppleKeyboard::keyReleased(uint8_t k)
{
keysDown[k] = false;
// Special handling: apple keys
if (k == LA) {
mmu->isOpenApplePressed = false;
return;
}
if (k == RA) {
mmu->isClosedApplePressed = false;
return;
}
if (k == LOCK) {
// Nothing to do when the caps lock key is released.
return;
}
if (anyKeyIsDown) {
anyKeyIsDown = false;
for (int i=0; i<sizeof(keysDown); i++) {
if (keysDown[i] && !isVirtualKey(i)) {
anyKeyIsDown = true;
break;
}
}
if (!anyKeyIsDown) {
mmu->setKeyDown(false);
}
}
}
void AppleKeyboard::maintainKeyboard(uint32_t cycleCount)
{
if (anyKeyIsDown) {
if (startRepeatTimer) {
if (cycleCount >= startRepeatTimer) {
// waiting to start repeating
startRepeatTimer = 0;
repeatTimer = 0;
// Will fall through...
} else {
// Don't fall through; not time to start repeating yet
return;
}
}
// already repeating; keep it up
if (cycleCount >= repeatTimer) {
mmu->keyboardInput(keyThatIsRepeating);
repeatTimer = cycleCount + REPEATAGAIN;
}
}
}

57
apple/applekeyboard.h Normal file
View File

@ -0,0 +1,57 @@
#ifndef __APPLEKEYBOARD_H
#define __APPLEKEYBOARD_H
#include <stdint.h>
#include "vmkeyboard.h"
#include "applemmu.h"
class AppleKeyboard : public VMKeyboard {
public:
AppleKeyboard(AppleMMU *m);
virtual ~AppleKeyboard();
virtual void keyDepressed(uint8_t k);
virtual void keyReleased(uint8_t k);
virtual void maintainKeyboard(uint32_t cycleCount);
protected:
bool isVirtualKey(uint8_t kc);
uint8_t translateKeyWithModifiers(uint8_t k);
private:
AppleMMU *mmu;
bool capsLockEnabled;
// This is a trade-off. I'm choosing speed over RAM size. If we need
// to reclaim RAM, we can get some bytes here at the expense of speed.
// These are flags for whether or not each of the individual keys are
// down, so that we can repeat appropriately. We're tracking state
// of all of the keys because of special modifier key situations.
// It's lazily using 256 bytes instead of whatever 62 we'd actually need.
bool keysDown[256];
bool anyKeyIsDown;
// While one - and only one - key is down, we repeat keypresses
// after about "534 to 801 milliseconds" (UTA2E, p. 7-15); and then
// while repeating, we send that keypress (reset keystrobe) about 15
// times a second (every 66667-ish CPU cycles).
//
// startRepeatTimer is the time (in CPU clock cycles) when we will
// start repeating the key that's currently down (note: rollover
// happens every 4925 seconds because it's a 32-bit counter, which means
// that roughly once every 82 minutes it's possible that a key will begin
// repeating early).
//
// keyThatIsRepeating is set to the actual key pressed.
// repeatTimer is the cpu cycle count at which we would repeat again.
// (It also has the rollover problem once every 82 minutes.)
uint32_t startRepeatTimer;
uint8_t keyThatIsRepeating;
uint32_t repeatTimer;
};
#endif

853
apple/applemmu.cpp Normal file
View File

@ -0,0 +1,853 @@
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <stdio.h>
#include <unistd.h>
#endif
#include "applemmu.h"
#include "applemmu-rom.h"
#include "physicalspeaker.h"
#include "cpu.h"
#include "globals.h"
// apple //e memory map
/*
page 0x00: zero page (straight ram)
page 0x01: stack (straight ram)
page 0x02:
page 0x03:
text/lores page 1: 0x0400 - 0x7FF
text/lores page 2: 0x0800 - 0xBFF
pages 0x0C - 0x1F: straight ram
hires page 1: pages 0x20 - 0x3F
hires page 2: pages 0x40 - 0x5F
pages 0x60 - 0xBF: straight ram
page 0xc0: I/O switches
pages 0xc1 - 0xcf: slot ROMs
pages 0xd0 - 0xdf: Basic ROM
pages 0xe0 - 0xff: monitor ROM
*/
AppleMMU::AppleMMU(AppleDisplay *display)
{
anyKeyDown = false;
keyboardStrobe = 0x00;
isOpenApplePressed = false;
isClosedApplePressed = false;
for (int8_t i=0; i<=7; i++) {
slots[i] = NULL;
}
allocateMemory();
this->display = display;
this->display->setSwitches(&switches);
resetRAM(); // initialize RAM, load ROM
}
AppleMMU::~AppleMMU()
{
delete display;
// FIXME: clean up the memory we allocated
}
void AppleMMU::Reset()
{
resetRAM();
resetDisplay(); // sets the switches properly
}
uint8_t AppleMMU::read(uint16_t address)
{
if (address >= 0xC000 &&
address <= 0xC0FF) {
return readSwitches(address);
}
uint8_t res = readPages[(address & 0xFF00) >> 8][address & 0xFF];
return res;
}
// Bypass MMU and read directly from a given page - also bypasses switches
uint8_t AppleMMU::readDirect(uint16_t address, uint8_t fromPage)
{
return ramPages[(address & 0xFF00) >> 8][fromPage][address & 0xFF];
}
void AppleMMU::write(uint16_t address, uint8_t v)
{
if (address >= 0xC000 &&
address <= 0xC0FF) {
return writeSwitches(address, v);
}
// Don't allow writes to ROM
if (address >= 0xC100 && address <= 0xCFFF)
return;
if (address >= 0xD000 && address <= 0xFFFF && !writebsr) {
// memory-protected, so don't allow writes
return;
}
writePages[(address & 0xFF00) >> 8][address & 0xFF] = v;
if (address >= 0x400 &&
address <= 0x7FF) {
display->writeLores(address, v);
return;
}
if (address >= 0x2000 &&
address <= 0x5FFF) {
display->writeHires(address, v);
}
}
// FIXME: this is no longer "MMU", is it?
void AppleMMU::resetDisplay()
{
updateMemoryPages();
display->modeChange();
}
void AppleMMU::handleMemorySwitches(uint16_t address, uint16_t lastSwitch)
{
// many of these are spelled out here:
// http://apple2.org.za/gswv/a2zine/faqs/csa2pfaq.html
switch (address) {
// These are write-only and perform no action on read
case 0xC000: // CLR80STORE
switches &= ~S_80STORE;
updateMemoryPages();
break;
case 0xC001: // SET80STORE
switches |= S_80STORE;
updateMemoryPages();
break;
case 0xC002: // CLRAUXRD read from main 48k RAM
auxRamRead = false;
break;
case 0xC003: // SETAUXRD read from aux/alt 48k
auxRamRead = true;
break;
case 0xC004: // CLRAUXWR write to main 48k RAM
auxRamWrite = false;
break;
case 0xC005: // SETAUXWR write to aux/alt 48k
auxRamWrite = true;
break;
case 0xC006: // CLRCXROM use ROM on cards
intcxrom = false;
break;
case 0xC007: // SETCXROM use internal ROM
intcxrom = true;
break;
case 0xC008: // CLRAUXZP use main zero page, stack, LC
altzp = false;
break;
case 0xC009: // SETAUXZP use alt zero page, stack, LC
altzp = true;
break;
case 0xC00A: // CLRC3ROM use internal slot 3 ROM
slot3rom = false;
break;
case 0xC00B: // SETC3ROM use external slot 3 ROM
slot3rom = true;
break;
// these are probably read/write ?
case 0xC080: // READBSR2 and shadow copy
case 0xC084:
bank1 = false; // LC RAM bank2, Read and Write-prot RAM
readbsr = true; // read from bank2
writebsr = false; // write-protected
break;
case 0xC081: // ROMIN
case 0xC085:
bank1 = false; // LC RAM bank2, read ROM, write-enable RAM
readbsr = false; // if it's read twice in a row
writebsr = writebsr || ((lastSwitch & 0xF3) == (address & 0xF3));
break;
case 0xC082:
case 0xC086:
bank1 = false; // LC RAM bank2, Read ROM instead of RAM,
readbsr = false; // WR-protect RAM
writebsr = false;
break;
case 0xC083: // READWRBSR2
case 0xC087:
bank1 = false;
readbsr = true;
writebsr = writebsr || ((lastSwitch & 0xF3) == (address & 0xF3));
break;
case 0xC088: // READBSR1
case 0xC08C:
bank1 = true;
readbsr = true;
writebsr = false;
break;
case 0xC089: // WRITEBSR1
case 0xC08D:
bank1 = true;
readbsr = false;
writebsr = writebsr || ((lastSwitch & 0xF3) == (address & 0xF3));
break;
case 0xC08A: // OFFBSR1
case 0xC08E:
bank1 = true;
readbsr = false;
writebsr = false;
break;
case 0xC08B: // READWRBSR1
case 0xC08F:
bank1 = true;
readbsr = true;
writebsr = writebsr || ((lastSwitch & 0xF3) == (address & 0xF3));
break;
}
updateMemoryPages();
}
// many (most? all?) switches are documented here:
// http://apple2.org.za/gswv/a2zine/faqs/csa2pfaq.html
uint8_t AppleMMU::readSwitches(uint16_t address)
{
static uint16_t lastReadSwitch = 0x0000;
static uint16_t thisReadSwitch = 0x0000;
lastReadSwitch = thisReadSwitch;
thisReadSwitch = address;
// If this is a read for any of the slot switches, and we have
// hardware in that slot, then return its result.
if (address >= 0xC090 && address <= 0xC0FF) {
for (uint8_t i=1; i<=7; i++) {
if (address >= (0xC080 | (i << 4)) &&
address <= (0xC08F | (i << 4))) {
if (slots[i]) {
return slots[i]->readSwitches(address & ~(0xC080 | (i<<4)));
}
else
return FLOATING;
}
}
}
switch (address) {
case 0xC010:
// consume the keyboard strobe flag
keyboardStrobe &= 0x7F;
return (anyKeyDown ? 0x80 : 0x00);
#if 0
case 0xC000:
case 0xC001:
case 0xC002:
case 0xC003:
case 0xC004:
case 0xC005:
case 0xC006:
case 0xC007:
case 0xC008:
case 0xC009:
case 0xC00A:
case 0xC00B:
// nothing happens in read mode for these, so don't call this:
// handleMemorySwitches(address, lastReadSwitch);
case 0xC00C:
case 0xC00D:
case 0xC00E:
case 0xC00F:
// But according to UTA2E, these ALL return keyboard strobe data.
// We do that after the switch statement so we get the other affects
// from reads to these addresses within the switch statement.
break;
#endif
case 0xC080:
case 0xC081:
case 0xC082:
case 0xC083:
case 0xC084:
case 0xC085:
case 0xC086:
case 0xC087:
case 0xC088:
case 0xC089:
case 0xC08A:
case 0xC08B:
case 0xC08C:
case 0xC08D:
case 0xC08E:
case 0xC08F:
// but read does affect these, same as write
handleMemorySwitches(address, lastReadSwitch);
break;
case 0xC00C: // CLR80VID disable 80-col video mode
if (switches & S_80COL) {
switches &= ~S_80COL;
resetDisplay();
}
break;
case 0xC00D: // SET80VID enable 80-col video mode
if (!(switches & S_80COL)) {
switches |= S_80COL;
resetDisplay();
}
break;
case 0xC00E: // CLRALTCH use main char set - norm LC, flash UC
switches &= ~S_ALTCH;
break;
case 0xC00F: // SETALTCH use alt char set - norm inverse, LC; no flash
switches |= S_ALTCH;
break;
case 0xC011: // RDLCBNK2
return bank1 ? 0x00 : 0x80;
case 0xC012: // RDLCRAM
return readbsr ? 0x80 : 0x00;
case 0xC013: // RDRAMRD
return auxRamRead ? 0x80 : 0x00;
case 0xC014: // RDRAMWR
return auxRamWrite ? 0x80 : 0x00;
case 0xC015: // RDCXROM
return intcxrom ? 0x80 : 0x00;
case 0xC016: // RDAUXZP
return altzp ? 0x80 : 0x00;
case 0xC017: // RDC3ROM
return slot3rom ? 0x80 : 0x00;
case 0xC018: // RD80COL
return (switches & S_80STORE) ? 0x80 : 0x00;
// 0xC019: RDVBLBAR -- is the vertical blanking low?
case 0xC01A: // RDTEXT
return ( (switches & S_TEXT) ? 0x80 : 0x00 );
case 0xC01B: // RDMIXED
return ( (switches & S_MIXED) ? 0x80 : 0x00 );
case 0xC01C: // RDPAGE2
return ( (switches & S_PAGE2) ? 0x80 : 0x00 );
case 0xC01D: // RDHIRES
return ( (switches & S_HIRES) ? 0x80 : 0x00 );
case 0xC01E: // RDALTCH
return ( (switches & S_ALTCH) ? 0x80 : 0x00 );
case 0xC01F: // RD80VID
return ( (switches & S_80COL) ? 0x80 : 0x00 );
case 0xC030: // SPEAKER
g_speaker->toggleAtCycle(g_cpu->cycles);
break;
case 0xC050: // CLRTEXT
if (switches & S_TEXT) {
switches &= ~S_TEXT;
resetDisplay();
}
return FLOATING;
case 0xC051: // SETTEXT
if (!(switches & S_TEXT)) {
switches |= S_TEXT;
resetDisplay();
}
return FLOATING;
case 0xC052: // CLRMIXED
if (switches & S_MIXED) {
switches &= ~S_MIXED;
resetDisplay();
}
return FLOATING;
case 0xC053: // SETMIXED
if (!(switches & S_MIXED)) {
switches |= S_MIXED;
resetDisplay();
}
return FLOATING;
case 0xC054: // PAGE1
if (switches & S_PAGE2) {
switches &= ~S_PAGE2;
if (!(switches & S_80COL)) {
resetDisplay();
} else {
updateMemoryPages();
}
}
return FLOATING;
case 0xC055: // PAGE2
if (!(switches & S_PAGE2)) {
switches |= S_PAGE2;
if (!(switches & S_80COL)) {
resetDisplay();
} else {
updateMemoryPages();
}
}
return FLOATING;
case 0xC056: // CLRHIRES
if (switches & S_HIRES) {
switches &= ~S_HIRES;
resetDisplay();
}
return FLOATING;
case 0xC057: // SETHIRES
if (!(switches & S_HIRES)) {
switches |= S_HIRES;
resetDisplay();
}
return FLOATING;
case 0xC05E: // DHIRES ON
if (!(switches & S_DHIRES)) {
switches |= S_DHIRES;
resetDisplay();
}
return FLOATING;
case 0xC05F: // DHIRES OFF
if (switches & S_DHIRES) {
switches &= ~S_DHIRES;
resetDisplay();
}
return FLOATING;
// paddles
case 0xC061: // OPNAPPLE
return isOpenApplePressed ? 0x80 : 0x00;
case 0xC062: // CLSAPPLE
return isClosedApplePressed ? 0x80 : 0x00;
case 0xC070: // PDLTRIG
// It doesn't matter if we update readPages or writePages, because 0xC0
// has only one page.
readPages[0xC0][0x64] = readPages[0xC0][0x65] = 0xFF;
g_paddles->startReading();
return FLOATING;
}
if (address >= 0xc000 && address <= 0xc00f) {
// This is the keyboardStrobe support referenced in the switch statement above.
return keyboardStrobe;
}
return readPages[address >> 8][address & 0xFF];
}
void AppleMMU::writeSwitches(uint16_t address, uint8_t v)
{
// fixme: combine these with the last read switch
static uint16_t lastWriteSwitch = 0x0000;
static uint16_t thisWriteSwitch = 0x0000;
lastWriteSwitch = thisWriteSwitch;
thisWriteSwitch = address;
// If this is a write for any of the slot switches, and we have
// hardware in that slot, then return its result.
if (address >= 0xC090 && address <= 0xC0FF) {
for (uint8_t i=1; i<=7; i++) {
if (address >= (0xC080 | (i << 4)) &&
address <= (0xC08F | (i << 4))) {
if (slots[i]) {
slots[i]->writeSwitches(address & ~(0xC080 | (i<<4)), v);
return;
}
}
}
}
switch (address) {
case 0xC010:
case 0xC011: // Per Understanding the Apple //e, p. 7-3:
case 0xC012: // a write to any $C01x address causes
case 0xC013: // a clear of the keyboard strobe.
case 0xC014:
case 0xC015:
case 0xC016:
case 0xC017:
case 0xC018:
case 0xC019:
case 0xC01A:
case 0xC01B:
case 0xC01C:
case 0xC01D:
case 0xC01E:
case 0xC01F:
keyboardStrobe &= 0x7F;
return;
case 0xC050: // graphics mode
if (switches & S_TEXT) {
switches &= ~S_TEXT;
resetDisplay();
}
return;
case 0xC051:
if (!(switches & S_TEXT)) {
switches |= S_TEXT;
resetDisplay();
}
return;
case 0xC052: // "no mixed"
if (switches & S_MIXED) {
switches &= ~S_MIXED;
resetDisplay();
}
return;
case 0xC053: // "mixed"
if (!(switches & S_MIXED)) {
switches |= S_MIXED;
resetDisplay();
}
return;
case 0xC054: // page2 off
if (switches & S_PAGE2) {
switches &= ~S_PAGE2;
if (!(switches & S_80COL)) {
resetDisplay();
} else {
updateMemoryPages();
}
}
return;
case 0xC055: // page2 on
if (!(switches & S_PAGE2)) {
switches |= S_PAGE2;
if (!(switches & S_80COL)) {
resetDisplay();
} else {
updateMemoryPages();
}
}
return;
case 0xC056: // hires off
if (switches & S_HIRES) {
switches &= ~S_HIRES;
resetDisplay();
}
return;
case 0xC057: // hires on
if (!(switches & S_HIRES)) {
switches |= S_HIRES;
resetDisplay();
}
return;
case 0xC05E: // DHIRES ON
if (!(switches & S_DHIRES)) {
switches |= S_DHIRES;
resetDisplay();
}
return;
case 0xC05F: // DHIRES OFF
if (switches & S_DHIRES) {
switches &= ~S_DHIRES;
resetDisplay();
}
return;
// paddles
case 0xC070:
g_paddles->startReading();
writePages[0xC0][0x64] = writePages[0xC0][0x65] = 0xFF;
break;
case 0xC000:
case 0xC001:
case 0xC002:
case 0xC003:
case 0xC004:
case 0xC005:
case 0xC006:
case 0xC007:
case 0xC008:
case 0xC009:
case 0xC00A:
case 0xC00B:
case 0xC080:
case 0xC081:
case 0xC082:
case 0xC083:
case 0xC084:
case 0xC085:
case 0xC086:
case 0xC087:
case 0xC088:
case 0xC089:
case 0xC08A:
case 0xC08B:
case 0xC08C:
case 0xC08D:
case 0xC08E:
case 0xC08F:
handleMemorySwitches(address, lastWriteSwitch);
break;
case 0xC00C: // CLR80VID disable 80-col video mode
if (switches & S_80COL) {
switches &= ~S_80COL;
resetDisplay();
}
break;
case 0xC00D: // SET80VID enable 80-col video mode
if (!(switches & S_80COL)) {
switches |= S_80COL;
resetDisplay();
}
break;
case 0xC00E: // CLRALTCH use main char set - norm LC, flash UC
switches &= ~S_ALTCH;
break;
case 0xC00F: // SETALTCH use alt char set - norm inverse, LC; no flash
switches |= S_ALTCH;
break;
}
}
void AppleMMU::keyboardInput(uint8_t v)
{
keyboardStrobe = v | 0x80;
anyKeyDown = true;
}
void AppleMMU::setKeyDown(bool isTrue)
{
anyKeyDown = isTrue;
}
void AppleMMU::triggerPaddleTimer(uint8_t paddle)
{
writePages[0xC0][0x64 + paddle] = 0x00;
}
void AppleMMU::resetRAM()
{
switches = S_TEXT;
bank1 = true;
auxRamRead = auxRamWrite = false;
readbsr = writebsr = false;
altzp = false;
intcxrom = false;
slot3rom = false;
for (uint8_t i=0; i<0xFF; i++) {
for (uint8_t j=0; j<5; j++) {
if (ramPages[i][j]) {
for (uint16_t k=0; k<0x100; k++) {
ramPages[i][j][k] = 0;
}
}
}
readPages[i] = writePages[i] = ramPages[i][0];
}
// Load ROM
#ifdef TEENSYDUINO
for (uint16_t i=0x80; i<=0xFF; i++) {
for (uint16_t k=0; k<0x100; k++) {
uint16_t idx = ((i-0x80) << 8) | k;
uint8_t v = pgm_read_byte(&romData[idx]);
for (uint8_t j=0; j<5; j++) {
if (ramPages[i][j]) {
ramPages[i][j][k] = v;
}
}
}
}
#else
for (uint16_t i=0x80; i<=0xFF; i++) {
for (uint16_t k=0; k<0x100; k++) {
uint16_t idx = ((i-0x80) << 8) | k;
uint8_t v = romData[idx];
for (uint8_t j=0; j<5; j++) {
if (ramPages[i][j]) {
ramPages[i][j][k] = v;
}
}
}
}
#endif
for (uint8_t slotnum = 0; slotnum <= 7; slotnum++) {
if (slots[slotnum]) {
slots[slotnum]->loadROM(ramPages[0xC0 + slotnum][0]);
}
}
}
void AppleMMU::setSlot(int8_t slotnum, Slot *peripheral)
{
slots[slotnum] = peripheral;
if (slots[slotnum]) {
slots[slotnum]->loadROM(ramPages[0xC0 + slotnum][0]);
}
}
void AppleMMU::allocateMemory()
{
for (uint16_t i=0; i<0xC0; i++) {
for (uint8_t j=0; j<2; j++) {
ramPages[i][j] = (uint8_t *)malloc(0x100);
}
for (uint8_t j=2; j<5; j++) {
ramPages[i][j] = NULL;
}
readPages[i] = ramPages[i][0];
writePages[i] = ramPages[i][0];
}
for (uint16_t i=0xC0; i<0x100; i++) {
for (uint8_t j=0; j<5; j++) {
ramPages[i][j] = (uint8_t *)malloc(0x100);
}
readPages[i] = ramPages[i][0];
writePages[i] = ramPages[i][0];
}
}
void AppleMMU::updateMemoryPages()
{
if (auxRamRead) {
for (uint8_t idx = 0x02; idx < 0xc0; idx++) {
readPages[idx] = ramPages[idx][1];
}
} else {
for (uint8_t idx = 0x02; idx < 0xc0; idx++) {
readPages[idx] = ramPages[idx][0];
}
}
if (auxRamWrite) {
for (uint8_t idx = 0x02; idx < 0xc0; idx++) {
writePages[idx] = ramPages[idx][1];
}
} else {
for (uint8_t idx = 0x02; idx < 0xc0; idx++) {
writePages[idx] = ramPages[idx][0];
}
}
if (switches & S_80STORE) {
if (switches & S_PAGE2) {
for (uint8_t idx = 0x04; idx < 0x08; idx++) {
readPages[idx] = ramPages[idx][1];
writePages[idx] = ramPages[idx][1];
}
// If HIRES is on, then we honor the PAGE2 setting; otherwise, we don't
for (uint8_t idx = 0x20; idx < 0x40; idx++) {
readPages[idx] = ramPages[idx][(switches & S_HIRES) ? 1 : 0];
writePages[idx] = ramPages[idx][(switches & S_HIRES) ? 1 : 0];
}
} else {
for (uint8_t idx = 0x04; idx < 0x08; idx++) {
readPages[idx] = ramPages[idx][0];
writePages[idx] = ramPages[idx][0];
}
if (switches & S_HIRES) {
// PAGE2 is off, so we set this back to 0 regardless
for (uint8_t idx = 0x20; idx < 0x40; idx++) {
readPages[idx] = ramPages[idx][0];
writePages[idx] = ramPages[idx][0];
}
}
}
}
if (intcxrom) {
for (uint8_t idx = 0xc1; idx < 0xd0; idx++) {
readPages[idx] = ramPages[idx][1];
}
} else {
for (uint8_t idx = 0xc1; idx < 0xd0; idx++) {
readPages[idx] = ramPages[idx][0];
}
}
if (altzp) {
for (uint8_t idx = 0x00; idx < 0x02; idx++) {
readPages[idx] = ramPages[idx][1];
writePages[idx] = ramPages[idx][1];
}
} else {
for (uint8_t idx = 0x00; idx < 0x02; idx++) {
readPages[idx] = ramPages[idx][0];
writePages[idx] = ramPages[idx][0];
}
}
if (readbsr) {
if (bank1) {
for (uint8_t idx = 0xd0; idx < 0xe0; idx++) {
readPages[idx] = ramPages[idx][altzp ? 2 : 1];
}
} else {
for (uint8_t idx = 0xd0; idx < 0xe0; idx++) {
readPages[idx] = ramPages[idx][altzp ? 4 : 3];
}
}
for (uint16_t idx = 0xe0; idx < 0x100; idx++) {
readPages[idx] = ramPages[idx][altzp ? 2 : 1];
}
} else {
for (uint16_t idx = 0xd0; idx < 0x100; idx++) {
readPages[idx] = ramPages[idx][0];
}
}
if (writebsr) {
if (bank1) {
for (uint8_t idx = 0xd0; idx < 0xe0; idx++) {
writePages[idx] = ramPages[idx][altzp ? 2 : 1];
}
} else {
for (uint8_t idx = 0xd0; idx < 0xe0; idx++) {
writePages[idx] = ramPages[idx][altzp ? 4 : 3];
}
}
for (uint16_t idx = 0xe0; idx < 0x100; idx++) {
writePages[idx] = ramPages[idx][altzp ? 2 : 1];
}
} else {
for (uint16_t idx = 0xd0; idx < 0x100; idx++) {
writePages[idx] = ramPages[idx][0];
}
}
}

91
apple/applemmu.h Normal file
View File

@ -0,0 +1,91 @@
#ifndef __APPLEMMU_H
#define __APPLEMMU_H
#include <stdlib.h>
#include "appledisplay.h"
#include "slot.h"
#include "mmu.h"
// when we read a nondeterministic result, we return FLOATING. Maybe
// some day we can come back here and figure out how to return what
// the Apple would have.
#define FLOATING 0
// Switches activated by various memory locations
enum {
S_TEXT = 0x0001,
S_MIXED = 0x0002,
S_HIRES = 0x0004,
S_PAGE2 = 0x0008,
S_80COL = 0x0010,
S_ALTCH = 0x0020,
S_80STORE = 0x0040,
S_DHIRES = 0x0080
};
typedef bool (*callback_t)(void *);
class AppleVM;
class AppleMMU : public MMU {
friend class AppleVM;
public:
AppleMMU(AppleDisplay *display);
virtual ~AppleMMU();
virtual uint8_t read(uint16_t address);
virtual uint8_t readDirect(uint16_t address, uint8_t fromPage);
virtual void write(uint16_t address, uint8_t v);
virtual void Reset();
void keyboardInput(uint8_t v);
void setKeyDown(bool isTrue);
void triggerPaddleTimer(uint8_t paddle);
void resetRAM(); // used by BIOS on cold boot
void setSlot(int8_t slotnum, Slot *peripheral);
protected:
void allocateMemory();
void resetDisplay();
uint8_t readSwitches(uint16_t address);
void writeSwitches(uint16_t address, uint8_t v);
void handleMemorySwitches(uint16_t address, uint16_t lastSwitch);
void updateMemoryPages();
private:
AppleDisplay *display;
uint16_t switches;
public: // debuggign
bool auxRamRead;
bool auxRamWrite;
bool bank1;
bool readbsr;
bool writebsr;
bool altzp;
bool intcxrom;
bool slot3rom;
Slot *slots[8]; // slots 0-7
uint8_t *ramPages[0x100][5];
uint8_t *readPages[0x100];
uint8_t *writePages[0x100];
uint8_t keyboardStrobe;
bool anyKeyDown;
public:
// FIXME: build a private API for these
bool isOpenApplePressed;
bool isClosedApplePressed;
};
#endif

99
apple/applevm.cpp Normal file
View File

@ -0,0 +1,99 @@
#include "applevm.h"
#include "filemanager.h"
#include "cpu.h"
#include "appledisplay.h"
#include "applekeyboard.h"
#include "physicalkeyboard.h"
#include "globals.h"
AppleVM::AppleVM()
{
// FIXME: all this typecasting makes me knife-stabby
vmdisplay = new AppleDisplay(videoBuffer);
mmu = new AppleMMU((AppleDisplay *)vmdisplay);
vmdisplay->SetMMU((AppleMMU *)mmu);
disk6 = new DiskII((AppleMMU *)mmu);
((AppleMMU *)mmu)->setSlot(6, disk6);
keyboard = new AppleKeyboard((AppleMMU *)mmu);
#ifdef TEENSYDUINO
teensyClock = new TeensyClock((AppleMMU *)mmu);
((AppleMMU *)mmu)->setSlot(7, teensyClock);
#endif
}
AppleVM::~AppleVM()
{
#ifdef TEENSYDUINO
delete teensyClock;
#endif
delete disk6;
}
// fixme: make member vars
unsigned long paddleCycleTrigger[2] = {0, 0};
void AppleVM::triggerPaddleInCycles(uint8_t paddleNum,uint16_t cycleCount)
{
paddleCycleTrigger[paddleNum] = cycleCount + g_cpu->cycles;
}
void AppleVM::cpuMaintenance(uint32_t cycles)
{
for (uint8_t i=0; i<2; i++) {
if (paddleCycleTrigger[i] && cycles >= paddleCycleTrigger[i]) {
((AppleMMU *)mmu)->triggerPaddleTimer(i);
paddleCycleTrigger[i] = 0;
}
}
keyboard->maintainKeyboard(cycles);
}
void AppleVM::Reset()
{
disk6->Reset();
((AppleMMU *)mmu)->resetRAM();
mmu->Reset();
g_cpu->pc = (((AppleMMU *)mmu)->read(0xFFFD) << 8) | ((AppleMMU *)mmu)->read(0xFFFC);
// give the keyboard a moment to depress keys upon startup
keyboard->maintainKeyboard(0);
}
void AppleVM::Monitor()
{
g_cpu->pc = 0xff69; // "call -151"
((AppleMMU *)mmu)->readSwitches(0xC054); // make sure we're in page 1
((AppleMMU *)mmu)->readSwitches(0xC056); // and that hires is off
((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);
}
void AppleVM::ejectDisk(uint8_t drivenum)
{
disk6->ejectDisk(drivenum);
}
void AppleVM::insertDisk(uint8_t drivenum, const char *filename, bool drawIt)
{
disk6->insertDisk(drivenum, filename, drawIt);
}
VMKeyboard * AppleVM::getKeyboard()
{
return keyboard;
}

41
apple/applevm.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef __APPLEVM_H
#define __APPLEVM_H
#include "cpu.h"
#include "appledisplay.h"
#include "diskii.h"
#include "vmkeyboard.h"
#ifdef TEENSYDUINO
#include "teensy-clock.h"
#endif
#include "vm.h"
class AppleVM : public VM {
public:
AppleVM();
virtual ~AppleVM();
void cpuMaintenance(uint32_t cycles);
virtual void Reset();
void Monitor();
virtual void triggerPaddleInCycles(uint8_t paddleNum,uint16_t cycleCount);
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;
VMKeyboard *keyboard;
#ifdef TEENSYDUINO
TeensyClock *teensyClock;
#endif
};
#endif

470
apple/diskii.cpp Normal file
View File

@ -0,0 +1,470 @@
#include "diskii.h"
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#endif
#include "applemmu.h" // for FLOATING
#include "globals.h"
#include "diskii-rom.h"
DiskII::DiskII(AppleMMU *mmu)
{
this->trackBuffer = new RingBuffer(NIBTRACKSIZE);
this->rawTrackBuffer = (uint8_t *)malloc(4096);
this->mmu = mmu;
curTrack = 0;
trackDirty = false;
writeMode = false;
writeProt = false; // FIXME: expose an interface to this
writeLatch = 0x00;
disk[0] = disk[1] = -1;
indicatorIsOn[0] = indicatorIsOn[1] = 0;
selectedDisk = 0;
diskType[0] = diskType[1] = dosDisk;
}
DiskII::~DiskII()
{
delete this->trackBuffer; this->trackBuffer = NULL;
free(this->rawTrackBuffer); this->rawTrackBuffer = NULL;
}
void DiskII::Reset()
{
curTrack = 0;
trackDirty = false;
writeMode = false;
writeProt = false; // FIXME: expose an interface to this
writeLatch = 0x00;
ejectDisk(0);
ejectDisk(1);
}
uint8_t DiskII::readSwitches(uint8_t s)
{
switch (s) {
case 0x00: // change stepper motor phase
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x07:
step(s);
break;
case 0x08: // drive off
indicatorIsOn[selectedDisk] = 99;
g_display->drawDriveStatus(selectedDisk, false);
flushTrack();
break;
case 0x09: // drive on
indicatorIsOn[selectedDisk] = 100;
g_display->drawDriveStatus(selectedDisk, true);
break;
case 0x0A: // select drive 1
select(0);
break;
case 0x0B: // select drive 2
select(1);
break;
case 0x0C: // shift one read or write byte
writeLatch = readOrWriteByte();
break;
case 0x0D: // load data register (latch)
// This is complex and incomplete. cf. Logic State Sequencer,
// UTA2E, p. 9-14
if (!writeMode && indicatorIsOn[selectedDisk]) {
if (isWriteProtected())
writeLatch |= 0x80;
else
writeLatch &= 0x7F;
}
break;
case 0x0E: // set read mode
setWriteMode(false);
// FIXME: with this shortcut here, disk access speeds up ridiculously.
// Is this breaking anything?
return ( (readOrWriteByte() & 0x7F) |
(isWriteProtected() ? 0x80 : 0x00) );
break;
case 0x0F: // set write mode
setWriteMode(true);
break;
}
// FIXME: improve the spin-down here. We need a CPU cycle callback
// for some period of time instead of this silly decrement counter.
if (!indicatorIsOn[selectedDisk]) {
// printf("Unexpected read while disk isn't on?\n");
indicatorIsOn[selectedDisk] = 100;
g_display->drawDriveStatus(selectedDisk, 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,
// p. 9-12, note 2)
return (s & 1) ? FLOATING : writeLatch;
}
void DiskII::writeSwitches(uint8_t s, uint8_t v)
{
switch (s) {
case 0x00: // change stepper motor phase
case 0x01:
case 0x02:
case 0x03:
case 0x04:
case 0x05:
case 0x06:
case 0x07:
step(s);
break;
case 0x08: // drive off
break;
case 0x09: // drive on
break;
case 0x0A: // select drive 1
select(0);
break;
case 0x0B: // select drive 2
select(1);
break;
case 0x0C: // shift one read or write byte
readOrWriteByte();
break;
case 0x0D: // drive write
break;
case 0x0E: // set read mode
setWriteMode(false);
break;
case 0x0F: // set write mode
setWriteMode(true);
break;
}
// All writes update the latch
if (writeMode) {
writeLatch = v;
}
}
// where phase is the address poked & 0x07 (e.g. "0xC0E0 & 0x07")
void DiskII::step(uint8_t phase)
{
static int mag[4] = { 0,0,0,0 };
static int pmag[4] = { 0, 0, 0, 0 };
static int ppmag[4] = { 0, 0, 0, 0 };
static int pnum = 0;
static int ppnum = 0;
static int trackPos = 0;
static int prevTrack = 0;
// phase &= 7;
int magnet_number = phase >> 1;
// shuffle data down
ppmag[ppnum] = pmag[ppnum];
ppnum = pnum;
pmag[pnum] = mag[pnum];
pnum = magnet_number;
if ((phase & 1) == 0) {
mag[magnet_number] = 0;
} else {
if (ppmag[(magnet_number + 1) & 3]) {
if (--trackPos < 0) {
trackPos = 0;
// recalibrate...
}
}
if (ppmag[(magnet_number - 1) & 3]) {
// FIXME: don't go beyond the end of the media. For a 35-track disk, that's 68 == ((35-1) * 2).
if (++trackPos > 68) {
trackPos = 68;
}
}
mag[magnet_number] = 1;
}
curTrack = (trackPos + 1) / 2;
if (curTrack != prevTrack) {
// We're about to change tracks - be sure to flush the track if we've written to it
if (trackDirty) {
flushTrack();
}
// step to the appropriate track
trackDirty = false;
prevTrack = curTrack;
trackBuffer->clear();
}
}
bool DiskII::isWriteProtected()
{
return (writeProt ? 0xFF : 0x00);
}
void DiskII::setWriteMode(bool enable)
{
writeMode = enable;
}
static uint8_t _lc(char c)
{
if (c >= 'A' && c <= 'Z') {
c = c - 'A' + 'a';
}
return c;
}
static bool _endsWithI(const char *s1, const char *s2)
{
if (strlen(s2) > strlen(s1)) {
return false;
}
const char *p = &s1[strlen(s1)-1];
int16_t l = strlen(s2)-1;
while (l >= 0) {
if (_lc(*p--) != _lc(s2[l]))
return false;
l--;
}
return true;
}
void DiskII::insertDisk(int8_t driveNum, const char *filename, bool drawIt)
{
ejectDisk(driveNum);
disk[driveNum] = g_filemanager->openFile(filename);
if (drawIt)
g_display->drawDriveDoor(driveNum, false);
if (_endsWithI(filename, ".nib")) {
diskType[driveNum] = nibDisk;
} else if (_endsWithI(filename, ".po")) {
diskType[driveNum] = prodosDisk;
} else {
diskType[driveNum] = dosDisk;
#ifndef TEENSYDUINO
// debugging: make a nib copy of the image to play with
// convertDskToNib("/tmp/debug.nib");
#endif
}
}
void DiskII::ejectDisk(int8_t driveNum)
{
if (disk[driveNum] != -1) {
g_filemanager->closeFile(disk[driveNum]);
disk[driveNum] = -1;
g_display->drawDriveDoor(driveNum, true);
}
}
void DiskII::select(int8_t which)
{
if (which != 0 && which != 1)
return;
if (which != selectedDisk) {
indicatorIsOn[selectedDisk] = 0;
g_display->drawDriveStatus(selectedDisk, false);
flushTrack(); // in case it's dirty: flush before changing drives
trackBuffer->clear();
}
// set the selected disk drive
selectedDisk = which;
}
uint8_t DiskII::readOrWriteByte()
{
if (disk[selectedDisk] == -1) {
// printf("NO DISK\n");
return GAP;
}
if (writeMode && !writeProt) {
if (!trackBuffer->hasData()) {
// printf("some sort of error happened - trying to write to uninitialized track?\n");
return 0;
}
trackDirty = true;
// It's possible that a badly behaving OS could try to write more
// data than we have buffer to handle. Don't let it. We should
// only need something like 500 bytes, at worst. In the typical
// case, we're talking about something like
//
// ~5 bytes of GAP
// 3 bytes of sector prolog
// 2 bytes of volume
// 2 bytes of track
// 2 bytes of sector
// 2 bytes of checksum
// 2 bytes of epilog
// ~5 bytes of GAP
// 3 bytes of data prolog
// 342 bytes of GRP-encoded (6:2) data
// 1 byte of checksum
// 3 bytes of epilog
// 1 byte of GAP
// == 373 bytes
//
// ... so if we get up to the full 1024 we've allocated, there's
// something suspicious happening.
if (writeLatch < 0x96) {
// Can't write a de-nibblized byte...
g_display->debugMsg("DII: bad write");
return 0;
}
trackBuffer->replaceByte(writeLatch);
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.
if (!trackBuffer->hasData()) {
trackDirty = false;
trackBuffer->clear();
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);
}
return trackBuffer->peekNext();
}
const char *DiskII::DiskName(int8_t num)
{
if (disk[num] != -1)
return g_filemanager->fileName(disk[num]);
return "";
}
void DiskII::loadROM(uint8_t *toWhere)
{
#ifdef TEENSYDUINO
Serial.println("loading slot rom");
for (uint16_t i=0; i<=0xFF; i++) {
toWhere[i] = pgm_read_byte(&romData[i]);
}
#else
printf("loading slot rom\n");
memcpy(toWhere, romData, 256);
#endif
}
void DiskII::flushTrack()
{
if (!trackDirty) {
return;
}
// safety check: if we're write-protected, then how did we get here?
if (writeProt) {
g_display->debugMsg("Write Protected");
return;
}
if (!trackBuffer->hasData()) {
// Dunno what happened - we're writing but haven't initialized the sector buffer?
return;
}
if (diskType[selectedDisk] == nibDisk) {
// Write the whole track out exactly as we've got it. Hopefully
// someone has re-calcuated appropriate checksums on it...
g_display->debugMsg("Not writing Nib image");
return;
}
nibErr e = denibblizeTrack(trackBuffer, rawTrackBuffer, diskType[selectedDisk], curTrack);
switch (e) {
case errorShortTrack:
g_display->debugMsg("DII: short track");
trackBuffer->clear();
return;
case errorMissingSectors:
g_display->debugMsg("DII: missing sectors");
trackBuffer->clear();
break;
case errorNone:
break;
}
// ok, write the track!
g_filemanager->seekBlock(disk[selectedDisk], curTrack * 16);
g_filemanager->writeTrack(disk[selectedDisk], rawTrackBuffer);
trackDirty = false;
}

64
apple/diskii.h Normal file
View File

@ -0,0 +1,64 @@
#ifndef __DISKII_H
#define __DISKII_H
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <stdint.h>
#include <stdio.h>
#endif
#include "filemanager.h"
#include "applemmu.h"
#include "slot.h"
#include "RingBuffer.h"
#include "nibutil.h"
class DiskII : public Slot {
public:
DiskII(AppleMMU *mmu);
virtual ~DiskII();
virtual void Reset(); // used by BIOS cold-boot
virtual uint8_t readSwitches(uint8_t s);
virtual void writeSwitches(uint8_t s, uint8_t v);
virtual void loadROM(uint8_t *toWhere);
void insertDisk(int8_t driveNum, const char *filename, bool drawIt = true);
void ejectDisk(int8_t driveNum);
const char *DiskName(int8_t num);
void flushTrack();
private:
void step(uint8_t phase);
bool isWriteProtected();
void setWriteMode(bool enable);
void select(int8_t which); // 0 or 1 for drives 1 and 2, respectively
uint8_t readOrWriteByte();
#ifndef TEENSYDUINO
void convertDskToNib(const char *outFN);
#endif
private:
uint8_t curTrack;
bool trackDirty; // does this track need flushing to disk?
uint8_t writeLatch;
RingBuffer *trackBuffer; // nibblized data
uint8_t *rawTrackBuffer; // not nibblized data
bool writeMode;
bool writeProt;
AppleMMU *mmu;
int8_t disk[2];
uint8_t indicatorIsOn[2];
uint8_t diskType[2];
int8_t selectedDisk;
};
#endif

335
apple/font.h Normal file
View File

@ -0,0 +1,335 @@
const unsigned char ucase_glyphs[512] =
{
/* : 0x40 @ */
0x1c, 0x22, 0x2a, 0x3a, 0x1a, 0x02, 0x3c, 0x00,
/* : 0x41 A */
0x08, 0x14, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x00,
/* : 0x42 B */
0x1e, 0x22, 0x22, 0x1e, 0x22, 0x22, 0x1e, 0x00,
/* : 0x43 C */
0x1c, 0x22, 0x02, 0x02, 0x02, 0x22, 0x1c, 0x00,
/* : 0x44 D */
0x1e, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1e, 0x00,
/* : 0x45 E */
0x3e, 0x02, 0x02, 0x1e, 0x02, 0x02, 0x3e, 0x00,
/* : 0x46 F */
0x3e, 0x02, 0x02, 0x1e, 0x02, 0x02, 0x02, 0x00,
/* : 0x47 G */
0x3c, 0x02, 0x02, 0x02, 0x32, 0x22, 0x3c, 0x00,
/* : 0x48 H */
0x22, 0x22, 0x22, 0x3e, 0x22, 0x22, 0x22, 0x00,
/* : 0x49 I */
0x1c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00,
/* : 0x4a */
0x20, 0x20, 0x20, 0x20, 0x20, 0x22, 0x1c, 0x00,
/* : 0x4b */
0x22, 0x12, 0x0a, 0x06, 0x0a, 0x12, 0x22, 0x00,
/* : 0x4c */
0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x3e, 0x00,
/* : 0x4d */
0x22, 0x36, 0x2a, 0x2a, 0x22, 0x22, 0x22, 0x00,
/* : 0x4e */
0x22, 0x22, 0x26, 0x2a, 0x32, 0x22, 0x22, 0x00,
/* : 0x4f */
0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00,
/* : 0x50 */
0x1e, 0x22, 0x22, 0x1e, 0x02, 0x02, 0x02, 0x00,
/* : 0x51 */
0x1c, 0x22, 0x22, 0x22, 0x2a, 0x12, 0x2c, 0x00,
/* : 0x52 */
0x1e, 0x22, 0x22, 0x1e, 0x0a, 0x12, 0x22, 0x00,
/* : 0x53 */
0x1c, 0x22, 0x02, 0x1c, 0x20, 0x22, 0x1c, 0x00,
/* : 0x54 */
0x3e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00,
/* : 0x55 */
0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c, 0x00,
/* : 0x56 */
0x22, 0x22, 0x22, 0x22, 0x22, 0x14, 0x08, 0x00,
/* : 0x57 */
0x22, 0x22, 0x22, 0x2a, 0x2a, 0x36, 0x22, 0x00,
/* : 0x58 */
0x22, 0x22, 0x14, 0x08, 0x14, 0x22, 0x22, 0x00,
/* : 0x59 */
0x22, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08, 0x00,
/* : 0x5a */
0x3e, 0x20, 0x10, 0x08, 0x04, 0x02, 0x3e, 0x00,
/* : 0x5b */
0x3e, 0x06, 0x06, 0x06, 0x06, 0x06, 0x3e, 0x00,
/* : 0x5c */
0x00, 0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x00,
/* : 0x5d */
0x3e, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3e, 0x00,
/* : 0x5e */
0x00, 0x00, 0x08, 0x14, 0x22, 0x00, 0x00, 0x00,
/* : 0x5f */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00,
/* : 0x20 space */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* : 0x21 ! */
0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x08, 0x00,
/* : 0x22 */
0x14, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00,
/* : 0x23 */
0x14, 0x14, 0x3e, 0x14, 0x3e, 0x14, 0x14, 0x00,
/* : 0x24 */
0x08, 0x3c, 0x0a, 0x1c, 0x28, 0x1e, 0x08, 0x00,
/* : 0x25 */
0x06, 0x26, 0x10, 0x08, 0x04, 0x32, 0x30, 0x00,
/* : 0x26 */
0x04, 0x0a, 0x0a, 0x04, 0x2a, 0x12, 0x2c, 0x00,
/* : 0x27 */
0x08, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
/* : 0x28 */
0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08, 0x00,
/* : 0x29 */
0x08, 0x10, 0x20, 0x20, 0x20, 0x10, 0x08, 0x00,
/* : 0x2a */
0x08, 0x2a, 0x1c, 0x08, 0x1c, 0x2a, 0x08, 0x00,
/* : 0x2b */
0x00, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00, 0x00,
/* : 0x2c */
0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x04, 0x00,
/* : 0x2d */
0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x00,
/* : 0x2e */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
/* : 0x2f */
0x00, 0x20, 0x10, 0x08, 0x04, 0x02, 0x00, 0x00,
/* : 0x30 */
0x1c, 0x22, 0x32, 0x2a, 0x26, 0x22, 0x1c, 0x00,
/* : 0x31 */
0x08, 0x0c, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00,
/* : 0x32 */
0x1c, 0x22, 0x20, 0x18, 0x04, 0x02, 0x3e, 0x00,
/* : 0x33 */
0x3e, 0x20, 0x10, 0x18, 0x20, 0x22, 0x1c, 0x00,
/* : 0x34 */
0x10, 0x18, 0x14, 0x12, 0x3e, 0x10, 0x10, 0x00,
/* : 0x35 */
0x3e, 0x02, 0x1e, 0x20, 0x20, 0x22, 0x1c, 0x00,
/* : 0x36 */
0x38, 0x04, 0x02, 0x1e, 0x22, 0x22, 0x1c, 0x00,
/* : 0x37 */
0x3e, 0x20, 0x10, 0x08, 0x04, 0x04, 0x04, 0x00,
/* : 0x38 */
0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c, 0x00,
/* : 0x39 */
0x1c, 0x22, 0x22, 0x3c, 0x20, 0x10, 0x0e, 0x00,
/* : 0x3a */
0x00, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, 0x00,
/* : 0x3b */
0x00, 0x00, 0x08, 0x00, 0x08, 0x08, 0x04, 0x00,
/* : 0x3c */
0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00,
/* : 0x3d */
0x00, 0x00, 0x3e, 0x00, 0x3e, 0x00, 0x00, 0x00,
/* : 0x3e */
0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00,
/* : 0x3f */
0x1c, 0x22, 0x10, 0x08, 0x08, 0x00, 0x08, 0x00
};
const unsigned char lcase_glyphs[256] =
{
/* : 0x60 */
0x02, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00,
/* : 0x61 */
0x00, 0x00, 0x1c, 0x20, 0x3c, 0x22, 0x3c, 0x00,
/* : 0x62 */
0x02, 0x02, 0x1e, 0x22, 0x22, 0x22, 0x1e, 0x00,
/* : 0x63 */
0x00, 0x00, 0x3c, 0x02, 0x02, 0x02, 0x3c, 0x00,
/* : 0x64 */
0x20, 0x20, 0x3c, 0x22, 0x22, 0x22, 0x3c, 0x00,
/* : 0x65 */
0x00, 0x00, 0x1c, 0x22, 0x3e, 0x02, 0x3c, 0x00,
/* : 0x66 */
0x18, 0x24, 0x04, 0x0e, 0x04, 0x04, 0x04, 0x00,
/* : 0x67 */
0x00, 0x00, 0x1c, 0x22, 0x22, 0x3c, 0x20, 0x1c,
/* : 0x68 */
0x02, 0x02, 0x1e, 0x22, 0x22, 0x22, 0x22, 0x00,
/* : 0x69 */
0x08, 0x00, 0x0c, 0x08, 0x08, 0x08, 0x1c, 0x00,
/* : 0x6a */
0x10, 0x00, 0x18, 0x10, 0x10, 0x10, 0x12, 0x0c,
/* : 0x6b */
0x02, 0x02, 0x22, 0x12, 0x0e, 0x12, 0x22, 0x00,
/* : 0x6c */
0x0c, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1c, 0x00,
/* : 0x6d */
0x00, 0x00, 0x16, 0x2a, 0x2a, 0x2a, 0x2a, 0x00,
/* : 0x6e */
0x00, 0x00, 0x1e, 0x22, 0x22, 0x22, 0x22, 0x00,
/* : 0x6f */
0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x1c, 0x00,
/* : 0x70 */
0x00, 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x02, 0x02,
/* : 0x71 */
0x00, 0x00, 0x3c, 0x22, 0x22, 0x3c, 0x20, 0x20,
/* : 0x72 */
0x00, 0x00, 0x3a, 0x06, 0x02, 0x02, 0x02, 0x00,
/* : 0x73 */
0x00, 0x00, 0x3c, 0x02, 0x1c, 0x20, 0x1e, 0x00,
/* : 0x74 */
0x04, 0x04, 0x3e, 0x04, 0x04, 0x24, 0x18, 0x00,
/* : 0x75 */
0x00, 0x00, 0x22, 0x22, 0x22, 0x32, 0x2c, 0x00,
/* : 0x76 */
0x00, 0x00, 0x22, 0x22, 0x22, 0x14, 0x08, 0x00,
/* : 0x77 */
0x00, 0x00, 0x22, 0x2a, 0x2a, 0x2a, 0x14, 0x00,
/* : 0x78 */
0x00, 0x00, 0x22, 0x14, 0x08, 0x14, 0x22, 0x00,
/* : 0x79 */
0x00, 0x00, 0x22, 0x22, 0x22, 0x3c, 0x20, 0x1c,
/* : 0x7a */
0x00, 0x00, 0x3e, 0x10, 0x08, 0x04, 0x3e, 0x00,
/* : 0x7b */
0x38, 0x0c, 0x0c, 0x06, 0x0c, 0x0c, 0x38, 0x00,
/* : 0x7c */
0x08, 0x08, 0x08, 0x00, 0x08, 0x08, 0x08, 0x00,
/* : 0x7d */
0x0e, 0x18, 0x18, 0x30, 0x18, 0x18, 0x0e, 0x00,
/* : 0x7e */
0x2c, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* : 0x7f */
0x00, 0x2a, 0x14, 0x2a, 0x14, 0x2a, 0x00, 0x00
};
const unsigned char mousetext_glyphs[256] =
{
/* : 0x00 */
0x10, 0x08, 0x36, 0x7f, 0x3f, 0x3f, 0x7e, 0x36,
/* : 0x01 */
0x10, 0x08, 0x36, 0x41, 0x21, 0x21, 0x4a, 0x36,
/* : 0x02 */
0x00, 0x00, 0x02, 0x06, 0x0e, 0x1e, 0x36, 0x42,
/* : 0x03 */
0x7f, 0x22, 0x14, 0x08, 0x08, 0x14, 0x22, 0x7f,
/* : 0x04 */
0x00, 0x40, 0x20, 0x11, 0x0a, 0x04, 0x04, 0x00,
/* : 0x05 */
0x7f, 0x3f, 0x5f, 0x6e, 0x75, 0x7b, 0x7b, 0x7f,
/* : 0x06 */
0x70, 0x60, 0x7e, 0x31, 0x79, 0x30, 0x3f, 0x02,
/* : 0x07 */
0x00, 0x18, 0x07, 0x00, 0x07, 0x0c, 0x08, 0x70,
/* : 0x08 */
0x08, 0x04, 0x02, 0x7f, 0x02, 0x04, 0x08, 0x00,
/* : 0x09 */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a,
/* : 0x0a */
0x08, 0x08, 0x08, 0x08, 0x49, 0x2a, 0x1c, 0x08,
/* : 0x0b */
0x08, 0x1c, 0x2a, 0x49, 0x08, 0x08, 0x08, 0x08,
/* : 0x0c */
0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/* : 0x0d */
0x40, 0x40, 0x40, 0x44, 0x46, 0x7f, 0x06, 0x04,
/* : 0x0e */
0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
/* : 0x0f */
0x13, 0x18, 0x1c, 0x7e, 0x1c, 0x18, 0x10, 0x6f,
/* : 0x10 */
0x64, 0x0c, 0x1c, 0x3f, 0x1c, 0x0c, 0x04, 0x7b,
/* : 0x11 */
0x40, 0x48, 0x08, 0x7f, 0x3e, 0x1c, 0x48, 0x40,
/* : 0x12 */
0x40, 0x48, 0x1c, 0x3e, 0x7f, 0x08, 0x48, 0x40,
/* : 0x13 */
0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00,
/* : 0x14 */
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x7f,
/* : 0x15 */
0x08, 0x10, 0x20, 0x7f, 0x20, 0x10, 0x08, 0x00,
/* : 0x16 */
0x2a, 0x55, 0x2a, 0x55, 0x2a, 0x55, 0x2a, 0x55,
/* : 0x17 */
0x55, 0x2a, 0x55, 0x2a, 0x55, 0x2a, 0x55, 0x2a,
/* : 0x18 */
0x00, 0x3e, 0x41, 0x01, 0x01, 0x01, 0x7f, 0x00,
/* : 0x19 */
0x00, 0x00, 0x3f, 0x40, 0x40, 0x40, 0x7f, 0x00,
/* : 0x1a */
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
/* : 0x1b */
0x08, 0x1c, 0x3e, 0x7f, 0x3e, 0x1c, 0x08, 0x00,
/* : 0x1c */
0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f,
/* : 0x1d */
0x14, 0x14, 0x77, 0x00, 0x77, 0x14, 0x14, 0x00,
/* : 0x1e */
0x7f, 0x40, 0x40, 0x4c, 0x4c, 0x40, 0x40, 0x7f,
/* : 0x1f */
0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01
};
const unsigned char interface_glyphs[256] =
{
/* : 0x00 ----------------------- menu borders */
0x00, 0x00, 0x00, 0x00, 0x78, 0x08, 0x08, 0x08,
/* : 0x01 */
0x00, 0x00, 0x00, 0x00, 0x0f, 0x08, 0x08, 0x08,
/* : 0x02 */
0x08, 0x08, 0x08, 0x08, 0x78, 0x00, 0x00, 0x00,
/* : 0x03 */
0x08, 0x08, 0x08, 0x08, 0x0f, 0x00, 0x00, 0x00,
/* : 0x04 */
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
/* : 0x05 */
0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
/* : 0x06 */
0x08, 0x08, 0x08, 0x08, 0x78, 0x08, 0x08, 0x08,
/* : 0x07 */
0x08, 0x08, 0x08, 0x08, 0x0f, 0x08, 0x08, 0x08,
/* : 0x08 */
0x00, 0x00, 0x00, 0x00, 0x7f, 0x08, 0x08, 0x08,
/* : 0x09 */
0x08, 0x08, 0x08, 0x08, 0x7f, 0x00, 0x00, 0x00,
/* : 0x0A */
0x08, 0x08, 0x08, 0x08, 0x7f, 0x08, 0x08, 0x08,
/* : 0x0B ----------------------- disk icon quad */
0x00, 0x7e, 0x02, 0x02, 0x02, 0x42, 0x22, 0x22,
/* : 0x0C */
0x00, 0x7f, 0x40, 0x20, 0x40, 0x43, 0x44, 0x44,
/* : 0x0D */
0x22, 0x42, 0x02, 0x02, 0x02, 0x02, 0x7e, 0x00,
/* : 0x0E */
0x44, 0x43, 0x40, 0x41, 0x41, 0x41, 0x7e, 0x00,
/* : 0x0F ----------------------- unlock icon */
0x1c, 0x24, 0x04, 0x3e, 0x22, 0x22, 0x2a, 0x3e,
/* : 0x10 ----------------------- reverse return arrow */
0x01, 0x01, 0x01, 0x11, 0x31, 0x7f, 0x30, 0x10,
/* : 0x11 ----------------------- mini-spacebar visual */
0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x3e, 0x00,
/* : 0x12 ----------------------- glyph_joystick */
0x08, 0x08, 0x08, 0x77, 0x08, 0x08, 0x08, 0x08,
/* : 0x13 ----------------------- glyph_ctrl */
0x08, 0x1c, 0x3e, 0x63, 0x7b, 0x63, 0x7f, 0x00,
/* : 0x14 ----------------------- glyph_lowercase */
0x7f, 0x63, 0x5f, 0x43, 0x5d, 0x43, 0x7f, 0x00,
/* : 0x15 ----------------------- glyph_uppercase */
0x77, 0x6b, 0x5d, 0x41, 0x5d, 0x5d, 0x7f, 0x00,
/* : 0x16 ----------------------- glyph_showalt */
0x7f, 0x77, 0x77, 0x41, 0x77, 0x77, 0x7f, 0x00,
/* : 0x17 ----------------------- glyph_backspace */
0x00, 0x08, 0x04, 0x7e, 0x04, 0x08, 0x00, 0x00,
/* : 0x18 ----------------------- glyph_joystick_kpad */
0x08, 0x2a, 0x08, 0x77, 0x08, 0x2a, 0x08, 0x08,
/* : 0x19 ----------------------- glyph_leftspace */
0x00, 0x7e, 0x02, 0x42, 0x42, 0x42, 0x02, 0x7e,
/* : 0x1A ----------------------- glyph_midspace */
0x00, 0x7f, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x7f,
/* : 0x1B ----------------------- glyph_rightspace */
0x00, 0x3f, 0x20, 0x21, 0x21, 0x21, 0x20, 0x3f,
/* : 0x1C ----------------------- glyph_esc */
0x7f, 0x63, 0x5d, 0x41, 0x7d, 0x43, 0x7f, 0x00,
/* : 0x1D ----------------------- glyph_return left */
0x00, 0x00, 0x00, 0x10, 0x18, 0x7c, 0x18, 0x10,
/* : 0x1E ----------------------- glyph_return right */
0x20, 0x20, 0x20, 0x20, 0x20, 0x3f, 0x00, 0x00,
/* : 0x1F ----------------------- glyph_nonactionable */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

331
apple/nibutil.cpp Normal file
View File

@ -0,0 +1,331 @@
#include "nibutil.h"
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#endif
// Long gaps are more "correct" in the sense that they're
// nib-disk-like; but they mean the VM has to chew on a lot of disk
// gaps to find the real data, which takes a noticeable amount of
// time. With this off, we present a minimum number of gaps (that
// hopefully aren't too short for the ROM to be able to write
// correctly)
//#define LONGGAPS
#define DISK_VOLUME 254
// dos 3.3 to physical sector conversion
const static uint8_t dephys[16] = {
0x00, 0x07, 0x0e, 0x06, 0x0d, 0x05, 0x0c, 0x04,
0x0b, 0x03, 0x0a, 0x02, 0x09, 0x01, 0x08, 0x0f };
// Prodos to physical sector conversion
const uint8_t deProdosPhys[] = {
0x00, 0x08, 0x01, 0x09, 0x02, 0x0a, 0x03, 0x0b,
0x04, 0x0c, 0x05, 0x0d, 0x06, 0x0e, 0x07, 0x0f };
const static uint8_t _trans[64] = {0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff};
const static uint8_t _detrans[0x80] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x08, 0x0C, 0x00, 0x10, 0x14, 0x18,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20,
0x00, 0x00, 0x00, 0x24, 0x28, 0x2C, 0x30, 0x34,
0x00, 0x00, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C,
0x00, 0x50, 0x54, 0x58, 0x5C, 0x60, 0x64, 0x68,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x6C, 0x00, 0x70, 0x74, 0x78,
0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x80, 0x84,
0x00, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0xA0,
0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA8, 0xAC,
0x00, 0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8,
0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0,
0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC };
void nibblizeTrack(RingBuffer *trackBuffer, uint8_t *rawTrackBuffer,
uint8_t diskType, int8_t track)
{
int checksum;
for (uint8_t sector=0; sector<16; sector++) {
for (uint8_t i=0;
#ifdef LONGGAPS
i < (sector==0 ? 0x63 : 0x13);
#else
i < 8;
#endif
i++) {
trackBuffer->addByte(GAP);
}
trackBuffer->addByte(0xD5); // prolog
trackBuffer->addByte(0xAA);
trackBuffer->addByte(0x96);
trackBuffer->addByte(nib1(DISK_VOLUME));
trackBuffer->addByte(nib2(DISK_VOLUME));
trackBuffer->addByte(nib1(track));
trackBuffer->addByte(nib2(track));
trackBuffer->addByte(nib1(sector));
trackBuffer->addByte(nib2(sector));
checksum = DISK_VOLUME ^ track ^ sector;
trackBuffer->addByte(nib1(checksum));
trackBuffer->addByte(nib2(checksum));
trackBuffer->addByte(0xDE); // epilog
trackBuffer->addByte(0xAA);
trackBuffer->addByte(0xEB); // Not strictly necessary, but the DiskII controller does it, so we will too.
// The DiskII controller puts out 5 GAP bytes here.
for (uint8_t i=0; i<5; i++) {
trackBuffer->addByte(GAP);
}
trackBuffer->addByte(0xD5); // data prolog
trackBuffer->addByte(0xAA);
trackBuffer->addByte(0xAD);
uint8_t physicalSector = (diskType == prodosDisk ? deProdosPhys[sector] : dephys[sector]);
encodeData(trackBuffer, &rawTrackBuffer[physicalSector * 256]);
trackBuffer->addByte(0xDE); // data epilog
trackBuffer->addByte(0xAA);
trackBuffer->addByte(0xEB);
// trackBuffer->addByte(GAP);
}
}
#define SIXBIT_SPAN 0x56 // 86 bytes
// Pop the next 343 bytes off of trackBuffer, which should be 342
// 6:2-bit GCR encoded values, which we decode back in to 256 8-byte
// output values; and one checksum byte.
//
// Return true if we've successfully consumed 343 bytes from
// trackBuf. This reads from the circular buffer trackBuffer, so if
// there's not enough data there, the results are somewhat
// unpredictable.
bool decodeData(RingBuffer *trackBuffer, uint16_t startAt, uint8_t *output)
{
// Basic check that there's enough buffer data in trackBuffer. Note
// that we're not checking it against startAt; we could be wrapping
// around.
if (trackBuffer->count() < 343)
return false;
static uint8_t workbuf[342];
for (int i=0; i<342; i++) {
uint8_t in = trackBuffer->peek(startAt++) & 0x7F; // strip high bit
workbuf[i] = _detrans[in];
}
// fixme: collapse this in to the previous loop
uint8_t prev = 0;
for (int i=0; i<342; i++) {
workbuf[i] = prev ^ workbuf[i];
prev = workbuf[i];
}
// Put the checksum on the track - only necessary if we're about to
// write the nibblized version of the track back out
/* uint16_t cursor = trackBuffer->Cursor();
trackBuffer->setPeekCursor(startAt++);
trackBuffer->replaceByte(prev); // 'prev' holds the checksum
trackBuffer->setPeekCursor(cursor); // put it back where we found it
*/
// Start with all of the bytes with 6 bits of data
for (uint16_t i=0; i<256; i++) {
output[i] = workbuf[SIXBIT_SPAN + i] & 0xFC; // 6 bits
}
// Then pull in all of the 2-bit values, which are stuffed 3 to a byte. That gives us
// 4 bits more than we need - the last two skip two of the bits.
for (uint8_t i=0; i<SIXBIT_SPAN; i++) {
// This byte (workbuf[i]) has 2 bits for each of 3 output bytes:
// i, SIXBIT_SPAN+i, and 2*SIXBIT_SPAN+i
uint8_t thisbyte = workbuf[i];
output[ i] |= ((thisbyte & 0x08) >> 3) | ((thisbyte & 0x04) >> 1);
output[ SIXBIT_SPAN + i] |= ((thisbyte & 0x20) >> 5) | ((thisbyte & 0x10) >> 3);
if (i < SIXBIT_SPAN-2) {
output[2*SIXBIT_SPAN + i] |= ((thisbyte & 0x80) >> 7) | ((thisbyte & 0x40) >> 5);
}
}
// FIXME: check or update the checksum?
return true;
}
void encodeData(RingBuffer *trackBuffer, uint8_t *data)
{
int16_t i;
int ptr2 = 0;
int ptr6 = 0x56;
static int nibbles[0x156];
for (i=0; i<0x156; i++) {
nibbles[i] = 0;
}
int idx2 = 0x55;
for (int idx6 = 0x101; idx6 >= 0; idx6--) {
int val6 = data[idx6 & 0xFF];
int val2 = nibbles[ptr2 + idx2];
val2 = (val2 << 1) | (val6 & 1);
val6 >>= 1;
val2 = (val2 << 1) | (val6 & 1);
val6 >>= 1;
// There are 2 "extra" bytes of 2-bit data that we ignore here.
if (ptr6 + idx6 < 0x156) {
nibbles[ptr6 + idx6] = val6;
}
if (ptr2 + idx2 < 0x156) {
nibbles[ptr2 + idx2] = val2;
}
if (--idx2 < 0) {
idx2 = 0x55;
}
}
int lastv = 0;
for (int idx = 0; idx < 0x156; idx++) {
int val = nibbles[idx];
trackBuffer->addByte(_trans[lastv ^ val]);
lastv = val;
}
trackBuffer->addByte(_trans[lastv]);
}
nibErr denibblizeTrack(RingBuffer *trackBuffer, uint8_t *rawTrackBuffer,
uint8_t diskType, int8_t track)
{
// We can't tell exactly what the length should be, b/c there might
// be varying numbers of GAP bytes. But we can tell, generally, that
// this is the minimum acceptable length that might hold all the
// track data.
if (trackBuffer->count() < 16*MINNIBSECTORSIZE) {
return errorShortTrack;
}
// bitmask of the sectors that we've found while decoding. We should
// find all 16.
uint16_t sectorsUpdated = 0;
// loop through the data twice, so we make sure we read anything
// that crosses the end/start boundary
// FIXME: if this approach works, we probably want 1/16th extra, not 2*
for (uint16_t i=0; i<2*trackBuffer->count(); ) {
// Find the prolog
while (trackBuffer->peek(i++) != 0xD5)
;
if (trackBuffer->peek(i++) != 0xAA) {
continue;
}
if (trackBuffer->peek(i++) != 0x96) {
continue;
}
// And now we should be in the header section
uint8_t volumeID = denib(trackBuffer->peek(i),
trackBuffer->peek(i+1));
i += 2;
uint8_t trackID = denib(trackBuffer->peek(i),
trackBuffer->peek(i+1));
i += 2;
uint8_t sectorNum = denib(trackBuffer->peek(i),
trackBuffer->peek(i+1));
i += 2;
uint8_t headerChecksum = denib(trackBuffer->peek(i),
trackBuffer->peek(i+1));
i += 2;
if (headerChecksum != (volumeID ^ trackID ^ sectorNum)) {
continue;
}
// check for the epilog
if (trackBuffer->peek(i++) != 0xDE) {
continue;
}
if (trackBuffer->peek(i++) != 0xAA) {
continue;
}
// Skip to the data prolog
while (trackBuffer->peek(i++) != 0xD5)
;
if (trackBuffer->peek(i++) != 0xAA) {
continue;
}
if (trackBuffer->peek(i++) != 0xAD) {
continue;
}
// Decode the data in to a temporary buffer: we don't want to overwrite
// something valid with partial data
uint8_t output[256];
if (!decodeData(trackBuffer, i, output)) {
continue;
}
i += 343;
// Check the data epilog
if (trackBuffer->peek(i++) != 0xDE) {
continue;
}
if (trackBuffer->peek(i++) != 0xAA) {
continue;
}
if (trackBuffer->peek(i++) != 0xEB) {
continue;
}
// We've got a whole block! Put it in the rawTrackBuffer and mark
// the bit for it in sectorsUpdated.
// FIXME: if trackID != curTrack, that's an error?
uint8_t targetSector;
if (diskType == prodosDisk) {
targetSector = deProdosPhys[sectorNum];
} else {
targetSector = dephys[sectorNum];
}
memcpy(&rawTrackBuffer[targetSector * 256],
output,
256);
sectorsUpdated |= (1 << sectorNum);
}
// Check that we found all of the sectors for this track
if (sectorsUpdated != 0xFFFF) {
return errorMissingSectors;
}
return errorNone;
}

44
apple/nibutil.h Normal file
View File

@ -0,0 +1,44 @@
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#endif
#include "ringbuffer.h"
#define NIBTRACKSIZE 0x1A00
// Minimum viable nibblized sector size. With GAP bytes, could be much longer.
#define MINNIBSECTORSIZE (343 + 13 + 3)
#define nib1(a) (((a & 0xAA) >> 1) | 0xAA)
#define nib2(b) (((b & 0x55) ) | 0xAA)
#define denib(a, b) ((((a) & ~0xAA) << 1) | ((b) & ~0xAA))
#define GAP 0xFF
enum {
dosDisk = 0,
prodosDisk = 1,
nibDisk = 2
};
enum nibErr {
errorNone = 0,
errorShortTrack = 1,
errorMissingSectors = 2
};
void nibblizeTrack(RingBuffer *trackBuffer, uint8_t *rawTrackBuffer,
uint8_t diskType, int8_t track);
nibErr denibblizeTrack(RingBuffer *trackBuffer, uint8_t *rawTrackBuffer,
uint8_t diskType, int8_t track);
bool decodeData(RingBuffer *trackBuffer, uint16_t startAt, uint8_t *output);
void encodeData(RingBuffer *trackBuffer, uint8_t *data);

9
apple/slot.cpp Normal file
View File

@ -0,0 +1,9 @@
#include "slot.h"
Slot::Slot()
{
}
Slot::~Slot()
{
}

25
apple/slot.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef __SLOT_H
#define __SLOT_H
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <stdint.h>
#include <stdio.h>
#endif
class Slot {
public:
Slot();
~Slot();
virtual void Reset() = 0; // for use at cold-boot
virtual uint8_t readSwitches(uint8_t s) = 0;
virtual void writeSwitches(uint8_t s, uint8_t v) = 0;
virtual void loadROM(uint8_t *toWhere) = 0;
};
#endif

107
bios-font.h Normal file
View File

@ -0,0 +1,107 @@
// SmallFont.c from UTFT library
// Font Size : 8x12
// Memory usage : 1144 bytes
// # characters : 95
#ifndef TEENSYDUINO
#define PROGMEM
#endif
// xsize: 8; ysize: 0x0C; offset: 0x20; numchars: 0x5F
uint8_t BiosFont[1140] PROGMEM={
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // <Space>
0x00,0x00,0x20,0x20,0x20,0x20,0x20,0x20,0x00,0x20,0x00,0x00, // !
0x00,0x28,0x50,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // "
0x00,0x00,0x28,0x28,0xFC,0x28,0x50,0xFC,0x50,0x50,0x00,0x00, // #
0x00,0x20,0x78,0xA8,0xA0,0x60,0x30,0x28,0xA8,0xF0,0x20,0x00, // $
0x00,0x00,0x48,0xA8,0xB0,0x50,0x28,0x34,0x54,0x48,0x00,0x00, // %
0x00,0x00,0x20,0x50,0x50,0x78,0xA8,0xA8,0x90,0x6C,0x00,0x00, // &
0x00,0x40,0x40,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // '
0x00,0x04,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x08,0x04,0x00, // (
0x00,0x40,0x20,0x10,0x10,0x10,0x10,0x10,0x10,0x20,0x40,0x00, // )
0x00,0x00,0x00,0x20,0xA8,0x70,0x70,0xA8,0x20,0x00,0x00,0x00, // *
0x00,0x00,0x20,0x20,0x20,0xF8,0x20,0x20,0x20,0x00,0x00,0x00, // +
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x40,0x80, // ,
0x00,0x00,0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00, // -
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x40,0x00,0x00, // .
0x00,0x08,0x10,0x10,0x10,0x20,0x20,0x40,0x40,0x40,0x80,0x00, // /
0x00,0x00,0x70,0x88,0x88,0x88,0x88,0x88,0x88,0x70,0x00,0x00, // 0
0x00,0x00,0x20,0x60,0x20,0x20,0x20,0x20,0x20,0x70,0x00,0x00, // 1
0x00,0x00,0x70,0x88,0x88,0x10,0x20,0x40,0x80,0xF8,0x00,0x00, // 2
0x00,0x00,0x70,0x88,0x08,0x30,0x08,0x08,0x88,0x70,0x00,0x00, // 3
0x00,0x00,0x10,0x30,0x50,0x50,0x90,0x78,0x10,0x18,0x00,0x00, // 4
0x00,0x00,0xF8,0x80,0x80,0xF0,0x08,0x08,0x88,0x70,0x00,0x00, // 5
0x00,0x00,0x70,0x90,0x80,0xF0,0x88,0x88,0x88,0x70,0x00,0x00, // 6
0x00,0x00,0xF8,0x90,0x10,0x20,0x20,0x20,0x20,0x20,0x00,0x00, // 7
0x00,0x00,0x70,0x88,0x88,0x70,0x88,0x88,0x88,0x70,0x00,0x00, // 8
0x00,0x00,0x70,0x88,0x88,0x88,0x78,0x08,0x48,0x70,0x00,0x00, // 9
0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x00,0x20,0x00,0x00, // :
0x00,0x00,0x00,0x00,0x00,0x20,0x00,0x00,0x00,0x20,0x20,0x00, // ;
0x00,0x04,0x08,0x10,0x20,0x40,0x20,0x10,0x08,0x04,0x00,0x00, // <
0x00,0x00,0x00,0x00,0xF8,0x00,0x00,0xF8,0x00,0x00,0x00,0x00, // =
0x00,0x40,0x20,0x10,0x08,0x04,0x08,0x10,0x20,0x40,0x00,0x00, // >
0x00,0x00,0x70,0x88,0x88,0x10,0x20,0x20,0x00,0x20,0x00,0x00, // ?
0x00,0x00,0x70,0x88,0x98,0xA8,0xA8,0xB8,0x80,0x78,0x00,0x00, // @
0x00,0x00,0x20,0x20,0x30,0x50,0x50,0x78,0x48,0xCC,0x00,0x00, // A
0x00,0x00,0xF0,0x48,0x48,0x70,0x48,0x48,0x48,0xF0,0x00,0x00, // B
0x00,0x00,0x78,0x88,0x80,0x80,0x80,0x80,0x88,0x70,0x00,0x00, // C
0x00,0x00,0xF0,0x48,0x48,0x48,0x48,0x48,0x48,0xF0,0x00,0x00, // D
0x00,0x00,0xF8,0x48,0x50,0x70,0x50,0x40,0x48,0xF8,0x00,0x00, // E
0x00,0x00,0xF8,0x48,0x50,0x70,0x50,0x40,0x40,0xE0,0x00,0x00, // F
0x00,0x00,0x38,0x48,0x80,0x80,0x9C,0x88,0x48,0x30,0x00,0x00, // G
0x00,0x00,0xCC,0x48,0x48,0x78,0x48,0x48,0x48,0xCC,0x00,0x00, // H
0x00,0x00,0xF8,0x20,0x20,0x20,0x20,0x20,0x20,0xF8,0x00,0x00, // I
0x00,0x00,0x7C,0x10,0x10,0x10,0x10,0x10,0x10,0x90,0xE0,0x00, // J
0x00,0x00,0xEC,0x48,0x50,0x60,0x50,0x50,0x48,0xEC,0x00,0x00, // K
0x00,0x00,0xE0,0x40,0x40,0x40,0x40,0x40,0x44,0xFC,0x00,0x00, // L
0x00,0x00,0xD8,0xD8,0xD8,0xD8,0xA8,0xA8,0xA8,0xA8,0x00,0x00, // M
0x00,0x00,0xDC,0x48,0x68,0x68,0x58,0x58,0x48,0xE8,0x00,0x00, // N
0x00,0x00,0x70,0x88,0x88,0x88,0x88,0x88,0x88,0x70,0x00,0x00, // O
0x00,0x00,0xF0,0x48,0x48,0x70,0x40,0x40,0x40,0xE0,0x00,0x00, // P
0x00,0x00,0x70,0x88,0x88,0x88,0x88,0xE8,0x98,0x70,0x18,0x00, // Q
0x00,0x00,0xF0,0x48,0x48,0x70,0x50,0x48,0x48,0xEC,0x00,0x00, // R
0x00,0x00,0x78,0x88,0x80,0x60,0x10,0x08,0x88,0xF0,0x00,0x00, // S
0x00,0x00,0xF8,0xA8,0x20,0x20,0x20,0x20,0x20,0x70,0x00,0x00, // T
0x00,0x00,0xCC,0x48,0x48,0x48,0x48,0x48,0x48,0x30,0x00,0x00, // U
0x00,0x00,0xCC,0x48,0x48,0x50,0x50,0x30,0x20,0x20,0x00,0x00, // V
0x00,0x00,0xA8,0xA8,0xA8,0x70,0x50,0x50,0x50,0x50,0x00,0x00, // W
0x00,0x00,0xD8,0x50,0x50,0x20,0x20,0x50,0x50,0xD8,0x00,0x00, // X
0x00,0x00,0xD8,0x50,0x50,0x20,0x20,0x20,0x20,0x70,0x00,0x00, // Y
0x00,0x00,0xF8,0x90,0x10,0x20,0x20,0x40,0x48,0xF8,0x00,0x00, // Z
0x00,0x38,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x38,0x00, // [
0x00,0x40,0x40,0x40,0x20,0x20,0x10,0x10,0x10,0x08,0x00,0x00, // <Backslash>
0x00,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x70,0x00, // ]
0x00,0x20,0x50,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ^
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC, // _
0x00,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // '
0x00,0x00,0x00,0x00,0x00,0x30,0x48,0x38,0x48,0x3C,0x00,0x00, // a
0x00,0x00,0xC0,0x40,0x40,0x70,0x48,0x48,0x48,0x70,0x00,0x00, // b
0x00,0x00,0x00,0x00,0x00,0x38,0x48,0x40,0x40,0x38,0x00,0x00, // c
0x00,0x00,0x18,0x08,0x08,0x38,0x48,0x48,0x48,0x3C,0x00,0x00, // d
0x00,0x00,0x00,0x00,0x00,0x30,0x48,0x78,0x40,0x38,0x00,0x00, // e
0x00,0x00,0x1C,0x20,0x20,0x78,0x20,0x20,0x20,0x78,0x00,0x00, // f
0x00,0x00,0x00,0x00,0x00,0x3C,0x48,0x30,0x40,0x78,0x44,0x38, // g
0x00,0x00,0xC0,0x40,0x40,0x70,0x48,0x48,0x48,0xEC,0x00,0x00, // h
0x00,0x00,0x20,0x00,0x00,0x60,0x20,0x20,0x20,0x70,0x00,0x00, // i
0x00,0x00,0x10,0x00,0x00,0x30,0x10,0x10,0x10,0x10,0x10,0xE0, // j
0x00,0x00,0xC0,0x40,0x40,0x5C,0x50,0x70,0x48,0xEC,0x00,0x00, // k
0x00,0x00,0xE0,0x20,0x20,0x20,0x20,0x20,0x20,0xF8,0x00,0x00, // l
0x00,0x00,0x00,0x00,0x00,0xF0,0xA8,0xA8,0xA8,0xA8,0x00,0x00, // m
0x00,0x00,0x00,0x00,0x00,0xF0,0x48,0x48,0x48,0xEC,0x00,0x00, // n
0x00,0x00,0x00,0x00,0x00,0x30,0x48,0x48,0x48,0x30,0x00,0x00, // o
0x00,0x00,0x00,0x00,0x00,0xF0,0x48,0x48,0x48,0x70,0x40,0xE0, // p
0x00,0x00,0x00,0x00,0x00,0x38,0x48,0x48,0x48,0x38,0x08,0x1C, // q
0x00,0x00,0x00,0x00,0x00,0xD8,0x60,0x40,0x40,0xE0,0x00,0x00, // r
0x00,0x00,0x00,0x00,0x00,0x78,0x40,0x30,0x08,0x78,0x00,0x00, // s
0x00,0x00,0x00,0x20,0x20,0x70,0x20,0x20,0x20,0x18,0x00,0x00, // t
0x00,0x00,0x00,0x00,0x00,0xD8,0x48,0x48,0x48,0x3C,0x00,0x00, // u
0x00,0x00,0x00,0x00,0x00,0xEC,0x48,0x50,0x30,0x20,0x00,0x00, // v
0x00,0x00,0x00,0x00,0x00,0xA8,0xA8,0x70,0x50,0x50,0x00,0x00, // w
0x00,0x00,0x00,0x00,0x00,0xD8,0x50,0x20,0x50,0xD8,0x00,0x00, // x
0x00,0x00,0x00,0x00,0x00,0xEC,0x48,0x50,0x30,0x20,0x20,0xC0, // y
0x00,0x00,0x00,0x00,0x00,0x78,0x10,0x20,0x20,0x78,0x00,0x00, // z
0x00,0x18,0x10,0x10,0x10,0x20,0x10,0x10,0x10,0x10,0x18,0x00, // {
0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10, // |
0x00,0x60,0x20,0x20,0x20,0x10,0x20,0x20,0x20,0x20,0x60,0x00, // }
0x40,0xA4,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // ~
};

29524
display-bg.h Normal file

File diff suppressed because it is too large Load Diff

29
filemanager.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef __FILEMANAGER_H
#define __FILEMANAGER_H
#include <stdint.h>
#define MAXFILES 4 // how many results we can simultaneously manage
#define DIRPAGESIZE 10 // how many results in one readDir
#ifndef MAXPATH
#define MAXPATH 255
#endif
class FileManager {
public:
virtual ~FileManager() {};
virtual int8_t openFile(const char *name) = 0;
virtual void closeFile(int8_t fd) = 0;
virtual const char *fileName(int8_t fd) = 0;
virtual int8_t readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx) = 0;
virtual void seekBlock(int8_t fd, uint16_t block, bool isNib = false) = 0;
virtual bool readTrack(int8_t fd, uint8_t *toWhere, bool isNib = false) = 0;
virtual bool readBlock(int8_t fd, uint8_t *toWhere, bool isNib = false) = 0;
virtual bool writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib = false) = 0;
virtual bool writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib = false) = 0;
};
#endif

11
globals.cpp Normal file
View File

@ -0,0 +1,11 @@
#include "globals.h"
FileManager *g_filemanager = NULL;
Cpu *g_cpu = NULL;
VM *g_vm = NULL;
PhysicalDisplay *g_display = NULL;
PhysicalKeyboard *g_keyboard = NULL;
PhysicalSpeaker *g_speaker = NULL;
PhysicalPaddles *g_paddles = NULL;
int16_t g_volume;
uint8_t g_displayType = 3; // FIXME m_perfectcolor

19
globals.h Normal file
View File

@ -0,0 +1,19 @@
#include <stdint.h>
#include "filemanager.h"
#include "cpu.h"
#include "vm.h"
#include "physicaldisplay.h"
#include "physicalkeyboard.h"
#include "physicalspeaker.h"
#include "physicalpaddles.h"
extern FileManager *g_filemanager;
extern Cpu *g_cpu;
extern VM *g_vm;
extern PhysicalDisplay *g_display;
extern PhysicalKeyboard *g_keyboard;
extern PhysicalSpeaker *g_speaker;
extern PhysicalPaddles *g_paddles;
extern int16_t g_volume;
extern uint8_t g_displayType;

411
opencv/aiie.cpp Normal file
View File

@ -0,0 +1,411 @@
#include <stdio.h>
#include <unistd.h>
#include <curses.h>
#include <termios.h>
#include <pthread.h>
#include <mach/mach_time.h>
// Derived from http://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x
#include "applevm.h"
#include "opencv-display.h"
#include "opencv-keyboard.h"
#include "dummy-speaker.h"
#include "opencv-paddles.h"
#include "opencv-filemanager.h"
#include "globals.h"
//#define SHOWFPS
//#define SHOWPC
//#define DEBUGCPU
//#define SHOWMEMPAGE
#define ORWL_NANO (+1.0E-9)
#define ORWL_GIGA UINT64_C(1000000000)
#define NANOSECONDS_PER_SECOND 1000000000UL
#define CYCLES_PER_SECOND 1023000UL
#define NANOSECONDS_PER_CYCLE (NANOSECONDS_PER_SECOND / CYCLES_PER_SECOND)
struct timespec nextInstructionTime, startTime;
uint64_t hitcount = 0;
uint64_t misscount = 0;
static double orwl_timebase = 0.0;
static uint64_t orwl_timestart = 0;
static void _init_darwin_shim(void) {
mach_timebase_info_data_t tb = { 0 };
mach_timebase_info(&tb);
orwl_timebase = tb.numer;
orwl_timebase /= tb.denom;
orwl_timestart = mach_absolute_time();
}
int do_gettime(struct timespec *tp) {
double diff = (mach_absolute_time() - orwl_timestart) * orwl_timebase;
tp->tv_sec = diff * ORWL_NANO;
tp->tv_nsec = diff - (tp->tv_sec * ORWL_GIGA);
return 0;
}
#define NB_ENABLE 1
#define NB_DISABLE 0
int send_rst = 0;
pthread_t cpuThreadID;
void sigint_handler(int n)
{
send_rst = 1;
}
void nonblock(int state)
{
struct termios ttystate;
//get the terminal state
tcgetattr(STDIN_FILENO, &ttystate);
if (state==NB_ENABLE)
{
//turn off canonical mode
ttystate.c_lflag &= ~ICANON;
//minimum of number input read.
ttystate.c_cc[VMIN] = 1;
}
else if (state==NB_DISABLE)
{
//turn on canonical mode
ttystate.c_lflag |= ICANON;
}
//set the terminal attributes.
tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);
}
uint8_t read(void *arg, uint16_t address)
{
// no action; this is a dummy function until we've finished initializing...
return 0x00;
}
void write(void *arg, uint16_t address, uint8_t v)
{
// no action; this is a dummy function until we've finished initializing...
}
// adds the number of microseconds that 'cycles' takes to *start and
// returns it in *out
void timespec_add_cycles(struct timespec *start,
uint32_t cycles,
struct timespec *out)
{
out->tv_sec = start->tv_sec;
out->tv_nsec = start->tv_nsec;
uint64_t nanosToAdd = NANOSECONDS_PER_CYCLE * cycles;
out->tv_sec += (nanosToAdd / NANOSECONDS_PER_SECOND);
out->tv_nsec += (nanosToAdd % NANOSECONDS_PER_SECOND);
if (out->tv_nsec >= 1000000000L) {
out->tv_sec++ ;
out->tv_nsec -= 1000000000L;
}
}
void timespec_diff(struct timespec *start,
struct timespec *end,
struct timespec *diff,
bool *negative) {
struct timespec t;
if (negative)
{
*negative = false;
}
// if start > end, swizzle...
if ( (start->tv_sec > end->tv_sec) || ((start->tv_sec == end->tv_sec) && (start->tv_nsec > end->tv_nsec)) )
{
t=*start;
*start=*end;
*end=t;
if (negative)
{
*negative = true;
}
}
// assuming time_t is signed ...
if (end->tv_nsec < start->tv_nsec)
{
t.tv_sec = end->tv_sec - start->tv_sec - 1;
t.tv_nsec = 1000000000 + end->tv_nsec - start->tv_nsec;
}
else
{
t.tv_sec = end->tv_sec - start->tv_sec;
t.tv_nsec = end->tv_nsec - start->tv_nsec;
}
diff->tv_sec = t.tv_sec;
diff->tv_nsec = t.tv_nsec;
}
// tsCompare: return -1, 0, 1 for (a < b), (a == b), (a > b)
int8_t tsCompare(struct timespec *A, struct timespec *B)
{
if (A->tv_sec < B->tv_sec)
return -1;
if (A->tv_sec > B->tv_sec)
return 1;
if (A->tv_nsec < B->tv_nsec)
return -1;
if (A->tv_nsec > B->tv_nsec)
return 1;
return 0;
}
struct timespec tsSubtract(struct timespec time1, struct timespec time2)
{
struct timespec result;
if ((time1.tv_sec < time2.tv_sec) ||
((time1.tv_sec == time2.tv_sec) &&
(time1.tv_nsec <= time2.tv_nsec))) {/* TIME1 <= TIME2? */
result.tv_sec = result.tv_nsec = 0 ;
} else {/* TIME1 > TIME2 */
result.tv_sec = time1.tv_sec - time2.tv_sec ;
if (time1.tv_nsec < time2.tv_nsec) {
result.tv_nsec = time1.tv_nsec + 1000000000L - time2.tv_nsec ;
result.tv_sec-- ;/* Borrow a second. */
} else {
result.tv_nsec = time1.tv_nsec - time2.tv_nsec ;
}
}
return (result) ;
}
static void *cpu_thread(void *dummyptr) {
struct timespec currentTime;
#if 0
int policy;
struct sched_param param;
pthread_getschedparam(pthread_self(), &policy, &param);
param.sched_priority = sched_get_priority_max(policy);
pthread_setschedparam(pthread_self(), policy, &param);
#endif
_init_darwin_shim();
do_gettime(&startTime);
do_gettime(&nextInstructionTime);
printf("free-running\n");
while (1) {
// cycle down the CPU...
do_gettime(&currentTime);
struct timespec diff = tsSubtract(nextInstructionTime, currentTime);
if (diff.tv_sec >= 0 && diff.tv_nsec >= 0) {
hitcount++;
nanosleep(&diff, NULL);
} else {
misscount++;
}
#ifdef DEBUGCPU
uint8_t executed = g_cpu->Run(1);
#else
uint8_t executed = g_cpu->Run(24);
#endif
timespec_add_cycles(&startTime, g_cpu->cycles + executed, &nextInstructionTime);
// The paddles need to be triggered in real-time on the CPU
// clock. That happens from the VM's CPU maintenance poller.
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
#ifdef DEBUGCPU
{
uint8_t p = g_cpu->flags;
printf("OP: $%02x A: %02x X: %02x Y: %02x PC: $%04x SP: %02x Flags: %c%cx%c%c%c%c%c\n",
g_vm->getMMU()->read(g_cpu->pc),
g_cpu->a, g_cpu->x, g_cpu->y, g_cpu->pc, g_cpu->sp,
p & (1<<7) ? 'N':' ',
p & (1<<6) ? 'V':' ',
p & (1<<4) ? 'B':' ',
p & (1<<3) ? 'D':' ',
p & (1<<2) ? 'I':' ',
p & (1<<1) ? 'Z':' ',
p & (1<<0) ? 'C':' '
);
}
#endif
if (send_rst) {
#if 0
printf("Sending reset\n");
// g_cpu->Reset();
// testing startup keyboard presses - perform Apple //e self-test
g_vm->getKeyboard()->keyDepressed(RA);
g_vm->Reset();
g_cpu->Reset();
//((AppleVM *)g_vm)->insertDisk(0, "disks/DIAGS.DSK");
#else
MMU *mmu = g_vm->getMMU();
printf("PC: 0x%X\n", g_cpu->pc);
for (int i=g_cpu->pc; i<g_cpu->pc + 0x100; i++) {
printf("0x%X ", mmu->read(i));
}
printf("\n");
printf("Dropping to monitor\n");
// drop directly to monitor.
g_cpu->pc = 0xff69; // "call -151"
mmu->read(0xC054); // make sure we're in page 1
mmu->read(0xC056); // and that hires is off
mmu->read(0xC051); // and text mode is on
mmu->read(0xC08A); // and we have proper rom in place
mmu->read(0xc008); // main zero-page
mmu->read(0xc006); // rom from cards
mmu->write(0xc002 + mmu->read(0xc014)? 1 : 0, 0xff); // make sure aux ram read and write match
mmu->write(0x20, 0); // text window
mmu->write(0x21, 40);
mmu->write(0x22, 0);
mmu->write(0x23, 24);
mmu->write(0x33, '>');
mmu->write(0x48, 0); // from 0xfb2f: part of text init
#endif
send_rst = 0;
}
}
}
int main(int argc, char *argv[])
{
g_speaker = new DummySpeaker();
// create the filemanager - the interface to the host file system.
g_filemanager = new OpenCVFileManager();
// Construct the interface to the host display. This will need the
// VM's video buffer in order to draw the VM, but we don't have that
// yet. (The OpenCV display looks it up dynamically every blit() call, which
// we'll probably change as we get the Teensy version working.)
g_display = new OpenCVDisplay();
// paddles have to be created after g_display created the window
g_paddles = new OpenCVPaddles();
// Next create the virtual CPU. This needs the VM's MMU in order to run, but we don't have that yet.
g_cpu = new Cpu();
// Create the virtual machine. This may read from g_filemanager to get ROMs if necessary.
// (The actual Apple VM we've built has them compiled in, though.) It will create its virutal
// hardware (MMU, video driver, floppy, paddles, whatever).
g_vm = new AppleVM();
g_keyboard = new OpenCVKeyboard(g_vm->getKeyboard());
// Now that the VM exists and it has created an MMU, we tell the CPU how to access memory through the MMU.
g_cpu->SetMMU(g_vm->getMMU());
// Now that all the virtual hardware is glued together, reset the VM
g_vm->Reset();
g_cpu->rst();
g_display->blit();
g_display->redraw();
if (argc >= 2) {
printf("Inserting disk %s\n", argv[1]);
((AppleVM *)g_vm)->insertDisk(0, argv[1]);
}
if (argc == 3) {
printf("Inserting disk %s\n", argv[2]);
((AppleVM *)g_vm)->insertDisk(1, argv[2]);
}
nonblock(NB_ENABLE);
signal(SIGINT, sigint_handler);
printf("creating CPU thread\n");
if (!pthread_create(&cpuThreadID, NULL, &cpu_thread, (void *)NULL)) {
printf("thread created\n");
// pthread_setschedparam(cpuThreadID, SCHED_RR, PTHREAD_MAX_PRIORITY);
}
while (1) {
static uint8_t ctr = 0;
if (++ctr == 0) {
printf("hit: %llu; miss: %llu; pct: %f\n", hitcount, misscount, (double)misscount / (double)(misscount + hitcount));
}
// Make this a little friendlier, and the expense of some framerate?
// usleep(10000);
if (g_vm->vmdisplay->needsRedraw()) {
// make sure to clear the flag before drawing; there's no lock
// on didRedraw, so the other thread might update it
g_vm->vmdisplay->didRedraw();
g_display->blit();
}
g_keyboard->maintainKeyboard();
g_display->drawBatteryStatus(100);
#ifdef SHOWFPS
static time_t startAt = time(NULL);
static uint32_t loopCount = 0;
loopCount++;
time_t lenSecs = time(NULL) - startAt;
if (lenSecs >= 10) {
char buf[25];
sprintf(buf, "%lu FPS", loopCount / lenSecs);
g_display->debugMsg(buf);
if (lenSecs >= 60) {
startAt = time(NULL);
loopCount = 0;
}
}
#endif
#ifdef SHOWPC
{
char buf[25];
sprintf(buf, "%X", g_cpu->pc);
g_display->debugMsg(buf);
}
#endif
#ifdef SHOWMEMPAGE
{
char buf[40];
sprintf(buf, "AUX %c/%c BNK %d BSR %c/%c ZP %c 80 %c INT %c",
g_vm->auxRamRead?'R':'_',
g_vm->auxRamWrite?'W':'_',
g_vm->bank1,
g_vm->readbsr ? 'R':'_',
g_vm->writebsr ? 'W':'_',
g_vm->altzp ? 'Y':'_',
g_vm->_80store ? 'Y' : '_',
g_vm->intcxrom ? 'Y' : '_');
g_display->debugMsg(buf);
}
#endif
}
}

13
opencv/dummy-speaker.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "dummy-speaker.h"
DummySpeaker::~DummySpeaker()
{
}
void DummySpeaker::toggleAtCycle(uint32_t c)
{
}
void DummySpeaker::maintainSpeaker(uint32_t c)
{
}

15
opencv/dummy-speaker.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef __DUMMYSPEAKER_H
#define __DUMMYSPEAKER_H
#include <stdint.h>
#include "physicalspeaker.h"
class DummySpeaker : public PhysicalSpeaker {
public:
virtual ~DummySpeaker();
virtual void toggleAtCycle(uint32_t c);
virtual void maintainSpeaker(uint32_t c);
};
#endif

270
opencv/opencv-display.cpp Normal file
View File

@ -0,0 +1,270 @@
#include <ctype.h> // isgraph
#include "opencv-display.h"
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/features2d/features2d.hpp"
using namespace cv;
using namespace std;
#define WINDOWNAME "6502core"
#include "bios-font.h"
#include "display-bg.h"
#include "globals.h"
#include "applevm.h"
// RGB map of each of the lowres colors
const uint8_t loresPixelColors[16][3] = { { 0, 0, 0 }, // black
{ 195, 0, 48 }, // magenta
{ 0, 0, 130 }, // dark blue
{ 166, 52, 170 }, // purple
{ 0, 146, 0 }, // dark green
{ 105, 105, 105 }, // drak grey
{ 24, 113, 255 }, // medium blue
{ 12, 190, 235 }, // light blue
{ 150, 85, 40 }, // brown
{ 255, 24, 44 }, // orange
{ 150, 170, 170 }, // light gray
{ 255, 158, 150 }, // pink
{ 0, 255, 0 }, // green
{ 255, 255, 0 }, // yellow
{ 130, 255, 130 }, // aqua
{ 255, 255, 255 } // white
};
OpenCVDisplay::OpenCVDisplay()
{
pixels = new Mat(240*2, 320*2, CV_8UC3);
namedWindow(WINDOWNAME, CV_WINDOW_AUTOSIZE);
}
OpenCVDisplay::~OpenCVDisplay()
{
delete pixels; pixels = NULL;
}
void OpenCVDisplay::redraw()
{
// primarily for the device, where it's in and out of the
// bios. Draws the background image.
for (int y=0; y<240; y++) {
for (int x=0; x<320; x++) {
uint8_t *p = &displayBitmap[(y * 320 + x)*3];
drawPixel(x, y, p[0], p[1], p[2]);
}
}
if (g_vm) {
drawDriveDoor(0, ((AppleVM *)g_vm)->DiskName(0)[0] == '\0');
drawDriveDoor(1, ((AppleVM *)g_vm)->DiskName(1)[0] == '\0');
}
}
void OpenCVDisplay::drawDriveStatus(uint8_t which, bool isRunning)
{
// location of status indicator for left drive
uint16_t xoff = 125;
uint16_t yoff = 213;
// and right drive
if (which == 1)
xoff += 135;
for (int y=0; y<1; y++) {
for (int x=0; x<6; x++) {
drawPixel(x + xoff, y + yoff, isRunning ? 0xF800 : 0x8AA9);
}
}
}
void OpenCVDisplay::drawDriveDoor(uint8_t which, bool isOpen)
{
// location of drive door for left drive
uint16_t xoff = 55;
uint16_t yoff = 216;
// location for right drive
if (which == 1) {
xoff += 134;
}
for (int y=0; y<20; y++) {
for (int x=0; x<43; x++) {
uint8_t *p = &driveLatch[(y * 43 + x)*3];
if (isOpen) {
p = &driveLatchOpen[(y * 43 + x)*3];
}
drawPixel(x+xoff, y+yoff, p[0], p[1], p[2]);
}
}
}
void OpenCVDisplay::drawBatteryStatus(uint8_t percent)
{
uint16_t xoff = 300;
uint16_t yoff = 222;
// the area around the apple is 12 wide
// it's exactly 11 high
// the color is 210/202/159
float watermark = ((float)percent / 100.0) * 11;
for (int y=0; y<11; y++) {
uint8_t bgr = 210;
uint8_t bgg = 202;
uint8_t bgb = 159;
if (11-y > watermark) {
// black...
bgr = bgg = bgb = 0;
}
for (int x=0; x<11; x++) {
uint8_t *p = &appleBitmap[(y * 10 + (x-1))*4];
// It's RGBA; blend w/ background color
uint8_t r,g,b;
float alpha = (float)p[3] / 255.0;
r = (float)p[0] * alpha + (bgr * (1.0 - alpha));
g = (float)p[1] * alpha + (bgg * (1.0 - alpha));
b = (float)p[2] * alpha + (bgb * (1.0 - alpha));
drawPixel(x+xoff, y+yoff, r, g, b);
}
}
}
#define BASEX 36
#define BASEY 26
void OpenCVDisplay::blit()
{
uint8_t *videoBuffer = g_vm->videoBuffer; // FIXME: poking deep
for (uint8_t y=0; y<192; y++) {
for (uint16_t x=0; x<280; x++) {
uint16_t pixel = (y*320+x)/2;
uint8_t colorIdx;
if (x & 1) {
colorIdx = videoBuffer[pixel] & 0x0F;
} else {
colorIdx = videoBuffer[pixel] >> 4;
}
// OpenCV is using BGR. This pixel-doubles both axes.
for (uint8_t xoff=0; xoff<2; xoff++) {
for (uint8_t yoff=0; yoff<2; yoff++) {
pixels->at<uchar>(y*2+yoff+BASEY, (x*2+xoff+BASEX)*3 + 0) = (loresPixelColors[colorIdx][2]) & 0xFF;
pixels->at<uchar>(y*2+yoff+BASEY, (x*2+xoff+BASEX)*3 + 1) = (loresPixelColors[colorIdx][1]) & 0xFF;
pixels->at<uchar>(y*2+yoff+BASEY, (x*2+xoff+BASEX)*3 + 2) = (loresPixelColors[colorIdx][0]) & 0xFF;
}
}
}
}
if (overlayMessage[0]) {
drawString(M_SELECTDISABLED, 1, 240 - 16 - 12, overlayMessage);
}
imshow(WINDOWNAME, *pixels);
}
void OpenCVDisplay::drawPixel(uint16_t x, uint8_t y, uint16_t color)
{
uint8_t
r = (color & 0xF800) >> 8,
g = (color & 0x7E0) >> 3,
b = (color & 0x1F) << 3;
// Pixel-doubling
for (int yoff=0; yoff<2; yoff++) {
for (int xoff=0; xoff<2; xoff++) {
pixels->at<uchar>(y*2+yoff, (x*2+xoff)*3 + 0) = b;
pixels->at<uchar>(y*2+yoff, (x*2+xoff)*3 + 1) = g;
pixels->at<uchar>(y*2+yoff, (x*2+xoff)*3 + 2) = r;
}
}
}
void OpenCVDisplay::drawPixel(uint16_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b)
{
// Pixel-doubling
for (int yoff=0; yoff<2; yoff++) {
for (int xoff=0; xoff<2; xoff++) {
pixels->at<uchar>(y*2+yoff, (x*2+xoff)*3 + 0) = b;
pixels->at<uchar>(y*2+yoff, (x*2+xoff)*3 + 1) = g;
pixels->at<uchar>(y*2+yoff, (x*2+xoff)*3 + 2) = r;
}
}
}
void OpenCVDisplay::drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c)
{
int8_t xsize = 8,
ysize = 0x0C,
offset = 0x20;
uint16_t temp;
c -= offset;// font starts with a space
uint16_t offPixel, onPixel;
switch (mode) {
case M_NORMAL:
onPixel = 0xFFFF;
offPixel = 0x0010;
break;
case M_SELECTED:
onPixel = 0x0000;
offPixel = 0xFFFF;
break;
case M_DISABLED:
default:
onPixel = 0x7BEF;
offPixel = 0x0000;
break;
case M_SELECTDISABLED:
onPixel = 0x7BEF;
offPixel = 0xFFE0;
break;
}
temp=(c*ysize);
for (int8_t y_off = 0; y_off <= ysize; y_off++) {
uint8_t ch = BiosFont[temp];
for (int8_t x_off = 0; x_off <= xsize; x_off++) {
if (ch & (1 << (7-x_off))) {
drawPixel(x + x_off, y + y_off, onPixel);
} else {
drawPixel(x + x_off, y + y_off, offPixel);
}
}
temp++;
}
}
void OpenCVDisplay::drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str)
{
int8_t xsize = 8; // width of a char in this font
for (int8_t i=0; i<strlen(str); i++) {
drawCharacter(mode, x, y, str[i]);
x += xsize; // fixme: any inter-char spacing?
}
}
void OpenCVDisplay::debugMsg(const char *msg)
{
printf("%s\n", msg);
}

44
opencv/opencv-display.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef __OPENCV_DISPLAY_H
#define __OPENCV_DISPLAY_H
#include <stdlib.h>
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/features2d/features2d.hpp"
#include "physicaldisplay.h"
enum {
M_NORMAL = 0,
M_SELECTED = 1,
M_DISABLED = 2,
M_SELECTDISABLED = 3
};
class OpenCVDisplay : public PhysicalDisplay {
public:
OpenCVDisplay();
virtual ~OpenCVDisplay();
virtual void blit();
virtual void redraw();
virtual void drawDriveDoor(uint8_t which, bool isOpen);
virtual void drawDriveStatus(uint8_t which, bool isRunning);
virtual void drawBatteryStatus(uint8_t percent);
void drawPixel(uint16_t x, uint8_t y, uint16_t color);
void drawPixel(uint16_t x, uint8_t y, uint8_t r, uint8_t g, uint8_t b);
virtual void drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c);
virtual void drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str);
virtual void debugMsg(const char *msg);
private:
cv::Mat *pixels;
};
#endif

View File

@ -0,0 +1,189 @@
#include <string.h> // strcpy
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include "opencv-filemanager.h"
OpenCVFileManager::OpenCVFileManager()
{
numCached = 0;
}
OpenCVFileManager::~OpenCVFileManager()
{
}
int8_t OpenCVFileManager::openFile(const char *name)
{
// See if there's a hole to re-use...
for (int i=0; i<numCached; i++) {
if (cachedNames[i][0] == '\0') {
strncpy(cachedNames[i], name, MAXPATH-1);
cachedNames[i][MAXPATH-1] = '\0'; // safety: ensure string terminator
fileSeekPositions[i] = 0;
return i;
}
}
// check for too many open files
if (numCached >= MAXFILES)
return -1;
// No, so we'll add it to the end
strncpy(cachedNames[numCached], name, MAXPATH-1);
cachedNames[numCached][MAXPATH-1] = '\0'; // safety: ensure string terminator
fileSeekPositions[numCached] = 0;
numCached++;
return numCached-1;
}
void OpenCVFileManager::closeFile(int8_t fd)
{
// invalid fd provided?
if (fd < 0 || fd >= numCached)
return;
// clear the name
cachedNames[fd][0] = '\0';
}
const char *OpenCVFileManager::fileName(int8_t fd)
{
if (fd < 0 || fd >= numCached)
return NULL;
return cachedNames[fd];
}
int8_t OpenCVFileManager::readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx)
{
// not used in this version
return -1;
}
void OpenCVFileManager::seekBlock(int8_t fd, uint16_t block, bool isNib)
{
if (fd < 0 || fd >= numCached)
return;
if (isNib) {
fileSeekPositions[fd] = block * 416;
} else {
fileSeekPositions[fd] = block * 256;
}
}
bool OpenCVFileManager::readTrack(int8_t fd, uint8_t *toWhere, bool isNib)
{
if (fd < 0 || fd >= numCached)
return false;
if (cachedNames[fd][0] == 0)
return false;
// open, seek, read, close.
bool ret = false;
int ffd = open(cachedNames[fd], O_RDONLY);
if (ffd) {
lseek(ffd, fileSeekPositions[fd], SEEK_SET);
if (isNib) {
ret = (read(ffd, toWhere, 0x1A00) == 0x1A00);
} else {
ret = (read(ffd, toWhere, 256 * 16) == 256 * 16);
}
close(ffd);
}
return ret;
}
bool OpenCVFileManager::readBlock(int8_t fd, uint8_t *toWhere, bool isNib)
{
// open, seek, read, close.
if (fd < 0 || fd >= numCached)
return false;
if (cachedNames[fd][0] == 0)
return false;
// open, seek, read, close.
bool ret = false;
int ffd = open(cachedNames[fd], O_RDONLY);
if (ffd) {
lseek(ffd, fileSeekPositions[fd], SEEK_SET);
if (isNib) {
ret = (read(ffd, toWhere, 416) == 416);
} else {
ret = (read(ffd, toWhere, 256) == 256);
}
close(ffd);
}
return ret;
}
bool OpenCVFileManager::writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib)
{
// open, seek, write, close.
if (fd < 0 || fd >= numCached)
return false;
if (cachedNames[fd][0] == 0)
return false;
// don't know how to do this without seeking through the nibblized
// track data, so just give up for now
if (isNib)
return false;
// open, seek, write, close.
int ffd = open(cachedNames[fd], O_WRONLY);
if (ffd) {
if (lseek(ffd, fileSeekPositions[fd], SEEK_SET) != fileSeekPositions[fd]) {
printf("ERROR: failed to seek to %lu\n", fileSeekPositions[fd]);
return false;
}
if (write(ffd, fromWhere, 256) != 256) {
printf("ERROR: failed to write 256 bytes\n");
return false;
}
close(ffd);
}
return true;
}
bool OpenCVFileManager::writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib)
{
// open, seek, write, close.
if (fd < 0 || fd >= numCached)
return false;
if (cachedNames[fd][0] == 0)
return false;
// open, seek, write, close.
int ffd = open(cachedNames[fd], O_WRONLY);
if (ffd) {
if (lseek(ffd, fileSeekPositions[fd], SEEK_SET) != fileSeekPositions[fd]) {
printf("ERROR: failed to seek to %lu\n", fileSeekPositions[fd]);
return false;
}
int16_t wrsize = 256 * 16;
if (isNib)
wrsize = 0x1A00;
if (write(ffd, fromWhere, wrsize) != wrsize) {
printf("ERROR: failed to write bytes\n");
return false;
}
close(ffd);
}
return true;
}

View File

@ -0,0 +1,31 @@
#ifndef __OPENCV_FILEMANAGER_H
#define __OPENCV_FILEMANAGER_H
#include "filemanager.h"
#include <stdint.h>
class OpenCVFileManager : public FileManager {
public:
OpenCVFileManager();
virtual ~OpenCVFileManager();
virtual int8_t openFile(const char *name);
virtual void closeFile(int8_t fd);
virtual const char *fileName(int8_t fd);
virtual int8_t readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx);
virtual void seekBlock(int8_t fd, uint16_t block, bool isNib = false);
virtual bool readTrack(int8_t fd, uint8_t *toWhere, bool isNib = false);
virtual bool readBlock(int8_t fd, uint8_t *toWhere, bool isNib = false);
virtual bool writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib = false);
virtual bool writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib = false);
private:
int8_t numCached;
char cachedNames[MAXFILES][MAXPATH];
unsigned long fileSeekPositions[MAXFILES];
};
#endif

223
opencv/opencv-keyboard.cpp Normal file
View File

@ -0,0 +1,223 @@
#include "opencv-keyboard.h"
/*
* OpenCV doesn't support very sophisticated keyboard interaction.
* You can't query modifier keys; you don't get up-and-down
* events. There's just a simple "has any key been pressed" method.
*/
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/features2d/features2d.hpp"
using namespace cv;
using namespace std;
OpenCVKeyboard::OpenCVKeyboard(VMKeyboard *k) : PhysicalKeyboard(k)
{
}
OpenCVKeyboard::~OpenCVKeyboard()
{
}
typedef struct {
int8_t actualKey;
bool shifted;
} keymapChar;
// keymap starts at space (32), ends at delete (127).
// Note that this maps them based on what the Apple //e expects. It's the
// same as a modern US keyboard.
const keymapChar keymap[96] =
{ { ' ', false }, // space
{ '1', true }, // !
{ '\'', true }, // "
{ '3', true }, // #
{ '4', true }, // $
{ '5', true }, // %
{ '7', true }, // &
{ '\'', false}, // '
{ '9', true }, // (
{ '0', true }, // )
{ '8', true }, // *
{ '=', true }, // +
{ ',', false }, // ,
{ '-', false }, // -
{ '.', false }, // .
{ '/', false }, // /
{ '0', false }, // 0
{ '1', false }, // 1
{ '2', false }, // 2
{ '3', false }, // 3
{ '4', false }, // 4
{ '5', false }, // 5
{ '6', false }, // 6
{ '7', false }, // 7
{ '8', false }, // 8
{ '9', false }, // 9
{ ':', true }, // :
{ ';', false }, // ;
{ ',', true }, // <
{ '=', false }, // =
{ '.', true }, // >
{ '/', true }, // ?
{ '2', true }, // @
{ 'A', true }, // A
{ 'B', true }, // B
{ 'C', true }, // C
{ 'D', true }, // D
{ 'E', true }, // E
{ 'F', true }, // F
{ 'G', true }, // G
{ 'H', true }, // H
{ 'I', true }, // I
{ 'J', true }, // J
{ 'K', true }, // K
{ 'L', true }, // L
{ 'M', true }, // M
{ 'N', true }, // N
{ 'O', true }, // O
{ 'P', true }, // P
{ 'Q', true }, // Q
{ 'R', true }, // R
{ 'S', true }, // S
{ 'T', true }, // T
{ 'U', true }, // U
{ 'V', true }, // V
{ 'W', true }, // W
{ 'X', true }, // X
{ 'Y', true }, // Y
{ 'Z', true }, // Z
#ifndef TEENSYDUINO
{ LA, false }, // [
#else
{ '[', false }, // [
#endif
{ '\\', false}, // \ ...
#ifndef TEENSYDUINO
{ RA, false }, // ]
#else
{ ']', false }, // ]
#endif
{ '6', true }, // ^
{ '-', true }, // _
{ '`', false }, // `
{ 'A', false }, // a
{ 'B', false }, // b
{ 'C', false }, // c
{ 'D', false }, // d
{ 'E', false }, // e
{ 'F', false }, // f
{ 'G', false }, // g
{ 'H', false }, // h
{ 'I', false }, // i
{ 'J', false }, // j
{ 'K', false }, // k
{ 'L', false }, // l
{ 'M', false }, // m
{ 'N', false }, // n
{ 'O', false }, // o
{ 'P', false }, // p
{ 'Q', false }, // q
{ 'R', false }, // r
{ 'S', false }, // s
{ 'T', false }, // t
{ 'U', false }, // u
{ 'V', false }, // v
{ 'W', false }, // w
{ 'X', false }, // x
{ 'Y', false }, // y
{ 'Z', false }, // z
{ '[', true }, // {
{ '\\', true }, // |
{ ']', true }, // }
{ '`', true }, // ~
{ DEL, false } // delete
};
void OpenCVKeyboard::maintainKeyboard()
{
static int invalidCount = 0;
int c = cv::waitKey(1);
// OpenCV relies on KeyRepeat to deliver individual presses. We want
// to guess about "it's still down": so, unless we get > 3 invalid
// "-1" values in a row, we're gonna assume it's still pressed.
if (c == -1) {
invalidCount++;
if (invalidCount < 4) {
return;
}
} else {
invalidCount = 0;
}
if (c == -1) {
if (lastKey >= ' ' && lastKey <= 127) {
vmkeyboard->keyReleased(keymap[lastKey-' '].actualKey);
if (keymap[lastKey-' '].shifted) {
vmkeyboard->keyReleased(LSHFT);
}
} else {
vmkeyboard->keyReleased(lastKey);
}
lastKey = -1;
return;
}
if (c == 0xF700) {
c = UARR; // up
} else if (c == 0xF701) {
c = DARR; // down
} else if (c == 0xF702) {
c = LARR; // left
} else if (c == 0xF703) {
c = RARR; // right
}
// If we're already repeating this key, do nothing
if (lastKey != -1 &&
lastKey == c) {
return;
}
// If it's a different key, then do a key up
if (lastKey != -1) {
if (lastKey >= ' ' && lastKey <= 127) {
vmkeyboard->keyReleased(keymap[lastKey-' '].actualKey);
if (keymap[lastKey-' '].shifted) {
vmkeyboard->keyReleased(LSHFT);
}
} else {
vmkeyboard->keyReleased(lastKey);
}
lastKey = -1;
}
// Now it's a new keypress - do a key down
if (c >= ' ' && c <= 127) {
if (keymap[c-' '].shifted) {
vmkeyboard->keyDepressed(LSHFT);
}
vmkeyboard->keyDepressed(keymap[c-' '].actualKey);
lastKey = c;
return;
}
// Any other key that could have been reported:
// ESC RET TAB LA RA LARR RARR DARR UARR
vmkeyboard->keyDepressed(c);
// anything else isn't reported by OpenCV, so we can't report it
// specifically. So these are ignored:
// _CTRL LSHFT RSHFT LOCK LA RA
lastKey = c;
}

17
opencv/opencv-keyboard.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef __OPENCV_KEYBOARD_H
#define __OPENCV_KEYBOARD_H
#include "physicalkeyboard.h"
#include "vmkeyboard.h"
class OpenCVKeyboard : public PhysicalKeyboard {
public:
OpenCVKeyboard(VMKeyboard *k);
virtual ~OpenCVKeyboard();
virtual void maintainKeyboard();
private:
int lastKey;
};
#endif

55
opencv/opencv-paddles.cpp Normal file
View File

@ -0,0 +1,55 @@
#include <stdio.h>
#include "opencv-paddles.h"
#include "opencv2/core/core.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/calib3d/calib3d.hpp"
#include "opencv2/features2d/features2d.hpp"
using namespace cv;
using namespace std;
// FIXME: abstract this somewhere
#define WINDOWNAME "6502core"
#define WINDOWHEIGHT (240*2)
#define WINDOWWIDTH (320*2)
#include "globals.h"
static void mouseCallback(int event, int x, int y, int flags, void* userdata)
{
OpenCVPaddles *a = (OpenCVPaddles *)userdata;
if (event == EVENT_MOUSEMOVE) {
a->p0 = ((float) x / (float)WINDOWWIDTH) * 255.0;
a->p1 = ((float) y / (float)WINDOWHEIGHT) * 255.0;
}
}
OpenCVPaddles::OpenCVPaddles()
{
p0 = p1 = 127;
setMouseCallback(WINDOWNAME, mouseCallback, this);
}
OpenCVPaddles::~OpenCVPaddles()
{
}
void OpenCVPaddles::startReading()
{
g_vm->triggerPaddleInCycles(0, 12 * p0);
g_vm->triggerPaddleInCycles(1, 12 * p1);
}
uint8_t OpenCVPaddles::paddle0()
{
return p0;
}
uint8_t OpenCVPaddles::paddle1()
{
return p1;
}

17
opencv/opencv-paddles.h Normal file
View File

@ -0,0 +1,17 @@
#include <stdint.h>
#include "physicalpaddles.h"
class OpenCVPaddles : public PhysicalPaddles {
public:
OpenCVPaddles();
virtual ~OpenCVPaddles();
virtual void startReading();
virtual uint8_t paddle0();
virtual uint8_t paddle1();
public:
uint8_t p0;
uint8_t p1;
};

26
physicaldisplay.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef __PHYSICALDISPLAY_H
#define __PHYSICALDISPLAY_H
#include <string.h> // strncpy
class PhysicalDisplay {
public:
PhysicalDisplay() { overlayMessage[0] = '\0'; }
virtual ~PhysicalDisplay() {};
virtual void redraw() = 0; // total redraw, assuming nothing
virtual void blit() = 0; // redraw just the VM display area
virtual void drawDriveDoor(uint8_t which, bool isOpen) = 0;
virtual void drawDriveStatus(uint8_t which, bool isRunning) = 0;
virtual void drawBatteryStatus(uint8_t percent) = 0;
virtual void drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c) = 0;
virtual void drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str) = 0;
virtual void debugMsg(const char *msg) { strncpy(overlayMessage, msg, sizeof(overlayMessage)); }
protected:
char overlayMessage[40];
};
#endif

36
physicalkeyboard.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef __PHYSICALKEYBOARD_H
#define __PHYSICALKEYBOARD_H
#include <stdint.h>
#include "vmkeyboard.h"
#define ESC 0x1B
#define DEL 0x7F
#define RET 0x0D
#define TAB 0x09
#define LARR 0x08 // control-H
#define RARR 0x15 // control-U
#define DARR 0x0A
#define UARR 0x0B
// Virtual keys
#define _CTRL 0x81
#define LSHFT 0x82
#define RSHFT 0x83
#define LOCK 0x84 // caps lock
#define LA 0x85 // left (open) apple, aka paddle0 button
#define RA 0x86 // right (closed) apple aka paddle1 button
class PhysicalKeyboard {
public:
PhysicalKeyboard(VMKeyboard *k) { this->vmkeyboard = k; }
virtual ~PhysicalKeyboard() {};
virtual void maintainKeyboard() = 0;
protected:
VMKeyboard *vmkeyboard;
};
#endif

15
physicalpaddles.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef __PHYSICALPADDLES_H
#define __PHYSICALPADDLES_H
#include <stdint.h>
class PhysicalPaddles {
public:
virtual ~PhysicalPaddles() {}
virtual void startReading() = 0;
virtual uint8_t paddle0() = 0;
virtual uint8_t paddle1() = 0;
};
#endif

15
physicalspeaker.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef __PHYSICALSPEAKER_H
#define __PHYSICALSPEAKER_H
#include <stdint.h>
class PhysicalSpeaker {
public:
virtual ~PhysicalSpeaker() {}
virtual void toggleAtCycle(uint32_t c) = 0;
virtual void maintainSpeaker(uint32_t c) = 0;
};
#endif

58
util/genrom.pl Executable file
View File

@ -0,0 +1,58 @@
#!/usr/bin/perl
use strict;
use warnings;
my $romfile = shift || die "Must provide the path to an Apple //e ROM image";
my $diskrom = shift || die "Must also provide the path to an Apple //e Disk II ROM image";
validate($romfile, 32768, "an Apple //e ROM image");
validate($diskrom, 256, "a DiskII ROM image");
dumpRom($romfile, "apple/applemmu-rom.h", "romData", 32768);
dumpRom($diskrom, "apple/diskii-rom.h", "romData", 256);
exit 0;
sub validate {
my ($filename, $expectedSize, $description) = @_;
my @info = lstat($filename);
die "Unable to stat '$filename' file: $!"
unless scalar @info;
die "This doesn't look like $description"
unless ($info[7] == $expectedSize);
}
sub dumpRom {
my ($input, $output, $dataname, $datasize) = @_;
open(IN, "<", $input) || die "Unable to open $input file: $!";
open(OUT, ">", "$output") || die "Unable to create $output: $!";
print OUT <<EOF
#ifndef TEENSYDUINO
#define PROGMEM
#endif
static
uint8_t romData[32768] PROGMEM = {
EOF
;
my $data;
my $count = 0;
while (sysread(IN, $data, 1)) {
print OUT sprintf("0x%.2X, ", ord($data));
$count++;
if ($count % 8 == 0) {
print OUT "\n";
}
}
print OUT "};\n";
close OUT;
close IN;
}

30
vm.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef __VM_H
#define __VM_H
#include <stdint.h>
#include <stdlib.h> // for calloc
#include "mmu.h"
#include "vmdisplay.h"
#include "vmkeyboard.h"
class VM {
public:
VM() { mmu=NULL; vmdisplay = NULL; videoBuffer = (uint8_t *)calloc(320*240/2, 1); hasIRQ = false;}
virtual ~VM() { if (mmu) delete mmu; if (vmdisplay) delete vmdisplay; free(videoBuffer); }
virtual void SetMMU(MMU *mmu) { this->mmu = mmu; }
virtual MMU *getMMU() { return mmu; }
virtual VMKeyboard *getKeyboard() = 0;
virtual void Reset() = 0;
virtual void triggerPaddleInCycles(uint8_t paddleNum, uint16_t cycleCount) = 0;
uint8_t *videoBuffer;
VMDisplay *vmdisplay;
MMU *mmu;
bool hasIRQ;
};
#endif

20
vmdisplay.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef __VMDISPLAY_H
#define __VMDISPLAY_H
class MMU;
class VMDisplay {
public:
VMDisplay(uint8_t *vb) { videoBuffer = vb; }
virtual ~VMDisplay() { videoBuffer = NULL; };
virtual void SetMMU(MMU *m) { mmu = m; }
virtual bool needsRedraw() = 0;
virtual void didRedraw() = 0;
MMU *mmu;
uint8_t *videoBuffer;
};
#endif

15
vmkeyboard.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef __VMKEYBOARD_H
#define __VMKEYBOARD_H
#include <stdint.h>
class VMKeyboard {
public:
virtual ~VMKeyboard() {}
virtual void keyDepressed(uint8_t k) = 0;
virtual void keyReleased(uint8_t k) = 0;
virtual void maintainKeyboard(uint32_t cycleCount) = 0;
};
#endif