First Commit

This commit is contained in:
Nick Metcalfe 2017-10-29 12:21:14 +11:00
parent bad445d035
commit 3e7d708a6d
70 changed files with 151004 additions and 0 deletions

403
AppleEmu.ino Normal file
View File

@ -0,0 +1,403 @@
#include <Arduino.h>
#include <ff.h> // uSDFS
#include <SPI.h>
#include <EEPROM.h>
#include <TimeLib.h>
#include <TimerOne.h>
#include "src/bios.h"
#include "src/cpu.h"
#include "src/applevm.h"
#include "src/teensy-display.h"
#include "src/teensy-usb-keyboard.h"
#include "src/teensy-speaker.h"
#include "src/teensy-filemanager.h"
#define RESETPIN 39
#define BATTERYPIN A19
#define SPEAKERPIN A21
#include "src/globals.h"
#include "src/teensy-crash.h"
//volatile double nextInstructionMicros;
//volatile double startMicros;
volatile uint64_t nextInstructionMicros;
volatile uint64_t startMicros;
FATFS fatfs; /* File system object */
BIOS bios;
enum {
D_NONE = 0,
D_SHOWFPS = 1,
D_SHOWMEMFREE = 2,
D_SHOWPADDLES = 3,
D_SHOWPC = 4,
D_SHOWCYCLES = 5,
D_SHOWBATTERY = 6,
D_SHOWTIME = 7
};
int8_t debugMode = D_NONE;
static time_t getTeensy3Time() { return Teensy3Clock.get(); }
#define ESP_TXD 51
#define ESP_CHPD 52
#define ESP_RST 53
#define ESP_RXD 40
#define ESP_GPIO0 41
#define ESP_GPIO2 42
void setup()
{
Serial.begin(230400);
//Init RGB LED early
pinMode(REDPIN, OUTPUT);
pinMode(GREENPIN, OUTPUT);
pinMode(BLUEPIN, OUTPUT);
analogWrite(BLUEPIN, 64);
/* while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
Serial.println("hi");
*/
delay(100); // let the serial port connect if it's gonna
enableFaultHandler();
// set the Time library to use Teensy 3.0's RTC to keep time
setSyncProvider(getTeensy3Time);
delay(100); // don't know if we need this
if (timeStatus() == timeSet) {
Serial.println("RTC set from Teensy");
} else {
Serial.println("Error while setting RTC");
}
TCHAR *device = (TCHAR *)_T("0:/");
f_mount (&fatfs, device, 0); /* Mount/Unmount a logical drive */
pinMode(RESETPIN, INPUT);
digitalWrite(RESETPIN, HIGH);
analogReference(EXTERNAL); // 3.3v external, or 1.7v internal. We need 1.7 internal for the battery level, which means we're gonna have to do something about the paddles :/
analogReadRes(12); // We only need 8 bits of resolution (0-255) for battery & paddles
analogReadAveraging(32); // ?? dunno if we need this or not.
//analogWriteResolution(12);
pinMode(SPEAKERPIN, OUTPUT); // analog speaker output, used as digital volume control
pinMode(BATTERYPIN, INPUT);
Serial.println("creating virtual hardware");
g_speaker = new TeensySpeaker(SPEAKERPIN);
Serial.println(" fm");
// First create the filemanager - the interface to the host file system.
g_filemanager = new TeensyFileManager();
// 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.
Serial.println(" display");
g_display = new TeensyDisplay();
// Next create the virtual CPU. This needs the VM's MMU in order to
// run, but we don't have that yet.
Serial.println(" cpu");
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).
Serial.println(" vm");
g_vm = new AppleVM();
// Now that the VM exists and it has created an MMU, we tell the CPU
// how to access memory through the MMU.
Serial.println(" [setMMU]");
g_cpu->SetMMU(g_vm->getMMU());
// And the physical keyboard needs hooks in to the virtual keyboard...
Serial.println(" keyboard");
g_keyboard = new TeensyUsbKeyboard(g_vm->getKeyboard());
// Now that all the virtual hardware is glued together, reset the VM
Serial.println("Resetting VM");
g_vm->Reset();
g_display->redraw();
// g_display->blit();
Serial.println("Reading prefs");
readPrefs(); // read from eeprom and set anything we need setting
Serial.println("free-running");
startMicros = 0;
nextInstructionMicros = micros();
// Debugging: insert a disk on startup...
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/UTIL/mock2dem.dsk", false);
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/JORJ/disk_s6d1.dsk", false);
// ((AppleVM *)g_vm)->insertDisk(0, "/A2DISKS/GAMES/ALIBABA.DSK", false);
pinMode(56, OUTPUT);
pinMode(57, OUTPUT);
Timer1.initialize(3);
Timer1.attachInterrupt(runCPU);
Timer1.start();
g_keyboard->setCaps(true);
//biosInterrupt();
}
// FIXME: move these memory-related functions elsewhere...
// This only gives you an estimated free mem size. It's not perfect.
uint32_t FreeRamEstimate()
{
uint32_t stackTop;
uint32_t heapTop;
// current position of the stack.
stackTop = (uint32_t) &stackTop;
// current position of heap.
void* hTop = malloc(1);
heapTop = (uint32_t) hTop;
free(hTop);
// The difference is the free, available ram.
return stackTop - heapTop;
}
#include "malloc.h"
int heapSize(){
return mallinfo().uordblks;
}
void biosInterrupt()
{
Timer1.stop();
// wait for the interrupt button to be released
while (digitalRead(RESETPIN) == LOW)
;
// invoke the BIOS
if (bios.runUntilDone()) {
// if it returned true, we have something to store persistently in EEPROM.
writePrefs();
}
// if we turned off debugMode, make sure to clear the debugMsg
if (debugMode == D_NONE) {
g_display->debugMsg("");
}
// clear the CPU next-step counters
g_cpu->cycles = 0;
nextInstructionMicros = micros();
startMicros = micros();
// Force the display to redraw
((AppleDisplay*)(g_vm->vmdisplay))->modeChange();
// Poll the keyboard before we start, so we can do selftest on startup
g_keyboard->maintainKeyboard();
Timer1.start();
}
//bool debugState = false;
//bool debugLCDState = false;
void runCPU()
{
if (micros() >= nextInstructionMicros) {
// Debugging: to watch when the CPU is triggered...
//debugState = !debugState;
// digitalWrite(56, debugState);
g_cpu->Run(24);
// These are timing-critical, for the audio and paddles.
// There's also a keyboard repeat in here that hopefully is
// minimal overhead...
g_speaker->beginMixing();
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
g_speaker->maintainSpeaker(g_cpu->cycles);
// The CPU of the Apple //e ran at 1.023 MHz. Adjust when we think
// the next instruction should run based on how long the execution
// was ((1000/1023) * numberOfCycles) - which is about 97.8%.
nextInstructionMicros = startMicros + ((double)g_cpu->cycles * 0.978d);
}
}
void loop()
{
if (digitalRead(RESETPIN) == LOW || biosRequest) {
// This is the BIOS interrupt. We immediately act on it.
biosInterrupt();
biosRequest = false;
}
((AppleVM*)g_vm)->disk6->fillDiskBuffer();
g_keyboard->maintainKeyboard();
//debugLCDState = !debugLCDState;
//digitalWrite(57, debugLCDState);
doDebugging();
// Only redraw if the CPU is caught up; and then we'll suspend the
// CPU to draw a full frame.
// Note that this breaks audio, b/c it's real-time and requires the
// CPU running to change the audio line's value. So we need to EITHER
//
// - delay the audio line by at least the time it takes for one
// display update, OR
// - lock display updates so the CPU can update the memory, but we
// keep drawing what was going to be displayed
//
// The Timer1.stop()/start() is bad. Using it, the display doesn't
// tear; but the audio is also broken. Taking it out, audio is good
// but the display tears.
if (g_screenSync) Timer1.stop();
g_vm->vmdisplay->needsRedraw();
AiieRect what = g_vm->vmdisplay->getDirtyRect();
g_vm->vmdisplay->didRedraw();
g_display->blit(what);
if (g_screenSync) Timer1.start();
if (bios.updateDiagnostics()) {
int level = g_battery;
if (level < 3000) level = 3000;
if (level > 4100) level = 4100;
level = map(level, 3000, 4100, 0, 100);
g_display->drawBatteryStatus(level);
//Serial.println(g_battery);
}
}
void doDebugging()
{
char buf[25];
switch (debugMode) {
case D_SHOWFPS:
// display some FPS data
static uint32_t startAt = millis();
static uint32_t loopCount = 0;
loopCount++;
time_t lenSecs;
lenSecs = (millis() - startAt) / 1000;
if (lenSecs >= 5) {
sprintf(buf, "%lu FPS", loopCount / lenSecs);
g_display->debugMsg(buf);
startAt = millis();
loopCount = 0;
}
break;
case D_SHOWMEMFREE:
sprintf(buf, "%lu %u", FreeRamEstimate(), heapSize());
g_display->debugMsg(buf);
break;
case D_SHOWPADDLES:
sprintf(buf, "%u %u", 0, 0);//g_paddles->paddle0(), g_paddles->paddle1());
g_display->debugMsg(buf);
break;
case D_SHOWPC:
sprintf(buf, "%X", g_cpu->pc);
g_display->debugMsg(buf);
break;
case D_SHOWCYCLES:
sprintf(buf, "%llX", g_cpu->cycles);
g_display->debugMsg(buf);
break;
case D_SHOWBATTERY:
sprintf(buf, "BAT %d", g_battery);
g_display->debugMsg(buf);
break;
case D_SHOWTIME:
sprintf(buf, "%.2d:%.2d:%.2d", hour(), minute(), second());
g_display->debugMsg(buf);
break;
}
}
typedef struct _prefs {
uint32_t magic;
int16_t volume;
uint8_t joyTrimX;
uint8_t joyTrimY;
uint8_t joySpeed;
uint8_t screenSync;
} prefs;
// Fun trivia: the Apple //e was in production from January 1983 to
// November 1993. And the 65C02 in them supported weird BCD math modes.
#define MAGIC 0x01831095
void readPrefs()
{
prefs p;
uint8_t *pp = (uint8_t *)&p;
for (uint8_t i=0; i<sizeof(prefs); i++) {
*pp++ = EEPROM.read(i);
}
if (p.magic == MAGIC) {
// looks valid! Use it.
Serial.println("prefs valid! Restoring volume");
if (p.volume > 15) p.volume = 15;
if (p.volume < 0) p.volume = 0;
if (p.joyTrimX > 239) p.joyTrimX = 239;
if (p.joyTrimX < 15) p.joyTrimX = 15;
if (p.joyTrimY > 239) p.joyTrimY = 239;
if (p.joyTrimY < 15) p.joyTrimY = 15;
if (p.joySpeed > 25) p.joySpeed = 25;
if (p.joySpeed < 1) p.joySpeed = 1;
g_volume = p.volume;
g_joyTrimX = p.joyTrimX;
g_joyTrimY = p.joyTrimY;
g_joySpeed = p.joySpeed;
g_screenSync = p.screenSync;
return;
}
// use defaults
g_volume = 10;
g_joyTrimX = 127;
g_joyTrimY = 127;
}
void writePrefs()
{
Serial.println("writing prefs");
prefs p;
uint8_t *pp = (uint8_t *)&p;
p.magic = MAGIC;
p.volume = g_volume;
p.joyTrimX = g_joyTrimX;
p.joyTrimY = g_joyTrimY;
p.joySpeed = g_joySpeed;
p.screenSync = g_screenSync;
for (uint8_t i=0; i<sizeof(prefs); i++) {
EEPROM.write(i, *pp++);
}
}

BIN
Bios.bfn Normal file

Binary file not shown.

BIN
GameBloke.fzz Normal file

Binary file not shown.

110729
Keypad Baseplate.gcode Normal file

File diff suppressed because it is too large Load Diff

BIN
Keypad Baseplate.stl Normal file

Binary file not shown.

BIN
pics/GameBloke.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

BIN
pics/cpuapart.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

BIN
pics/cpuback.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

BIN
pics/cpufront.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

BIN
pics/desktop.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

BIN
pics/front.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

BIN
pics/inside.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

BIN
pics/insideback.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
pics/keyboard.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

BIN
pics/keysupport.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
pics/portrait.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

BIN
pics/sandwich.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
pics/screen.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
pics/screenoff.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

BIN
pics/sideon.JPG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

113
src/RingBuf.cpp Normal file
View File

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

34
src/RingBuf.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef __RINGBUF_H
#define __RINGBUF_H
#include <stdint.h>
class RingBuf {
public:
RingBuf(int16_t length);
~RingBuf();
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

628
src/appledisplay.cpp Normal file
View File

@ -0,0 +1,628 @@
#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
*/
#define extendDirtyRect(x,y) { \
if (dirtyRect.left > x) { \
dirtyRect.left = x; \
} \
if (dirtyRect.right < x) { \
dirtyRect.right = x; \
} \
if (dirtyRect.top > y) { \
dirtyRect.top = y; \
} \
if (dirtyRect.bottom < y) { \
dirtyRect.bottom = y; \
} \
}
#if DISPLAYRUN == 512
#define drawPixel(c, x, y) { \
uint16_t idx = (((y) << 9) + (x)) >> 1; \
if ((x) & 1) { \
videoBuffer[idx] = (videoBuffer[idx] & 0xF0) | (c); \
} else { \
videoBuffer[idx] = (videoBuffer[idx] & 0x0F) | ((c) << 4); \
} \
}
#define draw2Pixels(cAB, x, y) { \
videoBuffer[(((y) <<9) + (x)) >> 1] = cAB; \
}
#else
#define drawPixel(c, x, y) { \
uint16_t idx = ((y) * DISPLAYRUN + (x)) / 2; \
if ((x) & 1) { \
videoBuffer[idx] = (videoBuffer[idx] & 0xF0) | (c); \
} else { \
videoBuffer[idx] = (videoBuffer[idx] & 0x0F) | ((c) << 4); \
} \
}
#define draw2Pixels(cAB, x, y) { \
videoBuffer[((y) * DISPLAYRUN + (x)) /2] = cAB; \
}
#endif
#define DrawLoresPixelAt(c, x, 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); \
} \
} \
}
#include "globals.h"
AppleDisplay::AppleDisplay(uint8_t *vb) : VMDisplay(vb)
{
this->switches = NULL;
this->dirty = true;
this->dirtyRect.left = this->dirtyRect.top = 0;
this->dirtyRect.right = 279;
this->dirtyRect.bottom = 191;
textColor = g_displayType == m_monochrome?c_green:c_white;
}
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;
}
// 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 */
}
inline void AppleDisplay::Draw14DoubleHiresPixelsAt(uint16_t addr)
{
// We will consult 4 bytes (2 in main, 2 in aux) for any single-byte
// write. Align to the first byte in that series based on what
// 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?
inline 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::redraw80ColumnText(uint8_t startingY)
{
uint8_t row, col;
col = -1; // will force us to deinterlaceAddress()
bool invert;
const uint8_t *cptr;
// FIXME: is there ever a case for 0x800, like in redraw40ColumnText?
uint16_t start = 0x400;
// Every time through this loop, we increment the column. That's going to be correct most of the time.
// Sometimes we'll get beyond the end (40 columns), and wind up on another line 8 rows down.
// Sometimes we'll get beyond the end, and we'll wind up in unused RAM.
// But this is an optimization (for speed) over just calling DrawCharacter() for every one.
for (uint16_t addr = start; addr <= start + 0x3FF; addr++,col++) {
if (col > 39 || row > 23) {
// Could be blanking space; we'll try to re-confirm...
deinterlaceAddress(addr, &row, &col);
}
// Only draw onscreen locations
if (row >= startingY && col <= 39 && row <= 23) {
// Even characters are in bank 0 ram. Odd characters are in bank
// 1 ram. Technically, this would need 560 columns to work
// correctly - and I don't have that, so it's going to be a bit
// wonky.
//
// First pass: draw two pixels on top of each other, clearing
// only if both are black. This would be blocky but probably
// passable if it weren't for the fact that characters are 7
// pixels wide, so we wind up sharing a half-pixel between two
// characters. So we'll render these as 3-pixel-wide characters
// and make sure they always even-align the drawing on the left
// side so we don't overwrite every other one on the left or
// right side.
// Draw the first of two characters
cptr = xlateChar(mmu->readDirect(addr, 1), &invert);
for (uint8_t y2 = 0; y2<8; y2++) {
uint8_t d = *(cptr + y2);
for (uint8_t x2 = 0; x2 <= 7; x2+=2) {
uint16_t basex = ((col * 2) * 7) & 0xFFFE; // even aligned
bool pixelOn = ( (d & (1<<x2)) | (d & (1<<(x2+1))) );
if (pixelOn) {
uint8_t val = (invert ? c_black : textColor);
drawPixel(val, (basex+x2)/2, row*8+y2);
} else {
uint8_t val = (invert ? textColor : c_black);
drawPixel(val, (basex+x2)/2, row*8+y2);
}
}
}
// Draw the second of two characters
cptr = xlateChar(mmu->readDirect(addr, 0), &invert);
for (uint8_t y2 = 0; y2<8; y2++) {
uint8_t d = *(cptr + y2);
for (uint8_t x2 = 0; x2 <= 7; x2+=2) {
uint16_t basex = ((col * 2 + 1) * 7) & 0xFFFE; // even aligned -- +1 for the second character
bool pixelOn = ( (d & (1<<x2)) | (d & (1<<(x2+1))) );
if (pixelOn) {
uint8_t val = (invert ? c_black : textColor);
drawPixel(val, (basex+x2)/2, row*8+y2);
} else {
uint8_t val = (invert ? textColor : c_black);
drawPixel(val, (basex+x2)/2, row*8+y2);
}
}
}
}
}
}
void AppleDisplay::redraw40ColumnText(uint8_t startingY)
{
bool invert;
uint16_t start = ((*switches) & S_PAGE2) ? 0x800 : 0x400;
uint8_t row, col;
col = -1; // will force us to deinterlaceAddress()
// Every time through this loop, we increment the column. That's going to be correct most of the time.
// Sometimes we'll get beyond the end (40 columns), and wind up on another line 8 rows down.
// Sometimes we'll get beyond the end, and we'll wind up in unused RAM.
// But this is an optimization (for speed) over just calling DrawCharacter() for every one.
for (uint16_t addr = start; addr <= start + 0x3FF; addr++,col++) {
if (col > 39 || row > 23) {
// Could be blanking space; we'll try to re-confirm...
deinterlaceAddress(addr, &row, &col);
}
// Only draw onscreen locations
if (row >= startingY && col <= 39 && row <= 23) {
const uint8_t *cptr = xlateChar(mmu->read(addr), &invert);
for (uint8_t y2 = 0; y2<8; y2++) {
uint8_t d = *(cptr + y2);
for (uint8_t x2 = 0; x2 < 7; x2++) {
if (d & 1) {
uint8_t val = (invert ? c_black : textColor);
drawPixel(val, col*7+x2, row*8+y2);
} else {
uint8_t val = (invert ? textColor : c_black);
drawPixel(val, col*7+x2, row*8+y2);
}
d >>= 1;
}
}
}
}
}
void AppleDisplay::redrawHires()
{
uint16_t start = ((*switches) & S_PAGE2) ? 0x4000 : 0x2000;
if ((*switches) & S_80STORE) {
// Apple IIe, technical nodes #3: 80STORE must be OFF to display Page 2
start = 0x2000;
}
// FIXME: check MIXED & don't redraw the lower area if it's set
for (uint16_t addr = start; addr <= start + 0x1FFF; addr+=2) {
if ((*switches) & S_DHIRES) {
// FIXME: inline & optimize
Draw14DoubleHiresPixelsAt(addr);
} else {
// FIXME: inline & optimize
Draw14HiresPixelsAt(addr);
}
}
}
void AppleDisplay::redrawLores()
{
// FIXME: can make more efficient by checking S_MIXED for lower bound
if (((*switches) & S_80COL) && ((*switches) & S_DHIRES)) {
for (uint16_t addr = 0x400; addr <= 0x400 + 0x3ff; addr++) {
uint8_t row, col;
deinterlaceAddress(addr, &row, &col);
if (col <= 39 && row <= 23) {
Draw80LoresPixelAt(mmu->readDirect(addr, 0), col, row, 1);
Draw80LoresPixelAt(mmu->readDirect(addr, 1), col, row, 0);
}
}
} else {
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);
}
}
}
}
void AppleDisplay::modeChange()
{
dirty = true;
}
void AppleDisplay::Draw80LoresPixelAt(uint8_t c, uint8_t x, uint8_t y, uint8_t offset)
{
// Just like 80-column text, this has a minor problem; we're taking
// 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);
}
}
}
void AppleDisplay::setSwitches(uint16_t *switches)
{
dirty = true;
dirtyRect.left = 0;
dirtyRect.right = 279;
dirtyRect.top = 0;
dirtyRect.bottom = 191;
this->switches = switches;
}
AiieRect AppleDisplay::getDirtyRect()
{
return dirtyRect;
}
bool AppleDisplay::needsRedraw()
{
if (dirty) {
// Figure out what graphics mode we're in and redraw it in its entirety.
if ((*switches) & S_TEXT) {
if ((*switches) & S_80COL) {
redraw80ColumnText(0);
} else {
redraw40ColumnText(0);
}
return true;
}
// Not text mode - what mode are we in?
if ((*switches) & S_HIRES) {
redrawHires();
} else {
redrawLores();
}
// Mixed graphics modes: draw text @ bottom
if ((*switches) & S_MIXED) {
if ((*switches) & S_80COL) {
redraw80ColumnText(20);
} else {
redraw40ColumnText(20);
}
}
}
return dirty;
}
void AppleDisplay::didRedraw()
{
dirty = false;
}
void AppleDisplay::displayTypeChanged()
{
textColor = g_displayType == m_monochrome?c_green:c_white;
}

81
src/appledisplay.h Normal file
View File

@ -0,0 +1,81 @@
#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();
virtual AiieRect getDirtyRect();
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);
void displayTypeChanged();
private:
bool deinterlaceAddress(uint16_t address, uint8_t *row, uint8_t *col);
bool deinterlaceHiresAddress(uint16_t address, uint8_t *row, uint16_t *col);
void Draw14DoubleHiresPixelsAt(uint16_t addr);
void Draw14HiresPixelsAt(uint16_t addr);
void Draw80LoresPixelAt(uint8_t c, uint8_t x, uint8_t y, uint8_t offset);
const unsigned char *xlateChar(uint8_t c, bool *invert);
void redraw40ColumnText(uint8_t startingY);
void redraw80ColumnText(uint8_t startingY);
void redrawHires();
void redrawLores();
private:
volatile bool dirty;
AiieRect dirtyRect;
uint16_t *switches; // pointer to the MMU's switches
uint16_t textColor;
};
#endif

370
src/applekeyboard.cpp Normal file
View File

@ -0,0 +1,370 @@
#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 <= 0x97) {
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;
}
// apply the apple keymap.
// FIXME: easier with an array, but is that better?
uint8_t AppleKeyboard::translateKeyWithModifiers(uint8_t k, uint8_t m)
{
// tolower, so we know what we're working with...
if (k >= 'A' && k <= 'Z') {
k = k - 'A' + 'a';
}
if (m & (USB_LEFT_CTRL | USB_RIGHT_CTRL | USB_LEFT_GUI | USB_RIGHT_GUI)) {
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 (m & (USB_LEFT_SHIFT | USB_RIGHT_SHIFT)) {
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 == JOY2) {
// Special handling: apple keys
mmu->isButton2Pressed = true;
return;
} else if (k == LOCK) {
// Special handling: caps lock
capsLockEnabled = !capsLockEnabled;
g_keyboard->setCaps(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 == JOY2) {
mmu->isButton2Pressed = false;
return;
}
if (k == LOCK) {
// Nothing to do when the caps lock key is released.
return;
}
if (anyKeyIsDown) {
anyKeyIsDown = false;
for (uint16_t i=0; i<sizeof(keysDown); i++) {
if (keysDown[i] && !isVirtualKey(i)) {
anyKeyIsDown = true;
break;
}
}
if (!anyKeyIsDown) {
mmu->setKeyDown(false);
}
}
}
void AppleKeyboard::keyDepressed(uint8_t k, uint8_t m)
{
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 (k && !isVirtualKey(k)) {
if (!anyKeyIsDown) {
mmu->setKeyDown(true);
anyKeyIsDown = true;
}
keyThatIsRepeating = translateKeyWithModifiers(k, m);
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 == JOY2) {
// Special handling: apple keys
mmu->isButton2Pressed = true;
return;
} else if (k == LOCK) {
// Special handling: caps lock
capsLockEnabled = !capsLockEnabled;
g_keyboard->setCaps(capsLockEnabled);
return;
} else if (k == SYSRQ) {
// Special handling: System request
biosRequest = true;
return;
}
}
void AppleKeyboard::keyReleased(uint8_t k, uint8_t m)
{
keysDown[k] = false;
// Special handling: apple keys
if (k == LA) {
mmu->isOpenApplePressed = false;
return;
}
if (k == RA) {
mmu->isClosedApplePressed = false;
return;
}
if (k == JOY2) {
mmu->isButton2Pressed = false;
return;
}
if (k == LOCK) {
// Nothing to do when the caps lock key is released.
return;
}
if (anyKeyIsDown) {
anyKeyIsDown = false;
for (uint16_t i=0; i<sizeof(keysDown); i++) {
if (keysDown[i] && !isVirtualKey(i)) {
anyKeyIsDown = true;
break;
}
}
if (!anyKeyIsDown) {
mmu->setKeyDown(false);
}
}
}
bool AppleKeyboard::getAnnunciator(uint8_t index){
return mmu->annunciators[index];
}
void AppleKeyboard::setButton(uint8_t index, bool val){
if (index == 0) mmu->isOpenApplePressed = val;
else if (index == 1) mmu->isClosedApplePressed = val;
else if (index == 2) mmu->isButton2Pressed = val;
}
void AppleKeyboard::setButtons(bool b0, bool b1, bool b2) {
mmu->isOpenApplePressed = b0;
mmu->isClosedApplePressed = b1;
mmu->isButton2Pressed = b2;
}
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;
}
}
}

64
src/applekeyboard.h Normal file
View File

@ -0,0 +1,64 @@
#ifndef __APPLEKEYBOARD_H
#define __APPLEKEYBOARD_H
#include <stdint.h>
#include "vmkeyboard.h"
#include "applemmu.h"
extern void biosInterrupt();
class AppleKeyboard : public VMKeyboard {
public:
AppleKeyboard(AppleMMU *m);
virtual ~AppleKeyboard();
virtual void keyDepressed(uint8_t k);
virtual void keyReleased(uint8_t k);
virtual void keyDepressed(uint8_t k, uint8_t m);
virtual void keyReleased(uint8_t k, uint8_t m);
virtual void setButton(uint8_t index, bool val);
virtual void setButtons(bool b0, bool b1, bool b2);
virtual bool getAnnunciator(uint8_t index);
virtual void maintainKeyboard(uint32_t cycleCount);
protected:
bool isVirtualKey(uint8_t kc);
uint8_t translateKeyWithModifiers(uint8_t k);
uint8_t translateKeyWithModifiers(uint8_t k, uint8_t m);
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

1146
src/applemmu-rom.h Normal file

File diff suppressed because it is too large Load Diff

993
src/applemmu.cpp Normal file
View File

@ -0,0 +1,993 @@
#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;
isButton2Pressed = false;
for (int8_t i=0; i<=7; i++) {
slots[i] = NULL;
}
for (int8_t i=0; i<3; i++) {
annunciators[i] = false;
}
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);
}
// If C800-CFFF isn't latched to a slot ROM, and we try to
// access a slot's memory space from C100-C7FF, then we need
// to latch in the slot's ROM.
if (slotLatch == -1 && address >= 0xc100 && address <= 0xc7ff) {
slotLatch = (address >> 8) & 0x07;
if (slotLatch == 3 && slot3rom) {
// Back off: UTA2E p. 5-28: don't latch in slot 3 ROM while
// the slot3rom flag is enabled
// fixme
slotLatch = 3;
} else {
updateMemoryPages();
}
}
// If we access CFFF, that unlatches slot ROM.
if (address == 0xCFFF) {
slotLatch = -1;
updateMemoryPages();
}
uint8_t res = readPages[address >> 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 >> 8][fromPage][address & 0xFF];
}
// Bypass MMU and write directly to a given page - also bypasses switches
void AppleMMU::writeDirect(uint16_t address, uint8_t fromPage, uint8_t val)
{
ramPages[address >> 8][fromPage][address & 0xFF] = val;
}
void AppleMMU::write(uint16_t address, uint8_t v)
{
if (address >= 0xC000 &&
address <= 0xC0FF) {
return writeSwitches(address, v);
}
// Don't allow writes to ROM
// Hard ROM, I/O, slots, whatnot
if (address >= 0xC100 && address <= 0xCFFF)
return;
// Bank-switched ROM/RAM areas
if (address >= 0xD000 && address <= 0xFFFF && !writebsr) {
return;
}
writePages[address >> 8][address & 0xFF] = v;
if (address >= 0x400 &&
address <= 0x7FF) {
// If it's text mode, or mixed mode, or lores graphics mode, then update.
if ((switches & S_TEXT) || (switches & S_MIXED) || (!(switches & S_HIRES))) {
// Force a redraw
display->modeChange();
}
return;
}
if (address >= 0x2000 &&
address <= 0x5FFF) {
if (switches & S_HIRES) {
// Force a redraw
display->modeChange();
}
}
}
// 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;
break;
case 0xC001: // SET80STORE
switches |= S_80STORE;
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;
// Registers C080 - C08F control bank switching.
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:
// Per ITA2E, p. 286:
// (address & 0x08) controls whether or not we are selecting from bank2. Per table 8-2,
// bank2 is active if address & 0x08 is zero. So if the bit is on, it's bank 1.
bank2 = (address & 0x08) ? false : true;
// (address & 0x04) is unused.
// (address & 0x02) is read-select: if it is set the same as
// (address & 0x01) then readbsr is true.
readbsr = ((address & 0x02) >> 1) == (address & 0x01);
// (address & 0x01) is write-select: if 1, we write BSR RAM; if 0, we write ROM.
// But it's a little more complicated than readbsr.
// Per UTA2E p. 5-23:
// "Writing to high RAM is enabled when the HRAMWRT' soft switch
// is reset. ... It is reset by even read access or any write
// access in the $C08X range. HRAMWRT' is reset by odd read
// access in the $C08X range when PRE-WRITE is set. It is set by
// even access in the CC08X range. Any other type of access
// causes HRAMWRT' to hold its current state."
if (address & 0x01) {
if (preWriteFlag)
writebsr = 1;
// Per UTA2E, p. 5-23: any other preWriteFlag leaves writebsr unchanged.
} else {
writebsr = false;
}
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);
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);
// UTA2E, p. 5-23: preWrite is set by odd read access, and reset
// by even read access
preWriteFlag = (address & 0x01);
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 bank2 ? 0x80 : 0x00;
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;
case 0xC019: // RDVBLBAR -- vertical blanking, for 4550 cycles of every 17030
// Should return 0 for 4550 of 17030 cycles. Since we're not really
// running full speed video, instead, I'm returning 0 for 4096 (2^12)
// of every 16384 (2^14) cycles; the math is easier.
if ((g_cpu->cycles & 0x3000) == 0x3000) {
return 0x00;
} else {
return 0xFF; // FIXME: is 0xFF correct? Or 0x80?
}
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->toggle();
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 0xC058: // annunciator 0 off
annunciators[0] = false;
g_keyboard->setAnnunciators();
return FLOATING;
case 0xC059: // annunciator 0 on
annunciators[0] = true;
g_keyboard->setAnnunciators();
return FLOATING;
case 0xC05A: // annunciator 1 off
annunciators[1] = false;
g_keyboard->setAnnunciators();
return FLOATING;
case 0xC05B: // annunciator 1 on
annunciators[1] = true;
g_keyboard->setAnnunciators();
return FLOATING;
case 0xC05C: // annunciator 2 off
annunciators[2] = false;
g_keyboard->setAnnunciators();
return FLOATING;
case 0xC05D: // annunciator 2 on
annunciators[2] = true;
g_keyboard->setAnnunciators();
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 0xC063: // Button 2
return isButton2Pressed ? 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_keyboard->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 0xC058: // annunciator 0 off
annunciators[0] = false;
g_keyboard->setAnnunciators();
return;
case 0xC059: // annunciator 0 on
annunciators[0] = true;
g_keyboard->setAnnunciators();
return;
case 0xC05A: // annunciator 1 off
annunciators[1] = false;
g_keyboard->setAnnunciators();
return;
case 0xC05B: // annunciator 1 on
annunciators[1] = true;
g_keyboard->setAnnunciators();
return;
case 0xC05C: // annunciator 2 off
annunciators[2] = false;
g_keyboard->setAnnunciators();
return;
case 0xC05D: // annunciator 2 on
annunciators[2] = true;
g_keyboard->setAnnunciators();
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_keyboard->startReading();
writePages[0xC0][0x64] = writePages[0xC0][0x65] = 0xFF;
break;
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:
// UTA2E, p. 5-23: preWrite is reset by any write access to these
preWriteFlag = 0;
// fall through...
case 0xC000:
case 0xC001:
case 0xC002:
case 0xC003:
case 0xC004:
case 0xC005:
case 0xC006:
case 0xC007:
case 0xC008:
case 0xC009:
case 0xC00A:
case 0xC00B:
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;
// Per UTA2E, p. 5-23:
// When a system reset occurs, all MMU soft switches are reset (turned off).
bank2 = false;
auxRamRead = auxRamWrite = false;
readbsr = writebsr = false;
altzp = false;
intcxrom = false;
slot3rom = false;
slotLatch = -1;
preWriteFlag = false;
// Clear all the pages
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;
}
}
}
// and set our expectation of what we're reading from/writing to
readPages[i] = writePages[i] = ramPages[i][0];
}
// Load system ROM
for (uint16_t i=0x80; i<=0xFF; i++) {
for (uint16_t k=0; k<0x100; k++) {
uint16_t idx = ((i-0x80) << 8) | k;
#ifdef TEENSYDUINO
uint8_t v = pgm_read_byte(&romData[idx]);
#else
uint8_t v = romData[idx];
#endif
for (int j=0; j<5; j++) {
// For the ROM section from 0xc100 .. 0xcfff, we load in to
// an alternate page space (INTCXROM).
if (i >= 0xc1 && i <= 0xcf) {
// If we want to convince the VM we've got 128k of RAM, we
// need to load C3 ROM in page 0 (but not 1, meaning there's
// a board installed); and C800.CFFF in both page [0] and [1]
// (meaning there's an extended 80-column ROM available,
// that is also physically in the slot).
// Everything else goes in page [1].
if (i == 0xc3)
ramPages[i][0][k] = v;
else if (i >= 0xc8)
ramPages[i][0][k] = ramPages[i][1][k] = v;
else
ramPages[i][1][k] = v;
} else {
// Everything else goes in page 0.
ramPages[i][0][k] = v;
}
}
}
}
// have each slot load its ROM
for (uint8_t slotnum = 0; slotnum <= 7; slotnum++) {
if (slots[slotnum]) {
slots[slotnum]->loadROM(ramPages[0xC0 + slotnum][0]);
}
}
// update the memory read/write flags &c. Not strictly necessary, if
// we're really setting all the RAM flags to the right default
// settings above - but better safe than sorry?
updateMemoryPages();
}
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);
if (ramPages[i][j] == NULL) Serial.println("MMU Out of memory!");
}
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<4; j++) {
ramPages[i][j] = (uint8_t *)malloc(0x100);
if (ramPages[i][j] == NULL) Serial.println("MMU Out of memory!");
}
for (uint8_t j=4; j<5; j++) {
ramPages[i][j] = NULL;
}
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) {
// When S_80STORE is on, we switch 400-800 and 2000-4000 based on S_PAGE2.
// The behavior is different based on whether HIRESON/OFF is set.
if (switches & S_PAGE2) {
// Regardless of HIRESON/OFF, pages 0x400-0x7ff are switched on S_PAGE2
for (uint8_t idx = 0x04; idx < 0x08; idx++) {
readPages[idx] = ramPages[idx][1];
writePages[idx] = ramPages[idx][1];
}
// but 2000-3fff switches based on S_PAGE2 only if HIRES is on.
// HIRESOFF: 400-7ff doesn't switch based on read/write flags
// b/c it switches based on S_PAGE2 instead
// HIRESON: 400-800, 2000-3fff doesn't switch
// b/c they switch based on S_PAGE2 instead
// 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];
}
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 (slot3rom) {
readPages[0xc3] = ramPages[0xc3][1];
for (int i=0xc8; i<=0xcf; i++) {
readPages[i] = ramPages[i][1];
}
}
}
// If slotLatch is set (!= -1), then we are mapping 2k of ROM
// for a given peripheral to C800..CFFF.
if (slotLatch != -1) {
// FIXME: the only peripheral we support this with right now is
// the 80-column card.
if (slotLatch == 3) {
for (int i=0xc8; i <= 0xcf; i++) {
readPages[i] = ramPages[i][1];
}
}
}
// set zero-page & stack pages based on altzp flag
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];
}
}
// Set bank-switched ram reading from readbsr & bank2
if (readbsr) {
// 0xD0 - 0xE0 has 4 possible banks:
if (!bank2) {
// Bank 1 RAM: either in main RAM (1) or in the extended memory
// card (3):
for (uint8_t idx = 0xd0; idx < 0xe0; idx++) {
readPages[idx] = ramPages[idx][altzp ? 3 : 1];
}
} else {
// Bank 2 RAM: either in main RAM (2) or in the extended memory
// card (4):
for (uint8_t idx = 0xd0; idx < 0xe0; idx++) {
readPages[idx] = ramPages[idx][altzp ? 4 : 2];
}
}
// ... but 0xE0 - 0xFF has just the motherboard RAM (1) and
// extended memory card RAM (2):
for (uint16_t idx = 0xe0; idx < 0x100; idx++) {
readPages[idx] = ramPages[idx][altzp ? 2 : 1];
}
} else {
// Built-in ROM
for (uint16_t idx = 0xd0; idx < 0x100; idx++) {
readPages[idx] = ramPages[idx][0];
}
}
if (writebsr) {
if (!bank2) {
for (uint8_t idx = 0xd0; idx < 0xe0; idx++) {
writePages[idx] = ramPages[idx][altzp ? 3 : 1];
}
} else {
for (uint8_t idx = 0xd0; idx < 0xe0; idx++) {
writePages[idx] = ramPages[idx][altzp ? 4 : 2];
}
}
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];
}
}
}

99
src/applemmu.h Normal file
View File

@ -0,0 +1,99 @@
#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 writeDirect(uint16_t address, uint8_t fromPage, uint8_t val);
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);
bool annunciators[3];
void resetDisplay();
void updateMemoryPages();
protected:
void allocateMemory();
uint8_t readSwitches(uint16_t address);
void writeSwitches(uint16_t address, uint8_t v);
void handleMemorySwitches(uint16_t address, uint16_t lastSwitch);
private:
AppleDisplay *display;
public: // 'public' for debugging
uint16_t switches;
bool auxRamRead;
bool auxRamWrite;
bool bank2;
bool readbsr;
bool writebsr;
bool altzp;
bool intcxrom;
bool slot3rom;
int8_t slotLatch;
bool preWriteFlag; // see UTA2E p. 5-23
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;
bool isButton2Pressed;
};
#endif

100
src/applevm.cpp Normal file
View File

@ -0,0 +1,100 @@
#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);
#else
sdlClock = new SDLClock((AppleMMU *)mmu);
((AppleMMU *)mmu)->setSlot(7, sdlClock);
#endif
}
AppleVM::~AppleVM()
{
#ifdef TEENSYDUINO
delete teensyClock;
#else
delete sdlClock;
#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
}
const char *AppleVM::DiskName(uint8_t drivenum)
{
return disk6->DiskName(drivenum);
}
void AppleVM::ejectDisk(uint8_t drivenum, bool drawIt)
{
disk6->ejectDisk(drivenum, drawIt);
}
void AppleVM::insertDisk(uint8_t drivenum, const char *filename, bool drawIt)
{
disk6->insertDisk(drivenum, filename, drawIt);
}
VMKeyboard * AppleVM::getKeyboard()
{
return keyboard;
}

44
src/applevm.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef __APPLEVM_H
#define __APPLEVM_H
#include "cpu.h"
#include "appledisplay.h"
#include "diskii.h"
#include "vmkeyboard.h"
#ifdef TEENSYDUINO
#include "teensy-clock.h"
#else
#include "sdl-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, bool drawIt = true);
void insertDisk(uint8_t drivenum, const char *filename, bool drawIt = true);
virtual VMKeyboard *getKeyboard();
DiskII *disk6;
protected:
VMKeyboard *keyboard;
#ifdef TEENSYDUINO
TeensyClock *teensyClock;
#else
SDLClock *sdlClock;
#endif
};
#endif

137
src/bios-font.h Normal file
View File

@ -0,0 +1,137 @@
// 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
const uint8_t BiosFont[1441] 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, // ~
0x00,0x00,0x00,0x20,0x60,0xFF,0xFF,0x60,0x20,0x00,0x00,0x00, //0x7F, 'bkspace'
0x00,0x00,0xE0,0x89,0x92,0xD2,0x8A,0x8A,0x8A,0xD1,0x00,0x00, //0x80, 'esc'
0x00,0x00,0xE0,0x42,0x52,0x4B,0x5B,0x5B,0x5B,0x4A,0x00,0x00, //0x81, 'tab'
0x00,0x00,0x40,0xA1,0x89,0x8D,0x89,0x89,0xA9,0x45,0x00,0x00, //0x82, 'ctl'
0x00,0x00,0x40,0xA8,0x88,0x4C,0x2A,0x2A,0xAA,0x4A,0x00,0x00, //0x83, 'sh'
0x00,0x00,0x14,0xA4,0x26,0xB4,0xA4,0xA4,0xA4,0xA2,0x00,0x00, //0x84, 'ift'
0x00,0x06,0x08,0x6E,0x99,0x83,0x84,0x84,0xC3,0x66,0x3C,0x00, //0x85, 'lapple'
0x00,0x06,0x08,0x6E,0xFF,0xFF,0xFC,0xFC,0xFF,0x7E,0x3C,0x00, //0x86, 'rapple'
0x00,0x00,0x00,0x70,0x8F,0x8F,0x8F,0x70,0x00,0x00,0x00,0x00, //0x87, 'ljoy'
0x00,0x00,0x00,0x0E,0xF1,0xF1,0xF1,0x0E,0x00,0x00,0x00,0x00, //0x88, 'rjoy'
0x00,0x00,0x38,0x44,0x44,0x44,0x38,0x38,0x38,0x38,0x00,0x00, //0x89, 'ujoy'
0x00,0x00,0x38,0x38,0x38,0x38,0x44,0x44,0x44,0x38,0x00,0x00, //0x8A, 'djoy'
0x00,0x00,0x03,0x0F,0x39,0xE1,0xE1,0x39,0x0F,0x03,0x00,0x00, //0x8B, 'lkey'
0x00,0x00,0xC0,0xF0,0x9C,0x87,0x87,0x9C,0xF0,0xC0,0x00,0x00, //0x8C, 'rkey'
0x00,0x00,0x18,0x18,0x3C,0x24,0x66,0x42,0xC3,0xFF,0x00,0x00, //0x8D, 'ukey'
0x00,0x00,0xFF,0xC3,0x42,0x66,0x24,0x3C,0x18,0x18,0x00,0x00, //0x8E, 'dkey'
0x00,0x00,0x03,0x03,0x23,0x63,0xFF,0xFE,0x60,0x20,0x00,0x00, //0x8F, 'ent'
0x00,0x00,0x40,0xA0,0x80,0x9B,0x55,0x35,0x33,0xB1,0x51,0x00, //0x90, 'srq'
0x00,0x00,0x40,0xAC,0x8A,0x4A,0x2C,0x28,0xA8,0x48,0x00,0x00, //0x91, 'sp'
0x00,0x00,0x00,0x8B,0x52,0x52,0xD3,0xD2,0xD2,0x4B,0x00,0x00, //0x92, 'ace'
0x00,0x00,0x80,0x84,0x8A,0x8A,0x8A,0x8A,0x8A,0xE4,0x00,0x00, //0x93, 'lo'
0x00,0x00,0x00,0x4A,0xAA,0x8C,0x8C,0x8A,0xAA,0x4A,0x00,0x00, //0x94, 'ck'
0x00,0x00,0x90,0x90,0xD5,0xB5,0x95,0x95,0x95,0x92,0x00,0x00, //0x95, 'nu'
0x00,0x00,0x00,0x00,0x44,0x44,0x44,0x44,0x44,0x77,0x00,0x00, //0x96, 'll'
0x00,0x00,0xFF,0x81,0x82,0x99,0x99,0x81,0x99,0xFF,0x00,0x00, //0x97, 'disk'
0x00,
};

947
src/bios.cpp Normal file
View File

@ -0,0 +1,947 @@
#include "bios.h"
#include "applevm.h"
#include "physicalkeyboard.h"
#include "teensy-usb-keyboard.h"
#include "cpu.h"
#include "teensy-filemanager.h"
#include "teensy-display.h"
#include "widgets.h"
#include "globals.h"
enum {
ACT_EXIT = 0,
ACT_RESET = 1,
ACT_REBOOT = 2,
ACT_DISK1 = 3,
ACT_DISK2 = 4,
ACT_VOLUME = 5,
ACT_SAVESTATE = 6,
ACT_RESTORESTATE = 7,
ACT_MONITOR = 8,
ACT_DISPLAYTYPE = 9,
ACT_SYNC = 10,
ACT_DEBUG = 11,
ACT_JOYMODE = 12,
ACT_JOYTRIM = 13,
ACT_KEYPRESETS = 14,
NUM_ACTIONS = 15
};
static const char *titles[NUM_ACTIONS] = { "Resume",
"Reset",
"Cold Reboot",
"%s Disk 1",
"%s Disk 2",
"Volume",
"Save State",
"Restore State",
"Drop to Monitor",
"Display: %s",
"Sync: %s",
"Debug: %s",
"Stick: %s",
"Stick %s",
"Keyboard Preset"
};
static const char* KeyNames[141] =
{
"\x95\x96","\x81\x41","\x81\x42","\x81\x43","\x81\x44","\x81\x45","\x81\x46","\x81G",
"\x8B","\x80","\x8E","\x8D","\x81L","\x8F","\x81N","\x81O",
"\x81P","\x81Q","\x81R","\x81S","\x81T","\x8C","\x81V","\x81W",
"\x81X","\x81Y","\x81Z","\x7F","28","29","30","31", //32
"\x91\x92","!","\"","#","$","%","&","\'","(",")","*","+",",","-",".","/", //48
"0","1","2","3","4","5","6","7","8","9",":",";","<","=",">","?","@", //66
"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q", //83
"R","S","T","U","V","W","X","Y","Z","[","\\","]","^","_","'","a","b", //100
"c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s", //117
"t","u","v","w","x","y","z","{","|","}","~","\x7F","?", //128
"\x81","L\x82\x63","R\x82\x83","\x93\x94","\x85","\x86","\x90","\x87","\x88","\x89","\x8A"
};
// FIXME: abstract the pin # rather than repeating it here
#define RESETPIN 39
#define BATTERYPIN A15
#define CHARGEPIN A16
extern int16_t g_volume; // FIXME: external global. icky.
extern int8_t debugMode; // and another. :/
int8_t joyMode;
Widgets widgets;
// FIXME: and these need abstracting out of the main .ino !
enum {
D_NONE = 0,
D_SHOWFPS = 1,
D_SHOWMEMFREE = 2,
D_SHOWPADDLES = 3,
D_SHOWPC = 4,
D_SHOWCYCLES = 5,
D_SHOWBATTERY = 6,
D_SHOWTIME = 7
};
const char *staticPathConcat(const char *rootPath, const char *filePath)
{
static char buf[MAXPATH];
strncpy(buf, rootPath, sizeof(buf)-1);
strncat(buf, filePath, sizeof(buf)-strlen(buf)-1);
return buf;
}
BIOS::BIOS()
{
strcpy(rootPath, "/");
selectedFile = -1;
for (int8_t i=0; i<BIOS_MAXFILES; i++) {
// Put end terminators in place; strncpy won't copy over them
fileDirectory[i][BIOS_MAXPATH] = '\0';
}
inMainMenu = false;
dirPage = 0;
joyMode = JOY_MODE_ANA_ABS;
}
BIOS::~BIOS()
{
}
#define BATT_CHG 0
#define BATT_FULL 1
#define BATT_OK 2
#define BATT_LOW 3
#define BATT_OUT 4
void updateLED() {
static uint8_t batState = BATT_CHG;
static uint8_t state;
if (g_charge > 15) state = BATT_CHG; //charging
else {
if (g_battery > 3400) state = BATT_OK; //Good
else if (g_battery > 3200) state = BATT_LOW; //Low
else if (g_battery > 1000) state = BATT_OUT; //Expired
else state = BATT_FULL; //Fully charged (off)
}
if (state != batState) {
if (state == BATT_CHG) g_display->LED(0, 0, 64);
else if (state == BATT_OK) g_display->LED(0, 32, 0); //Good
else if (state == BATT_LOW) g_display->LED(32, 32, 0); //Low
else if (state == BATT_OUT) g_display->LED(32, 0, 0); //Expired
else g_display->LED(0, 32, 0); //Fully charged (off)
batState = state;
}
}
bool BIOS::updateDiagnostics() {
static unsigned long nextBattCheck = 0;
static float battery = 0;
static float charge = 0;
if (millis() >= nextBattCheck) {
// FIXME: what about rollover?
nextBattCheck = millis() + 500; //30 * 1000; // check every 30 seconds
//Lightly filtered
battery = analogRead(BATTERYPIN) / 2 + battery / 2;
charge = analogRead(CHARGEPIN) / 2 + charge / 2 ;
//vccLevel = 1195 * 4096 /analogRead(39);
//analogReference(INTERNAL);
//cpuTemp = 25.0 + 0.17083 * (2454.19 - analogRead(38));
//analogReference(DEFAULT);
g_battery = battery * 1.685f; //mV
g_charge = charge * 0.206f; //mA
updateLED();
return true;
}
return false;
}
void resetJoyMode() {
if (joyMode == JOY_MODE_JOYPORT1 || joyMode == JOY_MODE_JOYPORT2) {
joyMode = JOY_MODE_ANA_ABS;
g_keyboard->setJoymode(joyMode);
}
}
void BIOS::drawBiosScreen(int8_t prevAction) {
g_display->clrScr(210,202,159); //(0x0010);
g_display->fillRect(0, 0, 319, 15, DARK_BLUE);
g_display->drawLine(0,16, 320, 16, WHITE);
g_display->drawString(M_NORMAL, 2, 2, "BIOS Configuration");
g_display->fillRoundRect(4, 24, 132, 213, 6, DARK_BLUE);
g_display->drawRoundRect(4, 24, 132, 213, 6, WHITE);
drawKeyboard();
drawThumb();
drawInfo();
DrawMainMenu(prevAction);
}
int8_t savedSelection = ACT_EXIT;
bool BIOS::runUntilDone()
{
int8_t prevAction = savedSelection;
int8_t key = 0;
bool settingsDidChange = 0;
drawBiosScreen(prevAction);
while (1) {
inMainMenu = true;
key = GetAction(prevAction);
inMainMenu = false;
switch (prevAction) {
case ACT_EXIT:
goto done;
case ACT_REBOOT:
resetJoyMode();
ColdReboot();
goto done;
case ACT_RESET:
resetJoyMode();
WarmReset();
goto done;
case ACT_MONITOR:
((AppleVM *)g_vm)->Monitor();
goto done;
case ACT_DISPLAYTYPE:
if (key == LARR) {
g_displayType--;
if (g_displayType < 0) g_displayType = 3;
} else {
g_displayType++;
g_displayType %= 4; // FIXME: abstract max #
}
((AppleDisplay*)g_display)->displayTypeChanged();
break;
case ACT_DEBUG:
if (key == LARR) {
debugMode--;
if (debugMode < 0) debugMode = 7;
} else {
debugMode++;
debugMode %= 8; // FIXME: abstract max #
}
break;
case ACT_DISK1:
if (((AppleVM *)g_vm)->DiskName(0)[0] != '\0') {
((AppleVM *)g_vm)->ejectDisk(0, false);
drawInfo();
} else {
if (SelectDiskImage()) {
((AppleVM *)g_vm)->insertDisk(0, staticPathConcat(rootPath, fileDirectory[selectedFile]), false);
goto done;
}
drawBiosScreen(prevAction);
}
break;
case ACT_DISK2:
if (((AppleVM *)g_vm)->DiskName(1)[0] != '\0') {
((AppleVM *)g_vm)->ejectDisk(1, false);
} else {
if (SelectDiskImage()) {
((AppleVM *)g_vm)->insertDisk(1, staticPathConcat(rootPath, fileDirectory[selectedFile]), false);
goto done;
}
drawBiosScreen(prevAction);
}
break;
case ACT_VOLUME:
if (key == RARR) {
g_volume ++;
if (g_volume > 15) g_volume = 15;
settingsDidChange = true;
}
if (key == LARR) {
g_volume--;
if (g_volume < 0) g_volume = 0;
settingsDidChange = true;
}
break;
case ACT_SYNC:
g_screenSync = !g_screenSync;
settingsDidChange = true;
break;
case ACT_JOYMODE:
if (key == LARR) {
joyMode--;
if (joyMode < 0) joyMode = 3;
g_keyboard->setJoymode(joyMode);
ClearMenuItem((int)ACT_JOYTRIM);
DrawMenuItem((int)ACT_JOYTRIM, prevAction);
} else {
joyMode++;
if (joyMode > 3) joyMode = 0; // FIXME: abstract max #
g_keyboard->setJoymode(joyMode);
ClearMenuItem((int)ACT_JOYTRIM);
DrawMenuItem((int)ACT_JOYTRIM, prevAction);
}
break;
case ACT_JOYTRIM:
if (joyMode == JOY_MODE_ANA_ABS) {
if (stickTrim()) settingsDidChange = true;
} else {
if (stickSpeed()) settingsDidChange = true;
}
break;
case ACT_KEYPRESETS:
break;
case ACT_SAVESTATE:
saveState();
break;
case ACT_RESTORESTATE:
restoreState();
goto done;
}
}
done:
// Undo whatever damage we've done to the screen
g_display->redraw();
g_display->blit({0, 0, 191, 279});
// return true if any persistent setting changed that we want to store in eeprom
return settingsDidChange;
}
void BIOS::WarmReset()
{
g_cpu->Reset();
}
void BIOS::ColdReboot()
{
g_vm->Reset();
g_cpu->Reset();
}
const char KeyMappings[5][15] =
{
{ LJOY, RJOY, UJOY, DJOY, LA, RA, 'j', 'k', '1', UARR, '2', RET, LARR, DARR, RARR },
{ LJOY, RJOY, UJOY, DJOY, LA, RA, 'a', 'b', 'c', 'd', 'e', RET, 'f', 'g', 'h' },
{ LJOY, RJOY, UJOY, DJOY, LA, RA, '1', '2', 'u', 'i', 'o', RET, 'j', 'k', 'l' },
{ LARR, RARR, UARR, DARR, ' ', RET, 'p', 'k', '1', UARR, '2', ' ', LARR, DARR, RARR },
{ LJOY, RJOY, UJOY, DJOY, LA, RA, 'y', 'n', '1', '2', '3', RET, '4', '5', '6' },
};
void loadMap(uint8_t index) {
for (int count = 0; count < 15; count++) {
g_keyboard->setMapping(count, KeyMappings[index][count]);
}
}
void BIOS::biosIdle(){
static uint8_t batState = BATT_FULL;
if(updateDiagnostics()) {
if (inMainMenu) {
uint8_t state;
if (g_charge > 15) state = BATT_CHG; //charging
else if (g_battery > 1000) state = BATT_OK; //Discharging
else state = BATT_FULL; //Fully charged (off)
if (state != batState || state == BATT_OK) {
widgets.drawBatteryText();
batState = state;
}
}
}
yield();
}
void BIOS::drawKeyboard() {
//g_display->drawRoundRect(4, 180, 312, 58, 6, WHITE);
widgets.drawBattery(258, 140, WHITE);
g_display->setBackground(BLACK);
widgets.drawStick(140, 140, 48, 44, WHITE, KeyNames[g_keyboard->getMapping(0)], KeyNames[g_keyboard->getMapping(1)], KeyNames[g_keyboard->getMapping(2)], KeyNames[g_keyboard->getMapping(3)]);
widgets.drawButton(192, 155, WHITE, KeyNames[g_keyboard->getMapping(4)]);
widgets.drawButton(222, 141, WHITE, KeyNames[g_keyboard->getMapping(5)]);
widgets.drawKey(140, 188, 32, 22, WHITE, KeyNames[g_keyboard->getMapping(6)]);
widgets.drawKey(176, 188, 32, 22, WHITE, KeyNames[g_keyboard->getMapping(7)]);
widgets.drawKey(212, 188, 32, 22, WHITE, KeyNames[g_keyboard->getMapping(8)]);
widgets.drawKey(248, 188, 32, 22, WHITE, KeyNames[g_keyboard->getMapping(9)]);
widgets.drawKey(284, 188, 32, 22, WHITE, KeyNames[g_keyboard->getMapping(10)]);
widgets.drawKey(140, 214, 68, 22, WHITE, KeyNames[g_keyboard->getMapping(11)]);
widgets.drawKey(212, 214, 32, 22, WHITE, KeyNames[g_keyboard->getMapping(12)]);
widgets.drawKey(248, 214, 32, 22, WHITE, KeyNames[g_keyboard->getMapping(13)]);
widgets.drawKey(284, 214, 32, 22, WHITE, KeyNames[g_keyboard->getMapping(14)]);
g_display->setBackground(DARK_BLUE);
}
#define INFO_SIZE 22
void BIOS::drawInfo(){
char infoBuf[INFO_SIZE];
g_display->fillRect(141, 124, 8*INFO_SIZE, 13, DARK_BLUE);
g_display->drawRect(140, 123, 8*INFO_SIZE+2, 15, WHITE);
if (((AppleVM *)g_vm)->DiskName(0)[0] != '\0') {
char* start = strrchr(((AppleVM *)g_vm)->DiskName(0), '/') + 1;
int8_t length = strrchr(((AppleVM *)g_vm)->DiskName(0), '.') - start;
if (length > INFO_SIZE - 1) length = INFO_SIZE - 1;
if(start && length > 0) {
strncpy(infoBuf, start, length);
infoBuf[length] = '\0';
g_display->drawString(M_NORMAL, 141, 124, "\x97");
g_display->drawString(M_NORMAL, 151, 124, infoBuf);
}
}
/* if (((AppleVM *)g_vm)->DiskName(1) != '\0') {
strncpy(infoBuf, ((AppleVM *)g_vm)->DiskName(1), INFO_SIZE-1);
g_display->drawString(M_NORMAL, 150, 48, infoBuf);
}*/
}
void BIOS::drawThumb(){
g_display->drawRect(157, 24, 142, 98, WHITE);
g_display->drawThumb(158, 25);
}
uint8_t BIOS::GetAction(int8_t& selection)
{
uint8_t key;
while (1) {
UpdateMainMenu(selection);
while (!g_keyboard->kbhit() && digitalRead(RESETPIN) == HIGH) {
biosIdle();
// Wait for either a keypress or the reset button to be pressed
}
if (digitalRead(RESETPIN) == LOW) {
// wait until it's no longer pressed
while (digitalRead(RESETPIN) == HIGH)
;
delay(100); // wait long enough for it to debounce
// then return an exit code
selection = ACT_EXIT;
return SYSRQ;
}
key = g_keyboard->read();
switch (key) {
case SYSRQ:
selection = ACT_EXIT;
return key;
case DARR:
selection++;
selection %= NUM_ACTIONS;
break;
case UARR:
selection--;
if (selection < 0) selection = NUM_ACTIONS-1;
break;
case RET:
case LARR:
case RARR:
if (isActionActive(selection)) return key;
break;
case '1':
loadMap(0);
drawKeyboard();
break;
case '2':
loadMap(1);
drawKeyboard();
break;
case '3':
loadMap(2);
drawKeyboard();
break;
case '4':
loadMap(3);
drawKeyboard();
break;
case '5':
loadMap(4);
drawKeyboard();
break;
}
savedSelection = selection;
}
}
bool BIOS::isActionActive(int8_t action)
{
// don't return true for disk events that aren't valid
switch (action) {
case ACT_EXIT:
case ACT_RESET:
case ACT_REBOOT:
case ACT_MONITOR:
case ACT_DISPLAYTYPE:
case ACT_SYNC:
case ACT_DEBUG:
case ACT_DISK1:
case ACT_DISK2:
case ACT_VOLUME:
case ACT_JOYMODE:
case ACT_KEYPRESETS:
case ACT_SAVESTATE:
case ACT_RESTORESTATE:
return true;
case ACT_JOYTRIM:
return (joyMode == JOY_MODE_ANA_ABS || joyMode == JOY_MODE_ANA_REL);
}
/* NOTREACHED */
return false;
}
void BIOS::DrawMainMenu(int8_t selection)
{
g_display->fillRect(7, 27, 126, 207, DARK_BLUE);
for (int i=0; i<NUM_ACTIONS; i++) {
DrawMenuItem(i, selection);
}
}
void BIOS::UpdateMainMenu(int8_t selection)
{
static int8_t oldSelection = -1;
if (selection != oldSelection) {
if (oldSelection >= 0) {
ClearMenuItem(oldSelection);
DrawMenuItem(oldSelection, selection);
}
oldSelection = selection;
}
ClearMenuItem(selection);
DrawMenuItem(selection, selection);
}
void BIOS::DrawMenuItem(int8_t item, int8_t selection)
{
char buf[25];
if (item == ACT_DISK1 || item == ACT_DISK2) {
sprintf(buf, titles[item], ((AppleVM *)g_vm)->DiskName(item - ACT_DISK1)[0] ? "Eject" : "Insert");
} else if (item == ACT_DISPLAYTYPE) {
switch (g_displayType) {
case m_blackAndWhite:
sprintf(buf, titles[item], "B&W");
break;
case m_monochrome:
sprintf(buf, titles[item], "Mono");
break;
case m_ntsclike:
sprintf(buf, titles[item], "NTSC");
break;
case m_perfectcolor:
sprintf(buf, titles[item], "RGB");
break;
}
} else if (item == ACT_SYNC) {
if (g_screenSync) {
sprintf(buf, titles[item], "Screen");
} else {
sprintf(buf, titles[item], "Sound");
}
} else if (item == ACT_DEBUG) {
switch (debugMode) {
case D_NONE:
sprintf(buf, titles[item], "off");
break;
case D_SHOWFPS:
sprintf(buf, titles[item], "FPS");
break;
case D_SHOWMEMFREE:
sprintf(buf, titles[item], "FreeMem");
break;
case D_SHOWPADDLES:
sprintf(buf, titles[item], "Paddles");
break;
case D_SHOWPC:
sprintf(buf, titles[item], "PC");
break;
case D_SHOWCYCLES:
sprintf(buf, titles[item], "Cycles");
break;
case D_SHOWBATTERY:
sprintf(buf, titles[item], "Battery");
break;
case D_SHOWTIME:
sprintf(buf, titles[item], "Time");
break;
}
} else if (item == ACT_JOYMODE) {
switch (joyMode) {
case JOY_MODE_ANA_ABS:
sprintf(buf, titles[item], "ana ABS");
break;
case JOY_MODE_ANA_REL:
sprintf(buf, titles[item], "ana REL");
break;
case JOY_MODE_JOYPORT1:
sprintf(buf, titles[item], "joyport1");
break;
case JOY_MODE_JOYPORT2:
sprintf(buf, titles[item], "joyport2");
break;
}
} else if (item == ACT_JOYTRIM) {
if (joyMode == JOY_MODE_ANA_REL) {
sprintf(buf, titles[item], "Speed");
} else {
sprintf(buf, titles[item], "Trim");
}
} else {
strcpy(buf, titles[item]);
}
if (isActionActive(item)) {
g_display->drawString(selection == item ? M_SELECTHIGHLIGHT : M_NORMAL, 10, 30 + 13 * item, buf);
} else {
g_display->drawString(selection == item ? M_SELECTDISABLED : M_DISABLED, 10, 30 + 13 * item, buf);
}
// draw the volume bar
if (item == ACT_VOLUME) {
uint16_t volCutoff = 59.0 * (float)((float) g_volume / 15.0);
g_display->drawRoundRect(66,32 + 13 * ACT_VOLUME,64,10,3,selection == item ? YELLOW : WHITE);
g_display->fillRect(68,34 + 13 * ACT_VOLUME,volCutoff,5,selection == item ? YELLOW : WHITE);
}
}
void BIOS::ClearMenuItem(int8_t item)
{
g_display->fillRect(10, 30 + 13 * item, 120, 12, DARK_BLUE);
}
void drawCrossHairs(int16_t x, int16_t y, uint16_t color) {
g_display->drawRect(156+x, 23+y, 5, 5, color);
g_display->drawLine(158+x, 22+y, 158+x, 28+y, color);
g_display->drawLine(155+x, 25+y, 161+x, 25+y, color);
}
#define TRIM_STEP 4
#define TRIM_LOLIMIT 15
#define TRIM_HILIMIT 239
bool BIOS::stickTrim()
{
uint8_t trimX = g_joyTrimX;
uint8_t trimY = g_joyTrimY;
uint8_t oldX = 70;
uint8_t oldY = 49;
uint8_t x = oldX, y = oldY;
char buf[7];
g_display->fillRect(157, 24, 141, 97, WHITE);
g_display->drawRect(157, 24, 142, 98, BLACK);
while (1) {
x = (float)trimX / 255.0 * 140.0;
y = (float)trimY / 255.0 * 96.0;
if (x != oldX || y != oldY) {
drawCrossHairs(oldX, oldY, WHITE);
sprintf(buf, "X: %d ", trimX);
g_display->drawString(M_SELECTED, 159, 26, buf);
sprintf(buf, "Y: %d ", trimY);
g_display->drawString(M_SELECTED, 159, 38, buf);
drawCrossHairs(x, y, BLACK);
oldX = x;
oldY = y;
}
while (!g_keyboard->kbhit())
biosIdle();
switch (g_keyboard->read()) {
case LARR:
trimX -= TRIM_STEP;
if (trimX < TRIM_LOLIMIT) trimX = TRIM_LOLIMIT;
break;
case RARR:
trimX += TRIM_STEP;
if (trimX > TRIM_HILIMIT) trimX = TRIM_HILIMIT;
break;
case UARR:
trimY -= TRIM_STEP;
if (trimY < TRIM_LOLIMIT) trimY = TRIM_LOLIMIT;
break;
case DARR:
trimY += TRIM_STEP;
if (trimY > TRIM_HILIMIT) trimY = TRIM_HILIMIT;
break;
case RET:
g_joyTrimX = trimX;
g_joyTrimY = trimY;
drawThumb();
return true;
case ESC:
drawThumb();
return false;
}
}
}
void drawSpeedBar(uint8_t x) {
g_display->drawRoundRect(160,45,134,16,3,BLACK);
g_display->fillRect(162,47,129,11,WHITE);
g_display->fillRect(162,47,x,11,BLACK);
}
bool BIOS::stickSpeed()
{
uint8_t speedX = g_joySpeed;
uint8_t oldX = 70;
uint8_t x = oldX;
char buf[20];
g_display->fillRect(157, 24, 141, 97, WHITE);
g_display->drawRect(157, 24, 142, 98, BLACK);
while (1) {
x = (float)speedX / 26.0 * 129.0;
if (x != oldX) {
sprintf(buf, "Speed: %d ", speedX);
g_display->drawString(M_SELECTED, 159, 26, buf);
drawSpeedBar(x);
oldX = x;
}
while (!g_keyboard->kbhit())
biosIdle();
switch (g_keyboard->read()) {
case LARR:
speedX -= 1;
if (speedX < 1) speedX = 1;
break;
case RARR:
speedX += 1;
if (speedX > 25) speedX = 25;
break;
case RET:
g_joySpeed = speedX;
drawThumb();
return true;
case ESC:
drawThumb();
return false;
}
}
}
//---------------------------------------------------------------------------------------------------
// Memory state image
bool BIOS::saveState() {
uint8_t fn = g_filemanager->openFile("/state.img");
if (fn == -1) return false;
g_filemanager->writeState(fn);
g_filemanager->closeFile(fn);
return true;
}
bool BIOS::restoreState() {
uint8_t fn = g_filemanager->openFile("/state.img");
if (fn == -1) return false;
g_filemanager->readState(fn);
g_filemanager->closeFile(fn);
return true;
}
//---------------------------------------------------------------------------------------------------
// Disk stuff
// return true if the user selects an image
// sets selectedFile (index; -1 = "nope") and fileDirectory[][] (names of up to BIOS_MAXFILES files)
bool BIOS::SelectDiskImage()
{
static int8_t sel = 0;
static int8_t oldSel = 0;
static int8_t page = 0;
static int8_t oldPage = 0;
bool force = true;
while (1) {
// Serial.print(page);
// Serial.print(",");
// Serial.println(force);
if (page != oldPage || force) {
DrawDiskNames(page, sel, force);
force = false;
oldPage = page;
}
else if (sel != oldSel) {
DrawDiskName(oldSel, page, sel);
DrawDiskName(sel, page, sel);
oldSel = sel;
}
while (!g_keyboard->kbhit())
biosIdle();
switch (g_keyboard->read()) {
case DARR:
sel++;
sel %= BIOS_MAXFILES + 2;
break;
case UARR:
sel--;
if (sel < 0) sel = BIOS_MAXFILES + 1;
break;
case RET:
if (sel == 0) {
page--;
if (page < 0) page = 0;
// else sel = BIOS_MAXFILES + 1;
}
else if (sel == BIOS_MAXFILES+1) {
if (hasNextPage) page++;
//sel = 0;
} else {
if (strcmp(fileDirectory[sel-1], "../") == 0) {
// Go up a directory (strip a directory name from rootPath)
stripDirectory();
page = 0;
force = true;
//sel = 0;
continue;
} else if (fileDirectory[sel-1][strlen(fileDirectory[sel-1])-1] == '/') {
// Descend in to the directory. FIXME: file path length?
strcat(rootPath, fileDirectory[sel-1]);
sel = oldSel = 0;
page = oldPage = 0;
force = true;
continue;
} else {
selectedFile = sel - 1;
return true;
}
}
break;
case ESC:
return false;
}
}
}
void BIOS::stripDirectory()
{
rootPath[strlen(rootPath)-1] = '\0'; // remove the last character
while (rootPath[0] && rootPath[strlen(rootPath)-1] != '/') {
rootPath[strlen(rootPath)-1] = '\0'; // remove the last character again
}
// We're either at the previous directory, or we've nulled out the whole thing.
if (rootPath[0] == '\0') {
// Never go beyond this
strcpy(rootPath, "/");
}
}
#define FILENAME_LENGTH 40
void BIOS::DrawDiskName(uint8_t index, uint8_t page, int8_t selection)
{
if (index == 0) {
if (page == 0) {
g_display->drawString(selection == 0 ? M_SELECTDISABLED : M_DISABLED, 6, 50, "<Prev>");
} else {
g_display->drawString(selection == 0 ? M_SELECTED : M_NORMAL, 6, 50, "<Prev>");
}
return;
}
if (index <= BIOS_MAXFILES) {
uint8_t i = index - 1;
if ((fileCount != -1 && i < fileCount)) {
char buf[FILENAME_LENGTH];
strncpy(buf, fileDirectory[i], FILENAME_LENGTH - 1);
buf[FILENAME_LENGTH - 1] = '\0';
g_display->drawString((i == selection-1) ? M_SELECTED : M_NORMAL, 6, 50 + 14 * (i+1), buf);
} else {
g_display->drawString((i == selection-1) ? M_SELECTDISABLED : M_DISABLED, 6, 50+14*(i+1), "-");
}
return;
}
// FIXME: this doesn't accurately say whether or not there *are* more.
if (!hasNextPage) { //fileCount == BIOS_MAXFILES || fileCount == 0) {
g_display->drawString((BIOS_MAXFILES+1 == selection) ? M_SELECTDISABLED : M_DISABLED, 6, 50 + 14 * (BIOS_MAXFILES+1), "<Next>");
} else {
g_display->drawString(BIOS_MAXFILES+1 == selection ? M_SELECTED : M_NORMAL, 6, 50 + 14 * (BIOS_MAXFILES+1), "<Next>");
}
}
void BIOS::DrawDiskNames(uint8_t page, int8_t selection, bool force)
{
//static uint8_t fileCount = 0;
static int16_t oldPage = -1;
if (page != oldPage || force) {
fileCount = g_filemanager->readDir(rootPath, "dsk,nib", fileDirectory, page * BIOS_MAXFILES, BIOS_MAXPATH);
oldPage = page;
hasNextPage = (fileCount == 10); //HasNextPage(page);
}
((TeensyDisplay *)g_display)->clrScr(0x0010);
g_display->drawLine(0,16, 320, 16, WHITE);
g_display->drawString(M_NORMAL, 2, 2, "BIOS Configuration - pick disk");
/* if (page == 0) {
g_display->drawString(selection == 0 ? M_SELECTDISABLED : M_DISABLED, 6, 50, "<Prev>");
} else {
g_display->drawString(selection == 0 ? M_SELECTED : M_NORMAL, 6, 50, "<Prev>");
}
*/
uint8_t i;
for (i=0; i<BIOS_MAXFILES+2; i++) {
DrawDiskName(i, page, selection);// {
//if (fileCount != -1 && i < fileCount) {
//char buf[FILENAME_LENGTH];
//strncpy(buf, fileDirectory[i], FILENAME_LENGTH - 1);
//buf[FILENAME_LENGTH - 1] = '\0';
//g_display->drawString((i == selection-1) ? M_SELECTED : M_NORMAL, 6, 50 + 14 * (i+1), buf);
//} else {
// g_display->drawString((i == selection-1) ? M_SELECTDISABLED : M_DISABLED, 6, 50+14*(i+1), "-");
//}
}
/*
// FIXME: this doesn't accurately say whether or not there *are* more.
if (!hasNextPage) { //fileCount == BIOS_MAXFILES || fileCount == 0) {
g_display->drawString((i+1 == selection) ? M_SELECTDISABLED : M_DISABLED, 6, 50 + 14 * (i+1), "<Next>");
} else {
g_display->drawString(i+1 == selection ? M_SELECTED : M_NORMAL, 6, 50 + 14 * (i+1), "<Next>");
}*/
}
uint8_t BIOS::GatherFilenames(uint8_t pageOffset)
{
uint16_t startNum = 10 * pageOffset;
uint8_t count = 0; // number we're including in our listing
// Serial.print(pageOffset);
// Serial.print(",");
// Serial.println(rootPath);
while (1) {
char fn[BIOS_MAXPATH];
// FIXME: add po, nib
int16_t idx = g_filemanager->readDir(rootPath, "dsk,nib", fn, startNum + count, BIOS_MAXPATH);
if (idx == -1) {
return count;
}
idx++;
strncpy(fileDirectory[count], fn, BIOS_MAXPATH);
count++;
if (count >= BIOS_MAXFILES) {
return count;
}
}
}
bool BIOS::HasNextPage(uint8_t pageOffset)
{
uint16_t startNum = BIOS_MAXFILES * pageOffset;
//uint8_t count = 0; // number we're including in our listing
char fn[BIOS_MAXPATH];
// FIXME: add po, nib
int16_t idx = g_filemanager->readDir(rootPath, "dsk,nib", fn, startNum + BIOS_MAXFILES, BIOS_MAXPATH);
return (idx != -1);
}

58
src/bios.h Normal file
View File

@ -0,0 +1,58 @@
#ifndef __BIOS_H
#define __BIOS_H
#include <Arduino.h>
#define BIOS_MAXFILES 10 // number of files in a page of listing
#define BIOS_MAXPATH 127 // maximum length of a single filename that we'll support
class BIOS {
public:
BIOS();
~BIOS();
// return true if a persistent change needs to be stored in EEPROM
bool runUntilDone();
bool updateDiagnostics();
private:
uint8_t GetAction(int8_t &prevAction);
bool isActionActive(int8_t action);
void DrawMainMenu(int8_t selection);
void UpdateMainMenu(int8_t selection);
void DrawMenuItem(int8_t item, int8_t selection);
void ClearMenuItem(int8_t item);
void drawKeyboard();
void drawInfo();
void drawThumb();
bool stickTrim();
bool stickSpeed();
void WarmReset();
void ColdReboot();
bool SelectDiskImage();
void DrawDiskName(uint8_t index, uint8_t page, int8_t selection);
void DrawDiskNames(uint8_t page, int8_t selection, bool force);
uint8_t GatherFilenames(uint8_t pageOffset);
bool HasNextPage(uint8_t pageOffset);
// bool GetFilename(uint8_t pageOffset, uint8_t index);
void stripDirectory();
bool saveState();
bool restoreState();
private:
void biosIdle();
void drawBiosScreen(int8_t prevAction);
int8_t selectedFile;
char fileDirectory[BIOS_MAXFILES][BIOS_MAXPATH+1];
int8_t fileCount;
bool hasNextPage;
bool inMainMenu;
// char fileName[BIOS_MAXPATH + 1];
char rootPath[255-BIOS_MAXPATH];
int8_t dirPage;
};
#endif

1062
src/cpu.cpp Normal file

File diff suppressed because it is too large Load Diff

77
src/cpu.h Normal file
View File

@ -0,0 +1,77 @@
#ifndef __CPU_H
#define __CPU_H
#include <stdlib.h>
#include <stdint.h>
class MMU;
// Flags (P) register bit definitions.
// Negative
#define F_N (1<<7)
// Overflow
#define F_V (1<<6)
#define F_UNK (1<<5) // What the heck is this?
// Break
#define F_B (1<<4)
// Decimal
#define F_D (1<<3)
// Interrupt Disable
#define F_I (1<<2)
// Zero
#define F_Z (1<<1)
// Carry
#define F_C (1<<0)
class Cpu {
public:
Cpu();
~Cpu();
void Reset();
void nmi();
void rst();
void brk();
void irq();
uint8_t Run(uint8_t numSteps);
uint8_t step();
uint8_t X();
uint8_t Y();
uint8_t A();
uint16_t PC();
uint8_t SP();
uint8_t P();
void stageIRQ();
protected:
// Stack manipulation
void pushS8(uint8_t b);
void pushS16(uint16_t w);
uint8_t popS8();
uint16_t popS16();
public:
void SetMMU(MMU *mmu) { this->mmu = mmu; }
public:
uint16_t pc;
uint8_t sp;
uint8_t a;
uint8_t x;
uint8_t y;
uint8_t flags;
uint64_t cycles;
bool irqPending;
MMU *mmu;
};
#endif

16
src/diskii-rom.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef TEENSYDUINO
#define PROGMEM
#endif
static uint8_t romData[256] PROGMEM =
{
162,32,160,0,162,3,134,60,138,10,36,60,240,16,5,60,73,255,41,126,176,8,74,208,251,152,157,86,3,200,232,
16,229,32,88,255,186,189,0,1,10,10,10,10,133,43,170,189,142,192,189,140,192,189,138,192,189,137,192,160,
80,189,128,192,152,41,3,10,5,43,170,189,129,192,169,86,32,168,252,136,16,235,133,38,133,61,133,65,169,
8,133,39,24,8,189,140,192,16,251,73,213,208,247,189,140,192,16,251,201,170,208,243,234,189,140,192,16,
251,201,150,240,9,40,144,223,73,173,240,37,208,217,160,3,133,64,189,140,192,16,251,42,133,60,189,140,
192,16,251,37,60,136,208,236,40,197,61,208,190,165,64,197,65,208,184,176,183,160,86,132,60,188,140,192,
16,251,89,214,2,164,60,136,153,0,3,208,238,132,60,188,140,192,16,251,89,214,2,164,60,145,38,200,208,239,
188,140,192,16,251,89,214,2,208,135,160,0,162,86,202,48,251,177,38,94,0,3,42,94,0,3,42,145,38,200,208,
238,230,39,230,61,165,61,205,0,8,166,43,144,219,76,1,8,0,0,0,0,0
};

526
src/diskii.cpp Normal file
View File

@ -0,0 +1,526 @@
#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 RingBuf(NIBTRACKSIZE);
this->rawTrackBuffer = (uint8_t *)malloc(4096);
if (this->rawTrackBuffer == NULL) Serial.println("DiskII Out of memory!");
this->mmu = mmu;
curTrack = 0;
trackDirty = false;
trackToRead = -1;
trackToFlush = -1;
writeMode = false;
writeProt = false; // FIXME: expose an interface to this
readWriteLatch = 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
readWriteLatch = 0x00;
ejectDisk(0);
ejectDisk(1);
}
void DiskII::checkFlush(int8_t track)
{
if (trackDirty && trackToFlush == -1) {
diskToFlush = selectedDisk;
trackToFlush = track;
trackDirty = false; // just so we don't overwrite disk/track to flush before continuing...
}
}
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->setDriveIndicator(selectedDisk, false); // FIXME: after a spell...
checkFlush(curTrack);
break;
case 0x09: // drive on
indicatorIsOn[selectedDisk] = 100;
g_display->setDriveIndicator(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
readWriteLatch = 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())
readWriteLatch |= 0x80;
else
readWriteLatch &= 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->setDriveIndicator(selectedDisk, true);
}
if (indicatorIsOn[selectedDisk] > 0 && indicatorIsOn[selectedDisk] < 100) {
// slowly spin it down...
if (--indicatorIsOn[selectedDisk] == 0) {
g_display->setDriveIndicator(selectedDisk, false);
}
}
// Any even address read returns the readWriteLatch (UTA2E Table 9.1,
// p. 9-12, note 2)
return (s & 1) ? FLOATING : readWriteLatch;
}
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) {
readWriteLatch = 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
checkFlush(prevTrack);
// step to the appropriate track
prevTrack = curTrack;
// mark it to be read
trackToRead = curTrack;
}
}
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, bool drawIt)
{
if (disk[driveNum] != -1) {
g_filemanager->closeFile(disk[driveNum]);
disk[driveNum] = -1;
if (drawIt) 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->setDriveIndicator(selectedDisk, false);
checkFlush(curTrack);
}
// set the selected disk drive
selectedDisk = which;
}
uint8_t DiskII::readOrWriteByte()
{
if (disk[selectedDisk] == -1) {
return GAP;
}
if (writeMode && !writeProt) {
if (!trackBuffer->hasData()) {
// Error: writing to empty track buffer? That's a raw write w/o
// knowing where we are on the disk.
return GAP;
}
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 (readWriteLatch < 0x96) {
// Can't write a de-nibblized byte...
g_display->debugMsg("DII: bad write");
return 0;
}
trackBuffer->replaceByte(readWriteLatch);
return 0;
}
// trackToRead is -1 when we have a filled buffer, or we have no data at all.
// trackToRead is != -1 when we're flushing our buffer and re-filling it.
//
// Don't fill it right here, b/c we don't want to bog down the CPU
// thread/ISR.
if (trackToRead == curTrack) {// waiting for a read to complete
return GAP;
}
if ((trackToRead != -1) || !trackBuffer->hasData()) {
checkFlush(curTrack);
// Need to read in a track of data and nibblize it. We'll return 0xFF
// until that completes.
// This might update trackToRead with a different track than the
// one we're reading. When we finish the read, we'll need to check
// to be sure that we're still trying to read the same track that
// we started with.
trackToRead = curTrack;
// While we're waiting for the sector to come around, we'll return
// GAP bytes.
return GAP;
}
return trackBuffer->peekNext();
}
void DiskII::fillDiskBuffer()
{
if (trackToFlush != -1) {
flushTrack(trackToFlush, diskToFlush); // in case it's dirty: flush before changing drives
trackBuffer->clear();
trackToFlush = -1;
}
// No work to do if trackToRead is -1
if (trackToRead == -1)
return;
trackDirty = false;
trackBuffer->clear();
int8_t trackWeAreReading = trackToRead;
int8_t diskWeAreUsing = selectedDisk;
trackBuffer->clear();
trackBuffer->setPeekCursor(0);
if (diskType[diskWeAreUsing] == nibDisk) {
// Read one nibblized sector at a time and jam it in trackBuf
// directly. We don't read the whole track at once only because
// of RAM constraints on the Teensy. There's no reason we
// couldn't, though, if RAM weren't at a premium.
for (int i=0; i<2; i++) {
g_filemanager->seekBlock(disk[diskWeAreUsing], trackWeAreReading * 16 + (i * 8), diskType[diskWeAreUsing] == nibDisk);
if (!g_filemanager->readBlocks(disk[diskWeAreUsing], rawTrackBuffer, 8, diskType[diskWeAreUsing] == nibDisk)) {
// FIXME: error handling?
trackToRead = -1;
return;
}
trackBuffer->addBytes(rawTrackBuffer, 416 * 8);
}
} else {
// It's a .dsk / .po disk image. Read the whole track in to
// rawTrackBuffer and nibblize it.
g_filemanager->seekBlock(disk[diskWeAreUsing], trackWeAreReading * 16, diskType[diskWeAreUsing] == nibDisk);
if (!g_filemanager->readTrack(disk[diskWeAreUsing], rawTrackBuffer, diskType[diskWeAreUsing] == nibDisk)) {
// FIXME: error handling?
trackToRead = -1;
return;
}
nibblizeTrack(trackBuffer, rawTrackBuffer, diskType[diskWeAreUsing], curTrack);
}
// Make sure we're still intending to read the track we just read
if (trackWeAreReading != trackToRead ||
diskWeAreUsing != selectedDisk) {
// Abort and let it start over next time
return;
}
// Buffer is full, we're done - reset trackToRead and that will let the reads reach the CPU!
trackToRead = -1;
}
const char *DiskII::DiskName(int8_t num)
{
if (disk[num] != -1)
return g_filemanager->fileName(disk[num]);
return "";
}
void DiskII::loadROM(uint8_t *toWhere)
{
#ifdef TEENSYDUINO
Serial.println("loading DiskII rom");
for (uint16_t i=0; i<=0xFF; i++) {
toWhere[i] = pgm_read_byte(&romData[i]);
}
#else
printf("loading DiskII rom\n");
memcpy(toWhere, romData, 256);
#endif
}
void DiskII::flushTrack(int8_t track, int8_t sel)
{
// 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[sel] == 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[sel], 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[sel], track * 16);
g_filemanager->writeTrack(disk[sel], rawTrackBuffer);
}

71
src/diskii.h Normal file
View File

@ -0,0 +1,71 @@
#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 "RingBuf.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, bool drawIt = true);
const char *DiskName(int8_t num);
void flushTrack(int8_t track, int8_t sel);
void fillDiskBuffer(); // called from main loop
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();
void checkFlush(int8_t track);
#ifndef TEENSYDUINO
void convertDskToNib(const char *outFN);
#endif
private:
volatile uint8_t curTrack;
volatile bool trackDirty; // does this track need flushing to disk?
uint8_t readWriteLatch;
RingBuf *trackBuffer; // nibblized data
uint8_t *rawTrackBuffer; // not nibblized data
bool writeMode;
bool writeProt;
AppleMMU *mmu;
int8_t disk[2];
volatile uint8_t indicatorIsOn[2];
uint8_t diskType[2];
volatile int8_t trackToRead; // -1 when we're idle; not -1 when we need to read a track.
volatile int8_t selectedDisk;
volatile int8_t trackToFlush; // -1 when there's none
volatile int8_t diskToFlush; // which selected disk are we writing to?
};
#endif

29526
src/display-bg.h Normal file

File diff suppressed because it is too large Load Diff

34
src/filemanager.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef __FILEMANAGER_H
#define __FILEMANAGER_H
#include <stdint.h>
#include "bios.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 fileDirectory[BIOS_MAXFILES][BIOS_MAXPATH+1], int16_t startIdx, uint16_t maxlen) = 0;
virtual int16_t readDir(const char *where, const char *suffix, char *outputFN, int16_t startIdx, uint16_t maxlen) = 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 readBlocks(int8_t fd, uint8_t *toWhere, uint8_t blocks, 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;
virtual bool readState(int8_t fd) = 0;
virtual bool writeState(int8_t fd) = 0;
};
#endif

335
src/font.h Normal file
View File

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

18
src/globals.cpp Normal file
View File

@ -0,0 +1,18 @@
#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;
PhysicalPrinter *g_printer = NULL;
uint16_t g_battery = 0;
uint16_t g_charge = 0;
int16_t g_volume = 15;
int8_t g_displayType = 3; // FIXME m_perfectcolor
uint8_t g_joyTrimX = 127;
uint8_t g_joyTrimY = 127;
uint8_t g_joySpeed = 5;
uint8_t g_screenSync = true;
bool biosRequest = false;

26
src/globals.h Normal file
View File

@ -0,0 +1,26 @@
#include <stdint.h>
#include "filemanager.h"
#include "cpu.h"
#include "vm.h"
#include "physicaldisplay.h"
#include "physicalkeyboard.h"
#include "physicalspeaker.h"
#include "physicalprinter.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 PhysicalPrinter *g_printer;
extern uint16_t g_battery;
extern uint16_t g_charge;
extern int16_t g_volume;
extern int8_t g_displayType;
extern uint8_t g_joyTrimX;
extern uint8_t g_joyTrimY;
extern uint8_t g_joySpeed;
extern uint8_t g_screenSync;
extern bool biosRequest;

33
src/lcg.cpp Normal file
View File

@ -0,0 +1,33 @@
#include "lcg.h"
/* Dead simple 8-bit Linear Congruential Generator (PRNG).
*
* An LCG is defined as
*
* X(n+1) = ( a * X(n) + c ) % m
*
* In this implementation:
* a = 2^7 = 128
* c = 251
* m = 256
*
* Like all LCGs, the low-order bits of this cycle quickly. The
* high-order bits have better (longer) periods.
*/
LCG::LCG(uint16_t s)
{
seed = s;
}
void LCG::srnd(uint16_t s)
{
seed = s;
}
uint8_t LCG::rnd()
{
seed = (seed << 7) - seed + 251;
return (uint8_t)(seed + (seed>>8));
}

17
src/lcg.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef __LCG_H
#define __LCG_H
#include <stdint.h>
class LCG {
public:
LCG(uint16_t s);
void srnd(uint16_t s);
uint8_t rnd();
private:
uint16_t seed;
};
#endif

18
src/mmu.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef __MMU_H
#define __MMU_H
#include <stdint.h>
class MMU {
public:
virtual ~MMU() {}
virtual void Reset() = 0;
virtual uint8_t read(uint16_t mem) = 0;
virtual void write(uint16_t mem, uint8_t val) = 0;
virtual uint8_t readDirect(uint16_t address, uint8_t fromPage) = 0;
virtual void writeDirect(uint16_t address, uint8_t fromPage, uint8_t val) = 0;
};
#endif

331
src/nibutil.cpp Normal file
View File

@ -0,0 +1,331 @@
#include "nibutil.h"
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#endif
// Long gaps are more "correct" in the sense that they're
// nib-disk-like; but they mean the VM has to chew on a lot of disk
// gaps to find the real data, which takes a noticeable amount of
// time. With this off, we present a minimum number of gaps (that
// hopefully aren't too short for the ROM to be able to write
// correctly)
//#define LONGGAPS
#define DISK_VOLUME 254
// dos 3.3 to physical sector conversion
const static uint8_t dephys[16] = {
0x00, 0x07, 0x0e, 0x06, 0x0d, 0x05, 0x0c, 0x04,
0x0b, 0x03, 0x0a, 0x02, 0x09, 0x01, 0x08, 0x0f };
// Prodos to physical sector conversion
const uint8_t deProdosPhys[] = {
0x00, 0x08, 0x01, 0x09, 0x02, 0x0a, 0x03, 0x0b,
0x04, 0x0c, 0x05, 0x0d, 0x06, 0x0e, 0x07, 0x0f };
const static uint8_t _trans[64] = {0x96, 0x97, 0x9a, 0x9b, 0x9d, 0x9e, 0x9f, 0xa6,
0xa7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff};
const static uint8_t _detrans[0x80] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04,
0x00, 0x00, 0x08, 0x0C, 0x00, 0x10, 0x14, 0x18,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x20,
0x00, 0x00, 0x00, 0x24, 0x28, 0x2C, 0x30, 0x34,
0x00, 0x00, 0x38, 0x3C, 0x40, 0x44, 0x48, 0x4C,
0x00, 0x50, 0x54, 0x58, 0x5C, 0x60, 0x64, 0x68,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x6C, 0x00, 0x70, 0x74, 0x78,
0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x80, 0x84,
0x00, 0x88, 0x8C, 0x90, 0x94, 0x98, 0x9C, 0xA0,
0x00, 0x00, 0x00, 0x00, 0x00, 0xA4, 0xA8, 0xAC,
0x00, 0xB0, 0xB4, 0xB8, 0xBC, 0xC0, 0xC4, 0xC8,
0x00, 0x00, 0xCC, 0xD0, 0xD4, 0xD8, 0xDC, 0xE0,
0x00, 0xE4, 0xE8, 0xEC, 0xF0, 0xF4, 0xF8, 0xFC };
void nibblizeTrack(RingBuf *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(RingBuf *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(RingBuf *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(RingBuf *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
src/nibutil.h Normal file
View File

@ -0,0 +1,44 @@
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#endif
#include "RingBuf.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(RingBuf *trackBuffer, uint8_t *rawTrackBuffer,
uint8_t diskType, int8_t track);
nibErr denibblizeTrack(RingBuf *trackBuffer, uint8_t *rawTrackBuffer,
uint8_t diskType, int8_t track);
bool decodeData(RingBuf *trackBuffer, uint16_t startAt, uint8_t *output);
void encodeData(RingBuf *trackBuffer, uint8_t *data);

65
src/physicaldisplay.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef __PHYSICALDISPLAY_H
#define __PHYSICALDISPLAY_H
#include <string.h> // strncpy
#include "vmdisplay.h" // FIXME: for AiieRect
#define BLACK 0x0000 // 0 black
#define MAGENTA 0xC006 // 1 magenta
#define DARK_BLUE 0x0010 // 2 dark blue
#define PURPLE 0xA1B5 // 3 purple
#define DARK_GREEN 0x0480 // 4 dark green
#define DARK_GREY 0x6B4D // 5 dark grey
#define BLUE 0x1B9F // 6 med blue
#define LIGHT_BLUE 0x0DFD // 7 light blue
#define BROWN 0x92A5 // 8 brown
#define ORANGE 0xF8C5 // 9 orange
#define LIGHT_GREY 0x9555 // 10 light gray
#define PINK 0xFCF2 // 11 pink
#define GREEN 0x07E0 // 12 green
#define YELLOW 0xFFE0 // 13 yellow
#define AQUA 0x87F0 // 14 aqua
#define WHITE 0xFFFF // 15 white
class PhysicalDisplay {
public:
PhysicalDisplay() { overlayMessage[0] = '\0'; }
virtual ~PhysicalDisplay() {};
virtual void redraw() = 0; // total redraw, assuming nothing
virtual void blit(AiieRect r) = 0; // redraw just the VM display area
virtual void clrScr() = 0;
virtual void clrScr(uint16_t color) = 0;
virtual void clrScr(uint8_t r, uint8_t g, uint8_t b) = 0;
virtual void drawDriveDoor(uint8_t which, bool isOpen) = 0;
virtual void setDriveIndicator(uint8_t which, bool isRunning) = 0;
virtual void drawBatteryStatus(uint8_t percent) = 0;
virtual void setBackground(uint16_t color) = 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)); }
virtual void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color) = 0;
virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) = 0;
virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) = 0;
virtual void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) = 0;
virtual void fillRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) = 0;
virtual void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) = 0;
virtual void drawCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color) = 0;
virtual void drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, uint16_t color) = 0;
virtual void fillCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color) = 0;
virtual void fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, int16_t delta, uint16_t color) = 0;
virtual void drawRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h, int16_t radius, uint16_t color) = 0;
virtual void fillRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h, int16_t radius, uint16_t color) = 0;
virtual void drawThumb(int16_t x0, int16_t y0) = 0; // draw the VM display area 1/2 size
virtual void LED(uint8_t r, uint8_t g, uint8_t b) = 0;
protected:
char overlayMessage[40];
};
#endif

81
src/physicalkeyboard.h Normal file
View File

@ -0,0 +1,81 @@
#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
#define SYSRQ 0x87
//Panel keys
#define LJOY 0x88
#define RJOY 0x89
#define UJOY 0x8A
#define DJOY 0x8B
#define BUT1 0x8C
#define BUT2 0x8D
#define JOYKEY 0x8E
#define NOTEKEY 0x8F
#define LWHTKEY 0x90
#define UKEY 0x91
#define RWHTKEY 0x92
#define ENTKEY 0x93
#define LKEY 0x94
#define DKEY 0x95
#define RKEY 0x96
//Special keys
#define JOY2 0x97
//USB modifiers
#define USB_LEFT_CTRL 0x01
#define USB_LEFT_SHIFT 0x02
#define USB_LEFT_ALT 0x04
#define USB_LEFT_GUI 0x08
#define USB_RIGHT_CTRL 0x10
#define USB_RIGHT_SHIFT 0x20
#define USB_RIGHT_ALT 0x40
#define USB_RIGHT_GUI 0x80
#define JOY_MODE_ANA_ABS 0
#define JOY_MODE_ANA_REL 1
#define JOY_MODE_JOYPORT1 2
#define JOY_MODE_JOYPORT2 3
class PhysicalKeyboard {
public:
PhysicalKeyboard(VMKeyboard *k) { this->vmkeyboard = k; }
virtual ~PhysicalKeyboard() {};
virtual void maintainKeyboard() = 0;
virtual bool kbhit() = 0;
virtual uint8_t read() = 0;
//Key joystick
virtual void startReading() = 0;
virtual uint8_t getMapping(uint8_t key) = 0;
virtual void setMapping(uint8_t key, uint8_t val) = 0;
virtual void setJoymode(uint8_t mode) = 0;
virtual void setAnnunciators() = 0;
virtual void setCaps(bool enabled) = 0;
protected:
VMKeyboard *vmkeyboard;
};
#endif

13
src/physicalprinter.h Normal file
View File

@ -0,0 +1,13 @@
#ifndef __PHYSICALPRINTER_H
#define __PHYSICALPRINTER_H
class PhysicalPrinter {
public:
virtual ~PhysicalPrinter() {}
// must be 960 pixels wide (120 bytes)
virtual void addLine(uint8_t *rowOfBits) = 0;
virtual void update() = 0;
virtual void moveDownPixels(uint8_t p) = 0;
};
#endif

17
src/physicalspeaker.h Normal file
View File

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

24
src/slot.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef __SLOT_H
#define __SLOT_H
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <stdint.h>
#include <stdio.h>
#endif
class Slot {
public:
virtual ~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

126
src/teensy-clock.cpp Normal file
View File

@ -0,0 +1,126 @@
#include <string.h> // memset
#include <TimeLib.h>
#include "teensy-clock.h"
#include "applemmu.h" // for FLOATING
/*
* http://apple2online.com/web_documents/prodos_technical_notes.pdf
*
* When ProDOS calls a clock card, the card deposits an ASCII string
* in the GETLN input buffer in the form: 07,04,14,22,46,57. The
* string translates as the following:
*
* 07 = the month, July
* 04 = the day of the week (00 = Sun)
* 14 = the date (00 to 31)
* 22 = the hour (00 to 23)
* 46 = the minute (00 to 59)
* 57 = the second (00 to 59)
*/
static void timeToProDOS(uint16_t year, uint8_t month, uint8_t day, uint8_t hour, uint8_t minute,
uint8_t proDOStimeOut[4])
{
proDOStimeOut[0] = ((year % 100) << 1) | (month >> 3);
proDOStimeOut[1] = ((month & 0x0F) << 5) | (day & 0x1F);
proDOStimeOut[2] = hour & 0x1F;
proDOStimeOut[3] = minute & 0x3F;
}
TeensyClock::TeensyClock(AppleMMU *mmu)
{
this->mmu = mmu;
}
TeensyClock::~TeensyClock()
{
}
void TeensyClock::Reset()
{
}
uint8_t TeensyClock::readSwitches(uint8_t s)
{
// When any switch is read, we'll put the current time in the prodos time buffer
tmElements_t tm;
breakTime(now(), tm);
// Put the date/time in the official ProDOS buffer
uint8_t prodosOut[4];
timeToProDOS(tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, prodosOut);
mmu->write(0xBF90, prodosOut[0]);
mmu->write(0xBF91, prodosOut[1]);
mmu->write(0xBF92, prodosOut[2]);
mmu->write(0xBF93, prodosOut[3]);
// and also generate a date/time that contains seconds, but not a
// year, which it also consumes
char ts[18];
sprintf(ts, "%.2d,%.2d,%.2d,%.2d,%.2d,%.2d",
tm.Month,
tm.Wday - 1, // Sunday should be 0, not 1
tm.Day,
tm.Hour,
tm.Minute,
tm.Second);
uint8_t i = 0;
while (ts[i]) {
mmu->write(0x200 + i, ts[i] | 0x80);
i++;
}
return FLOATING;
}
void TeensyClock::writeSwitches(uint8_t s, uint8_t v)
{
// printf("unimplemented write to the clock - 0x%X\n", v);
}
// FIXME: this assumes slot #7
void TeensyClock::loadROM(uint8_t *toWhere)
{
memset(toWhere, 0xEA, 256); // fill the page with NOPs
// ProDOS only needs these 4 bytes to recognize that a clock is present
toWhere[0x00] = 0x08; // PHP
toWhere[0x02] = 0x28; // PLP
toWhere[0x04] = 0x58; // CLI
toWhere[0x06] = 0x70; // BVS
// Pad out those bytes so they will return control well. The program
// at c700 becomes
//
// C700: PHP ; push to stack
// NOP ; filler (filled in by memory clear)
// PLP ; pop from stack
// RTS ; return
// CLI ; required to detect driver, but not used
// NOP ; filled in by memory clear
// BVS ; required to detect driver, but not used
toWhere[0x03] = 0x60; // RTS
// And it needs a small routing here to read/write it:
// 0x08: read
toWhere[0x08] = 0x4C; // JMP $C710
toWhere[0x09] = 0x10;
toWhere[0x0A] = 0xC7;
// 0x0b: write
toWhere[0x0B] = 0x8D; // STA $C0F0 (slot 7's first switch)
toWhere[0x0C] = 0xF0;
toWhere[0x0D] = 0xC0;
toWhere[0x0E] = 0x60; // RTS
// simple read
toWhere[0x10] = 0xAD; // LDA $C0F0 (slot 7's first switch)
toWhere[0x11] = 0xF0;
toWhere[0x12] = 0xC0;
toWhere[0x13] = 0x60; // RTS
}

30
src/teensy-clock.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef __TEENSYCLOCK_H
#define __TEENSYCLOCK_H
#ifdef TEENSYDUINO
#include <Arduino.h>
#else
#include <stdint.h>
#include <stdio.h>
#endif
#include "slot.h"
#include "applemmu.h"
class TeensyClock : public Slot {
public:
TeensyClock(AppleMMU *mmu);
virtual ~TeensyClock();
virtual void Reset();
virtual uint8_t readSwitches(uint8_t s);
virtual void writeSwitches(uint8_t s, uint8_t v);
virtual void loadROM(uint8_t *toWhere);
private:
AppleMMU *mmu;
};
#endif

72
src/teensy-crash.h Normal file
View File

@ -0,0 +1,72 @@
/*
* https://forum.pjrc.com/threads/186-Teensy-3-fault-handler-demonstration?highlight=crash+handler
*
* call this from setup():
* enableFaultHandler();
*
* On crash you see something like:
* !!!! Crashed at pc=0x490, lr=0x55B.
*
* which you can interpret thusly:
* $ arm-none-eabi-addr2line -s -f -C -e main.elf 0x490 0x55B
* Print:: println(char const*)
* Print.h:47
* main
* main_crash.cpp:86
*/
#define SCB_SHCSR_USGFAULTENA (uint32_t)1<<18
#define SCB_SHCSR_BUSFAULTENA (uint32_t)1<<17
#define SCB_SHCSR_MEMFAULTENA (uint32_t)1<<16
#define SCB_SHPR1_USGFAULTPRI *(volatile uint8_t *)0xE000ED20
#define SCB_SHPR1_BUSFAULTPRI *(volatile uint8_t *)0xE000ED19
#define SCB_SHPR1_MEMFAULTPRI *(volatile uint8_t *)0xE000ED18
// enable bus, usage, and mem fault handlers.
void enableFaultHandler()
{
SCB_SHCSR |= SCB_SHCSR_BUSFAULTENA | SCB_SHCSR_USGFAULTENA | SCB_SHCSR_MEMFAULTENA;
}
extern "C" {
void __attribute__((naked)) _fault_isr () {
uint32_t* sp=0;
// this is from "Definitive Guide to the Cortex M3" pg 423
asm volatile ( "TST LR, #0x4\n\t" // Test EXC_RETURN number in LR bit 2
"ITE EQ\n\t" // if zero (equal) then
"MRSEQ %0, MSP\n\t" // Main Stack was used, put MSP in sp
"MRSNE %0, PSP\n\t" // else Process stack was used, put PSP in sp
: "=r" (sp) : : "cc");
Serial.print("!!!! Crashed at pc=0x");
Serial.print(sp[6], 16);
Serial.print(", lr=0x");
Serial.print(sp[5], 16);
Serial.println(".");
Serial.flush();
// allow USB interrupts to preempt us:
SCB_SHPR1_BUSFAULTPRI = (uint8_t)255;
SCB_SHPR1_USGFAULTPRI = (uint8_t)255;
SCB_SHPR1_MEMFAULTPRI = (uint8_t)255;
while (1) {
digitalWrite(13, HIGH);
delay(100);
digitalWrite(13, LOW);
delay(100);
asm volatile (
"WFI" // Wait For Interrupt.
);
}
}
}
void hard_fault_isr(void) __attribute__ ((alias("_fault_isr")));
void memmanage_fault_isr(void) __attribute__ ((alias("_fault_isr")));
void bus_fault_isr(void) __attribute__ ((alias("_fault_isr")));
void usage_fault_isr(void) __attribute__ ((alias("_fault_isr")));

1007
src/teensy-display.cpp Normal file

File diff suppressed because it is too large Load Diff

115
src/teensy-display.h Normal file
View File

@ -0,0 +1,115 @@
#ifndef __TEENSY_DISPLAY_H
#define __TEENSY_DISPLAY_H
#include <Arduino.h>
#include "physicaldisplay.h"
#define TEENSY_DHEIGHT 240
#define TEENSY_DWIDTH 320
#define REDPIN 36
#define GREENPIN 37
#define BLUEPIN 38
enum {
M_NORMAL = 0,
M_SELECTED = 1,
M_DISABLED = 2,
M_SELECTDISABLED = 3,
M_HIGHLIGHT = 4,
M_SELECTHIGHLIGHT = 5
};
#define regtype volatile uint8_t
#define regsize uint8_t
#define cbi(reg, bitmask) *reg &= ~bitmask
#define sbi(reg, bitmask) *reg |= bitmask
#define pulse_high(reg, bitmask) sbi(reg, bitmask); cbi(reg, bitmask);
#define pulse_low(reg, bitmask) cbi(reg, bitmask); sbi(reg, bitmask);
#define cport(port, data) port &= data
#define sport(port, data) port |= data
#define swap(type, i, j) {type t = i; i = j; j = t;}
class UTFT;
class BIOS;
class TeensyDisplay : public PhysicalDisplay {
friend class BIOS;
public:
TeensyDisplay();
virtual ~TeensyDisplay();
virtual void blit(AiieRect r);
virtual void redraw();
virtual void clrScr();
virtual void clrScr(uint16_t color);
virtual void clrScr(uint8_t r, uint8_t g, uint8_t b);
virtual void setBackground(uint16_t color);
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 drawDriveDoor(uint8_t which, bool isOpen);
virtual void setDriveIndicator(uint8_t which, bool isRunning);
virtual void drawBatteryStatus(uint8_t percent);
virtual void LED(uint8_t r, uint8_t g, uint8_t b);
protected:
void moveTo(uint16_t col, uint16_t row);
void drawNextPixel(uint16_t color);
void redrawDriveIndicators();
private:
regtype *P_RS, *P_WR, *P_CS, *P_RST, *P_SDA, *P_SCL, *P_ALE;
regsize B_RS, B_WR, B_CS, B_RST, B_SDA, B_SCL, B_ALE;
uint8_t fch, fcl; // high and low foreground colors
uint8_t ledRed, ledGreen, ledBlue;
void LED();
void LEDflash();
void _fast_fill_16(int ch, int cl, long pix);
void setYX(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
void clrXY();
void setColor(byte r, byte g, byte b);
void setColor(uint16_t color);
virtual void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color);
virtual void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color);
virtual void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color);
virtual void drawRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
virtual void fillRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
virtual void fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color);
virtual void drawCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color);
virtual void drawCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, uint16_t color);
virtual void fillCircle(int16_t x0, int16_t y0, int16_t r, uint16_t color);
virtual void fillCircleHelper(int16_t x0, int16_t y0, int16_t r, uint8_t cornername, int16_t delta, uint16_t color);
virtual void drawRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h, int16_t radius, uint16_t color);
virtual void fillRoundRect(int16_t x0, int16_t y0, int16_t w, int16_t h, int16_t radius, uint16_t color);
virtual void drawThumb(int16_t x0, int16_t y0);
void drawPixel(uint16_t x, uint16_t y);
void drawPixel(uint16_t x, uint16_t y, uint16_t color);
void drawPixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b);
void LCD_Writ_Bus(uint8_t VH,uint8_t VL);
void LCD_Write_COM(uint8_t VL);
void LCD_Write_DATA(uint8_t VH,uint8_t VL);
void LCD_Write_DATA(uint8_t VL);
void LCD_Write_COM_DATA(uint8_t com1,uint16_t dat1);
bool needsRedraw;
bool driveIndicator[2];
bool driveIndicatorDirty;
};
#endif

521
src/teensy-filemanager.cpp Normal file
View File

@ -0,0 +1,521 @@
#include <Arduino.h>
#include <wchar.h>
#include "ff.h" // File System
#include "teensy-filemanager.h"
#include <string.h> // strcpy
#include "globals.h"
#include "applemmu.h"
#include "applevm.h"
// FIXME: globals are yucky.
DIR dir;
FILINFO fno;
FIL fil;
static TCHAR *char2tchar( const char *charString, int nn, TCHAR *output)
{
int ii;
for (ii=0; ii<nn; ii++) {
output[ii] = (TCHAR)charString[ii];
if (!charString[ii])
break;
}
return output;
}
static char * tchar2char( const TCHAR * tcharString, int nn, char * charString)
{ int ii;
for(ii = 0; ii<nn; ii++)
{ charString[ii] = (char)tcharString[ii];
if(!charString[ii]) break;
}
return charString;
}
TeensyFileManager::TeensyFileManager()
{
numCached = 0;
}
TeensyFileManager::~TeensyFileManager()
{
}
int8_t TeensyFileManager::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 TeensyFileManager::closeFile(int8_t fd)
{
// invalid fd provided?
if (fd < 0 || fd >= numCached)
return;
// clear the name
cachedNames[fd][0] = '\0';
}
const char *TeensyFileManager::fileName(int8_t fd)
{
if (fd < 0 || fd >= numCached)
return NULL;
return cachedNames[fd];
}
bool TeensyFileManager::readState(int8_t fd) {
AppleMMU *mmu;
mmu = (AppleMMU*)g_vm->getMMU();
if (cachedNames[fd][0] == 0)
return false;
// open, seek, read, close.
TCHAR buf[MAXPATH];
char2tchar(cachedNames[fd], MAXPATH, buf);
FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_READ);
if (rc) {
Serial.println("failed to open");
return false;
}
UINT v;
//Read disk names
char fileName[MAXPATH];
if (f_read(&fil, fileName, MAXPATH, &v)) return false;
if (v != MAXPATH) return false;
((AppleVM *)g_vm)->insertDisk(0, fileName, false);
if (f_read(&fil, fileName, MAXPATH, &v)) return false;
if (v != MAXPATH) return false;
((AppleVM *)g_vm)->insertDisk(1, fileName, false);
//Restore registers
uint8_t regs[7];
if (f_read(&fil, regs, 7, &v)) return false;
if (v != 7) return false;
g_cpu->pc = (regs[0] << 8) + regs[1];
g_cpu->sp = regs[2];
g_cpu->a = regs[3];
g_cpu->x = regs[4];
g_cpu->y = regs[5];
g_cpu->flags = regs[6];
//Read Switches
if (f_read(&fil, &mmu->switches, 2, &v)) return false;
if (v != 2) return false;
//Read Memory Switches
uint8_t mem;
if (f_read(&fil, &mem, 1, &v)) return false;
if (v != 1) return false;
mmu->auxRamRead = (mem & 128) > 0;
mmu->auxRamWrite = (mem & 64) > 0;
mmu->bank2 = (mem & 32) > 0;
mmu->readbsr = (mem & 16) > 0;
mmu->writebsr = (mem & 8) > 0;
mmu->altzp = (mem & 4) > 0;
mmu->intcxrom = (mem & 2) > 0;
mmu->slot3rom = (mem & 1) > 0;
//Restore memory
for (uint16_t i=0; i<0xC0; i++) {
for (uint8_t j=0; j<2; j++) {
f_read(&fil, mmu->ramPages[i][j], 256, &v);
if (v != 256) return false;
}
}
for (uint16_t i=0xC0; i<0x100; i++) {
for (uint8_t j=0; j<4; j++) {
f_read(&fil, mmu->ramPages[i][j], 256, &v);
if (v != 256) return false;
}
}
mmu->resetDisplay();
mmu->updateMemoryPages();
f_close(&fil);
return (v == 256);
}
bool TeensyFileManager::writeState(int8_t fd){
AppleMMU *mmu;
mmu = (AppleMMU*)g_vm->getMMU();
// open, seek, write, close.
if (fd < 0 || fd >= numCached)
return false;
if (cachedNames[fd][0] == 0)
return false;
// open, create/overwrite, close.
TCHAR buf[MAXPATH];
char2tchar(cachedNames[fd], MAXPATH, buf);
FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_WRITE | FA_CREATE_ALWAYS);
if (rc) return false;
UINT v;
//Write disk name
f_write(&fil, ((AppleVM *)g_vm)->disk6->DiskName(0), MAXPATH, &v);
if (v != MAXPATH) return false;
f_write(&fil, ((AppleVM *)g_vm)->disk6->DiskName(1), MAXPATH, &v);
if (v != MAXPATH) return false;
//Write Registers
uint8_t regs[7] = {(uint8_t)(g_cpu->pc >> 8), (uint8_t)(g_cpu->pc & 0xFF), g_cpu->sp, g_cpu->a, g_cpu->x, g_cpu->y, g_cpu->flags};
f_write(&fil, regs, 7, &v);
if (v != 7) return false;
//Write Switches
f_write(&fil, &mmu->switches, 2, &v);
if (v != 2) return false;
//Write Memory Switches
uint8_t mem = (mmu->auxRamRead?128:0 | mmu->auxRamWrite?64:0 | mmu->bank2?32:0 | mmu->readbsr?16:0 | mmu->writebsr?8:0 | mmu->altzp?4:0 | mmu->intcxrom?2:0 | mmu->slot3rom?1:0);
f_write(&fil, &mem, 1, &v);
if (v != 1) return false;
//Write Memory
for (uint16_t i=0; i<0xC0; i++) {
for (uint8_t j=0; j<2; j++) {
f_write(&fil, mmu->ramPages[i][j], 256, &v);
if (v != 256) return false;
}
}
for (uint16_t i=0xC0; i<0x100; i++) {
for (uint8_t j=0; j<4; j++) {
f_write(&fil, mmu->ramPages[i][j], 256, &v);
if (v != 256) return false;
}
}
f_close(&fil);
return (v == 256);
}
int8_t TeensyFileManager::readDir(const char *where, const char *suffix, char fileDirectory[BIOS_MAXFILES][BIOS_MAXPATH+1], int16_t startIdx, uint16_t maxlen)
{
// ... open, read, save next name, close, return 10 or less names.
// More efficient.
int16_t idxCount = 1;
int8_t idx = 0;
// First entry is always "../"
if (startIdx == 0) {
strcpy(fileDirectory[idx++], "../");
}
TCHAR buf[MAXPATH];
char2tchar(where, MAXPATH, buf);
buf[strlen(where)-1] = '\0'; // this library doesn't want trailing slashes
FRESULT rc = f_opendir(&dir, buf);
if (rc) {
Serial.printf("f_opendir '%s' failed: %d\n", where, rc);
return -1;
}
while (1) {
rc = f_readdir(&dir, &fno);
if (rc || !fno.fname[0]) {
// No more - all done.
f_closedir(&dir);
return idx;
}
if (fno.fname[0] == '.' || fno.fname[0] == '_' || fno.fname[0] == '~') {
// skip MAC fork files and any that have been deleted :/
continue;
}
// skip anything that has the wrong suffix
char fn[MAXPATH];
tchar2char(fno.fname, MAXPATH, fn);
if (suffix && !(fno.fattrib & AM_DIR) && strlen(fn) >= 3) {
const char *fsuff = &fn[strlen(fn)-3];
if (strstr(suffix, ",")) {
// multiple suffixes to check
bool matchesAny = false;
const char *p = suffix;
while (p && strlen(p)) {
if (!strncasecmp(fsuff, p, 3)) {
matchesAny = true;
break;
}
p = strstr(p, ",")+1;
}
if (!matchesAny)
//continue;
//} else {
// one suffix to check
if (strcasecmp(fsuff, suffix))
continue;
}
}
if (idxCount >= startIdx) {
if (fno.fattrib & AM_DIR) {
strcat(fn, "/");
}
strncpy(fileDirectory[idx++], fn, maxlen);
if (idx >= BIOS_MAXFILES) {
f_closedir(&dir);
return idx;
}
}
idxCount++;
}
/* NOTREACHED */
}
// suffix may be comma-separated
int16_t TeensyFileManager::readDir(const char *where, const char *suffix, char *outputFN, int16_t startIdx, uint16_t maxlen)
{
// ... open, read, save next name, close, return name. Horribly
// inefficient but hopefully won't break the sd layer. And if it
// works then we can make this more efficient later.
// First entry is always "../"
if (startIdx == 0) {
strcpy(outputFN, "../");
return 0;
}
int16_t idxCount = 1;
TCHAR buf[MAXPATH];
char2tchar(where, MAXPATH, buf);
buf[strlen(where)-1] = '\0'; // this library doesn't want trailing slashes
FRESULT rc = f_opendir(&dir, buf);
if (rc) {
Serial.printf("f_opendir '%s' failed: %d\n", where, rc);
return -1;
}
while (1) {
rc = f_readdir(&dir, &fno);
if (rc || !fno.fname[0]) {
// No more - all done.
f_closedir(&dir);
return -1;
}
if (fno.fname[0] == '.' || fno.fname[0] == '_' || fno.fname[0] == '~') {
// skip MAC fork files and any that have been deleted :/
continue;
}
// skip anything that has the wrong suffix
char fn[MAXPATH];
tchar2char(fno.fname, MAXPATH, fn);
if (suffix && !(fno.fattrib & AM_DIR) && strlen(fn) >= 3) {
const char *fsuff = &fn[strlen(fn)-3];
if (strstr(suffix, ",")) {
// multiple suffixes to check
bool matchesAny = false;
const char *p = suffix;
while (p && strlen(p)) {
if (!strncasecmp(fsuff, p, 3)) {
matchesAny = true;
break;
}
p = strstr(p, ",")+1;
}
if (!matchesAny)
//continue;
//} else {
// one suffix to check
if (strcasecmp(fsuff, suffix))
continue;
}
}
if (idxCount == startIdx) {
Serial.println(startIdx);
if (fno.fattrib & AM_DIR) {
strcat(fn, "/");
}
strncpy(outputFN, fn, maxlen);
f_closedir(&dir);
return idxCount;
}
idxCount++;
}
/* NOTREACHED */
}
void TeensyFileManager::seekBlock(int8_t fd, uint16_t block, bool isNib)
{
if (fd < 0 || fd >= numCached)
return;
fileSeekPositions[fd] = block * (isNib ? 416 : 256);
}
bool TeensyFileManager::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.
TCHAR buf[MAXPATH];
char2tchar(cachedNames[fd], MAXPATH, buf);
FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_READ);
if (rc) {
Serial.println("failed to open");
return false;
}
rc = f_lseek(&fil, fileSeekPositions[fd]);
if (rc) {
Serial.println("readTrack: seek failed");
f_close(&fil);
return false;
}
UINT v;
f_read(&fil, toWhere, isNib ? 0x1a00 : (256 * 16), &v);
f_close(&fil);
return (v == (isNib ? 0x1a00 : (256 * 16)));
}
bool TeensyFileManager::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.
TCHAR buf[MAXPATH];
char2tchar(cachedNames[fd], MAXPATH, buf);
FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_READ);
if (rc) {
Serial.println("failed to open");
return false;
}
rc = f_lseek(&fil, fileSeekPositions[fd]);
if (rc) {
Serial.println("readBlock: seek failed");
f_close(&fil);
return false;
}
UINT v;
f_read(&fil, toWhere, isNib ? 416 : 256, &v);
f_close(&fil);
return (v == (isNib ? 416 : 256));
}
bool TeensyFileManager::readBlocks(int8_t fd, uint8_t *toWhere, uint8_t blocks, bool isNib)
{
// open, seek, read, close.
if (fd < 0 || fd >= numCached)
return false;
if (cachedNames[fd][0] == 0)
return false;
// open, seek, read, close.
TCHAR buf[MAXPATH];
char2tchar(cachedNames[fd], MAXPATH, buf);
FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_READ);
if (rc) {
Serial.println("failed to open");
return false;
}
rc = f_lseek(&fil, fileSeekPositions[fd]);
if (rc) {
Serial.println("readBlock: seek failed");
f_close(&fil);
return false;
}
UINT v;
f_read(&fil, toWhere, (isNib ? 416 : 256) * blocks, &v);
f_close(&fil);
return (v == ((isNib ? 416 : 256) * blocks));
}
bool TeensyFileManager::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;
// can't write just a single block of a nibblized track
if (isNib)
return false;
// open, seek, write, close.
TCHAR buf[MAXPATH];
char2tchar(cachedNames[fd], MAXPATH, buf);
FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_WRITE);
if (rc) return false;
rc = f_lseek(&fil, fileSeekPositions[fd]);
UINT v;
f_write(&fil, fromWhere, 256, &v);
f_close(&fil);
return (v == 256);
}
bool TeensyFileManager::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.
TCHAR buf[MAXPATH];
char2tchar(cachedNames[fd], MAXPATH, buf);
FRESULT rc = f_open(&fil, (TCHAR*) buf, FA_WRITE);
if (rc) return false;
rc = f_lseek(&fil, fileSeekPositions[fd]);
UINT v;
f_write(&fil, fromWhere, isNib ? 0x1a00 : (256*16), &v);
f_close(&fil);
return (v == (isNib ? 0x1a00 : (256*16)));
}

35
src/teensy-filemanager.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef __TEENSYFILEMANAGER_H
#define __TEENSYFILEMANAGER_H
#include "filemanager.h"
#include "bios.h"
#include <stdint.h>
class TeensyFileManager : public FileManager {
public:
TeensyFileManager();
virtual ~TeensyFileManager();
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 fileDirectory[BIOS_MAXFILES][BIOS_MAXPATH+1], int16_t startIdx, uint16_t maxlen);
virtual int16_t readDir(const char *where, const char *suffix, char *outputFN, int16_t startIdx, uint16_t maxlen);
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 readBlocks(int8_t fd, uint8_t *toWhere, uint8_t blocks, 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);
virtual bool readState(int8_t fd);
virtual bool writeState(int8_t fd);
private:
int8_t numCached;
char cachedNames[MAXFILES][MAXPATH];
unsigned long fileSeekPositions[MAXFILES];
};
#endif

50
src/teensy-speaker.cpp Normal file
View File

@ -0,0 +1,50 @@
#include <Arduino.h>
#include "teensy-speaker.h"
#include "globals.h"
TeensySpeaker::TeensySpeaker(uint8_t pinNum) : PhysicalSpeaker()
{
toggleState = false;
needsToggle = false;
speakerPin = pinNum;
pinMode(speakerPin, OUTPUT); // analog speaker output, used as digital volume control
mixerValue = numMixed = 0;
}
TeensySpeaker::~TeensySpeaker()
{
}
void TeensySpeaker::toggle()
{
needsToggle = true;
}
void TeensySpeaker::maintainSpeaker(uint32_t c)
{
if (needsToggle) {
toggleState = !toggleState;
needsToggle = false;
}
mixerValue += (toggleState ? 0x1FF : 0x00);
mixerValue >>= (16-g_volume);
// FIXME: glad it's DAC0 and all, but... how does that relate to the pin passed in the constructor?
analogWriteDAC0(mixerValue);
}
void TeensySpeaker::beginMixing()
{
mixerValue = 0;
numMixed = 0;
}
void TeensySpeaker::mixOutput(uint8_t v)
{
mixerValue += v;
numMixed++;
}

27
src/teensy-speaker.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef __TEENSY_SPEAKER_H
#define __TEENSY_SPEAKER_H
#include "physicalspeaker.h"
class TeensySpeaker : public PhysicalSpeaker {
public:
TeensySpeaker(uint8_t pinNum);
virtual ~TeensySpeaker();
virtual void toggle();
virtual void maintainSpeaker(uint32_t c);
virtual void beginMixing();
virtual void mixOutput(uint8_t v);
private:
uint8_t speakerPin;
bool toggleState;
bool needsToggle;
uint32_t mixerValue;
uint8_t numMixed;
};
#endif

517
src/teensy-usb-keyboard.cpp Normal file
View File

@ -0,0 +1,517 @@
#include <Arduino.h>
#include "teensy-usb-keyboard.h"
#include "globals.h"
#include <USBHost_t36.h>
#include <Keypad.h>
#include "RingBuf.h"
USBHost myusb;
USBHub hub1(myusb);
//USBHub hub2(myusb);
//USBHub hub3(myusb);
KeyboardController keyboard1(myusb);
const byte ROWS = 3;
const byte COLS = 7;
//Panel keys and joystick
char keys[ROWS][COLS] = {
{ BUT1, LJOY, UJOY, RJOY, DJOY },
{ BUT2, ENTKEY, LKEY, DKEY, RKEY },
{ JOYKEY, NOTEKEY, LWHTKEY, UKEY, RWHTKEY }
};
uint8_t rowsPins[ROWS] = { 24, 25, 26 };
uint8_t colsPins[COLS] = { 27, 28, 29, 30, 31 };
Keypad keypad(makeKeymap(keys), rowsPins, colsPins, ROWS, COLS);
RingBuf bufferUsb(10); // 10 keys should be plenty, right?
static uint8_t panelBIOS[15] = { LARR, RARR, UARR, DARR, RET, ESC, '1', '2', '3', '4', '5', '6', '7', '8', '9' };
uint8_t panelMap[15] = { LJOY, RJOY, UJOY, DJOY, LA, RA, 'j', 'k', '1', UARR, '2', RET, LARR, DARR, RARR };
//PS2 101 key keyboard layout
const char ps2KeyLayout[102] =
{
0, 0, 0, 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
'y', 'z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', RET, ESC,
DEL, TAB, ' ', '-', '=', '[', ']','\\', '?', ';','\'', '`', ',', '.',
'/',LOCK, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
SYSRQ, 0, 0, 0, 0, 0, 0, 0, 0,RARR,LARR,DARR,UARR, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, LA, RA
};
static uint8_t shiftedNumber[] = { '<', // ,
'_', // -
'>', // .
'?', // /
')', // 0
'!', // 1
'@', // 2
'#', // 3
'$', // 4
'%', // 5
'^', // 6
'&', // 7
'*', // 8
'(', // 9
0, // (: is not a key)
':' // ;
};
//bool buffered;
bool leftShiftPressed;
bool rightShiftPressed;
bool ctrlPressed;
bool capsLock;
bool leftApplePressed;
bool rightApplePressed;
int8_t numPressed;
bool buffered;
VMKeyboard *vmKeyboard;
int16_t paddle0;
int16_t paddle1;
uint8_t currentJoyMode = JOY_MODE_ANA_ABS;
TeensyUsbKeyboard::TeensyUsbKeyboard(VMKeyboard *k) : PhysicalKeyboard(k)
{
keypad.setDebounceTime(5);
myusb.begin();
keyboard1.attachPress(onPress);
keyboard1.attachRelease(onRelease);
//keyboard1.rawOnly(true);
leftShiftPressed = false;
rightShiftPressed = false;
ctrlPressed = false;
capsLock = true;
leftApplePressed = false;
rightApplePressed = false;
buffered = false;
vmKeyboard = k;
numPressed = 0;
paddle0 = 127;
paddle1 = 127;
}
TeensyUsbKeyboard::~TeensyUsbKeyboard()
{
}
uint8_t TeensyUsbKeyboard::getMapping(uint8_t key) {
return panelMap[key];
}
void TeensyUsbKeyboard::setMapping(uint8_t key, uint8_t val) {
panelMap[key] = val;
}
void onPress(int unicode)
{
uint8_t key = keyboard1.getOemKey();
uint8_t modifiers = keyboard1.getModifiers();
if (key > 101) key = 101;
if (buffered) {
pressedKey(ps2KeyLayout[key], modifiers);
} else {
vmKeyboard->keyDepressed(ps2KeyLayout[key], modifiers);
}
}
void onRelease(int unicode)
{
uint8_t key = keyboard1.getOemKey();
uint8_t modifiers = keyboard1.getModifiers();
if (key > 101) key = 101;
if (buffered) {
releasedKey(ps2KeyLayout[key], modifiers);
} else {
vmKeyboard->keyReleased(ps2KeyLayout[key], modifiers);
}
}
void pressedKey(uint8_t key, uint8_t mod)
{
numPressed++;
if (key != SYSRQ && key & 0x80) {
// it's a modifier key.
switch (key) {
case _CTRL:
ctrlPressed = 1;
break;
case LSHFT:
leftShiftPressed = 1;
break;
case RSHFT:
rightShiftPressed = 1;
break;
case LOCK:
capsLock = !capsLock;
break;
case LA:
leftApplePressed = 1;
break;
case RA:
rightApplePressed = 1;
break;
}
return;
}
if (key == ' ' || key == DEL || key == ESC || key == RET || key == TAB || key == SYSRQ) {
//Serial.println((int)key);
bufferUsb.addByte(key);
return;
}
if (key >= 'a' &&
key <= 'z') {
if (ctrlPressed) {
bufferUsb.addByte(key - 'a' + 1);
return;
}
if (leftShiftPressed || rightShiftPressed || capsLock) {
bufferUsb.addByte(key - 'a' + 'A');
return;
}
bufferUsb.addByte(key);
return;
}
// FIXME: can we control-shift?
if (key >= ',' && key <= ';') {
if (leftShiftPressed || rightShiftPressed) {
bufferUsb.addByte(shiftedNumber[key - ',']);
return;
}
bufferUsb.addByte(key);
return;
}
if (leftShiftPressed || rightShiftPressed) {
uint8_t ret = 0;
switch (key) {
case '=':
ret = '+';
break;
case '[':
ret = '{';
break;
case ']':
ret = '}';
break;
case '\\':
ret = '|';
break;
case '\'':
ret = '"';
break;
case '`':
ret = '~';
break;
}
if (ret) {
bufferUsb.addByte(ret);
return;
}
}
// Everything else falls through.
bufferUsb.addByte(key);
}
void releasedKey(uint8_t key, uint8_t mod)
{
numPressed--;
if (key & 0x80) {
// it's a modifier key.
switch (key) {
case _CTRL:
ctrlPressed = 0;
break;
case LSHFT:
leftShiftPressed = 0;
break;
case RSHFT:
rightShiftPressed = 0;
break;
case LA:
leftApplePressed = 0;
break;
case RA:
rightApplePressed = 0;
break;
}
}
}
bool TeensyUsbKeyboard::kbhit()
{
//USB keyboard
if (!buffered) {
bufferUsb.clear();
buffered = true;
}
myusb.Task();
//Front panel keys
if (keypad.getKeys()) {
for (int i=0; i<LIST_MAX; i++) {
if ( keypad.key[i].stateChanged ) {
switch (keypad.key[i].kstate) {
case PRESSED:
pressedKey(panelBIOS[keypad.key[i].kchar - 0x88], 0);
break;
case RELEASED:
releasedKey(panelBIOS[keypad.key[i].kchar - 0x88], 0);
break;
case HOLD:
case IDLE:
break;
}
}
}
}
// For debugging: also allow USB serial to act as a keyboard
if (Serial.available()) {
bufferUsb.addByte(Serial.read());
}
return bufferUsb.hasData();
}
uint8_t TeensyUsbKeyboard::read()
{
if (bufferUsb.hasData()) {
return bufferUsb.consumeByte();
}
return 0;
}
//Set joystick mode to analog, joyport1 or joyport2
void TeensyUsbKeyboard::setJoymode(uint8_t mode) {
currentJoyMode = mode;
if (mode == JOY_MODE_ANA_ABS || mode == JOY_MODE_ANA_REL)
//Regular button interface
vmKeyboard->setButtons(false, false, false);
else if (mode == JOY_MODE_JOYPORT1 || mode == JOY_MODE_JOYPORT2)
//Joyport inputs are active low
vmKeyboard->setButtons(true, true, true);
}
//Joyport stick switches
bool stick_up = false;
bool stick_down = false;
bool stick_left = false;
bool stick_right = false;
bool stick_trig = false;
//Set joystick input buttons based on annunciator outputs
// Emulates joyport circuit
// http://lukazi.blogspot.nl/2009/04/game-controller-atari-joysticks.html
void updateJoyport() {
if (currentJoyMode != JOY_MODE_ANA_ABS && currentJoyMode != JOY_MODE_ANA_REL) {
if ((!vmKeyboard->getAnnunciator(0) && currentJoyMode == JOY_MODE_JOYPORT1) || //Joystick 1 enabled
(vmKeyboard->getAnnunciator(0) && currentJoyMode == JOY_MODE_JOYPORT2)) { //Joystick 2 enabled
vmKeyboard->setButton(0, !stick_trig); //Button
if (!vmKeyboard->getAnnunciator(1)) { //Joystick L/R or U/D
vmKeyboard->setButton(1, !stick_left);
vmKeyboard->setButton(2, !stick_right);
} else {
vmKeyboard->setButton(1, !stick_up);
vmKeyboard->setButton(2, !stick_down);
}
}
else vmKeyboard->setButtons(true, true, true); //No joystick
}
}
//Called from MMU when game port annunciators are changed
void TeensyUsbKeyboard::setAnnunciators() {
updateJoyport();
}
void updateRelStick() {
if (currentJoyMode == JOY_MODE_ANA_REL) {
//Relative Joystick Emulation
if (stick_left) {
paddle0 -= g_joySpeed;
if (paddle0 < 0) paddle0 = 0;
}
else if (stick_right) {
paddle0 += g_joySpeed;
if (paddle0 > 255) paddle0 = 255;
}
if (stick_up) {
paddle1 -= g_joySpeed;
if (paddle1 < 0) paddle1 = 0;
}
else if (stick_down) {
paddle1 += g_joySpeed;
if (paddle1 > 255) paddle1 = 255;
}
}
}
//Joystick emulation
// Returns true if key was handled by stick
bool pressStick(uint8_t key) {
//Joyport emulation
if (currentJoyMode != JOY_MODE_ANA_ABS) {
if (key == LJOY) {
stick_left = true;
stick_right = false;
updateJoyport();
return true;
}
if (key == RJOY) {
stick_left = false;
stick_right = true;
updateJoyport();
return true;
}
if (key == UJOY) {
stick_up = true;
stick_down = false;
updateJoyport();
return true;
}
if (key == DJOY) {
stick_up = false;
stick_down = true;
updateJoyport();
return true;
}
if (key == LA && currentJoyMode != JOY_MODE_ANA_REL) {
stick_trig = true;
updateJoyport();
return true;
}
if (key == RA && currentJoyMode != JOY_MODE_ANA_REL) {
return true;
}
} else {
//Absolute Analog stick emulation
if (key == LJOY) {
paddle0 = 0;
return true;
}
if (key == RJOY) {
paddle0 = 255;
return true;
}
if (key == UJOY) {
paddle1 = 0;
return true;
}
if (key == DJOY) {
paddle1 = 255;
return true;
}
}
return false;
}
bool releaseStick(uint8_t key) {
//Joyport emulation
if (currentJoyMode != JOY_MODE_ANA_ABS) {
if (key == LJOY || key == RJOY) {
stick_left = false;
stick_right = false;
updateJoyport();
return true;
}
if (key == UJOY || key == DJOY) {
stick_up = false;
stick_down = false;
updateJoyport();
return true;
}
if (key == LA && currentJoyMode != JOY_MODE_ANA_REL) {
stick_trig = false;
updateJoyport();
return true;
}
if (key == RA && currentJoyMode != JOY_MODE_ANA_REL) {
return true;
}
} else {
//Absolute Analog stick emulation
if (key == LJOY || key == RJOY) {
paddle0 = g_joyTrimX;
return true;
}
if (key == UJOY || key == DJOY) {
paddle1 = g_joyTrimY;
return true;
}
}
return false;
}
bool capsLed = false;
// This is a non-buffered interface to the physical keyboard, as used
// by the VM.
void TeensyUsbKeyboard::maintainKeyboard()
{
static bool oldCapsLed = false;
if (oldCapsLed != capsLed) {
oldCapsLed = capsLed;
//if (keyboard1.connected()) keyboard1.capsLock(capsLed);
}
// Serial.println("maintain");
buffered = false;
myusb.Task();
updateRelStick();
if (keypad.getKeys()) {
for (int i=0; i<LIST_MAX; i++) {
if ( keypad.key[i].stateChanged ) {
uint8_t kchar = panelMap[keypad.key[i].kchar - 0x88];
switch (keypad.key[i].kstate) {
case PRESSED:
if (!pressStick(kchar))
vmkeyboard->keyDepressed(kchar);
break;
case RELEASED:
if (!releaseStick(kchar))
vmkeyboard->keyReleased(kchar);
break;
case HOLD:
case IDLE:
break;
}
}
}
}
// For debugging: also allow USB serial to act as a keyboard
if (Serial.available()) {
int c = Serial.read();
vmkeyboard->keyDepressed(c);
vmkeyboard->keyReleased(c);
}
}
void TeensyUsbKeyboard::startReading()
{
g_vm->triggerPaddleInCycles(0, 12 * paddle0);
g_vm->triggerPaddleInCycles(1, 12 * paddle1);
}
void TeensyUsbKeyboard::setCaps(bool enabled) {
capsLed = enabled;
Serial.print("Set Leds:");
Serial.println(enabled?"On":"Off");
}

34
src/teensy-usb-keyboard.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef __TEENSY_USB_KEYBOARD_H
#define __TEENSY_USB_KEYBOARD_H
#include "physicalkeyboard.h"
void onPress(int unicode);
void onRelease(int unicode);
void pressedKey(uint8_t key, uint8_t mod);
void releasedKey(uint8_t key, uint8_t mod);
class TeensyUsbKeyboard : public PhysicalKeyboard {
public:
TeensyUsbKeyboard(VMKeyboard *k);
virtual ~TeensyUsbKeyboard();
// Interface used by the VM...
virtual void maintainKeyboard();
// Interface used by the BIOS...
virtual bool kbhit();
virtual uint8_t read();
//Key joystick
virtual void startReading();
virtual void setJoymode(uint8_t mode);
virtual void setAnnunciators();
virtual void setCaps(bool enabled);
//Panel mapping
virtual uint8_t getMapping(uint8_t key);
virtual void setMapping(uint8_t key, uint8_t val);
};
#endif

34
src/vm.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef __VM_H
#define __VM_H
#include <stdint.h>
#include <stdlib.h> // for calloc
#include "mmu.h"
#include "vmdisplay.h"
#include "vmkeyboard.h"
#define DISPLAYWIDTH 320
#define DISPLAYHEIGHT 240
#define DISPLAYRUN 320 // how wide each row is in pixels in the buffer (for faster math)
class VM {
public:
VM() { mmu=NULL; vmdisplay = NULL; videoBuffer = (uint8_t *)calloc(DISPLAYRUN * DISPLAYHEIGHT / 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

28
src/vmdisplay.h Normal file
View File

@ -0,0 +1,28 @@
#ifndef __VMDISPLAY_H
#define __VMDISPLAY_H
class MMU;
typedef struct {
uint8_t top;
uint16_t left;
uint8_t bottom;
uint16_t right;
} AiieRect;
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;
virtual AiieRect getDirtyRect() = 0;
MMU *mmu;
uint8_t *videoBuffer;
};
#endif

20
src/vmkeyboard.h Normal file
View File

@ -0,0 +1,20 @@
#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 keyDepressed(uint8_t k, uint8_t m) = 0;
virtual void keyReleased(uint8_t k, uint8_t m) = 0;
virtual void setButton(uint8_t index, bool val) = 0;
virtual void setButtons(bool b0, bool b1, bool b2) = 0;
virtual bool getAnnunciator(uint8_t index) = 0;
virtual void maintainKeyboard(uint32_t cycleCount) = 0;
};
#endif

83
src/widgets.cpp Normal file
View File

@ -0,0 +1,83 @@
#include "widgets.h"
#include "globals.h"
#include "teensy-display.h"
Widgets::Widgets()
{
}
Widgets::~Widgets()
{
}
void Widgets::drawBatteryText() {
uint16_t back = BLACK;
char buf[10];
if (g_charge > 15) {
sprintf(buf, "Charge"); //charging
back = BLUE;
}
else {
if (g_battery > 1000) {
int bat = g_battery;
if (bat > 4200) bat = 4200;
if (bat < 3000) bat = 3000;
if (bat > 3400) back = DARK_GREEN; //Good
else if (bat > 3200) back = BROWN; //Low
else back = ORANGE; //Expired
bat = map(bat, 3000, 4200, 0, 100);
sprintf(buf, "%d%%", bat);
}
else {
sprintf(buf, "Full"); //Fully charged (off)
back = DARK_BLUE;
}
}
g_display->fillRoundRect(battX, battY, 53, 20, 5, back);
g_display->drawRoundRect(battX, battY, 53, 20, 5, battColor);
g_display->drawRoundRect(battX+52, battY+7, 5, 6, 2, battColor);
g_display->setBackground(back);
g_display->drawString(M_NORMAL, battX+2, battY+4, " ");
g_display->drawString(M_NORMAL, (battX+27) - (strlen(buf) * 4), battY+4, buf);
g_display->setBackground(DARK_BLUE);
}
void Widgets::drawBattery(int16_t x, int16_t y, uint16_t color) {
battX = x;
battY = y;
battColor = color;
drawBatteryText();
}
//Caption 1 or 2 chars long
void Widgets::drawCaptionText(uint8_t style, uint16_t x, uint16_t y, const char* str) {
uint8_t len = strlen(str)>1?4:0;
g_display->drawString(style, x-len, y, str);
}
void Widgets::drawKey (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color, const char* str) {
g_display->drawRoundRect(x, y, w, h, 2, color);
g_display->fillRoundRect(x+2, y+2, w-4, h-4, 4, BLACK);
g_display->drawRoundRect(x+2, y+2, w-4, h-4, 4, color);
drawCaptionText(M_HIGHLIGHT, x+12, y+5, str);
}
void Widgets::drawButton (uint16_t x, uint16_t y, uint16_t color, const char* str) {
g_display->drawCircle(x+14, y+14, 14, color);
g_display->fillCircle(x+14, y+14, 12, BLACK);
g_display->drawCircle(x+14, y+14, 12, color);
drawCaptionText(M_HIGHLIGHT, x+10, y+8, str);
}
void Widgets::drawStick (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color, const char* str0, const char* str1, const char* str2, const char* str3) {
g_display->fillRoundRect(x, y, w, h, 6, BLACK);
g_display->drawRoundRect(x, y, w, h, 6, color);
g_display->drawCircle(x+(w/2), y+(h/2), 3, color);
uint8_t len = strlen(str0)>1?8:0;
g_display->drawString(M_HIGHLIGHT, x+(w/2)-14-len, y+(h/2)-6, str0);
g_display->drawString(M_HIGHLIGHT, x+(w/2)+6, y+(h/2)-6, str1);
drawCaptionText(M_HIGHLIGHT, x+(w/2)-3, y+(h/2)-16, str2);
drawCaptionText(M_HIGHLIGHT, x+(w/2)-3, y+(h/2)+4, str3);
}

25
src/widgets.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef __WIDGETS_H
#define __WIDGETS_H
#include <Arduino.h>
class Widgets {
public:
Widgets();
~Widgets();
void drawBattery(int16_t x, int16_t y, uint16_t color);
void drawBatteryText();
void drawCaptionText(uint8_t style, uint16_t x, uint16_t y, const char* str);
void drawKey (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color, const char* str);
void drawButton (uint16_t x, uint16_t y, uint16_t color, const char* str);
void drawStick (uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color, const char* str0, const char* str1, const char* str2, const char* str3);
private:
int16_t battX;
int16_t battY;
int16_t battColor;
};
#endif