functional code for the Teensy

This commit is contained in:
Jorj Bauer 2017-02-20 13:55:16 -05:00
parent 8e155646c9
commit 3af0b916d7
52 changed files with 2647 additions and 3 deletions

153
README.md
View File

@ -39,6 +39,159 @@ The MD5 sums of those two files are:
From those, the appropriate headers will be automatically generated by
"make roms" (or any other target that relies on the ROMs).
Building (for the Teensy)
=========================
The directory 'teensy' contains 'teensy.ino' - the Arduino development
environment project file. You'll need to open that up and compile from
within.
However.
I built this on a Mac, and I used a lot of symlinks because of
limitations in the Arduino IDE. There's no reason that shouldn't work
under Linux, but I have absolutely no idea what Windows will make of
it. I would expect trouble. No, I won't accept pull requests that
remove the symlinks and replace them with the bare files. Sorry.
Also, you'll have to build the ROM headers (above) with 'make roms'
before you can build the Teensy .ino file.
If anyone knows how to make the Arduino development environment do any
form of scripting that could be used to generate those headers, I'd
gladly adopt that instead of forcing folks to run the Perl script via
Makefile. And if you have a better way of dealing with subfolders of
code, with the Teensy-specific code segregated as it is, I'm all ears...
I compile this with optimization set to "Faster" for the Teensy 3.6 at
180MHz. There's no need to overclock the CPU -- but it does give
better video performance, all the way up to 240MHz. Do as you see fit
:)
Environment and Libraries
-------------------------
I built this with arduino 1.8.1 and TeensyDuino 1.35.
https://www.pjrc.com/teensy/td_download.html
These libraries I'm using right from Teensy's environment: TimerOne;
SPI; EEPROM; Time; Keypad.
There's an error in the Time library. On Macs, where the filesystem is
case-insensitive, you'll have to do something like this:
<pre>
$ mv /Applications/Arduino.app/Contents/Java//hardware/teensy/avr/libraries/Time/Time.h /Applications/Arduino.app/Contents/Java//hardware/teensy/avr/libraries/Time/_Time.h
</pre>
I'm also using these libraries that don't come with TeensyDuino:
### uSDFS ###
This has long filename support, where the Teensy SD library
doesn't. It's pretty messy code, though, and I hope to abandon it
eventually.
https://github.com/WMXZ-EU/uSDFS
### RingBuffer ###
My library for dealing with ring and circular buffers. Simplistic.
You'll need v1.2.0 or later.
https://github.com/JorjBauer/RingBuffer
### RadioHead ###
This library comes with TeensyDuino, but I found the version that's
with 1.35 hangs. Using the stock v1.70 seems to be fine.
http://www.airspayce.com/mikem/arduino/RadioHead
Running (on the Teensy)
=======================
The reset/menu button brings up a BIOS menu:
Resume
Reset
Cold Reboot
Drop to Monitor
Display: RGB
Debug: off
Insert/Eject Disk 1
Insert/Eject Disk 2
Volume +
Volume -
Reset
-----
This is the same as control-reset on the actual hardware. If you
want to execute the Apple //e self-test, then hold down the two
joystick buttons; hit the reset/menu key; and select "Reset".
Cold Reboot
-----------
This resets much of the hardware to a default state and forces a
reboot. (You can get the self-test using this, too.)
Drop to Monitor
---------------
"Drop to Monitor" tries fairly hard to get you back to a monitor
prompt. Useful for debugging, probably not for much else.
Display
-------
"Display" has four values, and they're only really implemented for
text and hi-res modes (not for lo-res modes). To describe them, I have
to talk about the details of the Apple II display system.
In hires modes, the Apple II can only display certain colors in
certain horizontal pixel columns. Because of how the composite video
out works, the color "carries over" from one pixel to its neighbor;
multiple pixels turned on in a row makes them all white. Which means
that, if you're trying to display a picture in hires mode, you get
color artifacts on the edges of white areas.
The Apple Color Composite Monitor had a button on it that turned on
"Monochrome" mode, with the finer resolution necessary to display the
pixels without the color cast. So its two display modes would be the
ones I call "NTSC-like" and "Black and White."
There are two other video modes. The "RGB" mode (the default, because
it's my preference) shows the color pixels as they're actually drawn
in to memory. That means there can't be a solid field of, for example,
orange; there can only be vertical stripes of orange with black
between them.
The last mode is "Monochrome" which looks like the original "Monitor
II", a black-and-green display.
Debug
-----
This has several settings:
off
Show FPS
Show mem free
Show paddles
Show PC
Show cycles
Show battery
Show time
... these are all fairly self-explanatory.
Building (on a Mac)
===================

View File

@ -18,7 +18,7 @@ class FileManager {
virtual const char *fileName(int8_t fd) = 0;
virtual int8_t readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx) = 0;
virtual int8_t readDir(const char *where, const char *suffix, char *outputFN, int8_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;

View File

@ -61,7 +61,7 @@ const char *OpenCVFileManager::fileName(int8_t fd)
return cachedNames[fd];
}
int8_t OpenCVFileManager::readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx)
int8_t OpenCVFileManager::readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx, uint8_t maxlen)
{
// not used in this version
return -1;

View File

@ -14,7 +14,7 @@ class OpenCVFileManager : public FileManager {
virtual const char *fileName(int8_t fd);
virtual int8_t readDir(const char *where, const char *suffix, char *outputFN, int8_t startIdx);
virtual int8_t readDir(const char *where, const char *suffix, char *outputFN, int8_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);

1
teensy/appledisplay.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/appledisplay.cpp

1
teensy/appledisplay.h Symbolic link
View File

@ -0,0 +1 @@
../apple/appledisplay.h

1
teensy/applekeyboard.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/applekeyboard.cpp

1
teensy/applekeyboard.h Symbolic link
View File

@ -0,0 +1 @@
../apple/applekeyboard.h

1
teensy/applemmu-rom.h Symbolic link
View File

@ -0,0 +1 @@
../apple/applemmu-rom.h

1
teensy/applemmu.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/applemmu.cpp

1
teensy/applemmu.h Symbolic link
View File

@ -0,0 +1 @@
../apple/applemmu.h

1
teensy/applevm.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/applevm.cpp

1
teensy/applevm.h Symbolic link
View File

@ -0,0 +1 @@
../apple/applevm.h

1
teensy/bios-font.h Symbolic link
View File

@ -0,0 +1 @@
../bios-font.h

421
teensy/bios.cpp Normal file
View File

@ -0,0 +1,421 @@
#include "bios.h"
#include "applevm.h"
#include "physicalkeyboard.h"
#include "teensy-keyboard.h"
#include "cpu.h"
#include "teensy-filemanager.h"
#include "teensy-display.h"
#include "globals.h"
enum {
ACT_EXIT = 0,
ACT_RESET = 1,
ACT_REBOOT = 2,
ACT_MONITOR = 3,
ACT_DISPLAYTYPE = 4,
ACT_DEBUG = 5,
ACT_DISK1 = 6,
ACT_DISK2 = 7,
ACT_VOLPLUS = 8,
ACT_VOLMINUS = 9,
NUM_ACTIONS = 10
};
const char *titles[NUM_ACTIONS] = { "Resume",
"Reset",
"Cold Reboot",
"Drop to Monitor",
"Display: %s",
"Debug: %s",
"%s Disk 1",
"%s Disk 2",
"Volume +",
"Volume -"
};
// FIXME: abstract the pin # rather than repeating it here
#define RESETPIN 39
extern int16_t g_volume; // FIXME: external global. icky.
extern uint8_t debugMode; // and another. :/
// 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, "/A2DISKS/");
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';
}
}
BIOS::~BIOS()
{
}
bool BIOS::runUntilDone()
{
int8_t prevAction = ACT_EXIT;
bool volumeDidChange = 0;
while (1) {
switch (prevAction = GetAction(prevAction)) {
case ACT_EXIT:
goto done;
case ACT_REBOOT:
ColdReboot();
goto done;
case ACT_RESET:
WarmReset();
goto done;
case ACT_MONITOR:
((AppleVM *)g_vm)->Monitor();
goto done;
case ACT_DISPLAYTYPE:
g_displayType++;
g_displayType %= 4; // FIXME: abstract max #
break;
case ACT_DEBUG:
debugMode++;
debugMode %= 8; // FIXME: abstract max #
break;
case ACT_DISK1:
if (((AppleVM *)g_vm)->DiskName(0)[0] != '\0') {
((AppleVM *)g_vm)->ejectDisk(0);
} else {
if (SelectDiskImage()) {
((AppleVM *)g_vm)->insertDisk(0, staticPathConcat(rootPath, fileDirectory[selectedFile]), false);
goto done;
}
}
break;
case ACT_DISK2:
if (((AppleVM *)g_vm)->DiskName(1)[0] != '\0') {
((AppleVM *)g_vm)->ejectDisk(1);
} else {
if (SelectDiskImage()) {
((AppleVM *)g_vm)->insertDisk(1, staticPathConcat(rootPath, fileDirectory[selectedFile]), false);
goto done;
}
}
break;
case ACT_VOLPLUS:
g_volume += 10;
if (g_volume > 255) {
g_volume = 255;
}
volumeDidChange = true;
break;
case ACT_VOLMINUS:
g_volume -= 10;
if (g_volume < 0) {
g_volume = 0;
}
volumeDidChange = true;
break;
}
}
done:
// Undo whatever damage we've done to the screen
g_display->redraw();
g_display->blit();
// return true if any persistent setting changed that we want to store in eeprom
return volumeDidChange;
}
void BIOS::WarmReset()
{
g_cpu->Reset();
}
void BIOS::ColdReboot()
{
g_vm->Reset();
g_cpu->Reset();
}
uint8_t BIOS::GetAction(int8_t selection)
{
while (1) {
DrawMainMenu(selection);
while (!((TeensyKeyboard *)g_keyboard)->kbhit() &&
(digitalRead(RESETPIN) == HIGH)) {
;
// 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
return ACT_EXIT;
}
switch (((TeensyKeyboard *)g_keyboard)->read()) {
case DARR:
selection++;
selection %= NUM_ACTIONS;
break;
case UARR:
selection--;
if (selection < 0)
selection = NUM_ACTIONS-1;
break;
case RET:
if (isActionActive(selection))
return selection;
break;
}
}
}
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_DEBUG:
case ACT_DISK1:
case ACT_DISK2:
return true;
case ACT_VOLPLUS:
return (g_volume < 255);
case ACT_VOLMINUS:
return (g_volume > 0);
}
/* NOTREACHED */
return false;
}
void BIOS::DrawMainMenu(int8_t selection)
{
((TeensyDisplay *)g_display)->clrScr();
g_display->drawString(M_NORMAL, 0, 12, "BIOS Configuration");
for (int i=0; i<NUM_ACTIONS; i++) {
char buf[25];
if (i == ACT_DISK1 || i == ACT_DISK2) {
sprintf(buf, titles[i], ((AppleVM *)g_vm)->DiskName(i - ACT_DISK1)[0] ? "Eject" : "Insert");
} else if (i == ACT_DISPLAYTYPE) {
switch (g_displayType) {
case m_blackAndWhite:
sprintf(buf, titles[i], "B&W");
break;
case m_monochrome:
sprintf(buf, titles[i], "Mono");
break;
case m_ntsclike:
sprintf(buf, titles[i], "NTSC-like");
break;
case m_perfectcolor:
sprintf(buf, titles[i], "RGB");
break;
}
} else if (i == ACT_DEBUG) {
switch (debugMode) {
case D_NONE:
sprintf(buf, titles[i], "off");
break;
case D_SHOWFPS:
sprintf(buf, titles[i], "Show FPS");
break;
case D_SHOWMEMFREE:
sprintf(buf, titles[i], "Show mem free");
break;
case D_SHOWPADDLES:
sprintf(buf, titles[i], "Show paddles");
break;
case D_SHOWPC:
sprintf(buf, titles[i], "Show PC");
break;
case D_SHOWCYCLES:
sprintf(buf, titles[i], "Show cycles");
break;
case D_SHOWBATTERY:
sprintf(buf, titles[i], "Show battery");
break;
case D_SHOWTIME:
sprintf(buf, titles[i], "Show time");
break;
}
} else {
strcpy(buf, titles[i]);
}
if (isActionActive(i)) {
g_display->drawString(selection == i ? M_SELECTED : M_NORMAL, 10, 50 + 14 * i, buf);
} else {
g_display->drawString(selection == i ? M_SELECTDISABLED : M_DISABLED, 10, 50 + 14 * i, buf);
}
}
// draw the volume bar
uint16_t volCutoff = 300.0 * (float)((float) g_volume / 256.0);
for (uint8_t y=200; y<=210; y++) {
((TeensyDisplay *)g_display)->moveTo(10, y);
for (uint16_t x = 0; x< 300; x++) {
((TeensyDisplay *)g_display)->drawNextPixel( x <= volCutoff ? 0xFFFF : 0x0010 );
}
}
}
// 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()
{
int8_t sel = 0;
int8_t page = 0;
while (1) {
DrawDiskNames(page, sel);
while (!((TeensyKeyboard *)g_keyboard)->kbhit())
;
switch (((TeensyKeyboard *)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) {
page++;
//sel = 0;
} else {
if (strcmp(fileDirectory[sel-1], "../") == 0) {
// Go up a directory (strip a directory name from rootPath)
stripDirectory();
page = 0;
//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 = 0;
page = 0;
continue;
} else {
selectedFile = sel - 1;
return true;
}
}
break;
}
}
}
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, "/");
}
}
void BIOS::DrawDiskNames(uint8_t page, int8_t selection)
{
uint8_t fileCount = GatherFilenames(page);
((TeensyDisplay *)g_display)->clrScr();
g_display->drawString(M_NORMAL, 0, 12, "BIOS Configuration - pick disk");
if (page == 0) {
g_display->drawString(selection == 0 ? M_SELECTDISABLED : M_DISABLED, 10, 50, "<Prev>");
} else {
g_display->drawString(selection == 0 ? M_SELECTED : M_NORMAL, 10, 50, "<Prev>");
}
uint8_t i;
for (i=0; i<BIOS_MAXFILES; i++) {
if (i < fileCount) {
g_display->drawString((i == selection-1) ? M_SELECTED : M_NORMAL, 10, 50 + 14 * (i+1), fileDirectory[i]);
} else {
g_display->drawString((i == selection-1) ? M_SELECTDISABLED : M_DISABLED, 10, 50+14*(i+1), "-");
}
}
// FIXME: this doesn't accurately say whether or not there *are* more.
if (fileCount == BIOS_MAXFILES || fileCount == 0) {
g_display->drawString((i+1 == selection) ? M_SELECTDISABLED : M_DISABLED, 10, 50 + 14 * (i+1), "<Next>");
} else {
g_display->drawString(i+1 == selection ? M_SELECTED : M_NORMAL, 10, 50 + 14 * (i+1), "<Next>");
}
}
uint8_t BIOS::GatherFilenames(uint8_t pageOffset)
{
uint8_t startNum = 10 * pageOffset;
uint8_t count = 0; // number we're including in our listing
while (1) {
char fn[BIOS_MAXPATH];
// FIXME: add po, nib
int8_t idx = g_filemanager->readDir(rootPath, "dsk", fn, startNum + count, BIOS_MAXPATH);
if (idx == -1) {
return count;
}
idx++;
strncpy(fileDirectory[count], fn, BIOS_MAXPATH);
count++;
if (count >= BIOS_MAXFILES) {
return count;
}
}
}

38
teensy/bios.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef __BIOS_H
#define __BIOS_H
#include <Arduino.h>
#define BIOS_MAXFILES 10 // number of files in a page of listing
#define BIOS_MAXPATH 40 // 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();
private:
uint8_t GetAction(int8_t prevAction);
bool isActionActive(int8_t action);
void DrawMainMenu(int8_t selection);
void WarmReset();
void ColdReboot();
bool SelectDiskImage();
void DrawDiskNames(uint8_t page, int8_t selection);
uint8_t GatherFilenames(uint8_t pageOffset);
void stripDirectory();
private:
int8_t selectedFile;
char fileDirectory[BIOS_MAXFILES][BIOS_MAXPATH+1];
char rootPath[255-BIOS_MAXPATH];
};
#endif

1
teensy/cpu.cpp Symbolic link
View File

@ -0,0 +1 @@
../cpu.cpp

1
teensy/cpu.h Symbolic link
View File

@ -0,0 +1 @@
../cpu.h

1
teensy/diskii-rom.h Symbolic link
View File

@ -0,0 +1 @@
../apple/diskii-rom.h

1
teensy/diskii.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/diskii.cpp

1
teensy/diskii.h Symbolic link
View File

@ -0,0 +1 @@
../apple/diskii.h

1
teensy/display-bg.h Symbolic link
View File

@ -0,0 +1 @@
../display-bg.h

1
teensy/filemanager.h Symbolic link
View File

@ -0,0 +1 @@
../filemanager.h

1
teensy/font.h Symbolic link
View File

@ -0,0 +1 @@
../apple/font.h

1
teensy/globals.cpp Symbolic link
View File

@ -0,0 +1 @@
../globals.cpp

1
teensy/globals.h Symbolic link
View File

@ -0,0 +1 @@
../globals.h

1
teensy/mmu.h Symbolic link
View File

@ -0,0 +1 @@
../mmu.h

1
teensy/nibutil.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/nibutil.cpp

1
teensy/nibutil.h Symbolic link
View File

@ -0,0 +1 @@
../apple/nibutil.h

1
teensy/physicaldisplay.h Symbolic link
View File

@ -0,0 +1 @@
../physicaldisplay.h

1
teensy/physicalkeyboard.h Symbolic link
View File

@ -0,0 +1 @@
../physicalkeyboard.h

1
teensy/physicalpaddles.h Symbolic link
View File

@ -0,0 +1 @@
../physicalpaddles.h

1
teensy/physicalspeaker.h Symbolic link
View File

@ -0,0 +1 @@
../physicalspeaker.h

1
teensy/slot.cpp Symbolic link
View File

@ -0,0 +1 @@
../apple/slot.cpp

1
teensy/slot.h Symbolic link
View File

@ -0,0 +1 @@
../apple/slot.h

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

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

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

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

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

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

560
teensy/teensy-display.cpp Normal file
View File

@ -0,0 +1,560 @@
#include <ctype.h> // isgraph
#include "teensy-display.h"
#include "bios-font.h"
#include "display-bg.h"
#define RS 16
#define WR 17
#define CS 18
#define RST 19
// Ports C&D of the Teensy connected to DB of the display
#define DB_0 15
#define DB_1 22
#define DB_2 23
#define DB_3 9
#define DB_4 10
#define DB_5 13
#define DB_6 11
#define DB_7 12
#define DB_8 2
#define DB_9 14
#define DB_10 7
#define DB_11 8
#define DB_12 6
#define DB_13 20
#define DB_14 21
#define DB_15 5
#define disp_x_size 239
#define disp_y_size 319
#define setPixel(color) { LCD_Write_DATA(((color)>>8),((color)&0xFF)); } // 565 RGB
#include "globals.h"
#include "applevm.h"
// RGB map of each of the lowres colors
const uint16_t loresPixelColors[16] = { 0x0000, // 0 black
0xC006, // 1 magenta
0x0010, // 2 dark blue
0xA1B5, // 3 purple
0x0480, // 4 dark green
0x6B4D, // 5 dark grey
0x1B9F, // 6 med blue
0x0DFD, // 7 light blue
0x92A5, // 8 brown
0xF8C5, // 9 orange
0x9555, // 10 light gray
0xFCF2, // 11 pink
0x07E0, // 12 green
0xFFE0, // 13 yellow
0x87F0, // 14 aqua
0xFFFF // 15 white
};
TeensyDisplay::TeensyDisplay()
{
pinMode(DB_8, OUTPUT);
pinMode(DB_9, OUTPUT);
pinMode(DB_10, OUTPUT);
pinMode(DB_11, OUTPUT);
pinMode(DB_12, OUTPUT);
pinMode(DB_13, OUTPUT);
pinMode(DB_14, OUTPUT);
pinMode(DB_15, OUTPUT);
pinMode(DB_0, OUTPUT);
pinMode(DB_1, OUTPUT);
pinMode(DB_2, OUTPUT);
pinMode(DB_3, OUTPUT);
pinMode(DB_4, OUTPUT);
pinMode(DB_5, OUTPUT);
pinMode(DB_6, OUTPUT);
pinMode(DB_7, OUTPUT);
P_RS = portOutputRegister(digitalPinToPort(RS));
B_RS = digitalPinToBitMask(RS);
P_WR = portOutputRegister(digitalPinToPort(WR));
B_WR = digitalPinToBitMask(WR);
P_CS = portOutputRegister(digitalPinToPort(CS));
B_CS = digitalPinToBitMask(CS);
P_RST = portOutputRegister(digitalPinToPort(RST));
B_RST = digitalPinToBitMask(RST);
pinMode(RS,OUTPUT);
pinMode(WR,OUTPUT);
pinMode(CS,OUTPUT);
pinMode(RST,OUTPUT);
// begin initialization
sbi(P_RST, B_RST);
delay(5);
cbi(P_RST, B_RST);
delay(15);
sbi(P_RST, B_RST);
delay(15);
cbi(P_CS, B_CS);
LCD_Write_COM_DATA(0x00,0x0001); // oscillator start [enable]
LCD_Write_COM_DATA(0x03,0xA8A4); // power control [%1010 1000 1010 1000] == DCT3, DCT1, BT2, DC3, DC1, AP2
LCD_Write_COM_DATA(0x0C,0x0000); // power control2 [0]
LCD_Write_COM_DATA(0x0D,0x080C); // power control3 [VRH3, VRH2, invalid bits]
LCD_Write_COM_DATA(0x0E,0x2B00); // power control4 VCOMG, VDV3, VDV1, VDV0
LCD_Write_COM_DATA(0x1E,0x00B7); // power control5 nOTP, VCM5, VCM4, VCM2, VCM1, VCM0
// LCD_Write_COM_DATA(0x01,0x2B3F); // driver control output REV, BGR, TB, MUX8, MUX5, MUX4, MUX3, MUX2, MUX1, MUX0
// Change that: TB = 0 please
LCD_Write_COM_DATA(0x01,0x293F); // driver control output REV, BGR, TB, MUX8, MUX5, MUX4, MUX3, MUX2, MUX1, MUX0
// LCD_Write_COM_DATA(0x01,0x693F); // driver control output RL, REV, BGR, TB, MUX8, MUX5, MUX4, MUX3, MUX2, MUX1, MUX0
LCD_Write_COM_DATA(0x02,0x0600); // LCD drive AC control B/C, EOR
LCD_Write_COM_DATA(0x10,0x0000); // sleep mode 0
// Change the (Y) order here to match above (TB=0)
//LCD_Write_COM_DATA(0x11,0x6070); // Entry mode DFM1, DFM0, TY0, ID1, ID0
//LCD_Write_COM_DATA(0x11,0x6050); // Entry mode DFM1, DFM0, TY0, ID0
LCD_Write_COM_DATA(0x11,0x6078); // Entry mode DFM1, DFM0, TY0, ID1, ID0, AM
LCD_Write_COM_DATA(0x05,0x0000); // compare reg1
LCD_Write_COM_DATA(0x06,0x0000); // compare reg2
LCD_Write_COM_DATA(0x16,0xEF1C); // horiz porch (default)
LCD_Write_COM_DATA(0x17,0x0003); // vertical porch
LCD_Write_COM_DATA(0x07,0x0233); // display control VLE1, GON, DTE, D1, D0
LCD_Write_COM_DATA(0x0B,0x0000); // frame cycle control
LCD_Write_COM_DATA(0x0F,0x0000); // gate scan start posn
LCD_Write_COM_DATA(0x41,0x0000); // vertical scroll control1
LCD_Write_COM_DATA(0x42,0x0000); // vertical scroll control2
LCD_Write_COM_DATA(0x48,0x0000); // first window start
LCD_Write_COM_DATA(0x49,0x013F); // first window end (0x13f == 319)
LCD_Write_COM_DATA(0x4A,0x0000); // second window start
LCD_Write_COM_DATA(0x4B,0x0000); // second window end
LCD_Write_COM_DATA(0x44,0xEF00); // horiz ram addr posn
LCD_Write_COM_DATA(0x45,0x0000); // vert ram start posn
LCD_Write_COM_DATA(0x46,0x013F); // vert ram end posn
LCD_Write_COM_DATA(0x30,0x0707); // γ control
LCD_Write_COM_DATA(0x31,0x0204);//
LCD_Write_COM_DATA(0x32,0x0204);//
LCD_Write_COM_DATA(0x33,0x0502);//
LCD_Write_COM_DATA(0x34,0x0507);//
LCD_Write_COM_DATA(0x35,0x0204);//
LCD_Write_COM_DATA(0x36,0x0204);//
LCD_Write_COM_DATA(0x37,0x0502);//
LCD_Write_COM_DATA(0x3A,0x0302);//
LCD_Write_COM_DATA(0x3B,0x0302);//
LCD_Write_COM_DATA(0x23,0x0000);// RAM write data mask1
LCD_Write_COM_DATA(0x24,0x0000); // RAM write data mask2
LCD_Write_COM_DATA(0x25,0x8000); // frame frequency (OSC3)
LCD_Write_COM_DATA(0x4f,0x0000); // Set GDDRAM Y address counter
LCD_Write_COM_DATA(0x4e,0x0000); // Set GDDRAM X address counter
#if 0
// Set data access speed optimization (?)
LCD_Write_COM_DATA(0x28, 0x0006);
LCD_Write_COM_DATA(0x2F, 0x12BE);
LCD_Write_COM_DATA(0x12, 0x6CEB);
#endif
LCD_Write_COM(0x22); // RAM data write
sbi(P_CS, B_CS);
// LCD initialization complete
setColor(255, 255, 255);
clrScr();
}
TeensyDisplay::~TeensyDisplay()
{
}
void TeensyDisplay::_fast_fill_16(int ch, int cl, long pix)
{
*(volatile uint8_t *)(&GPIOD_PDOR) = ch;
*(volatile uint8_t *)(&GPIOC_PDOR) = cl;
uint16_t blocks = pix / 16;
for (uint16_t i=0; i<blocks; i++) {
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
pulse_low(P_WR, B_WR);
}
if ((pix % 16) != 0) {
for (int i=0; i<(pix % 16); i++)
{
pulse_low(P_WR, B_WR);
}
}
}
void TeensyDisplay::redraw()
{
cbi(P_CS, B_CS);
clrXY();
sbi(P_RS, B_RS);
moveTo(0, 0);
for (int y=0; y<240; y++) {
for (int x=0; x<320; x++) {
uint8_t r = pgm_read_byte(&displayBitmap[(y * 320 + x)*3 + 0]);
uint8_t g = pgm_read_byte(&displayBitmap[(y * 320 + x)*3 + 1]);
uint8_t b = pgm_read_byte(&displayBitmap[(y * 320 + x)*3 + 2]);
uint16_t color16 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
setPixel(color16);
}
}
if (g_vm) {
drawDriveDoor(0, ((AppleVM *)g_vm)->DiskName(0)[0] == '\0');
drawDriveDoor(1, ((AppleVM *)g_vm)->DiskName(1)[0] == '\0');
}
cbi(P_CS, B_CS);
clrXY();
sbi(P_RS, B_RS);
}
void TeensyDisplay::clrScr()
{
cbi(P_CS, B_CS);
clrXY();
sbi(P_RS, B_RS);
_fast_fill_16(0, 0, ((disp_x_size+1)*(disp_y_size+1)));
sbi(P_CS, B_CS);
}
// The display flips X and Y, so expect to see "x" as "vertical"
// and "y" as "horizontal" here...
void TeensyDisplay::setYX(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
LCD_Write_COM_DATA(0x44, (y2<<8)+y1); // Horiz start addr, Horiz end addr
LCD_Write_COM_DATA(0x45, x1); // vert start pos
LCD_Write_COM_DATA(0x46, x2); // vert end pos
LCD_Write_COM_DATA(0x4e,y1); // RAM address set (horiz)
LCD_Write_COM_DATA(0x4f,x1); // RAM address set (vert)
LCD_Write_COM(0x22);
}
void TeensyDisplay::clrXY()
{
setYX(0, 0, disp_y_size, disp_x_size);
}
void TeensyDisplay::setColor(byte r, byte g, byte b)
{
fch=((r&248)|g>>5);
fcl=((g&28)<<3|b>>3);
}
void TeensyDisplay::setColor(uint16_t color)
{
fch = (uint8_t)(color >> 8);
fcl = (uint8_t)(color & 0xFF);
}
void TeensyDisplay::fillRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
if (x1>x2) {
swap(uint16_t, x1, x2);
}
if (y1 > y2) {
swap(uint16_t, y1, y2);
}
cbi(P_CS, B_CS);
setYX(x1, y1, x2, y2);
sbi(P_RS, B_RS);
_fast_fill_16(fch,fcl,((long(x2-x1)+1)*(long(y2-y1)+1)));
sbi(P_CS, B_CS);
}
void TeensyDisplay::drawPixel(uint16_t x, uint16_t y)
{
cbi(P_CS, B_CS);
setYX(x, y, x, y);
setPixel((fch<<8)|fcl);
sbi(P_CS, B_CS);
clrXY();
}
void TeensyDisplay::drawPixel(uint16_t x, uint16_t y, uint16_t color)
{
cbi(P_CS, B_CS);
setYX(x, y, x, y);
setPixel(color);
sbi(P_CS, B_CS);
clrXY();
}
void TeensyDisplay::drawPixel(uint16_t x, uint16_t y, uint8_t r, uint8_t g, uint8_t b)
{
uint16_t color16 = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3);
cbi(P_CS, B_CS);
setYX(x, y, x, y);
setPixel(color16);
sbi(P_CS, B_CS);
clrXY();
}
void TeensyDisplay::LCD_Writ_Bus(uint8_t ch, uint8_t cl)
{
*(volatile uint8_t *)(&GPIOD_PDOR) = ch;
*(volatile uint8_t *)(&GPIOC_PDOR) = cl;
pulse_low(P_WR, B_WR);
}
void TeensyDisplay::LCD_Write_COM(uint8_t VL)
{
cbi(P_RS, B_RS);
LCD_Writ_Bus(0x00, VL);
}
void TeensyDisplay::LCD_Write_DATA(uint8_t VH, uint8_t VL)
{
sbi(P_RS, B_RS);
LCD_Writ_Bus(VH,VL);
}
void TeensyDisplay::LCD_Write_DATA(uint8_t VL)
{
sbi(P_RS, B_RS);
LCD_Writ_Bus(0x00, VL);
}
void TeensyDisplay::LCD_Write_COM_DATA(uint8_t com1, uint16_t dat1)
{
LCD_Write_COM(com1);
LCD_Write_DATA(dat1>>8, dat1);
}
void TeensyDisplay::moveTo(uint16_t col, uint16_t row)
{
cbi(P_CS, B_CS);
// FIXME: eventually set drawing to the whole screen and leave it that way
// set drawing to the whole screen
// setYX(0, 0, disp_y_size, disp_x_size);
LCD_Write_COM_DATA(0x4e,row); // RAM address set (horiz)
LCD_Write_COM_DATA(0x4f,col); // RAM address set (vert)
LCD_Write_COM(0x22);
}
void TeensyDisplay::drawNextPixel(uint16_t color)
{
// Anything inside this object should call setPixel directly. This
// is primarily for the BIOS.
setPixel(color);
}
void TeensyDisplay::blit()
{
uint8_t *videoBuffer = g_vm->videoBuffer; // FIXME: poking deep
// remember these are "starts at pixel number" values, where 0 is the first.
#define HOFFSET 18
#define VOFFSET 13
// Define the horizontal area that we're going to draw in
LCD_Write_COM_DATA(0x45, HOFFSET); // offset by 20 to center it...
LCD_Write_COM_DATA(0x46, 279+HOFFSET);
// position the "write" address
LCD_Write_COM_DATA(0x4e,0+VOFFSET); // row
LCD_Write_COM_DATA(0x4f,HOFFSET); // col
// prepare the LCD to receive data bytes for its RAM
LCD_Write_COM(0x22);
// send the pixel data
sbi(P_RS, B_RS);
uint16_t pixel;
for (uint8_t y=0; y<192; y++) { // Drawing 192 of the 240 rows
pixel = y * (320/2)-1;
for (uint16_t x=0; x<280; x++) { // Drawing 280 of the 320 pixels
uint8_t colorIdx;
if (!(x & 0x01)) {
pixel++;
colorIdx = videoBuffer[pixel] >> 4;
} else {
colorIdx = videoBuffer[pixel] & 0x0F;
}
LCD_Writ_Bus(loresPixelColors[colorIdx]>>8,loresPixelColors[colorIdx]);
}
}
cbi(P_CS, B_CS);
// draw overlay, if any
if (overlayMessage[0]) {
// reset the viewport in order to draw the overlay...
LCD_Write_COM_DATA(0x45, 0);
LCD_Write_COM_DATA(0x46, 319);
drawString(M_SELECTDISABLED, 1, 240 - 16 - 12, overlayMessage);
}
}
void TeensyDisplay::drawCharacter(uint8_t mode, uint16_t x, uint8_t y, char c)
{
int8_t xsize = 8,
ysize = 0x0C,
offset = 0x20;
uint16_t temp;
c -= offset;// font starts with a space
uint16_t offPixel, onPixel;
switch (mode) {
case M_NORMAL:
onPixel = 0xFFFF;
offPixel = 0x0010;
break;
case M_SELECTED:
onPixel = 0x0000;
offPixel = 0xFFFF;
break;
case M_DISABLED:
default:
onPixel = 0x7BEF;
offPixel = 0x0000;
break;
case M_SELECTDISABLED:
onPixel = 0x7BEF;
offPixel = 0xFFE0;
break;
}
temp=(c*ysize);
for (int8_t y_off = 0; y_off <= ysize; y_off++) {
moveTo(x, y + y_off);
uint8_t ch = pgm_read_byte(&BiosFont[temp]);
for (int8_t x_off = 0; x_off <= xsize; x_off++) {
if (ch & (1 << (7-x_off))) {
setPixel(onPixel);
} else {
setPixel(offPixel);
}
}
temp++;
}
}
void TeensyDisplay::drawString(uint8_t mode, uint16_t x, uint8_t y, const char *str)
{
int8_t xsize = 8; // width of a char in this font
for (int8_t i=0; i<strlen(str); i++) {
drawCharacter(mode, x, y, str[i]);
x += xsize; // fixme: any inter-char spacing?
}
}
void TeensyDisplay::drawDriveDoor(uint8_t which, bool isOpen)
{
// location of drive door for left drive
uint16_t xoff = 55;
uint16_t yoff = 216;
// location for right drive
if (which == 1) {
xoff += 134;
}
for (int y=0; y<20; y++) {
for (int x=0; x<43; x++) {
uint8_t r, g, b;
if (isOpen) {
r = pgm_read_byte(&driveLatchOpen[(y * 43 + x)*3 + 0]);
g = pgm_read_byte(&driveLatchOpen[(y * 43 + x)*3 + 1]);
b = pgm_read_byte(&driveLatchOpen[(y * 43 + x)*3 + 2]);
} else {
r = pgm_read_byte(&driveLatch[(y * 43 + x)*3 + 0]);
g = pgm_read_byte(&driveLatch[(y * 43 + x)*3 + 1]);
b = pgm_read_byte(&driveLatch[(y * 43 + x)*3 + 2]);
}
drawPixel(x+xoff, y+yoff, r, g, b);
}
}
}
void TeensyDisplay::drawDriveStatus(uint8_t which, bool isRunning)
{
// location of status indicator for left drive
uint16_t xoff = 125;
uint16_t yoff = 213;
// and right drive
if (which == 1)
xoff += 135;
for (int y=0; y<2; y++) {
for (int x=0; x<6; x++) {
drawPixel(x + xoff, y + yoff, isRunning ? 0xF800 : 0x8AA9);
}
}
}
void TeensyDisplay::drawBatteryStatus(uint8_t percent)
{
uint16_t xoff = 300;
uint16_t yoff = 222;
// DEBUGGING: disabling this; it's drawing it a *lot*
return;
// the area around the apple is 12 wide
// it's exactly 11 high
// the color is 210/202/159
float watermark = ((float)percent / 100.0) * 11;
for (int y=0; y<11; y++) {
uint8_t bgr = 210;
uint8_t bgg = 202;
uint8_t bgb = 159;
if (11-y > watermark) {
// black...
bgr = bgg = bgb = 0;
}
for (int x=0; x<11; x++) {
uint8_t *p = &appleBitmap[(y * 10 + (x-1))*4];
// It's RGBA; blend w/ background color
uint8_t r,g,b;
float alpha = (float)pgm_read_byte(&appleBitmap[(y * 10 + (x-1))*4 + 3]) / 255.0;
r = (float)pgm_read_byte(&appleBitmap[(y * 10 + (x-1))*4 + 0])
* alpha + (bgr * (1.0 - alpha));
g = (float)pgm_read_byte(&appleBitmap[(y * 10 + (x-1))*4 + 1])
* alpha + (bgr * (1.0 - alpha));
b = (float)pgm_read_byte(&appleBitmap[(y * 10 + (x-1))*4 + 2])
* alpha + (bgr * (1.0 - alpha));
drawPixel(x+xoff, y+yoff, r, g, b);
}
}
}

81
teensy/teensy-display.h Normal file
View File

@ -0,0 +1,81 @@
#ifndef __TEENSY_DISPLAY_H
#define __TEENSY_DISPLAY_H
#include <Arduino.h>
#include "physicaldisplay.h"
enum {
M_NORMAL = 0,
M_SELECTED = 1,
M_DISABLED = 2,
M_SELECTDISABLED = 3
};
#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();
virtual void redraw();
void clrScr();
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 drawDriveStatus(uint8_t which, bool isRunning);
virtual void drawBatteryStatus(uint8_t percent);
protected:
void moveTo(uint16_t col, uint16_t row);
void drawNextPixel(uint16_t color);
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
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);
void fillRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
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;
};
#endif

View File

@ -0,0 +1,275 @@
#include <Arduino.h>
#include <wchar.h>
#include "ff.h" // File System
#include "teensy-filemanager.h"
#include <string.h> // strcpy
// 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];
}
// suffix may be comma-separated
int8_t TeensyFileManager::readDir(const char *where, const char *suffix, char *outputFN, int8_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;
}
int8_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) {
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::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);
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);
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)));
}

View File

@ -0,0 +1,30 @@
#ifndef __TEENSYFILEMANAGER_H
#define __TEENSYFILEMANAGER_H
#include "filemanager.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 *outputFN, int8_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 writeBlock(int8_t fd, uint8_t *fromWhere, bool isNib = false);
virtual bool writeTrack(int8_t fd, uint8_t *fromWhere, bool isNib = false);
private:
int8_t numCached;
char cachedNames[MAXFILES][MAXPATH];
unsigned long fileSeekPositions[MAXFILES];
};
#endif

231
teensy/teensy-keyboard.cpp Normal file
View File

@ -0,0 +1,231 @@
#include <Arduino.h>
#include "teensy-keyboard.h"
#include <Keypad.h>
#include <RingBuffer.h>
const byte ROWS = 5;
const byte COLS = 13;
char keys[ROWS][COLS] = {
{ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', DEL },
{ ESC, 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']' },
{ _CTRL, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', RET },
{ LSHFT, 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', RSHFT, 0 },
{ LOCK, '`', TAB, '\\', LA, ' ', RA, LARR, RARR, DARR, UARR, 0, 0 }
};
uint8_t rowsPins[ROWS] = { 33, 34, 35, 36, 37 };
uint8_t colsPins[COLS] = { 0, 1, 3, 4, 24, 25, 26, 27, 28, 29, 30, 31, 32 };
Keypad keypad(makeKeymap(keys), rowsPins, colsPins, ROWS, COLS);
RingBuffer buffer(10); // 10 keys should be plenty, right?
static uint8_t shiftedNumber[] = { '<', // ,
'_', // -
'>', // .
'?', // /
')', // 0
'!', // 1
'@', // 2
'#', // 3
'$', // 4
'%', // 5
'^', // 6
'&', // 7
'*', // 8
'(', // 9
0, // (: is not a key)
':' // ;
};
TeensyKeyboard::TeensyKeyboard(VMKeyboard *k) : PhysicalKeyboard(k)
{
keypad.setDebounceTime(5);
leftShiftPressed = false;
rightShiftPressed = false;
ctrlPressed = false;
capsLock = true;
leftApplePressed = false;
rightApplePressed = false;
numPressed = 0;
}
TeensyKeyboard::~TeensyKeyboard()
{
}
void TeensyKeyboard::pressedKey(uint8_t key)
{
numPressed++;
if (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) {
buffer.addByte(key);
return;
}
if (key >= 'a' &&
key <= 'z') {
if (ctrlPressed) {
buffer.addByte(key - 'a' + 1);
return;
}
if (leftShiftPressed || rightShiftPressed || capsLock) {
buffer.addByte(key - 'a' + 'A');
return;
}
buffer.addByte(key);
return;
}
// FIXME: can we control-shift?
if (key >= ',' && key <= ';') {
if (leftShiftPressed || rightShiftPressed) {
buffer.addByte(shiftedNumber[key - ',']);
return;
}
buffer.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) {
buffer.addByte(ret);
return;
}
}
// Everything else falls through.
buffer.addByte(key);
}
void TeensyKeyboard::releasedKey(uint8_t key)
{
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 TeensyKeyboard::kbhit()
{
if (keypad.getKeys()) {
for (int i=0; i<LIST_MAX; i++) {
if ( keypad.key[i].stateChanged ) {
switch (keypad.key[i].kstate) {
case PRESSED:
pressedKey(keypad.key[i].kchar);
break;
case RELEASED:
releasedKey(keypad.key[i].kchar);
break;
case HOLD:
case IDLE:
break;
}
}
}
}
// For debugging: also allow USB serial to act as a keyboard
if (Serial.available()) {
buffer.addByte(Serial.read());
}
return buffer.hasData();
}
int8_t TeensyKeyboard::read()
{
if (buffer.hasData()) {
return buffer.consumeByte();
}
return 0;
}
// This is a non-buffered interface to the physical keyboard, as used
// by the VM.
void TeensyKeyboard::maintainKeyboard()
{
if (keypad.getKeys()) {
for (int i=0; i<LIST_MAX; i++) {
if ( keypad.key[i].stateChanged ) {
switch (keypad.key[i].kstate) {
case PRESSED:
vmkeyboard->keyDepressed(keypad.key[i].kchar);
break;
case RELEASED:
vmkeyboard->keyReleased(keypad.key[i].kchar);
break;
case HOLD:
case IDLE:
break;
}
}
}
}
}

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

@ -0,0 +1,34 @@
#ifndef __TEENSY_KEYBOARD_H
#define __TEENSY_KEYBOARD_H
#include "physicalkeyboard.h"
class TeensyKeyboard : public PhysicalKeyboard {
public:
TeensyKeyboard(VMKeyboard *k);
virtual ~TeensyKeyboard();
// Interface used by the VM...
virtual void maintainKeyboard();
// Interface used by the BIOS...
bool kbhit();
int8_t read();
private:
void pressedKey(uint8_t key);
void releasedKey(uint8_t key);
private:
bool leftShiftPressed;
bool rightShiftPressed;
bool ctrlPressed;
bool capsLock;
bool leftApplePressed;
bool rightApplePressed;
int8_t numPressed;
};
#endif

53
teensy/teensy-paddles.cpp Normal file
View File

@ -0,0 +1,53 @@
#include "teensy-paddles.h"
/* C061: Open Apple (Paddle 0 button pressed if &= 0x80)
* C062: Closed Apple (Paddle 1 button pressed if &= 0x80)
* C064: PADDLE0 (sets bit 0x80 when value reached, increments of 11 us)
* C065: PADDLE1 (sets bit 0x80 when value reached, increments of 11 us)
* C070: "start reading paddle data" - "may take up to 3 milliseconds"
*/
#define PADDLE0 A24
#define PADDLE1 A23
#include "globals.h"
TeensyPaddles::TeensyPaddles()
{
pinMode(PADDLE0, INPUT);
pinMode(PADDLE1, INPUT);
}
TeensyPaddles::~TeensyPaddles()
{
}
uint8_t TeensyPaddles::paddle0()
{
uint8_t raw = 255 - analogRead(PADDLE0);
return raw;
// 40 .. 200 on the old joystick
if (raw >200) raw = 200;
if (raw < 40) raw = 40;
return map(raw, 40, 200, 0, 255);
}
uint8_t TeensyPaddles::paddle1()
{
uint8_t raw = analogRead(PADDLE1);
return raw;
// 60..200 on the old joystick
if (raw >200) raw = 200;
if (raw < 60) raw = 60;
return map(raw, 60, 200, 0, 255);
}
void TeensyPaddles::startReading()
{
g_vm->triggerPaddleInCycles(0, 12 * paddle0());
g_vm->triggerPaddleInCycles(1, 12 * paddle1());
}

13
teensy/teensy-paddles.h Normal file
View File

@ -0,0 +1,13 @@
#include <Arduino.h>
#include "physicalpaddles.h"
class TeensyPaddles : public PhysicalPaddles {
public:
TeensyPaddles();
virtual ~TeensyPaddles();
virtual uint8_t paddle0();
virtual uint8_t paddle1();
virtual void startReading();
};

33
teensy/teensy-speaker.cpp Normal file
View File

@ -0,0 +1,33 @@
#include <Arduino.h>
#include "teensy-speaker.h"
extern int16_t g_volume;
TeensySpeaker::TeensySpeaker(uint8_t pinNum) : PhysicalSpeaker()
{
speakerState = false;
speakerPin = pinNum;
pinMode(speakerPin, OUTPUT); // analog speaker output, used as digital volume control
nextTransitionAt = 0;
}
TeensySpeaker::~TeensySpeaker()
{
}
void TeensySpeaker::toggleAtCycle(uint32_t c)
{
// FIXME: could tell here if we dropped something - is nextTransitionAt already set? If so, we missed a toggle :(
nextTransitionAt = c;
}
void TeensySpeaker::maintainSpeaker(uint32_t c)
{
if (nextTransitionAt && c >= nextTransitionAt) {
nextTransitionAt = 0;
speakerState = !speakerState;
// FIXME: glad it's DAC0 and all, but... how does that relate to the pin passed in the constructor?
analogWriteDAC0(speakerState ? g_volume : 0); // max: 4095
}
}

22
teensy/teensy-speaker.h Normal file
View File

@ -0,0 +1,22 @@
#ifndef __TEENSY_SPEAKER_H
#define __TEENSY_SPEAKER_H
#include "physicalspeaker.h"
class TeensySpeaker : public PhysicalSpeaker {
public:
TeensySpeaker(uint8_t pinNum);
virtual ~TeensySpeaker();
virtual void toggleAtCycle(uint32_t c);
virtual void maintainSpeaker(uint32_t c);
private:
bool speakerState;
uint8_t speakerPin;
uint32_t nextTransitionAt;
};
#endif

440
teensy/teensy.ino Normal file
View File

@ -0,0 +1,440 @@
#include <Arduino.h>
#include <TimerOne.h>
#include <ff.h> // uSDFS
#include <SPI.h>
#include <EEPROM.h>
#include <TimeLib.h>
#include "bios.h"
#include "cpu.h"
#include "applevm.h"
#include "teensy-display.h"
#include "teensy-keyboard.h"
#include "teensy-speaker.h"
#include "teensy-paddles.h"
#include "teensy-filemanager.h"
#define RESETPIN 39
#define BATTERYPIN A19
#define SPEAKERPIN A21
//#define DEBUGCPU
#include "globals.h"
#include "teensy-crash.h"
volatile float nextInstructionMicros;
volatile float startMicros;
FATFS fatfs; /* File system object */
BIOS bios;
uint8_t videoBuffer[320*240/2];
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
};
uint8_t debugMode = D_NONE;
static time_t getTeensy3Time() { return Teensy3Clock.get(); }
/* Totally messing around with the RadioHead library */
#include <SPI.h>
#include <RH_NRF24.h>
#include <RHSoftwareSPI.h>
RHSoftwareSPI spi;
#define RF_CSN 40
#define RF_MOSI 41
#define RF_IRQ 42
#define RF_CE 53
#define RF_SCK 52
#define RF_MISO 51
RH_NRF24 nrf24(RF_CE, RF_CSN, spi);
void setup()
{
Serial.begin(230400);
/* 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");
}
spi.setPins(RF_MISO, RF_MOSI, RF_SCK);
if (!nrf24.init())
Serial.println("init failed");
// Defaults after init are 2.402 GHz (channel 2), 2Mbps, 0dBm
if (!nrf24.setChannel(1))
Serial.println("setChannel failed");
if (!nrf24.setRF(RH_NRF24::DataRate2Mbps, RH_NRF24::TransmitPower0dBm))
Serial.println("setRF failed");
Serial.println("nrf24 initialized");
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, instead of 1.7v internal
analogReadRes(8); // We only need 8 bits of resolution (0-255) for battery & paddles
analogReadAveraging(4); // ?? dunno if we need this or not.
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 TeensyKeyboard(g_vm->getKeyboard());
Serial.println(" paddles");
g_paddles = new TeensyPaddles();
// 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();
Timer1.initialize(3);
Timer1.attachInterrupt(runCPU);
Timer1.start();
}
/* We're running the timer that calls this at 1/3 "normal" speed, and
* then asking runCPU to run 48 steps (individual opcodes executed) of
* the CPU before returning. Then we figure out how many cycles
* elapsed during that run, and keep track of how many cycles we now
* have to "drain off" (how many extra ran during this attempt -- we
* expected at least 3, but might have gotten more). Then the next
* call here from the interrupt subtracts 3 cycles, on the assumption
* that 3 have passed, and we're good to go.
*
* This approach is reasonable: the 6502 instruction set takes an
* average of 4 clock cycles to execute. This compromise keeps us from
* chewing up the entire CPU on interrupt overhead, allowing us to
* focus on refreshing the LCD as fast as possible while sacrificing
* some small timing differences. Experimentally, paddle values seem
* to still read well up to 48 steps. At 2*48, the paddles drift at
* the low end, meaning there's probably an issue with timing.
*/
void runCPU()
{
// static bool outputState = false;
// outputState = !outputState;
// digitalWrite(56, outputState);
if (micros() >= nextInstructionMicros) {
#ifdef DEBUGCPU
g_cpu->Run(1);
#else
g_cpu->Run(24);
#endif
// 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%.
#ifdef DEBUGCPU
// ... have to slow down so the printing all works
nextInstructionMicros = startMicros + (float)g_cpu->cycles * 50;
#else
nextInstructionMicros = startMicros + (float)g_cpu->cycles * 0.978;
#endif
#ifdef DEBUGCPU
{
uint8_t p = g_cpu->flags;
Serial.printf("OP: $%02x A: %02x X: %02x Y: %02x PC: $%04x SP: %02x Flags: %c%cx%c%c%c%c%c\n",
g_vm->getMMU()->read(g_cpu->pc),
g_cpu->a, g_cpu->x, g_cpu->y, g_cpu->pc, g_cpu->sp,
p & (1<<7) ? 'N':' ',
p & (1<<6) ? 'V':' ',
p & (1<<4) ? 'B':' ',
p & (1<<3) ? 'D':' ',
p & (1<<2) ? 'I':' ',
p & (1<<1) ? 'Z':' ',
p & (1<<0) ? 'C':' '
);
}
#endif
}
g_speaker->maintainSpeaker(g_cpu->cycles);
((AppleVM *)g_vm)->cpuMaintenance(g_cpu->cycles);
}
// 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()
{
// Shut down the CPU
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();
// Restart the CPU
Timer1.start();
}
void loop()
{
static uint16_t ctr = -1;
/* testing the fault handler? uncomment this and it'll crash. */
// *((int*)0x0) = 1;
static unsigned long nextBattCheck = 0;
static int batteryLevel = 0; // static for debugging code! When done
// debugging, this can become a local
// in the appropriate block below
if (millis() >= nextBattCheck) {
// FIXME: what about rollover?
nextBattCheck = millis() + 1 * 1000; // once a minute? maybe? FIXME: Right now 1/sec
// FIXME: scale appropriately.
batteryLevel = analogRead(BATTERYPIN);
/* 205 is "near dead, do something about it right now" - 3.2v and lower.
* What's the top end? 216-ish?
*
* The reading fluctuates quite a lot - we should probably capture
* more and average it over a longer period before showing
* anything (FIXME)
*/
if (batteryLevel < 205)
batteryLevel = 205;
if (batteryLevel > 216)
batteryLevel = 216;
batteryLevel = map(batteryLevel, 205, 216, 0, 100);
((AppleVM *)g_vm)->batteryLevel( batteryLevel );
}
if ((++ctr & 0xFF) == 0) {
if (digitalRead(RESETPIN) == LOW) {
// This is the BIOS interrupt. We immediately act on it.
biosInterrupt();
}
if (g_vm->vmdisplay->needsRedraw()) {
// make sure to clear the flag before drawing; there's no lock
// on didRedraw, so the other thread might update it
g_vm->vmdisplay->didRedraw();
g_display->blit();
}
g_keyboard->maintainKeyboard();
{
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", 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, "%lX", g_cpu->cycles);
g_display->debugMsg(buf);
break;
case D_SHOWBATTERY:
sprintf(buf, "BAT %d", analogRead(BATTERYPIN));
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;
} 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 0x01831093
void readPrefs()
{
prefs p;
uint8_t *pp = (uint8_t *)&p;
Serial.println("reading prefs");
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 > 4095) {
p.volume = 4095;
}
if (p.volume < 0) {
p.volume = 0;
}
g_volume = p.volume;
return;
}
// use defaults
g_volume = 0;
}
// Writes to EEPROM slow down the Teensy 3.6's CPU to 120MHz automatically. Disable our timer
// while we're doing it and we'll just see a pause.
void writePrefs()
{
Timer1.stop();
Serial.println("writing prefs");
prefs p;
uint8_t *pp = (uint8_t *)&p;
p.magic = MAGIC;
p.volume = g_volume;
for (uint8_t i=0; i<sizeof(prefs); i++) {
EEPROM.write(i, *pp++);
}
Timer1.start();
}

1
teensy/vm.h Symbolic link
View File

@ -0,0 +1 @@
../vm.h

1
teensy/vmdisplay.h Symbolic link
View File

@ -0,0 +1 @@
../vmdisplay.h

1
teensy/vmkeyboard.h Symbolic link
View File

@ -0,0 +1 @@
../vmkeyboard.h