diff --git a/README.md b/README.md
index aae4d92..38974c6 100644
--- a/README.md
+++ b/README.md
@@ -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:
+
+
+ $ 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
+
+
+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)
===================
diff --git a/filemanager.h b/filemanager.h
index cbc9e5f..1bde8d2 100644
--- a/filemanager.h
+++ b/filemanager.h
@@ -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;
diff --git a/opencv/opencv-filemanager.cpp b/opencv/opencv-filemanager.cpp
index 80882ba..c10e539 100644
--- a/opencv/opencv-filemanager.cpp
+++ b/opencv/opencv-filemanager.cpp
@@ -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;
diff --git a/opencv/opencv-filemanager.h b/opencv/opencv-filemanager.h
index d415b37..82133d0 100644
--- a/opencv/opencv-filemanager.h
+++ b/opencv/opencv-filemanager.h
@@ -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);
diff --git a/teensy/appledisplay.cpp b/teensy/appledisplay.cpp
new file mode 120000
index 0000000..a506d9b
--- /dev/null
+++ b/teensy/appledisplay.cpp
@@ -0,0 +1 @@
+../apple/appledisplay.cpp
\ No newline at end of file
diff --git a/teensy/appledisplay.h b/teensy/appledisplay.h
new file mode 120000
index 0000000..19355ca
--- /dev/null
+++ b/teensy/appledisplay.h
@@ -0,0 +1 @@
+../apple/appledisplay.h
\ No newline at end of file
diff --git a/teensy/applekeyboard.cpp b/teensy/applekeyboard.cpp
new file mode 120000
index 0000000..b083db5
--- /dev/null
+++ b/teensy/applekeyboard.cpp
@@ -0,0 +1 @@
+../apple/applekeyboard.cpp
\ No newline at end of file
diff --git a/teensy/applekeyboard.h b/teensy/applekeyboard.h
new file mode 120000
index 0000000..9376c82
--- /dev/null
+++ b/teensy/applekeyboard.h
@@ -0,0 +1 @@
+../apple/applekeyboard.h
\ No newline at end of file
diff --git a/teensy/applemmu-rom.h b/teensy/applemmu-rom.h
new file mode 120000
index 0000000..a5461fe
--- /dev/null
+++ b/teensy/applemmu-rom.h
@@ -0,0 +1 @@
+../apple/applemmu-rom.h
\ No newline at end of file
diff --git a/teensy/applemmu.cpp b/teensy/applemmu.cpp
new file mode 120000
index 0000000..db957ef
--- /dev/null
+++ b/teensy/applemmu.cpp
@@ -0,0 +1 @@
+../apple/applemmu.cpp
\ No newline at end of file
diff --git a/teensy/applemmu.h b/teensy/applemmu.h
new file mode 120000
index 0000000..70bbed0
--- /dev/null
+++ b/teensy/applemmu.h
@@ -0,0 +1 @@
+../apple/applemmu.h
\ No newline at end of file
diff --git a/teensy/applevm.cpp b/teensy/applevm.cpp
new file mode 120000
index 0000000..e5faf7f
--- /dev/null
+++ b/teensy/applevm.cpp
@@ -0,0 +1 @@
+../apple/applevm.cpp
\ No newline at end of file
diff --git a/teensy/applevm.h b/teensy/applevm.h
new file mode 120000
index 0000000..a4dae41
--- /dev/null
+++ b/teensy/applevm.h
@@ -0,0 +1 @@
+../apple/applevm.h
\ No newline at end of file
diff --git a/teensy/bios-font.h b/teensy/bios-font.h
new file mode 120000
index 0000000..c09ff71
--- /dev/null
+++ b/teensy/bios-font.h
@@ -0,0 +1 @@
+../bios-font.h
\ No newline at end of file
diff --git a/teensy/bios.cpp b/teensy/bios.cpp
new file mode 100644
index 0000000..2428b0a
--- /dev/null
+++ b/teensy/bios.cpp
@@ -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; iMonitor();
+ 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; iDiskName(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, "");
+ } else {
+ g_display->drawString(selection == 0 ? M_SELECTED : M_NORMAL, 10, 50, "");
+ }
+
+ uint8_t i;
+ for (i=0; idrawString((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), "");
+ } else {
+ g_display->drawString(i+1 == selection ? M_SELECTED : M_NORMAL, 10, 50 + 14 * (i+1), "");
+ }
+}
+
+
+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;
+ }
+ }
+}
+
diff --git a/teensy/bios.h b/teensy/bios.h
new file mode 100644
index 0000000..67be37e
--- /dev/null
+++ b/teensy/bios.h
@@ -0,0 +1,38 @@
+#ifndef __BIOS_H
+#define __BIOS_H
+
+#include
+
+#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
diff --git a/teensy/cpu.cpp b/teensy/cpu.cpp
new file mode 120000
index 0000000..89beeaf
--- /dev/null
+++ b/teensy/cpu.cpp
@@ -0,0 +1 @@
+../cpu.cpp
\ No newline at end of file
diff --git a/teensy/cpu.h b/teensy/cpu.h
new file mode 120000
index 0000000..e9d4bdf
--- /dev/null
+++ b/teensy/cpu.h
@@ -0,0 +1 @@
+../cpu.h
\ No newline at end of file
diff --git a/teensy/diskii-rom.h b/teensy/diskii-rom.h
new file mode 120000
index 0000000..0a3edae
--- /dev/null
+++ b/teensy/diskii-rom.h
@@ -0,0 +1 @@
+../apple/diskii-rom.h
\ No newline at end of file
diff --git a/teensy/diskii.cpp b/teensy/diskii.cpp
new file mode 120000
index 0000000..b773c2b
--- /dev/null
+++ b/teensy/diskii.cpp
@@ -0,0 +1 @@
+../apple/diskii.cpp
\ No newline at end of file
diff --git a/teensy/diskii.h b/teensy/diskii.h
new file mode 120000
index 0000000..781bcbe
--- /dev/null
+++ b/teensy/diskii.h
@@ -0,0 +1 @@
+../apple/diskii.h
\ No newline at end of file
diff --git a/teensy/display-bg.h b/teensy/display-bg.h
new file mode 120000
index 0000000..3043c6a
--- /dev/null
+++ b/teensy/display-bg.h
@@ -0,0 +1 @@
+../display-bg.h
\ No newline at end of file
diff --git a/teensy/filemanager.h b/teensy/filemanager.h
new file mode 120000
index 0000000..b5f9cfe
--- /dev/null
+++ b/teensy/filemanager.h
@@ -0,0 +1 @@
+../filemanager.h
\ No newline at end of file
diff --git a/teensy/font.h b/teensy/font.h
new file mode 120000
index 0000000..ce7b944
--- /dev/null
+++ b/teensy/font.h
@@ -0,0 +1 @@
+../apple/font.h
\ No newline at end of file
diff --git a/teensy/globals.cpp b/teensy/globals.cpp
new file mode 120000
index 0000000..3fef46a
--- /dev/null
+++ b/teensy/globals.cpp
@@ -0,0 +1 @@
+../globals.cpp
\ No newline at end of file
diff --git a/teensy/globals.h b/teensy/globals.h
new file mode 120000
index 0000000..caa372d
--- /dev/null
+++ b/teensy/globals.h
@@ -0,0 +1 @@
+../globals.h
\ No newline at end of file
diff --git a/teensy/mmu.h b/teensy/mmu.h
new file mode 120000
index 0000000..cc56dfb
--- /dev/null
+++ b/teensy/mmu.h
@@ -0,0 +1 @@
+../mmu.h
\ No newline at end of file
diff --git a/teensy/nibutil.cpp b/teensy/nibutil.cpp
new file mode 120000
index 0000000..29b7732
--- /dev/null
+++ b/teensy/nibutil.cpp
@@ -0,0 +1 @@
+../apple/nibutil.cpp
\ No newline at end of file
diff --git a/teensy/nibutil.h b/teensy/nibutil.h
new file mode 120000
index 0000000..0bd1862
--- /dev/null
+++ b/teensy/nibutil.h
@@ -0,0 +1 @@
+../apple/nibutil.h
\ No newline at end of file
diff --git a/teensy/physicaldisplay.h b/teensy/physicaldisplay.h
new file mode 120000
index 0000000..8df37c8
--- /dev/null
+++ b/teensy/physicaldisplay.h
@@ -0,0 +1 @@
+../physicaldisplay.h
\ No newline at end of file
diff --git a/teensy/physicalkeyboard.h b/teensy/physicalkeyboard.h
new file mode 120000
index 0000000..75e8d2a
--- /dev/null
+++ b/teensy/physicalkeyboard.h
@@ -0,0 +1 @@
+../physicalkeyboard.h
\ No newline at end of file
diff --git a/teensy/physicalpaddles.h b/teensy/physicalpaddles.h
new file mode 120000
index 0000000..8f5c173
--- /dev/null
+++ b/teensy/physicalpaddles.h
@@ -0,0 +1 @@
+../physicalpaddles.h
\ No newline at end of file
diff --git a/teensy/physicalspeaker.h b/teensy/physicalspeaker.h
new file mode 120000
index 0000000..65d0f7e
--- /dev/null
+++ b/teensy/physicalspeaker.h
@@ -0,0 +1 @@
+../physicalspeaker.h
\ No newline at end of file
diff --git a/teensy/slot.cpp b/teensy/slot.cpp
new file mode 120000
index 0000000..375ae63
--- /dev/null
+++ b/teensy/slot.cpp
@@ -0,0 +1 @@
+../apple/slot.cpp
\ No newline at end of file
diff --git a/teensy/slot.h b/teensy/slot.h
new file mode 120000
index 0000000..e877fef
--- /dev/null
+++ b/teensy/slot.h
@@ -0,0 +1 @@
+../apple/slot.h
\ No newline at end of file
diff --git a/teensy/teensy-clock.cpp b/teensy/teensy-clock.cpp
new file mode 100644
index 0000000..2341d8a
--- /dev/null
+++ b/teensy/teensy-clock.cpp
@@ -0,0 +1,126 @@
+#include // memset
+#include
+
+#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
+}
+
diff --git a/teensy/teensy-clock.h b/teensy/teensy-clock.h
new file mode 100644
index 0000000..489dd9a
--- /dev/null
+++ b/teensy/teensy-clock.h
@@ -0,0 +1,30 @@
+#ifndef __TEENSYCLOCK_H
+#define __TEENSYCLOCK_H
+
+#ifdef TEENSYDUINO
+#include
+#else
+#include
+#include
+#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
diff --git a/teensy/teensy-crash.h b/teensy/teensy-crash.h
new file mode 100644
index 0000000..fab4026
--- /dev/null
+++ b/teensy/teensy-crash.h
@@ -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")));
diff --git a/teensy/teensy-display.cpp b/teensy/teensy-display.cpp
new file mode 100644
index 0000000..b51b446
--- /dev/null
+++ b/teensy/teensy-display.cpp
@@ -0,0 +1,560 @@
+#include // 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> 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 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);
+ }
+ }
+}
+
diff --git a/teensy/teensy-display.h b/teensy/teensy-display.h
new file mode 100644
index 0000000..00163d3
--- /dev/null
+++ b/teensy/teensy-display.h
@@ -0,0 +1,81 @@
+#ifndef __TEENSY_DISPLAY_H
+#define __TEENSY_DISPLAY_H
+
+#include
+#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
diff --git a/teensy/teensy-filemanager.cpp b/teensy/teensy-filemanager.cpp
new file mode 100644
index 0000000..104d3de
--- /dev/null
+++ b/teensy/teensy-filemanager.cpp
@@ -0,0 +1,275 @@
+#include
+#include
+#include "ff.h" // File System
+#include "teensy-filemanager.h"
+#include // 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= 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)));
+}
+
diff --git a/teensy/teensy-filemanager.h b/teensy/teensy-filemanager.h
new file mode 100644
index 0000000..910ab3b
--- /dev/null
+++ b/teensy/teensy-filemanager.h
@@ -0,0 +1,30 @@
+#ifndef __TEENSYFILEMANAGER_H
+#define __TEENSYFILEMANAGER_H
+
+#include "filemanager.h"
+#include
+
+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
diff --git a/teensy/teensy-keyboard.cpp b/teensy/teensy-keyboard.cpp
new file mode 100644
index 0000000..f1c3e9d
--- /dev/null
+++ b/teensy/teensy-keyboard.cpp
@@ -0,0 +1,231 @@
+#include
+#include "teensy-keyboard.h"
+#include
+#include
+
+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; ikeyDepressed(keypad.key[i].kchar);
+ break;
+ case RELEASED:
+ vmkeyboard->keyReleased(keypad.key[i].kchar);
+ break;
+ case HOLD:
+ case IDLE:
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/teensy/teensy-keyboard.h b/teensy/teensy-keyboard.h
new file mode 100644
index 0000000..7a10d73
--- /dev/null
+++ b/teensy/teensy-keyboard.h
@@ -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
diff --git a/teensy/teensy-paddles.cpp b/teensy/teensy-paddles.cpp
new file mode 100644
index 0000000..0434a75
--- /dev/null
+++ b/teensy/teensy-paddles.cpp
@@ -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());
+}
diff --git a/teensy/teensy-paddles.h b/teensy/teensy-paddles.h
new file mode 100644
index 0000000..daae610
--- /dev/null
+++ b/teensy/teensy-paddles.h
@@ -0,0 +1,13 @@
+#include
+
+#include "physicalpaddles.h"
+
+class TeensyPaddles : public PhysicalPaddles {
+ public:
+ TeensyPaddles();
+ virtual ~TeensyPaddles();
+
+ virtual uint8_t paddle0();
+ virtual uint8_t paddle1();
+ virtual void startReading();
+};
diff --git a/teensy/teensy-speaker.cpp b/teensy/teensy-speaker.cpp
new file mode 100644
index 0000000..8b5cdda
--- /dev/null
+++ b/teensy/teensy-speaker.cpp
@@ -0,0 +1,33 @@
+#include
+#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
+ }
+}
diff --git a/teensy/teensy-speaker.h b/teensy/teensy-speaker.h
new file mode 100644
index 0000000..49910c5
--- /dev/null
+++ b/teensy/teensy-speaker.h
@@ -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
diff --git a/teensy/teensy.ino b/teensy/teensy.ino
new file mode 100644
index 0000000..9b4dda2
--- /dev/null
+++ b/teensy/teensy.ino
@@ -0,0 +1,440 @@
+#include
+#include
+#include // uSDFS
+#include
+#include
+#include
+#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
+#include
+#include
+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 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