First Commit
403
AppleEmu.ino
Normal 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
GameBloke.fzz
Normal file
110729
Keypad Baseplate.gcode
Normal file
BIN
Keypad Baseplate.stl
Normal file
BIN
pics/GameBloke.png
Normal file
After Width: | Height: | Size: 317 KiB |
BIN
pics/cpuapart.JPG
Normal file
After Width: | Height: | Size: 148 KiB |
BIN
pics/cpuback.JPG
Normal file
After Width: | Height: | Size: 189 KiB |
BIN
pics/cpufront.JPG
Normal file
After Width: | Height: | Size: 184 KiB |
BIN
pics/desktop.JPG
Normal file
After Width: | Height: | Size: 140 KiB |
BIN
pics/front.JPG
Normal file
After Width: | Height: | Size: 81 KiB |
BIN
pics/inside.JPG
Normal file
After Width: | Height: | Size: 163 KiB |
BIN
pics/insideback.JPG
Normal file
After Width: | Height: | Size: 114 KiB |
BIN
pics/keyboard.JPG
Normal file
After Width: | Height: | Size: 157 KiB |
BIN
pics/keysupport.JPG
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
pics/portrait.JPG
Normal file
After Width: | Height: | Size: 146 KiB |
BIN
pics/sandwich.JPG
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
pics/screen.JPG
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
pics/screenoff.JPG
Normal file
After Width: | Height: | Size: 151 KiB |
BIN
pics/sideon.JPG
Normal file
After Width: | Height: | Size: 168 KiB |
113
src/RingBuf.cpp
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
993
src/applemmu.cpp
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
77
src/cpu.h
Normal 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
|
@ -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
|
@ -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
|
@ -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
34
src/filemanager.h
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
115
src/teensy-display.h
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|