mirror of
https://github.com/JorjBauer/aiie.git
synced 2024-12-26 08:29:31 +00:00
general VM structure and a test OpenCV target
This commit is contained in:
parent
680e57013f
commit
218b3b77a0
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
apple2e.rom
|
||||
disk.rom
|
||||
*~
|
||||
*.o
|
24
Makefile
24
Makefile
@ -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
|
||||
|
||||
|
48
README.md
48
README.md
@ -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
113
RingBuffer.cpp
Normal 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
34
RingBuffer.h
Normal 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
610
apple/appledisplay.cpp
Normal 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
75
apple/appledisplay.h
Normal 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
196
apple/applekeyboard.cpp
Normal 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
57
apple/applekeyboard.h
Normal 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
853
apple/applemmu.cpp
Normal 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
91
apple/applemmu.h
Normal 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
99
apple/applevm.cpp
Normal 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
41
apple/applevm.h
Normal 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
470
apple/diskii.cpp
Normal 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
64
apple/diskii.h
Normal 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
335
apple/font.h
Normal 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
331
apple/nibutil.cpp
Normal 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
44
apple/nibutil.h
Normal 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
9
apple/slot.cpp
Normal file
@ -0,0 +1,9 @@
|
||||
#include "slot.h"
|
||||
|
||||
Slot::Slot()
|
||||
{
|
||||
}
|
||||
|
||||
Slot::~Slot()
|
||||
{
|
||||
}
|
25
apple/slot.h
Normal file
25
apple/slot.h
Normal 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
107
bios-font.h
Normal 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
29524
display-bg.h
Normal file
File diff suppressed because it is too large
Load Diff
29
filemanager.h
Normal file
29
filemanager.h
Normal 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
11
globals.cpp
Normal 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
19
globals.h
Normal 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
411
opencv/aiie.cpp
Normal 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, ¶m);
|
||||
param.sched_priority = sched_get_priority_max(policy);
|
||||
pthread_setschedparam(pthread_self(), policy, ¶m);
|
||||
#endif
|
||||
|
||||
_init_darwin_shim();
|
||||
do_gettime(&startTime);
|
||||
do_gettime(&nextInstructionTime);
|
||||
|
||||
printf("free-running\n");
|
||||
while (1) {
|
||||
// cycle down the CPU...
|
||||
do_gettime(¤tTime);
|
||||
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
13
opencv/dummy-speaker.cpp
Normal 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
15
opencv/dummy-speaker.h
Normal 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
270
opencv/opencv-display.cpp
Normal 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
44
opencv/opencv-display.h
Normal 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
|
189
opencv/opencv-filemanager.cpp
Normal file
189
opencv/opencv-filemanager.cpp
Normal 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;
|
||||
}
|
31
opencv/opencv-filemanager.h
Normal file
31
opencv/opencv-filemanager.h
Normal 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
223
opencv/opencv-keyboard.cpp
Normal 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
17
opencv/opencv-keyboard.h
Normal 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
55
opencv/opencv-paddles.cpp
Normal 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
17
opencv/opencv-paddles.h
Normal 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
26
physicaldisplay.h
Normal 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
36
physicalkeyboard.h
Normal 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
15
physicalpaddles.h
Normal 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
15
physicalspeaker.h
Normal 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
58
util/genrom.pl
Executable 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
30
vm.h
Normal 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
20
vmdisplay.h
Normal 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
15
vmkeyboard.h
Normal 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
|
Loading…
Reference in New Issue
Block a user